diff options
Diffstat (limited to 'src/com')
337 files changed, 24108 insertions, 16495 deletions
diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java index 9fb4cfe7fa..e426ea3085 100644 --- a/src/com/vaadin/Application.java +++ b/src/com/vaadin/Application.java @@ -4,35 +4,56 @@ 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.ApplicationResource; -import com.vaadin.terminal.DownloadStream; +import com.vaadin.terminal.CombinedRequest; import com.vaadin.terminal.ErrorMessage; -import com.vaadin.terminal.ParameterHandler; +import com.vaadin.terminal.RequestHandler; import com.vaadin.terminal.SystemError; 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.server.AbstractApplicationServlet; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; -import com.vaadin.terminal.gwt.server.PortletApplicationContext; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractField; +import com.vaadin.ui.Root; +import com.vaadin.ui.Table; import com.vaadin.ui.Window; /** @@ -89,17 +110,223 @@ import com.vaadin.ui.Window; * @since 3.0 */ @SuppressWarnings("serial") -public abstract class Application implements URIHandler, - Terminal.ErrorListener, Serializable { +public class Application implements Terminal.ErrorListener, Serializable { - private final static Logger logger = Logger.getLogger(Application.class - .getName()); + /** + * 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. + */ + public static final String ROOT_PARAMETER = "root"; /** - * Id use for the next window that is opened. Access to this must be - * synchronized. + * 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 int nextWindowId = 1; + @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()); + } + } + + private final static Logger logger = Logger.getLogger(Application.class + .getName()); /** * Application context the application is running in. @@ -112,26 +339,11 @@ public abstract class Application implements URIHandler, private Object user; /** - * Mapping from window name to window instance. - */ - private final Hashtable<String, Window> windows = new Hashtable<String, Window>(); - - /** - * Main window of the application. - */ - private Window mainWindow = null; - - /** * The application's URL. */ 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 +364,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 +391,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) { + private ConverterFactory converterFactory = new DefaultConverterFactory(); - // For closed app, do not give any windows - if (!isRunning()) { - return null; - } + private LinkedList<RequestHandler> requestHandlers = new LinkedList<RequestHandler>(); - // Gets the window by name - final Window window = windows.get(name); + private int nextRootId = 0; + private Map<Integer, Root> roots = new HashMap<Integer, Root>(); - return window; - } + private boolean productionMode = true; - /** - * 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"); - } - - // If the name of the window is null, the window is automatically named - if (name == null) { - boolean accepted = false; - while (!accepted) { - - // 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); - - fireWindowAttachEvent(window); - - // If no main window is set, declare the window to be main window - if (getMainWindow() == null) { - mainWindow = window; - } - } - - /** - * 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. - * + * Keeps track of which roots have been inited. * <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)}. + * TODO Investigate whether this might be derived from the different states + * in getRootForRrequest. * </p> - * - * <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 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. @@ -532,11 +525,13 @@ public abstract class Application implements URIHandler, * configuration. * @param context * the context application will be running in. + * @param productionMode * */ public void start(URL applicationUrl, Properties applicationProperties, - ApplicationContext context) { + ApplicationContext context, boolean productionMode) { this.applicationUrl = applicationUrl; + this.productionMode = productionMode; properties = applicationProperties; this.context = context; init(); @@ -560,106 +555,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 +662,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 +920,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,10 +997,6 @@ 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(); } @@ -1280,6 +1073,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 +1736,534 @@ 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. + * </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 { + Class<? extends Root> rootClass = Class.forName(rootClassName) + .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); + } + } + + 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 + + Root.setCurrentRoot(root); + 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()); + } +} 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..22a4b6f9f7 100644 --- a/src/com/vaadin/data/Buffered.java +++ b/src/com/vaadin/data/Buffered.java @@ -77,7 +77,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 +99,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 +120,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 +139,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. * @@ -241,36 +295,29 @@ public interface Buffered extends Serializable { * * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() */ - public int getErrorLevel() { + public ErrorLevel getErrorLevel() { - int level = Integer.MIN_VALUE; + ErrorLevel level = null; for (int i = 0; i < causes.length; i++) { - final int causeLevel = (causes[i] instanceof ErrorMessage) ? ((ErrorMessage) causes[i]) - .getErrorLevel() : ErrorMessage.ERROR; - if (causeLevel > level) { + final ErrorLevel causeLevel = (causes[i] instanceof ErrorMessage) ? ((ErrorMessage) causes[i]) + .getErrorLevel() : ErrorLevel.ERROR; + if (level == null) { level = causeLevel; + } else { + if (causeLevel.intValue() > level.intValue()) { + level = causeLevel; + } } } - return level == Integer.MIN_VALUE ? ErrorMessage.ERROR : level; + return level == null ? ErrorLevel.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"); - } + target.addAttribute("level", getErrorLevel().getText()); // Paint all the exceptions for (int i = 0; i < causes.length; i++) { 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..36820c1caa 100644 --- a/src/com/vaadin/data/Validator.java +++ b/src/com/vaadin/data/Validator.java @@ -20,15 +20,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 +55,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> @@ -146,8 +139,8 @@ public interface Validator extends Serializable { * * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() */ - public final int getErrorLevel() { - return ErrorMessage.ERROR; + public final ErrorLevel getErrorLevel() { + return ErrorLevel.ERROR; } /* @@ -158,7 +151,7 @@ public interface Validator extends Serializable { */ public void paint(PaintTarget target) throws PaintException { target.startTag("error"); - target.addAttribute("level", "error"); + target.addAttribute("level", ErrorLevel.ERROR.getText()); // Error message final String message = getHtmlMessage(); diff --git a/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java new file mode 100644 index 0000000000..2584a4770b --- /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/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 9d88072c20..a74db6ce8a 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..91fcf004af --- /dev/null +++ b/src/com/vaadin/data/validator/DoubleRangeValidator.java @@ -0,0 +1,36 @@ +/* +@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..dec0bf0be9 100644 --- a/src/com/vaadin/data/validator/RegexpValidator.java +++ b/src/com/vaadin/data/validator/RegexpValidator.java @@ -62,8 +62,11 @@ 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/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..d799021506 --- /dev/null +++ b/src/com/vaadin/external/json/JSONException.java @@ -0,0 +1,28 @@ +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..465f27aaab --- /dev/null +++ b/src/com/vaadin/external/json/JSONStringer.java @@ -0,0 +1,78 @@ +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/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..94f4e3a5d5 100644 --- a/src/com/vaadin/terminal/CompositeErrorMessage.java +++ b/src/com/vaadin/terminal/CompositeErrorMessage.java @@ -29,7 +29,7 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { /** * Level of the error. */ - private int level; + private ErrorLevel level; /** * Constructor for CompositeErrorMessage. @@ -40,7 +40,7 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { */ public CompositeErrorMessage(ErrorMessage[] errorMessages) { errors = new ArrayList<ErrorMessage>(errorMessages.length); - level = Integer.MIN_VALUE; + level = ErrorLevel.INFORMATION; for (int i = 0; i < errorMessages.length; i++) { addErrorMessage(errorMessages[i]); @@ -63,7 +63,7 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { public CompositeErrorMessage( Collection<? extends ErrorMessage> errorMessages) { errors = new ArrayList<ErrorMessage>(errorMessages.size()); - level = Integer.MIN_VALUE; + level = ErrorLevel.INFORMATION; for (final Iterator<? extends ErrorMessage> i = errorMessages .iterator(); i.hasNext();) { @@ -81,7 +81,7 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { * * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() */ - public final int getErrorLevel() { + public final ErrorLevel getErrorLevel() { return level; } @@ -95,9 +95,8 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { 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.getErrorLevel().intValue() > level.intValue()) { + level = error.getErrorLevel(); } } } @@ -120,18 +119,7 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable { (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"); - } + target.addAttribute("level", level.getText()); // Paint all the exceptions for (final Iterator<ErrorMessage> i = errors.iterator(); i diff --git a/src/com/vaadin/terminal/DeploymentConfiguration.java b/src/com/vaadin/terminal/DeploymentConfiguration.java new file mode 100644 index 0000000000..403a6d68b7 --- /dev/null +++ b/src/com/vaadin/terminal/DeploymentConfiguration.java @@ -0,0 +1,77 @@ +/* +@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); +} 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..5e55bfbcf6 100644 --- a/src/com/vaadin/terminal/ErrorMessage.java +++ b/src/com/vaadin/terminal/ErrorMessage.java @@ -17,37 +17,98 @@ import java.io.Serializable; */ public interface ErrorMessage extends Paintable, 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 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/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..7f2464c9b9 100644 --- a/src/com/vaadin/terminal/SystemError.java +++ b/src/com/vaadin/terminal/SystemError.java @@ -69,8 +69,8 @@ public class SystemError extends RuntimeException implements ErrorMessage { /** * @see com.vaadin.terminal.ErrorMessage#getErrorLevel() */ - public final int getErrorLevel() { - return ErrorMessage.SYSTEMERROR; + public final ErrorLevel getErrorLevel() { + return ErrorLevel.SYSTEMERROR; } /** @@ -79,7 +79,7 @@ public class SystemError extends RuntimeException implements ErrorMessage { public void paint(PaintTarget target) throws PaintException { target.startTag("error"); - target.addAttribute("level", "system"); + target.addAttribute("level", ErrorLevel.SYSTEMERROR.getText()); String message = getHtmlMessage(); 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..8ec45ac725 100644 --- a/src/com/vaadin/terminal/UserError.java +++ b/src/com/vaadin/terminal/UserError.java @@ -18,31 +18,54 @@ import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; @SuppressWarnings("serial") public class UserError implements ErrorMessage { + public enum ContentMode { + /** + * Content mode, where the error contains only plain text. + */ + TEXT, + /** + * Content mode, where the error contains preformatted text. + */ + PREFORMATTED, + /** + * Formatted content mode, where the contents is XML restricted to the + * UIDL 1.0 formatting markups. + */ + UIDL, + /** + * Content mode, where the error contains XHTML. + */ + XHTML; + } + /** - * Content mode, where the error contains only plain text. + * @deprecated from 7.0, use {@link ContentMode#TEXT} instead   */ - public static final int CONTENT_TEXT = 0; + @Deprecated + public static final ContentMode CONTENT_TEXT = ContentMode.TEXT; /** - * Content mode, where the error contains preformatted text. + * @deprecated from 7.0, use {@link ContentMode#PREFORMATTED} instead   */ - public static final int CONTENT_PREFORMATTED = 1; + @Deprecated + public static final ContentMode CONTENT_PREFORMATTED = ContentMode.PREFORMATTED; /** - * Formatted content mode, where the contents is XML restricted to the UIDL - * 1.0 formatting markups. + * @deprecated from 7.0, use {@link ContentMode#UIDL} instead   */ - public static final int CONTENT_UIDL = 2; + @Deprecated + public static final ContentMode CONTENT_UIDL = ContentMode.UIDL; /** - * Content mode, where the error contains XHTML. + * @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. */ - private int mode = CONTENT_TEXT; + private ContentMode mode = ContentMode.TEXT; /** * Message in content mode. @@ -52,7 +75,7 @@ public class UserError implements ErrorMessage { /** * Error level. */ - private int level = ErrorMessage.ERROR; + private ErrorLevel level = ErrorLevel.ERROR; /** * Creates a textual error message of level ERROR. @@ -64,31 +87,21 @@ public class UserError implements ErrorMessage { msg = 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) { + if (contentMode == null) { + contentMode = ContentMode.TEXT; + } + if (errorLevel == null) { + errorLevel = ErrorLevel.ERROR; } - msg = message; mode = contentMode; level = errorLevel; } /* Documented in interface */ - public int getErrorLevel() { + public ErrorLevel getErrorLevel() { return level; } @@ -108,38 +121,25 @@ public class UserError implements ErrorMessage { 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"); - } + target.addAttribute("level", level.getText()); // Paint the message switch (mode) { - case CONTENT_TEXT: + case TEXT: target.addText(AbstractApplicationServlet.safeEscapeForHtml(msg)); break; - case CONTENT_UIDL: + case UIDL: target.addUIDL(msg); break; - case CONTENT_PREFORMATTED: + case PREFORMATTED: target.addText("<pre>" + AbstractApplicationServlet.safeEscapeForHtml(msg) + "</pre>"); break; - case CONTENT_XHTML: + case XHTML: target.addText(msg); break; } - target.endTag("error"); } 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 7844ccecf1..9bc05dee2e 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -22,14 +22,8 @@ <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.WidgetMapGenerator"> <when-type-is class="com.vaadin.terminal.gwt.client.WidgetMap" /> </generate-with> @@ -61,7 +55,6 @@ <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> @@ -73,6 +66,9 @@ </replace-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/client/ApplicationConfiguration.java b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java index 5782a0b822..fbf1b2c2d6 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java @@ -5,7 +5,6 @@ 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; @@ -14,13 +13,171 @@ 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.VUnknownComponentPaintable; 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,32 +196,27 @@ 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 Class<? extends Paintable>[] classes = new Class[1024]; + private Class<? extends VPaintableWidget>[] 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>(); public boolean usePortletURLs() { @@ -98,6 +250,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 +267,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 +369,26 @@ 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 VPaintableWidget> getWidgetClassByEncodedTag( + String tag) { try { int parseInt = Integer.parseInt(tag); return classes[parseInt]; } catch (Exception e) { // component was not present in mappings - return VUnknownComponent.class; + return VUnknownComponentPaintable.class; } } @@ -312,12 +398,12 @@ public class ApplicationConfiguration implements EntryPoint { String key = keyArray.get(i).intern(); int value = valueMap.getInt(key); classes[value] = widgetSet.getImplementationByClassName(key); - if (classes[value] == VUnknownComponent.class) { + if (classes[value] == VUnknownComponentPaintable.class) { if (unknownComponents == null) { unknownComponents = new HashMap<String, String>(); } unknownComponents.put("" + value, key); - } else if (key == "com.vaadin.ui.Window") { + } else if (key == "com.vaadin.ui.Root") { windowId = "" + value; } } @@ -398,7 +484,7 @@ public class ApplicationConfiguration implements EntryPoint { public void run() { pending = false; if (!isBusy()) { - Class<? extends Paintable> nextType = getNextType(); + Class<? extends VPaintableWidget> nextType = getNextType(); if (nextType == null) { // ensured that all widgets are loaded deferredWidgetLoader = null; @@ -411,8 +497,8 @@ public class ApplicationConfiguration implements EntryPoint { } } - private Class<? extends Paintable> getNextType() { - Class<? extends Paintable>[] deferredLoadedWidgets = widgetSet + private Class<? extends VPaintableWidget> getNextType() { + Class<? extends VPaintableWidget>[] deferredLoadedWidgets = widgetSet .getDeferredLoadedWidgets(); if (deferredLoadedWidgets.length <= nextWidgetIndex) { return null; @@ -443,10 +529,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); @@ -476,21 +558,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); }-*/; /** @@ -522,4 +605,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 944d9b5974..5fde5ba04c 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -28,20 +28,23 @@ 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.ApplicationConfiguration.ErrorMessage; 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.ui.VAbstractPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.VAbstractPaintableWidgetContainer; 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.VViewPaintable; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; import com.vaadin.terminal.gwt.server.AbstractCommunicationManager; @@ -52,10 +55,11 @@ import com.vaadin.terminal.gwt.server.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()}). + * {@link VPaintableWidget#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()}). * * TODO document better * @@ -86,6 +90,11 @@ public class ApplicationConnection { 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 @@ -124,9 +133,6 @@ public class ApplicationConnection { private final ArrayList<String> pendingVariables = new ArrayList<String>(); - private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap - .create(); - private WidgetSet widgetSet; private VContextMenu contextMenu = null; @@ -136,12 +142,14 @@ public class ApplicationConnection { private Timer loadTimer3; private Element loadElement; - private final VView view; + private final VViewPaintable view; protected boolean applicationRunning = false; private int activeRequests = 0; + protected boolean cssLoaded = false; + /** Parameters for this application connection loaded from the web-page */ private ApplicationConfiguration configuration; @@ -154,18 +162,16 @@ 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<VPaintableWidget> relativeSizeChanges = new ArrayList<VPaintableWidget>(); + private ArrayList<Widget> componentCaptionSizeChanges = new ArrayList<Widget>(); private Date requestStartTime; private boolean validatingLayouts = false; - private Set<Paintable> zeroWidthComponents = null; - - private Set<Paintable> zeroHeightComponents = null; + private Set<VPaintableWidget> zeroWidthComponents = null; - private Set<String> unregistryBag = new HashSet<String>(); + private Set<VPaintableWidget> zeroHeightComponents = null; public ApplicationConnection() { view = GWT.create(VView.class); @@ -186,7 +192,6 @@ public class ApplicationConnection { this.widgetSet = widgetSet; configuration = cnf; - windowName = configuration.getInitialWindowName(); ComponentLocator componentLocator = new ComponentLocator(this); @@ -209,10 +214,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 + incrementActiveRequests(); + + // initial UIDL provided in DOM, continue as if returned by request + handleJSONText(jsonText); + } } private native void initializeTestbenchHooks( @@ -238,10 +252,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; }-*/; @@ -366,39 +376,48 @@ public class ApplicationConnection { return (activeRequests > 0); } + public void incrementActiveRequests() { + if (activeRequests < 0) { + activeRequests = 1; + } else { + activeRequests++; + } + } + + public void decrementActiveRequests() { + if (activeRequests > 0) { + activeRequests--; + } + } + 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); @@ -419,9 +438,9 @@ public class ApplicationConnection { * * @param paintable */ - void highlightComponent(Paintable paintable) { + void highlightComponent(VPaintableWidget paintable) { String params = getRepaintAllParameters() + "&highlightComponent=" - + getPid(paintable); + + paintableMap.getPid(paintable); makeUidlRequest("", params, false); } @@ -455,9 +474,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); @@ -481,10 +499,6 @@ public class ApplicationConnection { public void onError(Request request, Throwable exception) { showCommunicationError(exception.getMessage()); endRequest(); - if (!applicationRunning) { - // start failed, let's try to start the next app - ApplicationConfiguration.startNextApplication(); - } } public void onResponseReceived(Request request, @@ -519,7 +533,7 @@ public class ApplicationConnection { (new Timer() { @Override public void run() { - activeRequests--; + decrementActiveRequests(); doUidlRequest(uri, payload, synchronous); } }).schedule(delay); @@ -555,29 +569,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); - 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); } }; @@ -602,6 +597,34 @@ public class ApplicationConnection { } /** + * Handles received UIDL JSON text, parsing it, and passing it on to the + * appropriate handlers, while logging timiing information. + * + * @param jsonText + */ + private void handleJSONText(String jsonText) { + final Date start = new Date(); + final ValueMap json; + try { + json = parseJSONResponse(jsonText); + } catch (final Exception e) { + endRequest(); + showCommunicationError(e.getMessage() + " - Original JSON-text:" + + jsonText); + 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 @@ -630,9 +653,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() { @@ -644,6 +665,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."); @@ -652,6 +674,17 @@ public class ApplicationConnection { } /** + * Checks whether or not the CSS is loaded. By default checks the size of + * the loading indicator element. + * + * @return + */ + protected boolean isCSSLoaded() { + return cssLoaded + || DOM.getElementPropertyInt(loadElement, "offsetHeight") != 0; + } + + /** * Shows the communication error notification. * * @param details @@ -659,9 +692,9 @@ public class ApplicationConnection { */ protected void showCommunicationError(String details) { VConsole.error("Communication error: " + details); - showError(details, configuration.getCommunicationErrorCaption(), - configuration.getCommunicationErrorMessage(), - configuration.getCommunicationErrorUrl()); + ErrorMessage communicationError = configuration.getCommunicationError(); + showError(details, communicationError.getCaption(), + communicationError.getMessage(), communicationError.getUrl()); } /** @@ -672,9 +705,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()); } /** @@ -715,7 +748,7 @@ public class ApplicationConnection { } protected void startRequest() { - activeRequests++; + incrementActiveRequests(); requestStartTime = new Date(); // show initial throbber if (loadTimer == null) { @@ -743,11 +776,11 @@ public class ApplicationConnection { checkForPendingVariableBursts(); runPostRequestHooks(configuration.getRootPanelId()); } - activeRequests--; + decrementActiveRequests(); // deferring to avoid flickering Scheduler.get().scheduleDeferred(new Command() { public void execute() { - if (activeRequests == 0) { + if (!hasActiveRequest()) { hideLoadingIndicator(); } } @@ -783,7 +816,8 @@ public class ApplicationConnection { 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")) { + if (!getPaintableMap().hasPaintable(id) + && !getPaintableMap().isDragAndDropPaintable(id)) { // variable owner does not exist anymore variableBurst.remove(i - 1); variableBurst.remove(i - 1); @@ -798,7 +832,8 @@ public class ApplicationConnection { if (loadElement == null) { loadElement = DOM.createDiv(); DOM.setStyleAttribute(loadElement, "position", "absolute"); - DOM.appendChild(view.getElement(), loadElement); + DOM.appendChild(view.getWidgetForPaintable().getElement(), + loadElement); VConsole.log("inserting load indicator"); } DOM.setElementProperty(loadElement, "className", "v-loading-indicator"); @@ -828,12 +863,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"); } @@ -937,12 +974,12 @@ public class ApplicationConnection { meta = json.getValueMap("meta"); if (meta.containsKey("repaintAll")) { repaintAll = true; - view.clear(); - idToPaintableDetail.clear(); + view.getWidgetForPaintable().clear(); + getPaintableMap().clear(); if (meta.containsKey("invalidLayouts")) { validatingLayouts = true; - zeroWidthComponents = new HashSet<Paintable>(); - zeroHeightComponents = new HashSet<Paintable>(); + zeroWidthComponents = new HashSet<VPaintableWidget>(); + zeroHeightComponents = new HashSet<VPaintableWidget>(); } } if (meta.containsKey("timedRedirect")) { @@ -966,7 +1003,7 @@ public class ApplicationConnection { // Process changes JsArray<ValueMap> changes = json.getJSValueMapArray("changes"); - ArrayList<Paintable> updatedWidgets = new ArrayList<Paintable>(); + ArrayList<VPaintableWidget> updatedVPaintableWidgets = new ArrayList<VPaintableWidget>(); relativeSizeChanges.clear(); componentCaptionSizeChanges.clear(); @@ -976,16 +1013,12 @@ public class ApplicationConnection { final UIDL change = changes.get(i).cast(); final UIDL uidl = change.getChildUIDL(0); // TODO optimize - final Paintable paintable = getPaintable(uidl.getId()); + final VPaintableWidget paintable = (VPaintableWidget) paintableMap + .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()); + updatedVPaintableWidgets.add(paintable); } else { if (!uidl.getTag().equals( configuration.getEncodedWindowTag())) { @@ -995,14 +1028,13 @@ public class ApplicationConnection { + uidl.getId() + ") rendered."); } else { String pid = uidl.getId(); - if (!idToPaintableDetail.containsKey(pid)) { - registerPaintable(pid, view); + if (!paintableMap.hasPaintable(pid)) { + paintableMap.registerPaintable(pid, view); } // VView does not call updateComponent so we // register any event listeners here - ComponentDetail cd = idToPaintableDetail - .get(pid); - cd.registerEventListenersFromUIDL(uidl); + paintableMap.registerEventListenersFromUIDL( + pid, uidl); // Finally allow VView to update itself view.updateFromUIDL(uidl, @@ -1021,22 +1053,20 @@ public class ApplicationConnection { } // Check which widgets' size has been updated - Set<Paintable> sizeUpdatedWidgets = new HashSet<Paintable>(); + Set<Widget> sizeUpdatedWidgets = new HashSet<Widget>(); - updatedWidgets.addAll(relativeSizeChanges); + updatedVPaintableWidgets.addAll(relativeSizeChanges); sizeUpdatedWidgets.addAll(componentCaptionSizeChanges); - for (Paintable paintable : updatedWidgets) { - ComponentDetail detail = idToPaintableDetail - .get(getPid(paintable)); - Widget widget = (Widget) paintable; - Size oldSize = detail.getOffsetSize(); + for (VPaintableWidget paintable : updatedVPaintableWidgets) { + Widget widget = paintable.getWidgetForPaintable(); + Size oldSize = paintableMap.getOffsetSize(paintable); Size newSize = new Size(widget.getOffsetWidth(), widget.getOffsetHeight()); if (oldSize == null || !oldSize.equals(newSize)) { - sizeUpdatedWidgets.add(paintable); - detail.setOffsetSize(newSize); + sizeUpdatedWidgets.add(widget); + paintableMap.setOffsetSize(paintable, newSize); } } @@ -1090,9 +1120,9 @@ public class ApplicationConnection { * idToPaintableDetail is already cleanded at the start of * the changeset handling, bypass cleanup. */ - unregistryBag.clear(); + paintableMap.purgeUnregistryBag(false); } else { - purgeUnregistryBag(); + paintableMap.purgeUnregistryBag(true); } // TODO build profiling for widget impl loading time @@ -1102,8 +1132,7 @@ public class ApplicationConnection { VConsole.log(" Processing time was " + String.valueOf(prosessingTime) + "ms for " + jsonText.length() + " characters of JSON"); - VConsole.log("Referenced paintables: " - + idToPaintableDetail.size()); + VConsole.log("Referenced paintables: " + paintableMap.size()); endRequest(); @@ -1112,22 +1141,6 @@ public class ApplicationConnection { 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); - } - } - // Redirect browser, null reloads current page private static native void redirect(String url) /*-{ @@ -1138,164 +1151,6 @@ public class ApplicationConnection { } }-*/; - 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; - }-*/; - - /** - * 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; - }-*/; - - /** - * 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(); - } - - /** - * 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) { - - // add to unregistry que - - 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); - } - } else { - unregistryBag.add(id); - if (p instanceof HasWidgets) { - unregisterChildPaintables((HasWidgets) p); - } - } - } - - 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; - } - // check if can be cleaned - Widget component = (Widget) componentDetail.getComponent(); - if (!component.isAttached()) { - // clean reference from ac to paintable - idToPaintableDetail.remove(id); - } - /* - * else NOP : same component has been reattached to another parent - * or replaced by another component implementation. - */ - } - - unregistryBag.clear(); - } - - /** - * 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); - } - } - } - - /** - * 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 void addVariableToQueue(String paintableId, String variableName, String encodedValue, boolean immediate, char type) { final String id = paintableId + VAR_FIELD_SEPARATOR + variableName @@ -1381,7 +1236,18 @@ public class ApplicationConnection { 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); } private void makeUidlRequest(String string) { @@ -1406,8 +1272,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; + VPaintable newValue, boolean immediate) { + String pid = paintableMap.getPid(newValue); addVariableToQueue(paintableId, variableName, pid, immediate, 'p'); } @@ -1567,7 +1433,7 @@ public class ApplicationConnection { * the id of the paintable that owns the variable * @param variableName * the name of the variable - * @param newValue + * @param map * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible @@ -1584,7 +1450,7 @@ public class ApplicationConnection { buf.append(escapeVariableValue(key)); buf.append(VAR_ARRAYITEM_SEPARATOR); if (transportType == 'p') { - buf.append(getPid((Paintable) value)); + buf.append(paintableMap.getPid((VPaintable) value)); } else { buf.append(escapeVariableValue(String.valueOf(value))); } @@ -1601,7 +1467,7 @@ public class ApplicationConnection { private char getTransportType(Object value) { if (value instanceof String) { return 's'; - } else if (value instanceof Paintable) { + } else if (value instanceof VPaintableWidget) { return 'p'; } else if (value instanceof Boolean) { return 'b'; @@ -1685,7 +1551,7 @@ public class ApplicationConnection { // first char tells the type in array buf.append(transportType); if (transportType == 'p') { - buf.append(getPid((Paintable) value)); + buf.append(paintableMap.getPid((VPaintable) value)); } else { buf.append(escapeVariableValue(String.valueOf(value))); } @@ -1766,24 +1632,18 @@ public class ApplicationConnection { * * @return Returns true iff no further painting is needed by caller */ - public boolean updateComponent(Widget component, UIDL uidl, + @Deprecated + public boolean updateComponent(VPaintableWidget paintable, UIDL uidl, boolean manageCaption) { - String pid = getPid(component.getElement()); + Widget component = paintable.getWidgetForPaintable(); + + String pid = paintableMap.getPid(paintable); 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")) { @@ -1792,7 +1652,7 @@ public class ApplicationConnection { // register the listened events by the server-side to the event-handler // of the component - componentDetail.registerEventListenersFromUIDL(uidl); + paintableMap.registerEventListenersFromUIDL(pid, uidl); // Visibility boolean visible = !uidl.getBooleanAttribute("invisible"); @@ -1802,11 +1662,7 @@ public class ApplicationConnection { // 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); - } - + updateCaption(paintable, uidl); } } @@ -1818,28 +1674,10 @@ public class ApplicationConnection { if (!visible) { // component is invisible, delete old size to notify parent, if // later make visible - componentDetail.setOffsetSize(null); + paintableMap.setOffsetSize(paintable, 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 @@ -1854,7 +1692,11 @@ public class ApplicationConnection { fw.setEnabled(enabled); } - TooltipInfo tooltipInfo = componentDetail.getTooltipInfo(null); + // Style names + component.setStyleName(getStyleName(component.getStylePrimaryName(), + uidl, component instanceof Field)); + + TooltipInfo tooltipInfo = paintableMap.getTooltipInfo(paintable, null); // Update tooltip if (uidl.hasAttribute(ATTRIBUTE_DESCRIPTION)) { tooltipInfo @@ -1863,6 +1705,11 @@ public class ApplicationConnection { tooltipInfo.setTitle(null); } + // Set captions + if (manageCaption) { + updateCaption(paintable, uidl); + } + // add error classname to components w/ error if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { tooltipInfo.setErrorUidl(uidl.getErrors()); @@ -1870,27 +1717,22 @@ public class ApplicationConnection { 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); + updateComponentSize(paintable, uidl); return false; } + @Deprecated + private void updateCaption(VPaintableWidget paintable, UIDL uidl) { + VPaintableWidgetContainer parent = paintable.getParentPaintable(); + parent.updateCaption(paintable, uidl); + } + /** * Generates the style name for the widget based on the given primary style * name (typically returned by Widget.getPrimaryStyleName()) and the UIDL. @@ -1939,6 +1781,7 @@ public class ApplicationConnection { styleBuf.append(MODIFIED_CLASSNAME); } + // add error classname to components w/ error if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { styleBuf.append(" "); styleBuf.append(primaryStyleName); @@ -1952,10 +1795,9 @@ public class ApplicationConnection { } return styleBuf.toString(); - } - private void updateComponentSize(ComponentDetail cd, UIDL uidl) { + private void updateComponentSize(VPaintableWidget paintable, UIDL uidl) { String w = uidl.hasAttribute("width") ? uidl .getStringAttribute("width") : ""; @@ -1970,20 +1812,22 @@ public class ApplicationConnection { // One or both is relative FloatSize relativeSize = new FloatSize(relativeWidth, relativeHeight); - if (cd.getRelativeSize() == null && cd.getOffsetSize() != null) { + + if (paintableMap.getRelativeSize(paintable) == null + && paintableMap.getOffsetSize(paintable) != null) { // The component has changed from absolute size to relative size - relativeSizeChanges.add(cd.getComponent()); + relativeSizeChanges.add(paintable); } - cd.setRelativeSize(relativeSize); + paintableMap.setRelativeSize(paintable, relativeSize); } else if (relativeHeight < 0.0 && relativeWidth < 0.0) { - if (cd.getRelativeSize() != null) { + if (paintableMap.getRelativeSize(paintable) != null) { // The component has changed from relative size to absolute size - relativeSizeChanges.add(cd.getComponent()); + relativeSizeChanges.add(paintable); } - cd.setRelativeSize(null); + paintableMap.setRelativeSize(paintable, null); } - Widget component = (Widget) cd.getComponent(); + Widget component = paintableMap.getWidget(paintable); // Set absolute sizes if (relativeHeight < 0.0) { component.setHeight(h); @@ -1995,7 +1839,7 @@ public class ApplicationConnection { // Set relative sizes if (relativeHeight >= 0.0 || relativeWidth >= 0.0) { // One or both is relative - handleComponentRelativeSize(cd); + handleComponentRelativeSize(paintable); } } @@ -2027,9 +1871,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()); + Set<Widget> set = new HashSet<Widget>(); + for (VPaintable paintable : paintableMap.getPaintables()) { + if (paintable instanceof VPaintableWidget) { + set.add(((VPaintableWidget) paintable).getWidgetForPaintable()); + } } Util.componentSizeUpdated(set); } @@ -2041,7 +1887,7 @@ public class ApplicationConnection { while (childWidgets.hasNext()) { final Widget child = childWidgets.next(); - if (child instanceof Paintable) { + if (getPaintableMap().isPaintable(child)) { if (handleComponentRelativeSize(child)) { /* @@ -2072,31 +1918,33 @@ public class ApplicationConnection { * @param child * @return true if the child has a relative size */ - private boolean handleComponentRelativeSize(ComponentDetail cd) { - if (cd == null) { + private boolean handleComponentRelativeSize(VPaintableWidget paintable) { + if (paintable == null) { return false; } - boolean debugSizes = false; + boolean debugSizes = true; - FloatSize relativeSize = cd.getRelativeSize(); + FloatSize relativeSize = paintableMap.getRelativeSize(paintable); if (relativeSize == null) { return false; } - Widget widget = (Widget) cd.getComponent(); + Widget widget = paintableMap.getWidget(paintable); boolean horizontalScrollBar = false; boolean verticalScrollBar = false; - Container parent = Util.getLayout(widget); + VPaintableWidgetContainer parentPaintable = paintable + .getParentPaintable(); RenderSpace renderSpace; // Parent-less components (like sub-windows) are relative to browser // window. - if (parent == null) { + if (parentPaintable == null) { renderSpace = new RenderSpace(Window.getClientWidth(), Window.getClientHeight()); } else { - renderSpace = parent.getAllocatedSpace(widget); + renderSpace = ((Container) parentPaintable.getWidgetForPaintable()) + .getAllocatedSpace(widget); } if (relativeSize.getHeight() >= 0) { @@ -2120,7 +1968,7 @@ public class ApplicationConnection { height -= renderSpace.getScrollbarSize(); } if (validatingLayouts && height <= 0) { - zeroHeightComponents.add(cd.getComponent()); + zeroHeightComponents.add(paintable); } height = (int) (height * relativeSize.getHeight() / 100.0); @@ -2130,14 +1978,20 @@ public class ApplicationConnection { } 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"); + VConsole.log("Widget " + + Util.getSimpleName(widget) + + "/" + + paintableMap.getPid(paintable) + + " relative height " + + relativeSize.getHeight() + + "% of " + + renderSpace.getHeight() + + "px (reported by " + + + Util.getSimpleName(parentPaintable) + + "/" + + (parentPaintable == null ? "?" : parentPaintable + .hashCode()) + ") : " + height + "px"); } widget.setHeight(height + "px"); } else { @@ -2169,7 +2023,7 @@ public class ApplicationConnection { width -= renderSpace.getScrollbarSize(); } if (validatingLayouts && width <= 0) { - zeroWidthComponents.add(cd.getComponent()); + zeroWidthComponents.add(paintable); } width = (int) (width * relativeSize.getWidth() / 100.0); @@ -2179,13 +2033,20 @@ public class ApplicationConnection { } 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"); + VConsole.log("Widget " + + Util.getSimpleName(widget) + + "/" + + paintableMap.getPid(paintable) + + " relative width " + + relativeSize.getWidth() + + "% of " + + renderSpace.getWidth() + + "px (reported by " + + Util.getSimpleName(parentPaintable) + + "/" + + (parentPaintable == null ? "?" : paintableMap + .getPid(parentPaintable)) + ") : " + width + + "px"); } widget.setWidth(width + "px"); } else { @@ -2205,9 +2066,8 @@ 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(paintableMap.getPaintable(widget)); } @@ -2219,8 +2079,7 @@ public class ApplicationConnection { * @return the the size if the paintable is relatively sized, -1 otherwise */ public FloatSize getRelativeSize(Widget widget) { - return idToPaintableDetail.get(getPid(widget.getElement())) - .getRelativeSize(); + return paintableMap.getRelativeSize(paintableMap.getPaintable(widget)); } /** @@ -2235,27 +2094,18 @@ public class ApplicationConnection { * 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; - + public VPaintableWidget getPaintable(UIDL uidl) { + final String pid = uidl.getId(); + if (!paintableMap.hasPaintable(pid)) { + // Create and register a new paintable if no old was found + VPaintableWidget p = widgetSet.createWidget(uidl, configuration); + if (p instanceof VAbstractPaintableWidget) { + ((VAbstractPaintableWidget) p).setConnection(this); + ((VAbstractPaintableWidget) p).init(); + } + paintableMap.registerPaintable(pid, p); } - } - - /** - * Returns a Paintable element by its root element - * - * @param element - * Root element of the paintable - */ - public Paintable getPaintable(Element element) { - return getPaintable(getPid(element)); + return (VPaintableWidget) paintableMap.getPaintable(pid); } /** @@ -2348,16 +2198,12 @@ public class ApplicationConnection { * Updating TooltipInfo is done in updateComponent method. * */ - public TooltipInfo getTooltipTitleInfo(Paintable titleOwner, Object key) { + public TooltipInfo getTooltipTitleInfo(VPaintableWidget 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 paintableMap.getTooltipInfo(titleOwner, key); } private final VTooltip tooltip = new VTooltip(this); @@ -2372,7 +2218,7 @@ public class ApplicationConnection { * @param event * @param owner */ - public void handleTooltipEvent(Event event, Paintable owner) { + public void handleTooltipEvent(Event event, VPaintableWidget owner) { tooltip.handleTooltipEvent(event, owner, null); } @@ -2390,24 +2236,12 @@ 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, VPaintableWidget 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. @@ -2427,11 +2261,13 @@ public class ApplicationConnection { @Override public void run() { VConsole.log("Running re-layout of " + view.getClass().getName()); - runDescendentsLayout(view); + runDescendentsLayout(view.getWidgetForPaintable()); isPending = false; } }; + private VPaintableMap paintableMap = GWT.create(VPaintableMap.class); + /** * Components can call this function to run all layout functions. This is * usually done, when component knows that its size has changed. @@ -2440,24 +2276,6 @@ public class ApplicationConnection { 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; - } - protected String getUidlSecurityKey() { return uidlSecurityKey; } @@ -2469,8 +2287,8 @@ 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); } /** @@ -2478,7 +2296,7 @@ public class ApplicationConnection { * * @return the main view */ - public VView getView() { + public VViewPaintable getView() { return view; } @@ -2488,7 +2306,7 @@ public class ApplicationConnection { * this method. * <p> * Component must also pipe events to - * {@link #handleTooltipEvent(Event, Paintable, Object)} method. + * {@link #handleTooltipEvent(Event, VPaintableWidget, Object)} method. * <p> * This method can also be used to deregister tooltips by using null as * tooltip @@ -2498,17 +2316,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, VPaintableWidget, 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(VPaintableWidget paintable, Object key, TooltipInfo tooltip) { - ComponentDetail componentDetail = idToPaintableDetail - .get(getPid(paintable)); - componentDetail.putAdditionalTooltip(key, tooltip); + paintableMap.registerTooltip(paintable, key, tooltip); } /** @@ -2532,9 +2349,9 @@ public class ApplicationConnection { * @return true if at least one listener has been registered on server side * for the event identified by eventIdentifier. */ - public boolean hasEventListeners(Paintable paintable, String eventIdentifier) { - return idToPaintableDetail.get(getPid(paintable)).hasEventListeners( - eventIdentifier); + public boolean hasEventListeners(VPaintableWidget paintable, + String eventIdentifier) { + return paintableMap.hasEventListeners(paintable, eventIdentifier); } /** @@ -2578,4 +2395,40 @@ public class ApplicationConnection { return uri; } + VPaintableMap getPaintableMap() { + return paintableMap; + } + + @Deprecated + public void unregisterPaintable(VPaintable p) { + paintableMap.unregisterPaintable(p); + } + + public VTooltip getVTooltip() { + return tooltip; + } + + @Deprecated + public void handleWidgetTooltipEvent(Event event, Widget owner, Object key) { + handleTooltipEvent(event, getPaintableMap().getPaintable(owner), key); + + } + + @Deprecated + public void handleWidgetTooltipEvent(Event event, Widget owner) { + handleTooltipEvent(event, getPaintableMap().getPaintable(owner)); + + } + + @Deprecated + public void registerWidgetTooltip(Widget owner, Object key, TooltipInfo info) { + registerTooltip(getPaintableMap().getPaintable(owner), key, info); + } + + @Deprecated + public boolean hasWidgetEventListeners(Widget widget, String eventIdentifier) { + return hasEventListeners(getPaintableMap().getPaintable(widget), + eventIdentifier); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java index fd80b917df..e5006f4a9c 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; /** @@ -192,14 +190,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; } @@ -220,23 +210,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. @@ -311,88 +284,6 @@ 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 - */ - public boolean isDSTInEffect() { - return getTimezoneOffset() != getRawTimezoneOffset(); - } - - /** - * 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. - * - * @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. */ public boolean isTouchDevice() { diff --git a/src/com/vaadin/terminal/gwt/client/ComponentDetail.java b/src/com/vaadin/terminal/gwt/client/ComponentDetail.java index 7fc93b2a3e..8e4e13aa1c 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentDetail.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentDetail.java @@ -11,14 +11,10 @@ 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() { + } /** @@ -53,20 +49,6 @@ class ComponentDetail { private HashMap<Object, TooltipInfo> additionalTooltips; /** - * @return the pid - */ - String getPid() { - return pid; - } - - /** - * @return the component - */ - Paintable getComponent() { - return component; - } - - /** * @return the relativeSize */ FloatSize getRelativeSize() { diff --git a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java index b4489df81e..f49f99a477 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java @@ -78,7 +78,7 @@ public class ComponentLocator { Element e = targetElement; while (true) { - pid = client.getPid(e); + pid = VPaintableMap.get(client).getPid(e); if (pid != null) { break; } @@ -94,7 +94,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 = ((VPaintableWidget) VPaintableMap.get(client).getPaintable(pid)) + .getWidgetForPaintable(); /* * Still if the Paintable contains a widget that implements @@ -364,7 +365,7 @@ public class ComponentLocator { return null; } - String pid = client.getPid(w.getElement()); + String pid = VPaintableMap.get(client).getPid(w.getElement()); if (isStaticPid(pid)) { return pid; } @@ -374,7 +375,7 @@ public class ComponentLocator { } else if (w instanceof VWindow) { VWindow win = (VWindow) w; ArrayList<VWindow> subWindowList = client.getView() - .getSubWindowList(); + .getWidgetForPaintable().getSubWindowList(); int indexOfSubWindow = subWindowList.indexOf(win); return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]"; } else if (w instanceof RootPanel) { @@ -434,10 +435,11 @@ public class ComponentLocator { if (part.equals(ROOT_ID)) { w = RootPanel.get(); } else if (part.equals("")) { - w = client.getView(); + w = client.getView().getWidgetForPaintable(); } else if (w == null) { // Must be static pid (PID_S*) - w = (Widget) client.getPaintable(part); + w = ((VPaintableWidget) VPaintableMap.get(client).getPaintable( + part)).getWidgetForPaintable(); } else if (part.startsWith("domChild[")) { // The target widget has been found and the rest identifies the // element @@ -463,7 +465,8 @@ public class ComponentLocator { * compatibility */ if (widgetClassName.equals("VWindow")) { - iterator = client.getView().getSubWindowList().iterator(); + iterator = client.getView().getWidgetForPaintable() + .getSubWindowList().iterator(); } else if (widgetClassName.equals("VContextMenu")) { return client.getContextMenu(); } else { diff --git a/src/com/vaadin/terminal/gwt/client/Console.java b/src/com/vaadin/terminal/gwt/client/Console.java index 483ab8e0fd..cf5402a2af 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<VPaintableWidget> zeroHeightComponents, + Set<VPaintableWidget> 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 index 83f104886f..b573fd934e 100644 --- a/src/com/vaadin/terminal/gwt/client/Container.java +++ b/src/com/vaadin/terminal/gwt/client/Container.java @@ -8,7 +8,11 @@ import java.util.Set; import com.google.gwt.user.client.ui.Widget; -public interface Container extends Paintable { +/** + * @deprecated To be removed before 7.0.0 + */ +@Deprecated +public interface Container { /** * Replace child of this layout with another component. @@ -33,23 +37,6 @@ public interface Container extends Paintable { 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. * @@ -58,7 +45,7 @@ public interface Container extends Paintable { * @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); + boolean requestLayout(Set<Widget> children); /** * Returns the size currently allocated for the child component. diff --git a/src/com/vaadin/terminal/gwt/client/EventHelper.java b/src/com/vaadin/terminal/gwt/client/EventHelper.java index 600baf8c9d..10822f48c9 100644 --- a/src/com/vaadin/terminal/gwt/client/EventHelper.java +++ b/src/com/vaadin/terminal/gwt/client/EventHelper.java @@ -40,7 +40,7 @@ import com.google.gwt.event.shared.HandlerRegistration; */ public class EventHelper { - public static HandlerRegistration updateFocusHandler(Paintable paintable, + public static HandlerRegistration updateFocusHandler(VPaintableWidget paintable, ApplicationConnection client, HandlerRegistration handlerRegistration) { if (client.hasEventListeners(paintable, FOCUS)) { @@ -57,7 +57,7 @@ public class EventHelper { return null; } - public static HandlerRegistration updateBlurHandler(Paintable paintable, + public static HandlerRegistration updateBlurHandler(VPaintableWidget paintable, ApplicationConnection client, HandlerRegistration handlerRegistration) { if (client.hasEventListeners(paintable, BLUR)) { 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 217013095a..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 = 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/NullConsole.java b/src/com/vaadin/terminal/gwt/client/NullConsole.java index 12df4b323b..31aa4f74d3 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<VPaintableWidget> zeroHeightComponents, + Set<VPaintableWidget> zeroWidthComponents) { } public void log(Throwable e) { diff --git a/src/com/vaadin/terminal/gwt/client/Paintable.java b/src/com/vaadin/terminal/gwt/client/Paintable.java deleted file mode 100644 index 62abeab5a0..0000000000 --- a/src/com/vaadin/terminal/gwt/client/Paintable.java +++ /dev/null @@ -1,18 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client; - -/** - * 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 Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client); -} diff --git a/src/com/vaadin/terminal/gwt/client/UIDL.java b/src/com/vaadin/terminal/gwt/client/UIDL.java index a6298af8d1..b828e644dd 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 VPaintableWidget#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 VPaintableWidget#updateFromUIDL(UIDL, ApplicationConnection) * updateFromUIDL()}. * * @return the name for this section @@ -516,9 +516,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 VPaintable getPaintableAttribute(String name, ApplicationConnection connection) { - return connection.getPaintable(getStringAttribute(name)); + return VPaintableMap.get(connection).getPaintable( + getStringAttribute(name)); } /** @@ -528,9 +529,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 VPaintable getPaintableVariable(String name, ApplicationConnection connection) { - return connection.getPaintable(getStringVariable(name)); + return VPaintableMap.get(connection).getPaintable( + getStringVariable(name)); } /** diff --git a/src/com/vaadin/terminal/gwt/client/Util.java b/src/com/vaadin/terminal/gwt/client/Util.java index 3dbbd22329..4514adf7e0 100644 --- a/src/com/vaadin/terminal/gwt/client/Util.java +++ b/src/com/vaadin/terminal/gwt/client/Util.java @@ -11,10 +11,8 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; -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; @@ -68,7 +66,7 @@ public class Util { }-*/; private static final int LAZY_SIZE_CHANGE_TIMEOUT = 400; - private static Set<Paintable> latelyChangedWidgets = new HashSet<Paintable>(); + private static Set<Widget> latelyChangedWidgets = new HashSet<Widget>(); private static Timer lazySizeChangeTimer = new Timer() { private boolean lazySizeChangeTimerScheduled = false; @@ -107,12 +105,12 @@ public class Util { * @param lazy * run componentSizeUpdated lazyly */ - public static void notifyParentOfSizeChange(Paintable widget, boolean lazy) { + public static void notifyParentOfSizeChange(Widget widget, boolean lazy) { if (lazy) { latelyChangedWidgets.add(widget); lazySizeChangeTimer.schedule(LAZY_SIZE_CHANGE_TIMEOUT); } else { - Set<Paintable> widgets = new HashSet<Paintable>(); + Set<Widget> widgets = new HashSet<Widget>(); widgets.add(widget); Util.componentSizeUpdated(widgets); } @@ -124,15 +122,14 @@ public class Util { * * @param paintables */ - public static void componentSizeUpdated(Set<Paintable> paintables) { - if (paintables.isEmpty()) { + public static void componentSizeUpdated(Set<Widget> widgets) { + if (widgets.isEmpty()) { return; } - Map<Container, Set<Paintable>> childWidgets = new HashMap<Container, Set<Paintable>>(); + Map<Container, Set<Widget>> childWidgets = new HashMap<Container, Set<Widget>>(); - for (Paintable paintable : paintables) { - Widget widget = (Widget) paintable; + for (Widget widget : widgets) { if (!widget.isAttached()) { continue; } @@ -144,19 +141,19 @@ public class Util { parent = parent.getParent(); } if (parent != null) { - Set<Paintable> set = childWidgets.get(parent); + Set<Widget> set = childWidgets.get(parent); if (set == null) { - set = new HashSet<Paintable>(); + set = new HashSet<Widget>(); childWidgets.put((Container) parent, set); } - set.add(paintable); + set.add(widget); } } - Set<Paintable> parentChanges = new HashSet<Paintable>(); + Set<Widget> parentChanges = new HashSet<Widget>(); for (Container parent : childWidgets.keySet()) { if (!parent.requestLayout(childWidgets.get(parent))) { - parentChanges.add(parent); + parentChanges.add((Widget) parent); } } @@ -196,48 +193,6 @@ public class Util { 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 +204,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 +230,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", 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 +247,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 +257,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 +285,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 +315,6 @@ public class Util { int offsetWidth = element.getOffsetWidth(); int offsetHeight = element.getOffsetHeight(); - // if (BrowserInfo.get().isIE6()) { if (offsetHeight < 1) { offsetHeight = 1; } @@ -420,7 +322,6 @@ public class Util { offsetWidth = 10; } element.getStyle().setPropertyPx("width", offsetWidth); - // } element.getStyle().setPropertyPx("height", offsetHeight); @@ -428,9 +329,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"); @@ -714,13 +613,7 @@ 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) { + ApplicationConnection client, HasWidgets container, Widget widget) { /* * Relative sized children must be updated first so the component has * the correct outer dimensions when signaling a size change to the @@ -732,7 +625,7 @@ public class Util { client.handleComponentRelativeSize(w); } - HashSet<Paintable> widgets = new HashSet<Paintable>(); + HashSet<Widget> widgets = new HashSet<Widget>(); widgets.add(widget); Util.componentSizeUpdated(widgets); } @@ -823,33 +716,6 @@ 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 @@ -867,30 +733,27 @@ 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 getChildPaintableForElement( + public static VPaintableWidget getChildPaintableForElement( ApplicationConnection client, Container parent, Element element) { Element rootElement = ((Widget) parent).getElement(); while (element != null && element != rootElement) { - Paintable paintable = client.getPaintable(element); + VPaintableWidget paintable = VPaintableMap.get(client) + .getPaintable(element); if (paintable == null) { String ownerPid = VCaption.getCaptionOwnerPid(element); if (ownerPid != null) { - paintable = client.getPaintable(ownerPid); + paintable = (VPaintableWidget) VPaintableMap.get(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. - } + if (paintable != null + && parent.hasChildComponent(paintable + .getWidgetForPaintable())) { + return paintable; } element = (Element) element.getParentElement(); @@ -906,7 +769,7 @@ public class Util { * <literal>element</literal> is not part of any child component, null is * returned. * - * This method returns the deepest nested Paintable. See + * This method returns the deepest nested VPaintableWidget. See * {@link #getChildPaintableForElement(ApplicationConnection, Container, Element)} * for the immediate child component of parent that contains the element. * @@ -916,18 +779,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 VPaintableWidget getPaintableForElement( ApplicationConnection client, Widget parent, Element element) { Element rootElement = parent.getElement(); while (element != null && element != rootElement) { - Paintable paintable = client.getPaintable(element); + VPaintableWidget paintable = VPaintableMap.get(client) + .getPaintable(element); if (paintable == null) { String ownerPid = VCaption.getCaptionOwnerPid(element); if (ownerPid != null) { - paintable = client.getPaintable(ownerPid); + paintable = (VPaintableWidget) VPaintableMap.get(client) + .getPaintable(ownerPid); } } @@ -965,6 +830,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 VPaintableWidget findPaintable(ApplicationConnection client, + Element element) { + Widget widget = Util.findWidget(element, null); + VPaintableMap vPaintableMap = VPaintableMap.get(client); + while (widget != null && !vPaintableMap.isPaintable(widget)) { + widget = widget.getParent(); + } + return vPaintableMap.getPaintable(widget); + + } + + /** * Helper method to find first instance of given Widget type found by * traversing DOM upwards from given element. * @@ -1073,7 +956,8 @@ public class Util { private static void printPaintablesVariables(ArrayList<String[]> vars, String id, ApplicationConnection c) { - Paintable paintable = c.getPaintable(id); + VPaintableWidget paintable = (VPaintableWidget) VPaintableMap.get(c) + .getPaintable(id); if (paintable != null) { VConsole.log("\t" + id + " (" + paintable.getClass() + ") :"); for (String[] var : vars) { 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..f6d8ffe58e 100644 --- a/src/com/vaadin/terminal/gwt/client/VCaption.java +++ b/src/com/vaadin/terminal/gwt/client/VCaption.java @@ -14,7 +14,7 @@ public class VCaption extends HTML { public static final String CLASSNAME = "v-caption"; - private final Paintable owner; + private final VPaintableWidget owner; private Element errorIndicatorElement; @@ -29,7 +29,6 @@ public class VCaption extends HTML { private final ApplicationConnection client; private boolean placedAfterComponent = false; - private boolean iconOnloadHandled = false; private int maxWidth = -1; @@ -49,13 +48,13 @@ public class VCaption extends HTML { * null * @param client */ - public VCaption(Paintable component, ApplicationConnection client) { + public VCaption(VPaintableWidget component, ApplicationConnection client) { super(); this.client = client; owner = component; if (client != null && owner != null) { - setOwnerPid(getElement(), client.getPid(owner)); + setOwnerPid(getElement(), VPaintableMap.get(client).getPid(owner)); } setStyleName(CLASSNAME); @@ -112,7 +111,6 @@ public class VCaption extends HTML { // Icon forces the caption to be above the component placedAfterComponent = false; - iconOnloadHandled = false; icon.setUri(uidl.getStringAttribute(ATTRIBUTE_ICON)); } else if (icon != null) { @@ -246,16 +244,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,7 +264,8 @@ 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.getWidgetForPaintable(), + true); } else { VConsole.log("Warning: Icon load event was not propagated because VCaption owner is unknown."); } @@ -301,7 +294,7 @@ public class VCaption extends HTML { * * @return owner Widget */ - public Paintable getOwner() { + public VPaintableWidget 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..bc1a240aa9 100644 --- a/src/com/vaadin/terminal/gwt/client/VCaptionWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/VCaptionWrapper.java @@ -5,19 +5,19 @@ 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; + VPaintableWidget paintable; - public VCaptionWrapper(Paintable toBeWrapped, ApplicationConnection client) { + public VCaptionWrapper(VPaintableWidget toBeWrapped, + ApplicationConnection client) { caption = new VCaption(toBeWrapped, client); add(caption); - widget = toBeWrapped; - add((Widget) widget); + paintable = toBeWrapped; + add(paintable.getWidgetForPaintable()); setStyleName(CLASSNAME); } @@ -26,7 +26,7 @@ public class VCaptionWrapper extends FlowPanel { setVisible(!uidl.getBooleanAttribute("invisible")); } - public Paintable getPaintable() { - return widget; + public VPaintableWidget getPaintable() { + return paintable; } } diff --git a/src/com/vaadin/terminal/gwt/client/VConsole.java b/src/com/vaadin/terminal/gwt/client/VConsole.java index a01fa16558..137b6eb2a6 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<VPaintableWidget> zeroHeightComponents, + Set<VPaintableWidget> 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 c43581b000..76c312676a 100644 --- a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java +++ b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java @@ -90,14 +90,14 @@ public class VDebugConsole extends VOverlay implements Console { for (ApplicationConnection a : ApplicationConfiguration .getRunningApplications()) { - Paintable paintable = Util.getPaintableForElement(a, - a.getView(), eventTarget); + VPaintableWidget paintable = Util.getPaintableForElement(a, + a.getView().getWidgetForPaintable(), eventTarget); if (paintable == null) { paintable = Util.getPaintableForElement(a, RootPanel.get(), eventTarget); } if (paintable != null) { - String pid = a.getPid(paintable); + String pid = VPaintableMap.get(a).getPid(paintable); VUIDLBrowser.highlight(paintable); label.setText("Currently focused :" + paintable.getClass() + " ID:" + pid); @@ -119,8 +119,8 @@ public class VDebugConsole extends VOverlay implements Console { .getClientY()); for (ApplicationConnection a : ApplicationConfiguration .getRunningApplications()) { - Paintable paintable = Util.getPaintableForElement(a, - a.getView(), eventTarget); + VPaintableWidget paintable = Util.getPaintableForElement(a, + a.getView().getWidgetForPaintable(), eventTarget); if (paintable == null) { paintable = Util.getPaintableForElement(a, RootPanel.get(), eventTarget); @@ -483,8 +483,8 @@ public class VDebugConsole extends VOverlay implements Console { }-*/; public void printLayoutProblems(ValueMap meta, ApplicationConnection ac, - Set<Paintable> zeroHeightComponents, - Set<Paintable> zeroWidthComponents) { + Set<VPaintableWidget> zeroHeightComponents, + Set<VPaintableWidget> zeroWidthComponents) { JsArray<ValueMap> valueMapArray = meta .getJSValueMapArray("invalidLayouts"); int size = valueMapArray.length(); @@ -521,9 +521,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<VPaintableWidget> zeroHeightComponents, ApplicationConnection ac) { + for (final VPaintableWidget paintable : zeroHeightComponents) { + final Container layout = Util.getLayout(paintable + .getWidgetForPaintable()); VerticalPanel errorDetails = new VerticalPanel(); errorDetails.add(new Label("" + Util.getSimpleName(paintable) @@ -547,7 +548,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 VPaintableWidget paintable = (VPaintableWidget) VPaintableMap + .get(ac).getPaintable(pid); SimpleTree errorNode = new SimpleTree(); VerticalPanel errorDetails = new VerticalPanel(); @@ -565,7 +567,8 @@ 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.getWidgetForPaintable() + .getElement(); Widget.setStyleName(element2, "invalidlayout", emphasisInUi.getValue()); } diff --git a/src/com/vaadin/terminal/gwt/client/VPaintable.java b/src/com/vaadin/terminal/gwt/client/VPaintable.java new file mode 100644 index 0000000000..f7b7eaba83 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/VPaintable.java @@ -0,0 +1,74 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +/** + * 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 VPaintable { + /** + * TODO + * + * @param uidl + * @param client + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client); + + // /** + // * Returns the id for this VPaintable. This must always be what has been + // set + // * using {@link #setId(String)}. + // * + // * @return The id for the VPaintable. + // */ + // public String getId(); + // + // /** + // * Sets the id for the VPaintable. This method is called once by the + // * framework when the VPaintable is initialized and should never be called + // * otherwise. + // * <p> + // * The VPaintable id is used to map the server and the client paintables + // * together. It is unique in this root and assigned by the framework. + // * </p> + // * + // * @param id + // * The id of the paintable. + // */ + // public void setId(String id); + + /** + * Gets ApplicationConnection instance that created this VPaintable. + * + * @return The ApplicationConnection as set by + * {@link #setConnection(ApplicationConnection)} + */ + // public ApplicationConnection getConnection(); + + /** + * Sets the reference to ApplicationConnection. This method is called by the + * framework when the VPaintable is created and should never be called + * otherwise. + * + * @param connection + * The ApplicationConnection that created this VPaintable + */ + // public void setConnection(ApplicationConnection connection); + + /** + * Tests whether the component is enabled or not. A user can not interact + * with disabled components. Disabled components are rendered in a style + * that indicates the status, usually in gray color. Children of a disabled + * component are also disabled. + * + * @return true if the component is enabled, false otherwise + */ + // public boolean isEnabled(); +} diff --git a/src/com/vaadin/terminal/gwt/client/VPaintableMap.java b/src/com/vaadin/terminal/gwt/client/VPaintableMap.java new file mode 100644 index 0000000000..aa3e8e3cb8 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/VPaintableMap.java @@ -0,0 +1,403 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.Paintable; +import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; +import com.vaadin.terminal.gwt.client.RenderInformation.Size; + +public class VPaintableMap { + + private Map<String, VPaintable> idToPaintable = new HashMap<String, VPaintable>(); + private Map<VPaintable, String> paintableToId = new HashMap<VPaintable, String>(); + + public static VPaintableMap get(ApplicationConnection applicationConnection) { + return applicationConnection.getPaintableMap(); + } + + @Deprecated + private final ComponentDetailMap idToComponentDetail = ComponentDetailMap + .create(); + + private Set<String> unregistryBag = new HashSet<String>(); + + /** + * Returns a Paintable by its paintable id + * + * @param id + * The Paintable id + */ + public VPaintable getPaintable(String pid) { + return idToPaintable.get(pid); + } + + /** + * Returns a Paintable element by its root element + * + * @param element + * Root element of the paintable + */ + public VPaintableWidget getPaintable(Element element) { + return (VPaintableWidget) getPaintable(getPid(element)); + } + + /** + * FIXME: What does this even do and why? + * + * @param pid + * @return + */ + public boolean isDragAndDropPaintable(String pid) { + return (pid.startsWith("DD")); + } + + /** + * Checks if a paintable with the given paintable id has been registered. + * + * @param pid + * The paintable id to check for + * @return true if a paintable has been registered with the given paintable + * id, false otherwise + */ + public boolean hasPaintable(String pid) { + return idToPaintable.containsKey(pid); + } + + /** + * Removes all registered paintable ids + */ + public void clear() { + idToPaintable.clear(); + paintableToId.clear(); + idToComponentDetail.clear(); + } + + @Deprecated + public Widget getWidget(VPaintableWidget paintable) { + return paintable.getWidgetForPaintable(); + } + + @Deprecated + public VPaintableWidget getPaintable(Widget widget) { + return getPaintable(widget.getElement()); + } + + public void registerPaintable(String pid, VPaintable paintable) { + ComponentDetail componentDetail = GWT.create(ComponentDetail.class); + idToComponentDetail.put(pid, componentDetail); + idToPaintable.put(pid, paintable); + paintableToId.put(paintable, pid); + if (paintable instanceof VPaintableWidget) { + VPaintableWidget pw = (VPaintableWidget) paintable; + setPid(pw.getWidgetForPaintable().getElement(), pid); + } + } + + private native void setPid(Element el, String pid) + /*-{ + el.tkPid = pid; + }-*/; + + /** + * Gets the paintableId for a specific paintable. + * <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 or null if the paintable could not + * be found + */ + public String getPid(VPaintable paintable) { + if (paintable == null) { + return null; + } + return paintableToId.get(paintable); + } + + @Deprecated + public String getPid(Widget widget) { + return getPid(widget.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 + */ + native String getPid(Element el) + /*-{ + return el.tkPid; + }-*/; + + /** + * 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 getElement(String pid) { + VPaintable p = getPaintable(pid); + if (p instanceof VPaintableWidget) { + return ((VPaintableWidget) p).getWidgetForPaintable().getElement(); + } + + return null; + } + + /** + * 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(VPaintable p) { + + // add to unregistry que + + if (p == null) { + VConsole.error("WARN: Trying to unregister null paintable"); + return; + } + String id = getPid(p); + Widget widget = null; + if (p instanceof VPaintableWidget) { + widget = ((VPaintableWidget) p).getWidgetForPaintable(); + } + + 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."); + // } + } else { + unregistryBag.add(id); + } + if (widget != null && widget instanceof HasWidgets) { + unregisterChildPaintables((HasWidgets) widget); + } + + } + + void purgeUnregistryBag(boolean unregisterPaintables) { + if (unregisterPaintables) { + for (String pid : unregistryBag) { + VPaintable paintable = getPaintable(pid); + if (paintable == null) { + /* + * this should never happen, but it does :-( See e.g. + * com.vaadin.tests.components.accordion.RemoveTabs (with + * test script) + */ + VConsole.error("Tried to unregister component (id=" + + pid + + ") that is never registered (or already unregistered)"); + continue; + } + Widget widget = null; + if (paintable instanceof VPaintableWidget) { + widget = ((VPaintableWidget) paintable) + .getWidgetForPaintable(); + } + + // check if can be cleaned + if (widget == null || !widget.isAttached()) { + // clean reference to paintable + idToComponentDetail.remove(pid); + idToPaintable.remove(pid); + paintableToId.remove(paintable); + } + /* + * else NOP : same component has been reattached to another + * parent or replaced by another component implementation. + */ + } + } + + unregistryBag.clear(); + } + + /** + * 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) { + // FIXME: This should be based on the paintable hierarchy + final Iterator<Widget> it = container.iterator(); + while (it.hasNext()) { + final Widget w = it.next(); + VPaintableWidget p = getPaintable(w); + if (p != null) { + // This will unregister the paintable and all its children + unregisterPaintable(p); + } else if (w instanceof HasWidgets) { + // For normal widget containers, unregister the children + unregisterChildPaintables((HasWidgets) w); + } + } + } + + /** + * FIXME: Should not be here + * + * @param pid + * @param uidl + */ + @Deprecated + public void registerEventListenersFromUIDL(String pid, UIDL uidl) { + ComponentDetail cd = idToComponentDetail.get(pid); + if (cd == null) { + throw new IllegalArgumentException("Pid must not be null"); + } + + cd.registerEventListenersFromUIDL(uidl); + + } + + /** + * FIXME: Should not be here + * + * @param paintable + * @return + */ + @Deprecated + public Size getOffsetSize(VPaintableWidget paintable) { + return getComponentDetail(paintable).getOffsetSize(); + } + + /** + * FIXME: Should not be here + * + * @param paintable + * @return + */ + @Deprecated + public FloatSize getRelativeSize(VPaintableWidget paintable) { + return getComponentDetail(paintable).getRelativeSize(); + } + + /** + * FIXME: Should not be here + * + * @param paintable + * @return + */ + @Deprecated + public void setOffsetSize(VPaintableWidget paintable, Size newSize) { + getComponentDetail(paintable).setOffsetSize(newSize); + } + + /** + * FIXME: Should not be here + * + * @param paintable + * @return + */ + @Deprecated + public void setRelativeSize(VPaintableWidget paintable, + FloatSize relativeSize) { + getComponentDetail(paintable).setRelativeSize(relativeSize); + + } + + private ComponentDetail getComponentDetail(VPaintableWidget paintable) { + return idToComponentDetail.get(getPid(paintable)); + } + + public int size() { + return idToPaintable.size(); + } + + /** + * FIXME: Should be moved to VAbstractPaintableWidget + * + * @param paintable + * @return + */ + @Deprecated + public TooltipInfo getTooltipInfo(VPaintableWidget paintable, Object key) { + return getComponentDetail(paintable).getTooltipInfo(key); + } + + @Deprecated + public TooltipInfo getWidgetTooltipInfo(Widget widget, Object key) { + return getTooltipInfo(getPaintable(widget), key); + } + + public Collection<? extends VPaintable> getPaintables() { + return Collections.unmodifiableCollection(paintableToId.keySet()); + } + + /** + * FIXME: Should not be here + * + * @param paintable + * @return + */ + @Deprecated + public void registerTooltip(VPaintableWidget paintable, Object key, + TooltipInfo tooltip) { + getComponentDetail(paintable).putAdditionalTooltip(key, tooltip); + + } + + /** + * FIXME: Should not be here + * + * @param paintable + * @return + */ + @Deprecated + public boolean hasEventListeners(VPaintableWidget paintable, + String eventIdentifier) { + return getComponentDetail(paintable).hasEventListeners(eventIdentifier); + } + + /** + * Tests if the widget is the root widget of a VPaintableWidget. + * + * @param widget + * The widget to test + * @return true if the widget is the root widget of a VPaintableWidget, + * false otherwise + */ + public boolean isPaintable(Widget w) { + return getPid(w) != null; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/VPaintableWidget.java b/src/com/vaadin/terminal/gwt/client/VPaintableWidget.java new file mode 100644 index 0000000000..11b6c9d0f6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/VPaintableWidget.java @@ -0,0 +1,31 @@ +/* +@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 VPaintableWidget extends VPaintable { + + /** + * TODO: Rename to getWidget + */ + public Widget getWidgetForPaintable(); + + /** + * Returns the parent {@link VPaintableWidgetContainer} + * + * @return + */ + // FIXME: Rename to getParent() + public VPaintableWidgetContainer getParentPaintable(); +} diff --git a/src/com/vaadin/terminal/gwt/client/VPaintableWidgetContainer.java b/src/com/vaadin/terminal/gwt/client/VPaintableWidgetContainer.java new file mode 100644 index 0000000000..baf266546d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/VPaintableWidgetContainer.java @@ -0,0 +1,45 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import com.google.gwt.user.client.ui.HasWidgets; + +/** + * An interface used by client-side paintables whose widget is a component + * container (implements {@link HasWidgets}). + */ +public interface VPaintableWidgetContainer extends VPaintableWidget { + + /** + * 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 paintable + * Child component for which service is requested. + * @param uidl + * UIDL of the child component. + */ + void updateCaption(VPaintableWidget paintable, UIDL uidl); + + /** + * Returns the children for this paintable. + * <p> + * The children for this paintable are defined as all + * {@link VPaintableWidget}s whose parent is this + * {@link VPaintableWidgetContainer}. + * </p> + * + * @return A collection of children for this paintable. An empty collection + * if there are no children. + */ + // public Collection<VPaintableWidget> getChildren(); + +} diff --git a/src/com/vaadin/terminal/gwt/client/VTooltip.java b/src/com/vaadin/terminal/gwt/client/VTooltip.java index ed8bd2eedd..0709fe531e 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 VPaintableWidget tooltipOwner; private boolean closing = false; private boolean opening = false; @@ -110,7 +110,7 @@ public class VTooltip extends VOverlay { } } - public void showTooltip(Paintable owner, Event event, Object key) { + public void showTooltip(VPaintableWidget owner, Event event, Object key) { if (closing && tooltipOwner == owner && tooltipKey == key) { // return to same tooltip, cancel closing closeTimer.cancel(); @@ -207,7 +207,7 @@ public class VTooltip extends VOverlay { } - public void handleTooltipEvent(Event event, Paintable owner, Object key) { + public void handleTooltipEvent(Event event, VPaintableWidget 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..e63bcf98d9 100644 --- a/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java +++ b/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java @@ -24,8 +24,7 @@ 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.VUnknownComponentPaintable; import com.vaadin.terminal.gwt.client.ui.VWindow; public class VUIDLBrowser extends SimpleTree { @@ -98,14 +97,11 @@ public class VUIDLBrowser extends SimpleTree { private String getNodeName(UIDL uidl, ApplicationConfiguration conf, String name) { - Class<? extends Paintable> widgetClassByDecodedTag = conf + Class<? extends VPaintableWidget> widgetClassByDecodedTag = conf .getWidgetClassByEncodedTag(name); - if (widgetClassByDecodedTag == VUnknownComponent.class) { + if (widgetClassByDecodedTag == VUnknownComponentPaintable.class) { return conf.getUnknownServerClassNameByEncodedTagName(name) + "(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 +126,8 @@ public class VUIDLBrowser extends SimpleTree { // same // host page for (ApplicationConnection applicationConnection : runningApplications) { - Paintable paintable = applicationConnection.getPaintable(uidl - .getId()); + VPaintableWidget paintable = (VPaintableWidget) VPaintableMap + .get(applicationConnection).getPaintable(uidl.getId()); highlight(paintable); if (event != null && event.getNativeEvent().getShiftKey()) { applicationConnection.highlightComponent(paintable); @@ -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,8 +239,8 @@ public class VUIDLBrowser extends SimpleTree { } } - static void highlight(Paintable paintable) { - Widget w = (Widget) paintable; + static void highlight(VPaintableWidget paintable) { + Widget w = paintable.getWidgetForPaintable(); if (w != null) { Style style = highlight.getStyle(); style.setTop(w.getAbsoluteTop(), 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/WidgetInstantiator.java b/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java index a5c75d27dd..fef88c38bf 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 VPaintableWidget get(); } diff --git a/src/com/vaadin/terminal/gwt/client/WidgetMap.java b/src/com/vaadin/terminal/gwt/client/WidgetMap.java index 51dac20132..3e02ad23ec 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetMap.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetMap.java @@ -9,15 +9,21 @@ abstract class WidgetMap { protected static HashMap<Class, WidgetInstantiator> instmap = new HashMap<Class, WidgetInstantiator>(); - public Paintable instantiate(Class<? extends Paintable> classType) { + // FIXME: Should use Paintable and not VPaintableWidget + public VPaintableWidget instantiate( + Class<? extends VPaintableWidget> classType) { return instmap.get(classType).get(); } - public abstract Class<? extends Paintable> getImplementationByServerSideClassName( + // FIXME: Should use Paintable and not VPaintableWidget + public abstract Class<? extends VPaintableWidget> getImplementationByServerSideClassName( String fullyqualifiedName); - public abstract Class<? extends Paintable>[] getDeferredLoadedWidgets(); + // FIXME: Should use Paintable and not VPaintableWidget + public abstract Class<? extends VPaintableWidget>[] getDeferredLoadedWidgets(); - public abstract void ensureInstantiator(Class<? extends Paintable> classType); + // FIXME: Should use Paintable and not VPaintableWidget + public abstract void ensureInstantiator( + Class<? extends VPaintableWidget> classType); } diff --git a/src/com/vaadin/terminal/gwt/client/WidgetSet.java b/src/com/vaadin/terminal/gwt/client/WidgetSet.java index fb77775549..390d79ce17 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetSet.java @@ -6,18 +6,9 @@ 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.VFilterSelectPaintable; +import com.vaadin.terminal.gwt.client.ui.VListSelectPaintable; +import com.vaadin.terminal.gwt.client.ui.VUnknownComponentPaintable; public class WidgetSet { @@ -30,7 +21,8 @@ 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 VPaintableWidget}. * * @param uidl * UIDL to be painted with returned component. @@ -40,7 +32,8 @@ public class WidgetSet { * @return New uninitialized and unregistered component that can paint given * UIDL. */ - public Paintable createWidget(UIDL uidl, ApplicationConfiguration conf) { + public VPaintableWidget createWidget(UIDL uidl, + 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. @@ -51,16 +44,15 @@ public class WidgetSet { * TODO should try to get rid of these exceptions here */ - final Class<? extends Paintable> classType = resolveWidgetType(uidl, - conf); - if (classType == null || classType == VUnknownComponent.class) { + final Class<? extends VPaintableWidget> classType = resolveWidgetType( + uidl, conf); + if (classType == null || classType == VUnknownComponentPaintable.class) { String serverSideName = conf .getUnknownServerClassNameByEncodedTagName(uidl.getTag()); - VUnknownComponent c = GWT.create(VUnknownComponent.class); + VUnknownComponentPaintable c = GWT + .create(VUnknownComponentPaintable.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 @@ -70,37 +62,23 @@ public class WidgetSet { } - protected Class<? extends Paintable> resolveWidgetType(UIDL uidl, + protected Class<? extends VPaintableWidget> resolveWidgetType(UIDL uidl, ApplicationConfiguration conf) { final String tag = uidl.getTag(); - Class<? extends Paintable> widgetClass = conf + Class<? extends VPaintableWidget> 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 (widgetClass == VFilterSelectPaintable.class) { if (uidl.hasAttribute("type")) { final String type = uidl.getStringAttribute("type").intern(); if ("legacy-multi" == type) { - return VListSelect.class; + return VListSelectPaintable.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; } @@ -129,12 +107,12 @@ public class WidgetSet { * @param applicationConfiguration * @return */ - public Class<? extends Paintable> getImplementationByClassName( + public Class<? extends VPaintableWidget> getImplementationByClassName( String fullyqualifiedName) { if (fullyqualifiedName == null) { - return VUnknownComponent.class; + return VUnknownComponentPaintable.class; } - Class<? extends Paintable> implementationByServerSideClassName = widgetMap + Class<? extends VPaintableWidget> implementationByServerSideClassName = widgetMap .getImplementationByServerSideClassName(fullyqualifiedName); /* @@ -143,26 +121,19 @@ public class WidgetSet { * 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); + if (fullyqualifiedName.equals("com.vaadin.ui.Select")) { + loadImplementation(VListSelectPaintable.class); } return implementationByServerSideClassName; } - public Class<? extends Paintable>[] getDeferredLoadedWidgets() { + public Class<? extends VPaintableWidget>[] getDeferredLoadedWidgets() { return widgetMap.getDeferredLoadedWidgets(); } - public void loadImplementation(Class<? extends Paintable> nextType) { + public void loadImplementation(Class<? extends VPaintableWidget> nextType) { widgetMap.ensureInstantiator(nextType); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java index 3deb140d30..c272f0ea75 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java @@ -17,10 +17,10 @@ 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.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; public abstract class ClickEventHandler implements DoubleClickHandler, ContextMenuHandler, MouseUpHandler { @@ -30,10 +30,11 @@ public abstract class ClickEventHandler implements DoubleClickHandler, private HandlerRegistration contextMenuHandlerRegistration; protected String clickEventIdentifier; - protected Paintable paintable; + protected VPaintableWidget paintable; private ApplicationConnection client; - public ClickEventHandler(Paintable paintable, String clickEventIdentifier) { + public ClickEventHandler(VPaintableWidget paintable, + String clickEventIdentifier) { this.paintable = paintable; this.clickEventIdentifier = clickEventIdentifier; } @@ -81,7 +82,8 @@ public abstract class ClickEventHandler implements DoubleClickHandler, protected void fireClick(NativeEvent event) { ApplicationConnection client = getApplicationConnection(); - String pid = getApplicationConnection().getPid(paintable); + String pid = VPaintableMap.get(getApplicationConnection()).getPid( + paintable); MouseEventDetails mouseDetails = new MouseEventDetails(event, getRelativeToElement()); @@ -126,11 +128,7 @@ public abstract class ClickEventHandler implements DoubleClickHandler, * or null if no relative coordinates can be calculated. */ protected Element getRelativeToElement() { - if (paintable instanceof Widget) { - return ((Widget) paintable).getElement(); - } - - return null; + return paintable.getWidgetForPaintable().getElement(); } }
\ No newline at end of file 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 1025752ca9..6b22f3c9f3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -21,7 +21,6 @@ import com.google.gwt.user.client.Event; 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; /** * A scrollhandlers similar to {@link ScrollPanel}. @@ -57,18 +56,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..896b992afc 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java @@ -10,25 +10,27 @@ 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.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; public abstract class LayoutClickEventHandler extends ClickEventHandler { - public LayoutClickEventHandler(Paintable paintable, + public LayoutClickEventHandler(VPaintableWidget paintable, String clickEventIdentifier) { super(paintable, clickEventIdentifier); } - protected abstract Paintable getChildComponent(Element element); + protected abstract VPaintableWidget getChildComponent(Element element); @Override protected void fireClick(NativeEvent event) { ApplicationConnection client = getApplicationConnection(); - String pid = getApplicationConnection().getPid(paintable); + String pid = VPaintableMap.get(getApplicationConnection()).getPid( + paintable); MouseEventDetails mouseDetails = new MouseEventDetails(event, getRelativeToElement()); - Paintable childComponent = getChildComponent((Element) event + VPaintableWidget childComponent = getChildComponent((Element) event .getEventTarget().cast()); Map<String, Object> parameters = new HashMap<String, Object>(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java b/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java index 934fcaa8b7..2bd578a45d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java @@ -15,13 +15,12 @@ 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.UIDL; import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; /** @@ -52,12 +51,12 @@ public class ShortcutActionHandler { } /** - * A focusable {@link Paintable} implementing this interface will be + * A focusable {@link VPaintableWidget} 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 VPaintableWidget { /** * This method is called by ShortcutActionHandler before firing the * shortcut if the Paintable is currently focused (aka the target of the @@ -111,7 +110,7 @@ public class ShortcutActionHandler { } } - public void handleKeyboardEvent(final Event event, Paintable target) { + public void handleKeyboardEvent(final Event event, VPaintableWidget target) { final int modifiers = KeyboardListenerCollection .getKeyboardModifiers(event); final char keyCode = (char) DOM.eventGetKeyCode(event); @@ -133,16 +132,12 @@ public class ShortcutActionHandler { } private void fireAction(final Event event, final ShortcutAction a, - Paintable target) { + VPaintableWidget 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 VPaintableWidget finalTarget = target; event.preventDefault(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/Table.java b/src/com/vaadin/terminal/gwt/client/ui/Table.java index 4b3ba0422e..018200ab05 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/Table.java +++ b/src/com/vaadin/terminal/gwt/client/ui/Table.java @@ -5,9 +5,9 @@ package com.vaadin.terminal.gwt.client.ui; import com.google.gwt.user.client.ui.HasWidgets; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.VPaintableWidget; -public interface Table extends Paintable, HasWidgets { +public interface Table extends VPaintableWidget, 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/VAbsoluteLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java index a6abc411f8..f95acfc43c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java @@ -4,7 +4,6 @@ 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; @@ -13,24 +12,19 @@ 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; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; public class VAbsoluteLayout extends ComplexPanel implements Container { @@ -50,26 +44,11 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { private Object previousStyleName; - private Map<String, AbsoluteWrapper> pidToComponentWrappper = new HashMap<String, AbsoluteWrapper>(); + 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); - } - }; + boolean rendering; public VAbsoluteLayout() { setElement(Document.get().createDivElement()); @@ -132,52 +111,13 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { } } - public boolean requestLayout(Set<Paintable> children) { + public boolean requestLayout(Set<Widget> 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 getWrapper(ApplicationConnection client, UIDL componentUIDL) { AbsoluteWrapper wrapper = pidToComponentWrappper.get(componentUIDL .getId()); if (wrapper == null) { @@ -211,9 +151,6 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { canvas.getStyle().setProperty("width", width); if (!rendering) { - if (BrowserInfo.get().isIE6()) { - relayoutWrappersForIe6(); - } relayoutRelativeChildren(); } } @@ -236,21 +173,10 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { 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; @@ -259,10 +185,10 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { private String bottom; private String zIndex; - private Paintable paintable; + private VPaintableWidget paintable; private VCaption caption; - public AbsoluteWrapper(Paintable paintable) { + public AbsoluteWrapper(VPaintableWidget paintable) { this.paintable = paintable; setStyleName(CLASSNAME + "-wrapper"); } @@ -288,7 +214,7 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { @Override public void setWidget(Widget w) { // this fixes #5457 (Widget implementation can change on-the-fly) - paintable = (Paintable) w; + paintable = VPaintableMap.get(client).getPaintable(w); super.setWidget(w); } @@ -302,8 +228,8 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { public void updateFromUIDL(UIDL componentUIDL) { setPosition(componentUIDL.getStringAttribute("css")); - if (getWidget() != paintable) { - setWidget((Widget) paintable); + if (getWidget() != paintable.getWidgetForPaintable()) { + setWidget(paintable.getWidgetForPaintable()); } UIDL childUIDL = componentUIDL.getChildUIDL(0); paintable.updateFromUIDL(childUIDL, client); @@ -311,7 +237,8 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { // 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); + client.handleComponentRelativeSize(paintable + .getWidgetForPaintable()); } } @@ -353,9 +280,6 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { style.setProperty("right", right); style.setProperty("bottom", bottom); - if (BrowserInfo.get().isIE6()) { - ie6Layout(); - } } updateCaptionPosition(); } @@ -369,60 +293,6 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { - 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(); - } } /** @@ -435,8 +305,12 @@ public class VAbsoluteLayout extends ComplexPanel implements Container { * @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) { + VPaintableWidget getComponent(Element element) { return Util.getPaintableForElement(client, this, element); } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayoutPaintable.java new file mode 100644 index 0000000000..9a0b6eacf6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayoutPaintable.java @@ -0,0 +1,84 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashSet; +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +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.EventId; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.VAbsoluteLayout.AbsoluteWrapper; + +public class VAbsoluteLayoutPaintable extends VAbstractPaintableWidgetContainer { + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this, EventId.LAYOUT_CLICK) { + + @Override + protected VPaintableWidget getChildComponent(Element element) { + return getWidgetForPaintable().getComponent(element); + } + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + }; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + getWidgetForPaintable().client = client; + // TODO margin handling + if (client.updateComponent(this, uidl, true)) { + getWidgetForPaintable().rendering = false; + return; + } + + clickEventHandler.handleEventHandlerRegistration(client); + + HashSet<String> unrenderedPids = new HashSet<String>( + getWidgetForPaintable().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()); + getWidgetForPaintable().getWrapper(client, componentUIDL) + .updateFromUIDL(cc); + } + } + + for (String pid : unrenderedPids) { + AbsoluteWrapper absoluteWrapper = getWidgetForPaintable().pidToComponentWrappper + .get(pid); + getWidgetForPaintable().pidToComponentWrappper.remove(pid); + absoluteWrapper.destroy(); + } + getWidgetForPaintable().rendering = false; + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + AbsoluteWrapper parent2 = (AbsoluteWrapper) (component + .getWidgetForPaintable()).getParent(); + parent2.updateCaption(uidl); + } + + @Override + protected Widget createWidget() { + return GWT.create(VAbsoluteLayout.class); + } + + @Override + public VAbsoluteLayout getWidgetForPaintable() { + return (VAbsoluteLayout) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAbstractPaintableWidget.java b/src/com/vaadin/terminal/gwt/client/ui/VAbstractPaintableWidget.java new file mode 100644 index 0000000000..09bf02ec43 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VAbstractPaintableWidget.java @@ -0,0 +1,103 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.VPaintableWidgetContainer; + +public abstract class VAbstractPaintableWidget implements VPaintableWidget { + + private Widget widget; + private ApplicationConnection connection; + private String id; + + /* State variables */ + private boolean enabled = true; + + /** + * Default constructor + */ + public VAbstractPaintableWidget() { + } + + /** + * Called after the application connection reference has been set up + */ + public void init() { + } + + /** + * 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 getWidgetForPaintable() { + if (widget == null) { + widget = createWidget(); + } + + return widget; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.VPaintable#getConnection() + */ + public final ApplicationConnection getConnection() { + return connection; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.VPaintable#setConnection(com.vaadin.terminal + * .gwt.client.ApplicationConnection) + */ + public final void setConnection(ApplicationConnection connection) { + this.connection = connection; + } + + public boolean isEnabled() { + return enabled; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public VPaintableWidgetContainer getParentPaintable() { + // FIXME: Return VPaintableWidgetContainer + // FIXME: Store hierarchy instead of doing lookup every time + + VPaintableMap paintableMap = VPaintableMap.get(getConnection()); + + Widget w = getWidgetForPaintable(); + while (w != null) { + w = w.getParent(); + if (paintableMap.isPaintable(w)) { + return (VPaintableWidgetContainer) paintableMap.getPaintable(w); + } + } + + return null; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAbstractPaintableWidgetContainer.java b/src/com/vaadin/terminal/gwt/client/ui/VAbstractPaintableWidgetContainer.java new file mode 100644 index 0000000000..1f78c02f58 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VAbstractPaintableWidgetContainer.java @@ -0,0 +1,17 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.VPaintableWidgetContainer; + +public abstract class VAbstractPaintableWidgetContainer extends + VAbstractPaintableWidget implements VPaintableWidgetContainer { + + /** + * Default constructor + */ + public VAbstractPaintableWidgetContainer() { + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanel.java index 8715cfffca..1aa9d92770 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanel.java @@ -6,10 +6,7 @@ package com.vaadin.terminal.gwt.client.ui; import java.util.Set; -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.event.dom.client.TouchCancelEvent; import com.google.gwt.event.dom.client.TouchCancelHandler; import com.google.gwt.event.dom.client.TouchEndEvent; @@ -18,9 +15,6 @@ import com.google.gwt.event.dom.client.TouchMoveEvent; 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.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; @@ -30,62 +24,18 @@ 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.Util; import com.vaadin.terminal.gwt.client.VConsole; -public class VSplitPanel extends ComplexPanel implements Container, +public class VAbstractSplitPanel extends ComplexPanel implements Container, ContainerResizedListener { 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 +44,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 +54,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,11 +72,11 @@ public class VSplitPanel extends ComplexPanel implements Container, private boolean positionReversed = false; - private String[] componentStyleNames; + String[] componentStyleNames; private Element draggingCurtain; - private ApplicationConnection client; + ApplicationConnection client; private String width = ""; @@ -137,14 +87,14 @@ public class VSplitPanel extends ComplexPanel implements Container, RenderInformation renderInformation = new RenderInformation(); - private String id; + String id; - private boolean immediate; + boolean immediate; - private boolean rendering = false; + boolean rendering = false; /* The current position of the split handle in either percentages or pixels */ - private String position; + String position; protected Element scrolledContainer; @@ -152,11 +102,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 +206,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 +219,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 +227,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,7 +241,7 @@ public class VSplitPanel extends ComplexPanel implements Container, } } - private void setSplitPosition(String pos) { + void setSplitPosition(String pos) { if (pos == null) { return; } @@ -478,7 +361,7 @@ public class VSplitPanel extends ComplexPanel implements Container, } - private void setFirstWidget(Widget w) { + void setFirstWidget(Widget w) { if (firstChild != null) { firstChild.removeFromParent(); } @@ -486,7 +369,7 @@ public class VSplitPanel extends ComplexPanel implements Container, firstChild = w; } - private void setSecondWidget(Widget w) { + void setSecondWidget(Widget w) { if (secondChild != null) { secondChild.removeFromParent(); } @@ -773,11 +656,11 @@ public class VSplitPanel extends ComplexPanel implements Container, } } - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { // content size change might cause change to its available space // (scrollbars) - for (Paintable paintable : child) { - client.handleComponentRelativeSize((Widget) paintable); + for (Widget widget : children) { + client.handleComponentRelativeSize(widget); } if (height != null && width != null) { /* @@ -796,10 +679,6 @@ public class VSplitPanel extends ComplexPanel implements Container, } - public void updateCaption(Paintable component, UIDL uidl) { - // TODO Implement caption handling - } - /** * Updates the new split position back to server. */ @@ -815,7 +694,7 @@ public class VSplitPanel extends ComplexPanel implements Container, client.updateVariable(id, "position", pos, immediate); } - private void setStylenames() { + void setStylenames() { final String splitterSuffix = (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" : "-vsplitter"); final String firstContainerSuffix = "-first-container"; @@ -850,4 +729,8 @@ public class VSplitPanel extends ComplexPanel implements Container, public boolean isEnabled() { return enabled; } + + public Widget getWidgetForPaintable() { + return this; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanelPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanelPaintable.java new file mode 100644 index 0000000000..84c3ab5221 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanelPaintable.java @@ -0,0 +1,140 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.dom.client.NativeEvent; +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.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public abstract class VAbstractSplitPanelPaintable extends + VAbstractPaintableWidgetContainer { + + public static final String SPLITTER_CLICK_EVENT_IDENTIFIER = "sp_click"; + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + // TODO Implement caption handling + } + + ClickEventHandler clickEventHandler = new ClickEventHandler(this, + SPLITTER_CLICK_EVENT_IDENTIFIER) { + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + if ((Event.getEventsSunk(getWidgetForPaintable().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 getWidgetForPaintable().addHandler(handler, type); + } else { + return getWidgetForPaintable().addDomHandler(handler, type); + } + } + + @Override + public void onContextMenu( + com.google.gwt.event.dom.client.ContextMenuEvent event) { + Element target = event.getNativeEvent().getEventTarget().cast(); + if (getWidgetForPaintable().splitter.isOrHasChild(target)) { + super.onContextMenu(event); + } + }; + + @Override + protected void fireClick(NativeEvent event) { + Element target = event.getEventTarget().cast(); + if (getWidgetForPaintable().splitter.isOrHasChild(target)) { + super.fireClick(event); + } + } + + @Override + protected Element getRelativeToElement() { + return null; + } + + }; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().client = client; + getWidgetForPaintable().id = uidl.getId(); + getWidgetForPaintable().rendering = true; + + getWidgetForPaintable().immediate = uidl.hasAttribute("immediate"); + + if (client.updateComponent(this, uidl, true)) { + getWidgetForPaintable().rendering = false; + return; + } + getWidgetForPaintable().setEnabled( + !uidl.getBooleanAttribute("disabled")); + + clickEventHandler.handleEventHandlerRegistration(client); + if (uidl.hasAttribute("style")) { + getWidgetForPaintable().componentStyleNames = uidl + .getStringAttribute("style").split(" "); + } else { + getWidgetForPaintable().componentStyleNames = new String[0]; + } + + getWidgetForPaintable().setLocked(uidl.getBooleanAttribute("locked")); + + getWidgetForPaintable().setPositionReversed( + uidl.getBooleanAttribute("reversed")); + + getWidgetForPaintable().setStylenames(); + + getWidgetForPaintable().position = uidl.getStringAttribute("position"); + getWidgetForPaintable().setSplitPosition( + getWidgetForPaintable().position); + + final VPaintableWidget newFirstChildPaintable = client + .getPaintable(uidl.getChildUIDL(0)); + final VPaintableWidget newSecondChildPaintable = client + .getPaintable(uidl.getChildUIDL(1)); + Widget newFirstChild = newFirstChildPaintable.getWidgetForPaintable(); + Widget newSecondChild = newSecondChildPaintable.getWidgetForPaintable(); + + if (getWidgetForPaintable().firstChild != newFirstChild) { + if (getWidgetForPaintable().firstChild != null) { + client.unregisterPaintable(VPaintableMap.get(client) + .getPaintable(getWidgetForPaintable().firstChild)); + } + getWidgetForPaintable().setFirstWidget(newFirstChild); + } + if (getWidgetForPaintable().secondChild != newSecondChild) { + if (getWidgetForPaintable().secondChild != null) { + client.unregisterPaintable(VPaintableMap.get(client) + .getPaintable(getWidgetForPaintable().secondChild)); + } + getWidgetForPaintable().setSecondWidget(newSecondChild); + } + newFirstChildPaintable.updateFromUIDL(uidl.getChildUIDL(0), client); + newSecondChildPaintable.updateFromUIDL(uidl.getChildUIDL(1), client); + + getWidgetForPaintable().renderInformation + .updateSize(getWidgetForPaintable().getElement()); + + // This is needed at least for cases like #3458 to take + // appearing/disappearing scrollbars into account. + client.runDescendentsLayout(getWidgetForPaintable()); + + getWidgetForPaintable().rendering = false; + + } + + @Override + public VAbstractSplitPanel getWidgetForPaintable() { + return (VAbstractSplitPanel) super.getWidgetForPaintable(); + } + + @Override + protected abstract VAbstractSplitPanel createWidget(); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java b/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java index 4d0776a5f9..3e4f21477b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java @@ -15,80 +15,40 @@ 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.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; public class VAccordion extends VTabsheetBase implements ContainerResizedListener { public static final String CLASSNAME = "v-accordion"; - private Set<Paintable> paintables = new HashSet<Paintable>(); + private Set<Widget> widgets = new HashSet<Widget>(); private String height; private String width = ""; - private HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>(); + HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>(); private RenderSpace renderSpace = new RenderSpace(0, 0, true); - private StackItem openTab = null; + StackItem openTab = null; - private boolean rendering = false; + boolean rendering = false; - private int selectedUIDLItemIndex = -1; + int selectedUIDLItemIndex = -1; - private RenderInformation renderInformation = new RenderInformation(); + RenderInformation renderInformation = new RenderInformation(); 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 +97,7 @@ public class VAccordion extends VTabsheetBase implements private StackItem moveStackItemIfNeeded(StackItem item, int newIndex, UIDL tabUidl) { UIDL tabContentUIDL = null; - Paintable tabContent = null; + VPaintableWidget tabContent = null; if (tabUidl.getChildCount() > 0) { tabContentUIDL = tabUidl.getChildUIDL(0); tabContent = client.getPaintable(tabContentUIDL); @@ -186,7 +146,7 @@ public class VAccordion extends VTabsheetBase implements return item; } - private void open(int itemIndex) { + void open(int itemIndex) { StackItem item = (StackItem) getWidget(itemIndex); boolean alreadyOpen = false; if (openTab != null) { @@ -209,7 +169,7 @@ public class VAccordion extends VTabsheetBase implements updateOpenTabSize(); } - private void close(StackItem item) { + void close(StackItem item) { if (!item.isOpen()) { return; } @@ -383,12 +343,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); } @@ -434,10 +394,6 @@ public class VAccordion extends VTabsheetBase implements setElement(DOM.createDiv()); caption = new VCaption(null, 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 +415,7 @@ public class VAccordion extends VTabsheetBase implements return content; } - public Widget getPaintable() { + public Widget getChildWidget() { if (getWidgetCount() > 1) { return getWidget(1); } else { @@ -467,14 +423,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); + VPaintableWidget oldPaintable = VPaintableMap.get(client) + .getPaintable(oldWidget); + VPaintableMap.get(client).unregisterPaintable(oldPaintable); + widgets.remove(oldWidget); remove(1); } - add((Widget) newPntbl, content); - paintables.add(newPntbl); + add(newWidget, content); + widgets.add(newWidget); } public void open() { @@ -496,10 +455,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,12 +463,13 @@ 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 VPaintableWidget newPntbl = client.getPaintable(contentUidl); + Widget newWidget = newPntbl.getWidgetForPaintable(); + if (getChildWidget() == null) { + add(newWidget, content); + widgets.add(newWidget); + } else if (getChildWidget() != newWidget) { + replaceWidget(newWidget); } newPntbl.updateFromUIDL(contentUidl, client); if (contentUidl.getBooleanAttribute("cached")) { @@ -521,7 +477,8 @@ public class VAccordion extends VTabsheetBase implements * The size of a cached, relative sized component must be * updated to report correct size. */ - client.handleComponentRelativeSize((Widget) newPntbl); + client.handleComponentRelativeSize(newPntbl + .getWidgetForPaintable()); } if (isOpen() && isDynamicHeight()) { setHeightFromWidget(); @@ -540,8 +497,8 @@ public class VAccordion extends VTabsheetBase implements return DOM.getFirstChild(content).getOffsetWidth(); } - public boolean contains(Paintable p) { - return (getPaintable() == p); + public boolean contains(VPaintableWidget p) { + return (getChildWidget() == p.getWidgetForPaintable()); } public boolean isCaptionVisible() { @@ -565,41 +522,38 @@ public class VAccordion extends VTabsheetBase implements @Override @SuppressWarnings("unchecked") - protected Iterator<Object> getPaintableIterator() { - return (Iterator) paintables.iterator(); + protected Iterator<Widget> getWidgetIterator() { + return widgets.iterator(); } public boolean hasChildComponent(Widget component) { - if (paintables.contains(component)) { - return true; - } else { - return false; + for (Widget w : widgets) { + if (w == component) { + return true; + } } + 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); + if (item.getChildWidget() == oldComponent) { + item.replaceWidget(newComponent); return; } } } - public void updateCaption(Paintable component, UIDL uidl) { - /* Accordion does not render its children's captions */ - } - - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { 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); + for (Widget widget : children) { + client.handleComponentRelativeSize(widget); } return true; @@ -643,15 +597,17 @@ public class VAccordion extends VTabsheetBase implements } @Override - protected Paintable getTab(int index) { + protected VPaintableWidget getTab(int index) { if (index < getWidgetCount()) { - return (Paintable) (getStackItem(index)).getPaintable(); + Widget w = getStackItem(index); + return VPaintableMap.get(client).getPaintable(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/VAccordionPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VAccordionPaintable.java new file mode 100644 index 0000000000..4136d02c30 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VAccordionPaintable.java @@ -0,0 +1,68 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.VAccordion.StackItem; + +public class VAccordionPaintable extends VTabsheetBasePaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + getWidgetForPaintable().selectedUIDLItemIndex = -1; + super.updateFromUIDL(uidl, client); + /* + * Render content after all tabs have been created and we know how large + * the content area is + */ + if (getWidgetForPaintable().selectedUIDLItemIndex >= 0) { + StackItem selectedItem = getWidgetForPaintable().getStackItem( + getWidgetForPaintable().selectedUIDLItemIndex); + UIDL selectedTabUIDL = getWidgetForPaintable().lazyUpdateMap + .remove(selectedItem); + getWidgetForPaintable().open( + getWidgetForPaintable().selectedUIDLItemIndex); + + selectedItem.setContent(selectedTabUIDL); + } else if (!uidl.getBooleanAttribute("cached") + && getWidgetForPaintable().openTab != null) { + getWidgetForPaintable().close(getWidgetForPaintable().openTab); + } + + getWidgetForPaintable().iLayout(); + // finally render possible hidden tabs + if (getWidgetForPaintable().lazyUpdateMap.size() > 0) { + for (Iterator iterator = getWidgetForPaintable().lazyUpdateMap + .keySet().iterator(); iterator.hasNext();) { + StackItem item = (StackItem) iterator.next(); + item.setContent(getWidgetForPaintable().lazyUpdateMap.get(item)); + } + getWidgetForPaintable().lazyUpdateMap.clear(); + } + + getWidgetForPaintable().renderInformation + .updateSize(getWidgetForPaintable().getElement()); + + getWidgetForPaintable().rendering = false; + } + + @Override + public VAccordion getWidgetForPaintable() { + return (VAccordion) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VAccordion.class); + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + /* Accordion does not render its children's captions */ + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAudio.java b/src/com/vaadin/terminal/gwt/client/ui/VAudio.java index 1fdfaca831..f6df827237 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VAudio.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VAudio.java @@ -6,11 +6,6 @@ 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.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.UIDL; public class VAudio extends VMediaBase { private static String CLASSNAME = "v-audio"; @@ -24,26 +19,8 @@ public class VAudio extends VMediaBase { } @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (client.updateComponent(this, uidl, true)) { - return; - } - super.updateFromUIDL(uidl, client); - Style style = audio.getStyle(); - - // Make sure that the controls are not clipped if visible. - if (shouldShowControls(uidl) - && (style.getHeight() == null || "".equals(style.getHeight()))) { - if (BrowserInfo.get().isChrome()) { - style.setHeight(32, Unit.PX); - } else { - style.setHeight(25, Unit.PX); - } - } - } - - @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/VAudioPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VAudioPaintable.java new file mode 100644 index 0000000000..dae602a668 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VAudioPaintable.java @@ -0,0 +1,37 @@ +package com.vaadin.terminal.gwt.client.ui; + +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; + +public class VAudioPaintable extends VMediaBasePaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + super.updateFromUIDL(uidl, client); + Style style = getWidgetForPaintable().getElement().getStyle(); + + // Make sure that the controls are not clipped if visible. + if (shouldShowControls(uidl) + && (style.getHeight() == null || "".equals(style.getHeight()))) { + if (BrowserInfo.get().isChrome()) { + style.setHeight(32, Unit.PX); + } else { + style.setHeight(25, Unit.PX); + } + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VAudio.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VButton.java b/src/com/vaadin/terminal/gwt/client/ui/VButton.java index 03de99ada6..21b5d8bbe8 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VButton.java @@ -21,16 +21,13 @@ 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; -public class VButton extends FocusWidget implements Paintable, ClickHandler, - FocusHandler, BlurHandler { +public class VButton extends FocusWidget implements ClickHandler, FocusHandler, + BlurHandler { public static final String CLASSNAME = "v-button"; private static final String CLASSNAME_PRESSED = "v-pressed"; @@ -42,7 +39,7 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, protected int mousedownX = 0; protected int mousedownY = 0; - protected String id; + protected String paintableId; protected ApplicationConnection client; @@ -65,7 +62,7 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, private int tabIndex = 0; - private boolean disableOnClick = false; + protected boolean disableOnClick = false; /* * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton @@ -88,10 +85,10 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, private boolean disallowNextClick = false; private boolean isHovering; - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; + protected HandlerRegistration focusHandlerRegistration; + protected HandlerRegistration blurHandlerRegistration; - private int clickShortcut = 0; + protected int clickShortcut = 0; public VButton() { super(DOM.createDiv()); @@ -113,64 +110,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); } @@ -191,7 +130,7 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, */ public void onBrowserEvent(Event event) { if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } if (DOM.eventGetType(event) == Event.ONLOAD) { Util.notifyParentOfSizeChange(this, true); @@ -353,7 +292,7 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, * .dom.client.ClickEvent) */ public void onClick(ClickEvent event) { - if (id == null || client == null) { + if (paintableId == null || client == null) { return; } if (BrowserInfo.get().isSafari()) { @@ -361,15 +300,16 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, } if (disableOnClick) { setEnabled(false); - client.updateVariable(id, "disabledOnClick", true, false); + client.updateVariable(paintableId, "disabledOnClick", true, false); } - client.updateVariable(id, "state", true, false); + client.updateVariable(paintableId, "state", true, false); // Add mouse details MouseEventDetails details = new MouseEventDetails( event.getNativeEvent(), getElement()); - client.updateVariable(id, "mousedetails", details.serialize(), true); + client.updateVariable(paintableId, "mousedetails", details.serialize(), + true); clickPending = false; } @@ -451,25 +391,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 @@ -522,11 +443,10 @@ public class VButton extends FocusWidget implements Paintable, ClickHandler, }-*/; public void onFocus(FocusEvent arg0) { - client.updateVariable(id, EventId.FOCUS, "", true); + client.updateVariable(paintableId, EventId.FOCUS, "", true); } public void onBlur(BlurEvent arg0) { - client.updateVariable(id, EventId.BLUR, "", true); + client.updateVariable(paintableId, EventId.BLUR, "", true); } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VButtonPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VButtonPaintable.java new file mode 100644 index 0000000000..98f2888700 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VButtonPaintable.java @@ -0,0 +1,91 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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.EventHelper; +import com.vaadin.terminal.gwt.client.UIDL; + +public class VButtonPaintable extends VAbstractPaintableWidget { + + 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; + } + + getWidgetForPaintable().focusHandlerRegistration = EventHelper + .updateFocusHandler(this, client, + getWidgetForPaintable().focusHandlerRegistration); + getWidgetForPaintable().blurHandlerRegistration = EventHelper + .updateBlurHandler(this, client, + getWidgetForPaintable().blurHandlerRegistration); + + // Save details + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getId(); + + // Set text + getWidgetForPaintable().setText(uidl.getStringAttribute("caption")); + + getWidgetForPaintable().disableOnClick = uidl + .hasAttribute(VButton.ATTR_DISABLE_ON_CLICK); + + // handle error + if (uidl.hasAttribute("error")) { + if (getWidgetForPaintable().errorIndicatorElement == null) { + getWidgetForPaintable().errorIndicatorElement = DOM + .createSpan(); + getWidgetForPaintable().errorIndicatorElement + .setClassName("v-errorindicator"); + } + getWidgetForPaintable().wrapper.insertBefore( + getWidgetForPaintable().errorIndicatorElement, + getWidgetForPaintable().captionElement); + + } else if (getWidgetForPaintable().errorIndicatorElement != null) { + getWidgetForPaintable().wrapper + .removeChild(getWidgetForPaintable().errorIndicatorElement); + getWidgetForPaintable().errorIndicatorElement = null; + } + + if (uidl.hasAttribute("icon")) { + if (getWidgetForPaintable().icon == null) { + getWidgetForPaintable().icon = new Icon(client); + getWidgetForPaintable().wrapper.insertBefore( + getWidgetForPaintable().icon.getElement(), + getWidgetForPaintable().captionElement); + } + getWidgetForPaintable().icon + .setUri(uidl.getStringAttribute("icon")); + } else { + if (getWidgetForPaintable().icon != null) { + getWidgetForPaintable().wrapper + .removeChild(getWidgetForPaintable().icon.getElement()); + getWidgetForPaintable().icon = null; + } + } + + if (uidl.hasAttribute("keycode")) { + getWidgetForPaintable().clickShortcut = uidl + .getIntAttribute("keycode"); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VButton.class); + } + + @Override + public VButton getWidgetForPaintable() { + return (VButton) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java index a2f03d6176..b5c07ca278 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java @@ -40,6 +40,7 @@ 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.label.VLabel; @SuppressWarnings("deprecation") public class VCalendarPanel extends FocusableFlexTable implements @@ -1142,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 @@ -1225,8 +1226,6 @@ public class VCalendarPanel extends FocusableFlexTable implements private ListBox sec; - private ListBox msec; - private ListBox ampm; /** @@ -1291,19 +1290,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()) { @@ -1337,16 +1323,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 } @@ -1422,13 +1398,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); } @@ -1440,9 +1409,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()); } @@ -1505,15 +1471,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); @@ -1696,8 +1653,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; @@ -1746,9 +1701,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/VCheckBox.java b/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java index c43c4d3dba..d92d7e37ed 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java @@ -4,10 +4,6 @@ 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; @@ -18,18 +14,17 @@ 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.Widget; 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 { + Field, FocusHandler, BlurHandler { + + public static final String VARIABLE_STATE = "state"; public static final String CLASSNAME = "v-checkbox"; @@ -39,12 +34,12 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements ApplicationConnection client; - private Element errorIndicatorElement; + Element errorIndicatorElement; - private Icon icon; + Icon icon; - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; + HandlerRegistration focusHandlerRegistration; + HandlerRegistration blurHandlerRegistration; public VCheckBox() { setStyleName(CLASSNAME); @@ -60,7 +55,7 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements event.getNativeEvent(), getElement()); client.updateVariable(id, "mousedetails", details.serialize(), false); - client.updateVariable(id, "state", getValue(), immediate); + client.updateVariable(id, VARIABLE_STATE, getValue(), immediate); } }); @@ -73,91 +68,6 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements } } - 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) @@ -169,7 +79,7 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements Util.notifyParentOfSizeChange(this, true); } if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } } @@ -190,4 +100,9 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements public void onBlur(BlurEvent arg0) { client.updateVariable(id, EventId.BLUR, "", true); } + + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCheckBoxPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VCheckBoxPaintable.java new file mode 100644 index 0000000000..06ef54c13a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VCheckBoxPaintable.java @@ -0,0 +1,96 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; +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.ApplicationConnection; +import com.vaadin.terminal.gwt.client.EventHelper; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VTooltip; + +public class VCheckBoxPaintable extends VAbstractPaintableWidget { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Save details + getWidgetForPaintable().client = client; + getWidgetForPaintable().id = uidl.getId(); + + // Ensure correct implementation + if (client.updateComponent(this, uidl, false)) { + return; + } + + getWidgetForPaintable().focusHandlerRegistration = EventHelper + .updateFocusHandler(this, client, + getWidgetForPaintable().focusHandlerRegistration); + getWidgetForPaintable().blurHandlerRegistration = EventHelper + .updateBlurHandler(this, client, + getWidgetForPaintable().blurHandlerRegistration); + + if (uidl.hasAttribute("error")) { + if (getWidgetForPaintable().errorIndicatorElement == null) { + getWidgetForPaintable().errorIndicatorElement = DOM + .createSpan(); + getWidgetForPaintable().errorIndicatorElement + .setInnerHTML(" "); + DOM.setElementProperty( + getWidgetForPaintable().errorIndicatorElement, + "className", "v-errorindicator"); + DOM.appendChild(getWidgetForPaintable().getElement(), + getWidgetForPaintable().errorIndicatorElement); + DOM.sinkEvents(getWidgetForPaintable().errorIndicatorElement, + VTooltip.TOOLTIP_EVENTS | Event.ONCLICK); + } else { + DOM.setStyleAttribute( + getWidgetForPaintable().errorIndicatorElement, + "display", ""); + } + } else if (getWidgetForPaintable().errorIndicatorElement != null) { + DOM.setStyleAttribute( + getWidgetForPaintable().errorIndicatorElement, "display", + "none"); + } + + if (uidl.hasAttribute("readonly")) { + getWidgetForPaintable().setEnabled(false); + } + + if (uidl.hasAttribute("icon")) { + if (getWidgetForPaintable().icon == null) { + getWidgetForPaintable().icon = new Icon(client); + DOM.insertChild(getWidgetForPaintable().getElement(), + getWidgetForPaintable().icon.getElement(), 1); + getWidgetForPaintable().icon + .sinkEvents(VTooltip.TOOLTIP_EVENTS); + getWidgetForPaintable().icon.sinkEvents(Event.ONCLICK); + } + getWidgetForPaintable().icon + .setUri(uidl.getStringAttribute("icon")); + } else if (getWidgetForPaintable().icon != null) { + // detach icon + DOM.removeChild(getWidgetForPaintable().getElement(), + getWidgetForPaintable().icon.getElement()); + getWidgetForPaintable().icon = null; + } + + // Set text + getWidgetForPaintable().setText(uidl.getStringAttribute("caption")); + getWidgetForPaintable() + .setValue( + uidl.getBooleanVariable(getWidgetForPaintable().VARIABLE_STATE)); + getWidgetForPaintable().immediate = uidl + .getBooleanAttribute("immediate"); + } + + @Override + public VCheckBox getWidgetForPaintable() { + return (VCheckBox) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VCheckBox.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VContextMenu.java b/src/com/vaadin/terminal/gwt/client/ui/VContextMenu.java index 8158fd90c2..e46a74346b 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; @@ -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 index 585180f8f4..e56d333dad 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java @@ -11,9 +11,6 @@ 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; @@ -22,42 +19,27 @@ 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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.ValueMap; -public class VCssLayout extends SimplePanel implements Paintable, Container { +public class VCssLayout extends SimplePanel implements Container { public static final String TAGNAME = "csslayout"; public static final String CLASSNAME = "v-" + TAGNAME; - private FlowPane panel = new FlowPane(); + 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); - } - }; + Element margin = DOM.createDiv(); private boolean hasHeight; private boolean hasWidth; - private boolean rendering; + boolean rendering; public VCssLayout() { super(); @@ -92,32 +74,6 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { } } - 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); } @@ -126,10 +82,6 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { 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>(); @@ -143,7 +95,7 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { public void updateRelativeSizes() { for (Widget w : getChildren()) { - if (w instanceof Paintable) { + if (w instanceof VPaintableWidget) { client.handleComponentRelativeSize(w); } } @@ -169,11 +121,11 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { 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; + final VPaintableWidget child = client.getPaintable(r); + final Widget widget = child.getWidgetForPaintable(); if (widget.getParent() == this) { - oldWidgets.remove(child); - VCaption vCaption = widgetToCaption.get(child); + oldWidgets.remove(widget); + VCaption vCaption = widgetToCaption.get(widget); if (vCaption != null) { addOrMove(vCaption, lastIndex++); oldWidgets.remove(vCaption); @@ -212,8 +164,10 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { // them for (Widget w : oldWidgets) { remove(w); - if (w instanceof Paintable) { - final Paintable p = (Paintable) w; + VPaintableMap paintableMap = VPaintableMap.get(client); + if (paintableMap.isPaintable(w)) { + final VPaintableWidget p = VPaintableMap.get(client) + .getPaintable(w); client.unregisterPaintable(p); } VCaption vCaption = widgetToCaption.remove(w); @@ -251,12 +205,12 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { } } - public void updateCaption(Paintable component, UIDL uidl) { - VCaption caption = widgetToCaption.get(component); + public void updateCaption(VPaintableWidget paintable, UIDL uidl) { + Widget widget = paintable.getWidgetForPaintable(); + VCaption caption = widgetToCaption.get(widget); if (VCaption.isNeeded(uidl)) { - Widget widget = (Widget) component; if (caption == null) { - caption = new VCaption(component, client); + caption = new VCaption(paintable, client); widgetToCaption.put(widget, caption); insert(caption, getWidgetIndex(widget)); lastIndex++; @@ -267,11 +221,11 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { caption.updateCaption(uidl); } else if (caption != null) { remove(caption); - widgetToCaption.remove(component); + widgetToCaption.remove(widget); } } - private Paintable getComponent(Element element) { + VPaintableWidget getComponent(Element element) { return Util .getPaintableForElement(client, VCssLayout.this, element); } @@ -307,7 +261,7 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { return space; } - public boolean requestLayout(Set<Paintable> children) { + public boolean requestLayout(Set<Widget> children) { if (hasSize()) { return true; } else { @@ -339,4 +293,32 @@ public class VCssLayout extends SimplePanel implements Paintable, Container { } return cssProperty; } + + public Widget getWidgetForPaintable() { + return this; + } + + /** + * Sets CSS classes for margin and spacing based on the given parameters. + * + * @param margins + * A {@link VMarginInfo} object that provides info on + * top/left/bottom/right margins + * @param spacing + * true to enable spacing, false otherwise + */ + protected void setMarginAndSpacingStyles(VMarginInfo margins, + boolean spacing) { + 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()); + + setStyleName(margin, VCssLayout.CLASSNAME + "-" + "spacing", spacing); + + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCssLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VCssLayoutPaintable.java new file mode 100644 index 0000000000..1b4db40962 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VCssLayoutPaintable.java @@ -0,0 +1,61 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; +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.EventId; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VCssLayoutPaintable extends VAbstractPaintableWidgetContainer { + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this, EventId.LAYOUT_CLICK) { + + @Override + protected VPaintableWidget getChildComponent(Element element) { + return getWidgetForPaintable().panel.getComponent(element); + } + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + }; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + + if (client.updateComponent(this, uidl, true)) { + getWidgetForPaintable().rendering = false; + return; + } + clickEventHandler.handleEventHandlerRegistration(client); + + getWidgetForPaintable().setMarginAndSpacingStyles( + new VMarginInfo(uidl.getIntAttribute("margins")), + uidl.hasAttribute("spacing")); + getWidgetForPaintable().panel.updateFromUIDL(uidl, client); + getWidgetForPaintable().rendering = false; + } + + @Override + public VCssLayout getWidgetForPaintable() { + return (VCssLayout) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VCssLayout.class); + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + getWidgetForPaintable().panel.updateCaption(component, uidl); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java b/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java index 1fb3f297ad..ca18104c86 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java @@ -6,78 +6,28 @@ 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; + ApplicationConnection client; + boolean rendering; private String width; - private RenderSpace renderSpace = new RenderSpace(); + 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 updateDynamicSize() { boolean updated = false; if (isDynamicWidth()) { int childWidth = Util.getRequiredWidth(getWidget()); @@ -118,11 +68,7 @@ public class VCustomComponent extends SimplePanel implements Container { } } - public void updateCaption(Paintable component, UIDL uidl) { - // NOP, custom component dont render composition roots caption - } - - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { // If a child grows in size, it will not necessarily be calculated // correctly unless we remove previous size definitions if (isDynamicWidth()) { @@ -165,4 +111,8 @@ public class VCustomComponent extends SimplePanel implements Container { } } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomComponentPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VCustomComponentPaintable.java new file mode 100644 index 0000000000..7c0568e28e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VCustomComponentPaintable.java @@ -0,0 +1,79 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VCustomComponentPaintable extends + VAbstractPaintableWidgetContainer { + + public void updateFromUIDL(UIDL uidl, final ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + if (client.updateComponent(this, uidl, true)) { + getWidgetForPaintable().rendering = false; + return; + } + getWidgetForPaintable().client = client; + + final UIDL child = uidl.getChildUIDL(0); + if (child != null) { + final VPaintableWidget paintable = client.getPaintable(child); + Widget widget = paintable.getWidgetForPaintable(); + if (widget != getWidgetForPaintable().getWidget()) { + if (getWidgetForPaintable().getWidget() != null) { + client.unregisterPaintable(VPaintableMap.get(client) + .getPaintable(getWidgetForPaintable().getWidget())); + getWidgetForPaintable().clear(); + } + getWidgetForPaintable().setWidget(widget); + } + paintable.updateFromUIDL(child, client); + } + + boolean updateDynamicSize = getWidgetForPaintable().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(getWidgetForPaintable()); + + } + }); + } + + getWidgetForPaintable().renderSpace.setWidth(getWidgetForPaintable() + .getElement().getOffsetWidth()); + getWidgetForPaintable().renderSpace.setHeight(getWidgetForPaintable() + .getElement().getOffsetHeight()); + + /* + * Needed to update client size if the size of this component has + * changed and the child uses relative size(s). + */ + client.runDescendentsLayout(getWidgetForPaintable()); + + getWidgetForPaintable().rendering = false; + } + + @Override + protected Widget createWidget() { + return GWT.create(VCustomComponent.class); + } + + @Override + public VCustomComponent getWidgetForPaintable() { + return (VCustomComponent) super.getWidgetForPaintable(); + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + // NOP, custom component dont render composition roots caption + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java index 2a40cd0fe3..7a5f630587 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java @@ -20,13 +20,14 @@ 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.Util; import com.vaadin.terminal.gwt.client.VCaption; import com.vaadin.terminal.gwt.client.VCaptionWrapper; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; /** * Custom Layout implements complex layout defined with HTML template. @@ -34,8 +35,8 @@ import com.vaadin.terminal.gwt.client.VCaptionWrapper; * @author Vaadin Ltd * */ -public class VCustomLayout extends ComplexPanel implements Paintable, - Container, ContainerResizedListener { +public class VCustomLayout extends ComplexPanel implements Container, + ContainerResizedListener { public static final String CLASSNAME = "v-customlayout"; @@ -43,21 +44,21 @@ 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<VPaintableWidget, VCaptionWrapper> paintableToCaptionWrapper = new HashMap<VPaintableWidget, 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; @@ -131,64 +132,8 @@ 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) { + void initializeHTML(UIDL uidl, ApplicationConnection client) { final String newTemplateContents = uidl .getStringAttribute("templateContents"); @@ -261,7 +206,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, return false; }-*/; - private boolean hasTemplate() { + boolean hasTemplate() { if (currentTemplateName == null && !hasTemplateContents) { return false; } else { @@ -292,7 +237,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) @@ -384,24 +329,26 @@ public class VCustomLayout extends ComplexPanel implements Paintable, } /** Update caption for given widget */ - public void updateCaption(Paintable component, UIDL uidl) { - VCaptionWrapper wrapper = widgetToCaptionWrapper.get(component); + public void updateCaption(VPaintableWidget paintable, UIDL uidl) { + VCaptionWrapper wrapper = paintableToCaptionWrapper.get(paintable); + Widget widget = paintable.getWidgetForPaintable(); if (VCaption.isNeeded(uidl)) { 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); + paintableToCaptionWrapper.put(paintable, wrapper); } wrapper.updateCaption(uidl); } 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)); + paintableToCaptionWrapper.remove(paintable); } } } @@ -421,14 +368,15 @@ public class VCustomLayout extends ComplexPanel implements Paintable, /** Removes given widget from the layout */ @Override public boolean remove(Widget w) { - client.unregisterPaintable((Paintable) w); + VPaintableWidget paintable = VPaintableMap.get(client).getPaintable(w); + client.unregisterPaintable(paintable); final String location = getLocation(w); if (location != null) { locationToWidget.remove(location); } - final VCaptionWrapper cw = widgetToCaptionWrapper.get(w); + final VCaptionWrapper cw = paintableToCaptionWrapper.get(paintable); if (cw != null) { - widgetToCaptionWrapper.remove(w); + paintableToCaptionWrapper.remove(paintable); return super.remove(cw); } else if (w != null) { return super.remove(w); @@ -447,7 +395,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, public void clear() { super.clear(); locationToWidget.clear(); - widgetToCaptionWrapper.clear(); + paintableToCaptionWrapper.clear(); } public void iLayout() { @@ -513,7 +461,7 @@ public class VCustomLayout extends ComplexPanel implements Paintable, } }-*/; - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { updateRelativeSizedComponents(true, true); if (width.equals("") || height.equals("")) { @@ -642,4 +590,8 @@ public class VCustomLayout extends ComplexPanel implements Paintable, return larger; } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VCustomLayoutPaintable.java new file mode 100644 index 0000000000..01190b39be --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VCustomLayoutPaintable.java @@ -0,0 +1,87 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashSet; +import java.util.Iterator; +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.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VCustomLayoutPaintable extends VAbstractPaintableWidgetContainer { + + /** Update the layout from UIDL */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().client = client; + // ApplicationConnection manages generic component features + if (client.updateComponent(this, uidl, true)) { + return; + } + + getWidgetForPaintable().pid = uidl.getId(); + if (!getWidgetForPaintable().hasTemplate()) { + // Update HTML template only once + getWidgetForPaintable().initializeHTML(uidl, client); + } + + // Evaluate scripts + VCustomLayout.eval(getWidgetForPaintable().scripts); + getWidgetForPaintable().scripts = null; + + getWidgetForPaintable().iLayout(); + // TODO Check if this is needed + client.runDescendentsLayout(getWidgetForPaintable()); + + Set<Widget> oldWidgets = new HashSet<Widget>(); + oldWidgets.addAll(getWidgetForPaintable().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"); + UIDL childUIDL = uidlForChild.getChildUIDL(0); + final VPaintableWidget childPaintable = client + .getPaintable(childUIDL); + Widget childWidget = childPaintable.getWidgetForPaintable(); + try { + getWidgetForPaintable().setWidget(childWidget, location); + childPaintable.updateFromUIDL(childUIDL, client); + } catch (final IllegalArgumentException e) { + // If no location is found, this component is not visible + } + oldWidgets.remove(childWidget); + } + } + for (Iterator<Widget> iterator = oldWidgets.iterator(); iterator + .hasNext();) { + Widget oldWidget = iterator.next(); + if (oldWidget.isAttached()) { + // slot of this widget is emptied, remove it + getWidgetForPaintable().remove(oldWidget); + } + } + + getWidgetForPaintable().iLayout(); + // TODO Check if this is needed + client.runDescendentsLayout(getWidgetForPaintable()); + + } + + @Override + public VCustomLayout getWidgetForPaintable() { + return (VCustomLayout) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VCustomLayout.class); + } + + public void updateCaption(VPaintableWidget paintable, UIDL uidl) { + getWidgetForPaintable().updateCaption(paintable, uidl); + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDateField.java b/src/com/vaadin/terminal/gwt/client/ui/VDateField.java index bc228675b2..14c3b99210 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDateField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VDateField.java @@ -10,19 +10,15 @@ 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; -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 +28,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 +60,7 @@ public class VDateField extends FlowPanel implements Paintable, Field { protected DateTimeService dts; - private boolean showISOWeekNumbers = false; + protected boolean showISOWeekNumbers = false; public VDateField() { setStyleName(CLASSNAME); @@ -77,84 +72,7 @@ public class VDateField extends FlowPanel implements Paintable, Field { public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (client != null) { - client.handleTooltipEvent(event, this); - } - } - - 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); + client.handleWidgetTooltipEvent(event, this); } } @@ -162,7 +80,7 @@ public class VDateField extends FlowPanel implements Paintable, Field { * 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 +149,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/VDateFieldCalendar.java index 91388edcaf..6bf1d4a3a7 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java @@ -7,20 +7,16 @@ package com.vaadin.terminal.gwt.client.ui; 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; /** * 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/VDateFieldCalendarPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendarPaintable.java new file mode 100644 index 0000000000..8dced06235 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendarPaintable.java @@ -0,0 +1,101 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +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.VCalendarPanel.FocusChangeListener; +import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener; + +public class VDateFieldCalendarPaintable extends VDateFieldPaintable { + + @Override + @SuppressWarnings("deprecation") + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + getWidgetForPaintable().calendarPanel + .setShowISOWeekNumbers(getWidgetForPaintable() + .isShowISOWeekNumbers()); + getWidgetForPaintable().calendarPanel + .setDateTimeService(getWidgetForPaintable() + .getDateTimeService()); + getWidgetForPaintable().calendarPanel + .setResolution(getWidgetForPaintable().getCurrentResolution()); + Date currentDate = getWidgetForPaintable().getCurrentDate(); + if (currentDate != null) { + getWidgetForPaintable().calendarPanel.setDate(new Date(currentDate + .getTime())); + } else { + getWidgetForPaintable().calendarPanel.setDate(null); + } + + if (getWidgetForPaintable().currentResolution > VDateField.RESOLUTION_DAY) { + getWidgetForPaintable().calendarPanel + .setTimeChangeListener(new TimeChangeListener() { + public void changed(int hour, int min, int sec, int msec) { + Date d = getWidgetForPaintable().getDate(); + if (d == null) { + // date currently null, use the value from + // calendarPanel + // (~ client time at the init of the widget) + d = (Date) getWidgetForPaintable().calendarPanel + .getDate().clone(); + } + d.setHours(hour); + d.setMinutes(min); + d.setSeconds(sec); + DateTimeService.setMilliseconds(d, msec); + + // Always update time changes to the server + getWidgetForPaintable().calendarPanel.setDate(d); + getWidgetForPaintable().updateValueFromPanel(); + } + }); + } + + if (getWidgetForPaintable().currentResolution <= VDateField.RESOLUTION_MONTH) { + getWidgetForPaintable().calendarPanel + .setFocusChangeListener(new FocusChangeListener() { + public void focusChanged(Date date) { + Date date2 = new Date(); + if (getWidgetForPaintable().calendarPanel.getDate() != null) { + date2.setTime(getWidgetForPaintable().calendarPanel + .getDate().getTime()); + } + /* + * Update the value of calendarPanel + */ + date2.setYear(date.getYear()); + date2.setMonth(date.getMonth()); + getWidgetForPaintable().calendarPanel + .setDate(date2); + /* + * Then update the value from panel to server + */ + getWidgetForPaintable().updateValueFromPanel(); + } + }); + } else { + getWidgetForPaintable().calendarPanel.setFocusChangeListener(null); + } + + // Update possible changes + getWidgetForPaintable().calendarPanel.renderCalendar(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VDateFieldCalendar.class); + } + + @Override + public VDateFieldCalendar getWidgetForPaintable() { + return (VDateFieldCalendar) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDateFieldPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VDateFieldPaintable.java new file mode 100644 index 0000000000..4aaf1c2dd3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VDateFieldPaintable.java @@ -0,0 +1,106 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.VConsole; + +public class VDateFieldPaintable extends VAbstractPaintableWidget { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Ensure correct implementation and let layout manage caption + if (client.updateComponent(this, uidl, true)) { + return; + } + + // Save details + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getId(); + getWidgetForPaintable().immediate = uidl + .getBooleanAttribute("immediate"); + + getWidgetForPaintable().readonly = uidl.getBooleanAttribute("readonly"); + getWidgetForPaintable().enabled = !uidl.getBooleanAttribute("disabled"); + + if (uidl.hasAttribute("locale")) { + final String locale = uidl.getStringAttribute("locale"); + try { + getWidgetForPaintable().dts.setLocale(locale); + getWidgetForPaintable().currentLocale = locale; + } catch (final LocaleNotLoadedException e) { + getWidgetForPaintable().currentLocale = getWidgetForPaintable().dts + .getLocale(); + VConsole.error("Tried to use an unloaded locale \"" + locale + + "\". Using default locale (" + + getWidgetForPaintable().currentLocale + ")."); + VConsole.error(e); + } + } + + // We show week numbers only if the week starts with Monday, as ISO 8601 + // specifies + getWidgetForPaintable().showISOWeekNumbers = uidl + .getBooleanAttribute(VDateField.WEEK_NUMBERS) + && getWidgetForPaintable().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; + } + + getWidgetForPaintable().currentResolution = newResolution; + + // Add stylename that indicates current resolution + getWidgetForPaintable() + .addStyleName( + VDateField.CLASSNAME + + "-" + + VDateField + .resolutionToString(getWidgetForPaintable().currentResolution)); + + final int year = uidl.getIntVariable("year"); + final int month = (getWidgetForPaintable().currentResolution >= VDateField.RESOLUTION_MONTH) ? uidl + .getIntVariable("month") : -1; + final int day = (getWidgetForPaintable().currentResolution >= VDateField.RESOLUTION_DAY) ? uidl + .getIntVariable("day") : -1; + final int hour = (getWidgetForPaintable().currentResolution >= VDateField.RESOLUTION_HOUR) ? uidl + .getIntVariable("hour") : 0; + final int min = (getWidgetForPaintable().currentResolution >= VDateField.RESOLUTION_MIN) ? uidl + .getIntVariable("min") : 0; + final int sec = (getWidgetForPaintable().currentResolution >= VDateField.RESOLUTION_SEC) ? uidl + .getIntVariable("sec") : 0; + + // Construct new date for this datefield (only if not null) + if (year > -1) { + getWidgetForPaintable().setCurrentDate( + new Date((long) getWidgetForPaintable().getTime(year, + month, day, hour, min, sec, 0))); + } else { + getWidgetForPaintable().setCurrentDate(null); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VDateField.class); + } + + @Override + public VDateField getWidgetForPaintable() { + return (VDateField) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java index 072754e0ce..0297e8df3f 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java @@ -4,10 +4,8 @@ package com.vaadin.terminal.gwt.client.ui; 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; @@ -26,12 +24,13 @@ 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.Util; import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.VPaintable; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.VTooltip; import com.vaadin.terminal.gwt.client.ValueMap; import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; @@ -95,7 +94,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements super.onBrowserEvent(event); if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } } @@ -109,16 +108,12 @@ 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(VPaintableMap.get(client).getPaintable( + VDragAndDropWrapper.this)); + VPaintableWidget paintable = Util.findPaintable(client, + (Element) event.getEventTarget().cast()); + Widget widget = paintable.getWidgetForPaintable(); transferable.setData("component", paintable); VDragEvent dragEvent = VDragAndDropManager.get().startDrag( transferable, event, true); @@ -129,8 +124,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements if (dragStartMode == WRAPPER) { dragEvent.createDragImage(getElement(), true); } else { - dragEvent.createDragImage(((Widget) paintable).getElement(), - true); + dragEvent.createDragImage(widget.getElement(), true); } return true; } @@ -144,58 +138,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 +186,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 +242,8 @@ public class VDragAndDropWrapper extends VCustomComponent implements } if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) { VTransferable transferable = new VTransferable(); - transferable.setDragSource(this); + transferable.setDragSource(VPaintableMap.get(client) + .getPaintable(this)); vaadinDragEvent = VDragAndDropManager.get().startDrag( transferable, event, false); @@ -459,8 +411,8 @@ 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); @@ -468,7 +420,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements } private String getPid() { - return client.getPid(this); + return VPaintableMap.get(client).getPid((VPaintable) this); } public VDropHandler getDropHandler() { @@ -547,8 +499,9 @@ public class VDragAndDropWrapper extends VCustomComponent implements } @Override - public Paintable getPaintable() { - return VDragAndDropWrapper.this; + public VPaintableWidget getPaintable() { + return VPaintableMap.get(client).getPaintable( + VDragAndDropWrapper.this); } public ApplicationConnection getApplicationConnection() { diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java index ce4a19462f..438ec49873 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java @@ -1,4 +1,4 @@ -/* +/* @VaadinApache2LicenseForJavaFiles@ */ diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperPaintable.java new file mode 100644 index 0000000000..2a3189d9ba --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperPaintable.java @@ -0,0 +1,71 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; + +public class VDragAndDropWrapperPaintable extends VCustomComponentPaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().client = client; + super.updateFromUIDL(uidl, client); + if (!uidl.hasAttribute("cached") && !uidl.hasAttribute("hidden")) { + UIDL acceptCrit = uidl.getChildByTagName("-ac"); + if (acceptCrit == null) { + getWidgetForPaintable().dropHandler = null; + } else { + if (getWidgetForPaintable().dropHandler == null) { + getWidgetForPaintable().dropHandler = getWidgetForPaintable().new CustomDropHandler(); + } + getWidgetForPaintable().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 (getWidgetForPaintable().fileIdToReceiver == null) { + getWidgetForPaintable().fileIdToReceiver = new HashMap<String, String>(); + } + if ("".equals(receiverUrl)) { + Integer id = Integer.parseInt(fileId); + int indexOf = getWidgetForPaintable().fileIds + .indexOf(id); + if (indexOf != -1) { + getWidgetForPaintable().files.remove(indexOf); + getWidgetForPaintable().fileIds.remove(indexOf); + } + } else { + getWidgetForPaintable().fileIdToReceiver.put(fileId, + receiverUrl); + } + } + } + getWidgetForPaintable().startNextUpload(); + + getWidgetForPaintable().dragStartMode = uidl + .getIntAttribute(VDragAndDropWrapper.DRAG_START_MODE); + getWidgetForPaintable().initDragStartMode(); + getWidgetForPaintable().html5DataFlavors = uidl + .getMapAttribute(VDragAndDropWrapper.HTML5_DATA_FLAVORS); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VDragAndDropWrapper.class); + } + + @Override + public VDragAndDropWrapper getWidgetForPaintable() { + return (VDragAndDropWrapper) super.getWidgetForPaintable(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java b/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java index 17a95388b9..549a7b4549 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java @@ -8,190 +8,31 @@ 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 class VEmbedded extends HTML { + public static String CLASSNAME = "v-embedded"; - private static String CLASSNAME = "v-embedded"; + protected String height; + protected String width; + protected Element browserElement; - private String height; - private String width; - private Element browserElement; + protected String type; - 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); - } - - }; + protected ApplicationConnection client; 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 (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); - - } 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 @@ -200,7 +41,7 @@ public class VEmbedded extends HTML implements Paintable { * The UIDL * @return Tags concatenated into a string */ - private String createFlashEmbed(UIDL uidl) { + 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 @@ -318,7 +159,7 @@ public class VEmbedded extends HTML implements Paintable { * @param uidl * @return */ - private static Map<String, String> getParameters(UIDL uidl) { + protected static Map<String, String> getParameters(UIDL uidl) { Map<String, String> parameters = new HashMap<String, String>(); Iterator<Object> childIterator = uidl.getChildIterator(); @@ -347,7 +188,7 @@ public class VEmbedded extends HTML implements Paintable { * @param client * @return */ - private String getSrc(UIDL uidl, ApplicationConnection client) { + protected String getSrc(UIDL uidl, ApplicationConnection client) { String url = client.translateVaadinUri(uidl.getStringAttribute("src")); if (url == null) { return ""; @@ -412,7 +253,7 @@ public class VEmbedded extends HTML implements Paintable { Util.notifyParentOfSizeChange(this, true); } - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } /** @@ -432,5 +273,4 @@ public class VEmbedded extends HTML implements Paintable { Unit.PX); } } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VEmbeddedPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VEmbeddedPaintable.java new file mode 100644 index 0000000000..bdb34b6d14 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VEmbeddedPaintable.java @@ -0,0 +1,198 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import java.util.Map; + +import com.google.gwt.core.client.GWT; +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.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.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.VTooltip; + +public class VEmbeddedPaintable extends VAbstractPaintableWidget { + + public static final String CLICK_EVENT_IDENTIFIER = "click"; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + // Save details + getWidgetForPaintable().client = client; + + boolean clearBrowserElement = true; + + clickEventHandler.handleEventHandlerRegistration(client); + + if (uidl.hasAttribute("type")) { + getWidgetForPaintable().type = uidl.getStringAttribute("type"); + if (getWidgetForPaintable().type.equals("image")) { + getWidgetForPaintable().addStyleName( + VEmbedded.CLASSNAME + "-image"); + Element el = null; + boolean created = false; + NodeList<Node> nodes = getWidgetForPaintable().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) { + getWidgetForPaintable().setHTML(""); + el = DOM.createImg(); + created = true; + 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", getWidgetForPaintable() + .getSrc(uidl, client)); + + if (created) { + // insert in dom late + getWidgetForPaintable().getElement().appendChild(el); + } + + /* + * Sink tooltip events so tooltip is displayed when hovering the + * image. + */ + getWidgetForPaintable().sinkEvents(VTooltip.TOOLTIP_EVENTS); + + } else if (getWidgetForPaintable().type.equals("browser")) { + getWidgetForPaintable().addStyleName( + VEmbedded.CLASSNAME + "-browser"); + if (getWidgetForPaintable().browserElement == null) { + getWidgetForPaintable().setHTML( + "<iframe width=\"100%\" height=\"100%\" frameborder=\"0\"" + + " allowTransparency=\"true\" src=\"\"" + + " name=\"" + uidl.getId() + + "\"></iframe>"); + getWidgetForPaintable().browserElement = DOM + .getFirstChild(getWidgetForPaintable().getElement()); + } + DOM.setElementAttribute(getWidgetForPaintable().browserElement, + "src", getWidgetForPaintable().getSrc(uidl, client)); + clearBrowserElement = false; + } else { + VConsole.log("Unknown Embedded type '" + + getWidgetForPaintable().type + "'"); + } + } else if (uidl.hasAttribute("mimetype")) { + final String mime = uidl.getStringAttribute("mimetype"); + if (mime.equals("application/x-shockwave-flash")) { + // Handle embedding of Flash + getWidgetForPaintable().addStyleName( + VEmbedded.CLASSNAME + "-flash"); + getWidgetForPaintable().setHTML( + getWidgetForPaintable().createFlashEmbed(uidl)); + + } else if (mime.equals("image/svg+xml")) { + getWidgetForPaintable().addStyleName( + VEmbedded.CLASSNAME + "-svg"); + String data; + Map<String, String> parameters = VEmbedded.getParameters(uidl); + if (parameters.get("data") == null) { + data = getWidgetForPaintable().getSrc(uidl, client); + } else { + data = "data:image/svg+xml," + parameters.get("data"); + } + getWidgetForPaintable().setHTML(""); + ObjectElement obj = Document.get().createObjectElement(); + obj.setType(mime); + obj.setData(data); + if (getWidgetForPaintable().width != null) { + obj.getStyle().setProperty("width", "100%"); + } + if (getWidgetForPaintable().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")); + } + getWidgetForPaintable().getElement().appendChild(obj); + + } else { + VConsole.log("Unknown Embedded mimetype '" + mime + "'"); + } + } else { + VConsole.log("Unknown Embedded; no type or mimetype attribute"); + } + + if (clearBrowserElement) { + getWidgetForPaintable().browserElement = null; + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VEmbedded.class); + } + + @Override + public VEmbedded getWidgetForPaintable() { + return (VEmbedded) super.getWidgetForPaintable(); + } + + protected final ClickEventHandler clickEventHandler = new ClickEventHandler( + this, CLICK_EVENT_IDENTIFIER) { + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + + }; +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java index 9a6cbdcd9b..7006a82fd1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java @@ -12,9 +12,7 @@ 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.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -48,7 +46,6 @@ 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.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; @@ -60,9 +57,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 +150,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(); @@ -762,12 +758,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 +773,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 +800,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) * @@ -822,7 +812,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (client != null) { - client.handleTooltipEvent(event, VFilterSelect.this); + client.handleWidgetTooltipEvent(event, VFilterSelect.this); } } @@ -837,7 +827,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 @@ -855,7 +845,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (client != null) { - client.handleTooltipEvent(event, VFilterSelect.this); + client.handleWidgetTooltipEvent(event, VFilterSelect.this); } /* @@ -868,73 +858,73 @@ 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; + protected boolean popupOpenerClicked; private String width = null; private int textboxPadding = -1; private int componentPadding = -1; - private int suggestionPopupMinWidth = 0; + 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; + protected String lastNewItemString; + protected boolean focused = false; private int horizPaddingAndBorder = 2; /** @@ -1037,204 +1027,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(""); - } - } - 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); @@ -1251,22 +1048,13 @@ 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); - } + protected void setTextboxText(final String text) { + tb.setText(text); } /* @@ -1289,7 +1077,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, * 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); @@ -1304,7 +1092,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; @@ -1354,7 +1142,7 @@ 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 == "") { panel.remove(selectedItemIcon); updateRootWidth(); @@ -1373,13 +1161,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; @@ -1635,7 +1417,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; @@ -1687,7 +1469,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, } addStyleDependentName("focus"); - if (client.hasEventListeners(this, EventId.FOCUS)) { + if (client.hasWidgetEventListeners(this, EventId.FOCUS)) { client.updateVariable(paintableId, EventId.FOCUS, "", true); } } @@ -1752,7 +1534,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, } removeStyleDependentName("focus"); - if (client.hasEventListeners(this, EventId.BLUR)) { + if (client.hasWidgetEventListeners(this, EventId.BLUR)) { client.updateVariable(paintableId, EventId.BLUR, "", true); } } @@ -1783,16 +1565,8 @@ public class VFilterSelect extends Composite implements Paintable, Field, 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); - } + horizPaddingAndBorder = Util.setWidthExcludingPaddingAndBorder(this, + width, horizPaddingAndBorder); if (initDone) { updateRootWidth(); @@ -1814,7 +1588,7 @@ public class VFilterSelect extends Composite implements Paintable, Field, * 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() { + protected void updateRootWidth() { if (width == null) { /* * When the width is not specified we must specify width for root @@ -1882,16 +1656,7 @@ 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; + return getOffsetWidth(); } /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelectPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VFilterSelectPaintable.java new file mode 100644 index 0000000000..3249ea17d5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VFilterSelectPaintable.java @@ -0,0 +1,244 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.VFilterSelect.FilterSelectSuggestion; + +public class VFilterSelectPaintable extends VAbstractPaintableWidget { + + /* + * (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) { + // Save details + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getId(); + + getWidgetForPaintable().readonly = uidl.hasAttribute("readonly"); + getWidgetForPaintable().enabled = !uidl.hasAttribute("disabled"); + + getWidgetForPaintable().tb.setEnabled(getWidgetForPaintable().enabled); + getWidgetForPaintable().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(VFilterSelect.ATTR_NO_TEXT_INPUT) + && uidl.getBooleanAttribute(VFilterSelect.ATTR_NO_TEXT_INPUT); + getWidgetForPaintable().setTextInputEnabled(!noTextInput); + + // not a FocusWidget -> needs own tabindex handling + if (uidl.hasAttribute("tabindex")) { + getWidgetForPaintable().tb.setTabIndex(uidl + .getIntAttribute("tabindex")); + } + + if (uidl.hasAttribute("filteringmode")) { + getWidgetForPaintable().filteringmode = uidl + .getIntAttribute("filteringmode"); + } + + getWidgetForPaintable().immediate = uidl.hasAttribute("immediate"); + + getWidgetForPaintable().nullSelectionAllowed = uidl + .hasAttribute("nullselect"); + + getWidgetForPaintable().nullSelectItem = uidl + .hasAttribute("nullselectitem") + && uidl.getBooleanAttribute("nullselectitem"); + + getWidgetForPaintable().currentPage = uidl.getIntVariable("page"); + + if (uidl.hasAttribute("pagelength")) { + getWidgetForPaintable().pageLength = uidl + .getIntAttribute("pagelength"); + } + + if (uidl.hasAttribute(VFilterSelect.ATTR_INPUTPROMPT)) { + // input prompt changed from server + getWidgetForPaintable().inputPrompt = uidl + .getStringAttribute(VFilterSelect.ATTR_INPUTPROMPT); + } else { + getWidgetForPaintable().inputPrompt = ""; + } + + getWidgetForPaintable().suggestionPopup.updateStyleNames(uidl); + + getWidgetForPaintable().allowNewItem = uidl + .hasAttribute("allownewitem"); + getWidgetForPaintable().lastNewItemString = null; + + getWidgetForPaintable().currentSuggestions.clear(); + if (!getWidgetForPaintable().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. + */ + getWidgetForPaintable().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. + */ + getWidgetForPaintable().suggestionPopup.menu.clearItems(); + + } + + final UIDL options = uidl.getChildUIDL(0); + if (uidl.hasAttribute("totalMatches")) { + getWidgetForPaintable().totalMatches = uidl + .getIntAttribute("totalMatches"); + } else { + getWidgetForPaintable().totalMatches = 0; + } + + // used only to calculate minimum popup width + String captions = Util.escapeHTML(getWidgetForPaintable().inputPrompt); + + for (final Iterator<?> i = options.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + final FilterSelectSuggestion suggestion = getWidgetForPaintable().new FilterSelectSuggestion( + optionUidl); + getWidgetForPaintable().currentSuggestions.add(suggestion); + if (optionUidl.hasAttribute("selected")) { + if (!getWidgetForPaintable().waitingForFilteringResponse + || getWidgetForPaintable().popupOpenerClicked) { + String newSelectedOptionKey = Integer.toString(suggestion + .getOptionKey()); + if (!newSelectedOptionKey + .equals(getWidgetForPaintable().selectedOptionKey) + || suggestion.getReplacementString().equals( + getWidgetForPaintable().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 + getWidgetForPaintable().setPromptingOff( + suggestion.getReplacementString()); + getWidgetForPaintable().selectedOptionKey = newSelectedOptionKey; + } + } + getWidgetForPaintable().currentSuggestion = suggestion; + getWidgetForPaintable().setSelectedItemIcon( + suggestion.getIconUri()); + } + + // Collect captions so we can calculate minimum width for textarea + if (captions.length() > 0) { + captions += "|"; + } + captions += Util.escapeHTML(suggestion.getReplacementString()); + } + + if ((!getWidgetForPaintable().waitingForFilteringResponse || getWidgetForPaintable().popupOpenerClicked) + && uidl.hasVariable("selected") + && uidl.getStringArrayVariable("selected").length == 0) { + // select nulled + if (!getWidgetForPaintable().waitingForFilteringResponse + || !getWidgetForPaintable().popupOpenerClicked) { + if (!getWidgetForPaintable().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... + */ + getWidgetForPaintable().prompting = false; + getWidgetForPaintable().setPromptingOn(); + } else { + // we have focus in field, prompting can't be set on, + // instead just clear the input + getWidgetForPaintable().tb.setValue(""); + } + } + getWidgetForPaintable().selectedOptionKey = null; + } + + if (getWidgetForPaintable().waitingForFilteringResponse + && getWidgetForPaintable().lastFilter.toLowerCase().equals( + uidl.getStringVariable("filter"))) { + getWidgetForPaintable().suggestionPopup.showSuggestions( + getWidgetForPaintable().currentSuggestions, + getWidgetForPaintable().currentPage, + getWidgetForPaintable().totalMatches); + getWidgetForPaintable().waitingForFilteringResponse = false; + if (!getWidgetForPaintable().popupOpenerClicked + && getWidgetForPaintable().selectPopupItemWhenResponseIsReceived != VFilterSelect.Select.NONE) { + // we're paging w/ arrows + if (getWidgetForPaintable().selectPopupItemWhenResponseIsReceived == VFilterSelect.Select.LAST) { + getWidgetForPaintable().suggestionPopup.menu + .selectLastItem(); + } else { + getWidgetForPaintable().suggestionPopup.menu + .selectFirstItem(); + } + + // This is used for paging so we update the keyboard selection + // variable as well. + MenuItem activeMenuItem = getWidgetForPaintable().suggestionPopup.menu + .getSelectedItem(); + getWidgetForPaintable().suggestionPopup.menu + .setKeyboardSelectedItem(activeMenuItem); + + // Update text field to contain the correct text + getWidgetForPaintable() + .setTextboxText(activeMenuItem.getText()); + getWidgetForPaintable().tb.setSelectionRange( + getWidgetForPaintable().lastFilter.length(), + activeMenuItem.getText().length() + - getWidgetForPaintable().lastFilter.length()); + + getWidgetForPaintable().selectPopupItemWhenResponseIsReceived = VFilterSelect.Select.NONE; // reset + } + if (getWidgetForPaintable().updateSelectionWhenReponseIsReceived) { + getWidgetForPaintable().suggestionPopup.menu + .doPostFilterSelectedItemAction(); + } + } + + // Calculate minumum textarea width + getWidgetForPaintable().suggestionPopupMinWidth = getWidgetForPaintable() + .minWidth(captions); + + getWidgetForPaintable().popupOpenerClicked = false; + + if (!getWidgetForPaintable().initDone) { + getWidgetForPaintable().updateRootWidth(); + } + + // Focus dependent style names are lost during the update, so we add + // them here back again + if (getWidgetForPaintable().focused) { + getWidgetForPaintable().addStyleDependentName("focus"); + } + + getWidgetForPaintable().initDone = true; + } + + @Override + protected Widget createWidget() { + return GWT.create(VFilterSelect.class); + } + + @Override + public VFilterSelect getWidgetForPaintable() { + return (VFilterSelect) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/VForm.java index c0a6e5b275..d70d7ed0d8 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VForm.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VForm.java @@ -16,12 +16,9 @@ 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; @@ -36,33 +33,33 @@ public class VForm extends ComplexPanel implements Container, KeyDownHandler { public static final String CLASSNAME = "v-form"; - private Container lo; - private Element legend = DOM.createLegend(); - private Element caption = DOM.createSpan(); + Widget lo; + Element legend = DOM.createLegend(); + Element caption = DOM.createSpan(); private Element errorIndicatorElement = DOM.createDiv(); - private Element desc = DOM.createDiv(); - private Icon icon; - private VErrorMessage errorMessage = new VErrorMessage(); + Element desc = DOM.createDiv(); + Icon icon; + VErrorMessage errorMessage = new VErrorMessage(); - private Element fieldContainer = DOM.createDiv(); + Element fieldContainer = DOM.createDiv(); - private Element footerContainer = DOM.createDiv(); + Element footerContainer = DOM.createDiv(); - private Element fieldSet = DOM.createFieldSet(); + Element fieldSet = DOM.createFieldSet(); - private Container footer; + Widget footer; - private ApplicationConnection client; + ApplicationConnection client; private RenderInformation renderInformation = new RenderInformation(); private int borderPaddingHorizontal = -1; - private boolean rendering = false; + boolean rendering = false; ShortcutActionHandler shortcutHandler; - private HandlerRegistration keyDownRegistration; + HandlerRegistration keyDownRegistration; public VForm() { setElement(DOM.createDiv()); @@ -84,139 +81,12 @@ public class VForm extends ComplexPanel implements Container, KeyDownHandler { 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); } @@ -244,16 +114,16 @@ public class VForm extends ComplexPanel implements Container, KeyDownHandler { } remove(oldComponent); if (oldComponent == lo) { - lo = (Container) newComponent; - add((Widget) lo, fieldContainer); + lo = newComponent; + add(newComponent, fieldContainer); } else { - footer = (Container) newComponent; - add((Widget) footer, footerContainer); + footer = newComponent; + add(newComponent, footerContainer); } } - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> child) { if (height != null && !"".equals(height) && width != null && !"".equals(width)) { @@ -273,11 +143,6 @@ public class VForm extends ComplexPanel implements Container, KeyDownHandler { } - 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)) { @@ -321,11 +186,23 @@ public class VForm extends ComplexPanel implements Container, KeyDownHandler { if (!rendering && height.equals("")) { // Width might affect height - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this); + Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this, + this); } } public void onKeyDown(KeyDownEvent event) { shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent())); } + + public Widget getWidgetForPaintable() { + return this; + } + + @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/VFormLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java index 174e66b7aa..b2357f11e1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java @@ -23,11 +23,12 @@ 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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.VTooltip; /** @@ -37,13 +38,13 @@ public class VFormLayout extends SimplePanel implements Container { private final static String CLASSNAME = "v-formlayout"; - private ApplicationConnection client; - private VFormLayoutTable table; + ApplicationConnection client; + VFormLayoutTable table; private String width = ""; private String height = ""; - private boolean rendering = false; + boolean rendering = false; public VFormLayout() { super(); @@ -81,8 +82,8 @@ public class VFormLayout extends SimplePanel implements Container { 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>(); + 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"); @@ -111,26 +112,30 @@ public class VFormLayout extends SimplePanel implements Container { 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); + final VPaintableWidget childPaintable = client + .getPaintable(childUidl); + Widget childWidget = childPaintable.getWidgetForPaintable(); + Caption caption = widgetToCaption.get(childWidget); if (caption == null) { - caption = new Caption(p, client); + caption = new Caption(childPaintable, client); caption.addClickHandler(this); - componentToCaption.put(p, caption); + widgetToCaption.put(childWidget, caption); } - ErrorFlag error = componentToError.get(p); + ErrorFlag error = widgetToError.get(childWidget); if (error == null) { error = new ErrorFlag(); - componentToError.put(p, error); + widgetToError.put(childWidget, 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); + + Widget oldWidget = getWidget(i, COLUMN_WIDGET); + if (oldWidget == null) { + setWidget(i, COLUMN_WIDGET, childWidget); + } else if (oldWidget != childWidget) { + final VPaintableWidget oldPaintable = VPaintableMap.get( + client).getPaintable(oldWidget); + client.unregisterPaintable(oldPaintable); + setWidget(i, COLUMN_WIDGET, childWidget); } getCellFormatter().setStyleName(i, COLUMN_WIDGET, CLASSNAME + "-contentcell"); @@ -144,7 +149,7 @@ public class VFormLayout extends SimplePanel implements Container { CLASSNAME + "-errorcell"); setWidget(i, COLUMN_ERRORFLAG, error); - p.updateFromUIDL(childUidl, client); + childPaintable.updateFromUIDL(childUidl, client); String rowstyles = CLASSNAME + "-row"; if (i == 0) { @@ -159,9 +164,11 @@ public class VFormLayout extends SimplePanel implements Container { } while (getRowCount() > i) { - final Paintable p = (Paintable) getWidget(i, COLUMN_WIDGET); + Widget w = getWidget(i, COLUMN_WIDGET); + final VPaintableWidget p = VPaintableMap.get(client) + .getPaintable(w); client.unregisterPaintable(p); - componentToCaption.remove(p); + widgetToCaption.remove(w); removeRow(i); } @@ -169,8 +176,8 @@ public class VFormLayout extends SimplePanel implements Container { * 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); + for (Widget p : widgetToCaption.keySet()) { + client.handleComponentRelativeSize(p); } } @@ -194,16 +201,20 @@ public class VFormLayout extends SimplePanel implements Container { 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); + VPaintableMap paintableMap = VPaintableMap.get(client); + VPaintableWidget oldPaintable = paintableMap + .getPaintable(oldComponent); + VPaintableWidget newPaintable = paintableMap + .getPaintable(newComponent); + Caption oldCap = widgetToCaption.get(oldComponent); + final Caption newCap = new Caption(newPaintable, client); newCap.addClickHandler(this); newCap.setStyleName(oldCap.getStyleName()); - componentToCaption.put((Paintable) newComponent, newCap); - ErrorFlag error = componentToError.get(newComponent); + widgetToCaption.put(newComponent, newCap); + ErrorFlag error = widgetToError.get(newComponent); if (error == null) { error = new ErrorFlag(); - componentToError.put((Paintable) newComponent, error); + widgetToError.put(newComponent, error); } setWidget(i, COLUMN_CAPTION, newCap); @@ -216,24 +227,26 @@ public class VFormLayout extends SimplePanel implements Container { } public boolean hasChildComponent(Widget component) { - return componentToCaption.containsKey(component); + return widgetToCaption.containsKey(component); } - public void updateCaption(Paintable component, UIDL uidl) { - final Caption c = componentToCaption.get(component); + public void updateCaption(VPaintableWidget paintable, UIDL uidl) { + final Caption c = widgetToCaption.get(paintable + .getWidgetForPaintable()); if (c != null) { c.updateCaption(uidl); } - final ErrorFlag e = componentToError.get(component); + final ErrorFlag e = widgetToError.get(paintable + .getWidgetForPaintable()); if (e != null) { - e.updateFromUIDL(uidl, component); + e.updateFromUIDL(uidl, paintable); } } public int getAllocatedWidth(Widget child, int availableWidth) { - Caption caption = componentToCaption.get(child); - ErrorFlag error = componentToError.get(child); + Caption caption = widgetToCaption.get(child); + ErrorFlag error = widgetToError.get(child); int width = availableWidth; if (caption != null) { @@ -266,21 +279,6 @@ public class VFormLayout extends SimplePanel implements Container { } } - 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(""); } @@ -293,15 +291,11 @@ public class VFormLayout extends SimplePanel implements Container { 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 final VPaintableWidget owner; private Element requiredFieldIndicator; @@ -318,7 +312,7 @@ public class VFormLayout extends SimplePanel implements Container { * return null * @param client */ - public Caption(Paintable component, ApplicationConnection client) { + public Caption(VPaintableWidget component, ApplicationConnection client) { super(); this.client = client; owner = component; @@ -411,6 +405,7 @@ public class VFormLayout extends SimplePanel implements Container { // 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"); @@ -429,7 +424,7 @@ public class VFormLayout extends SimplePanel implements Container { * * @return owner Widget */ - public Paintable getOwner() { + public VPaintableWidget getOwner() { return owner; } @@ -446,14 +441,14 @@ public class VFormLayout extends SimplePanel implements Container { private static final String CLASSNAME = VFormLayout.CLASSNAME + "-error-indicator"; Element errorIndicatorElement; - private Paintable owner; + private VPaintableWidget owner; public ErrorFlag() { setStyleName(CLASSNAME); sinkEvents(VTooltip.TOOLTIP_EVENTS); } - public void updateFromUIDL(UIDL uidl, Paintable component) { + public void updateFromUIDL(UIDL uidl, VPaintableWidget component) { owner = component; if (uidl.hasAttribute("error") && !uidl.getBooleanAttribute("hideErrors")) { @@ -481,7 +476,7 @@ public class VFormLayout extends SimplePanel implements Container { } - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { if (height.equals("") || width.equals("")) { // A dynamic size might change due to children changes return false; @@ -525,9 +520,14 @@ public class VFormLayout extends SimplePanel implements Container { table.setContentWidths(); if (height.equals("")) { // Width might affect height - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this); + Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this, + this); } } } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFormLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VFormLayoutPaintable.java new file mode 100644 index 0000000000..1efdcfc722 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VFormLayoutPaintable.java @@ -0,0 +1,39 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.VPaintableWidget; + +public class VFormLayoutPaintable extends VAbstractPaintableWidgetContainer { + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + + getWidgetForPaintable().client = client; + + if (client.updateComponent(this, uidl, true)) { + getWidgetForPaintable().rendering = false; + return; + } + + getWidgetForPaintable().table.updateFromUIDL(uidl, client); + + getWidgetForPaintable().rendering = false; + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + getWidgetForPaintable().table.updateCaption(component, uidl); + } + + @Override + public VFormLayout getWidgetForPaintable() { + return (VFormLayout) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VFormLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFormPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VFormPaintable.java new file mode 100644 index 0000000000..4b59d64a71 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VFormPaintable.java @@ -0,0 +1,173 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; +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.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VFormPaintable extends VAbstractPaintableWidgetContainer { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + getWidgetForPaintable().client = client; + getWidgetForPaintable().id = uidl.getId(); + + if (client.updateComponent(this, uidl, false)) { + getWidgetForPaintable().rendering = false; + return; + } + + boolean legendEmpty = true; + if (uidl.hasAttribute("caption")) { + getWidgetForPaintable().caption.setInnerText(uidl + .getStringAttribute("caption")); + legendEmpty = false; + } else { + getWidgetForPaintable().caption.setInnerText(""); + } + if (uidl.hasAttribute("icon")) { + if (getWidgetForPaintable().icon == null) { + getWidgetForPaintable().icon = new Icon(client); + getWidgetForPaintable().legend + .insertFirst(getWidgetForPaintable().icon.getElement()); + } + getWidgetForPaintable().icon + .setUri(uidl.getStringAttribute("icon")); + legendEmpty = false; + } else { + if (getWidgetForPaintable().icon != null) { + getWidgetForPaintable().legend + .removeChild(getWidgetForPaintable().icon.getElement()); + } + } + if (legendEmpty) { + getWidgetForPaintable().addStyleDependentName("nocaption"); + } else { + getWidgetForPaintable().removeStyleDependentName("nocaption"); + } + + if (uidl.hasAttribute("error")) { + final UIDL errorUidl = uidl.getErrors(); + getWidgetForPaintable().errorMessage.updateFromUIDL(errorUidl); + getWidgetForPaintable().errorMessage.setVisible(true); + } else { + getWidgetForPaintable().errorMessage.setVisible(false); + } + + if (uidl.hasAttribute("description")) { + getWidgetForPaintable().desc.setInnerHTML(uidl + .getStringAttribute("description")); + if (getWidgetForPaintable().desc.getParentElement() == null) { + getWidgetForPaintable().fieldSet.insertAfter( + getWidgetForPaintable().desc, + getWidgetForPaintable().legend); + } + } else { + getWidgetForPaintable().desc.setInnerHTML(""); + if (getWidgetForPaintable().desc.getParentElement() != null) { + getWidgetForPaintable().fieldSet + .removeChild(getWidgetForPaintable().desc); + } + } + + getWidgetForPaintable().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 + VPaintableWidget newFooter = client.getPaintable(uidl + .getChildUIDL(1)); + Widget newFooterWidget = newFooter.getWidgetForPaintable(); + if (getWidgetForPaintable().footer == null) { + getWidgetForPaintable().add(newFooter.getWidgetForPaintable(), + getWidgetForPaintable().footerContainer); + getWidgetForPaintable().footer = newFooterWidget; + } else if (newFooter != getWidgetForPaintable().footer) { + getWidgetForPaintable().remove(getWidgetForPaintable().footer); + client.unregisterPaintable(VPaintableMap.get(getConnection()) + .getPaintable(getWidgetForPaintable().footer)); + getWidgetForPaintable().add(newFooter.getWidgetForPaintable(), + getWidgetForPaintable().footerContainer); + } + getWidgetForPaintable().footer = newFooterWidget; + newFooter.updateFromUIDL(uidl.getChildUIDL(1), client); + // needed for the main layout to know the space it has available + getWidgetForPaintable().updateSize(); + } else { + if (getWidgetForPaintable().footer != null) { + getWidgetForPaintable().remove(getWidgetForPaintable().footer); + client.unregisterPaintable(VPaintableMap.get(getConnection()) + .getPaintable(getWidgetForPaintable().footer)); + // needed for the main layout to know the space it has available + getWidgetForPaintable().updateSize(); + } + } + + final UIDL layoutUidl = uidl.getChildUIDL(0); + VPaintableWidget newLayout = client.getPaintable(layoutUidl); + Widget newLayoutWidget = newLayout.getWidgetForPaintable(); + if (getWidgetForPaintable().lo == null) { + // Layout not rendered before + getWidgetForPaintable().lo = newLayoutWidget; + getWidgetForPaintable().add(newLayoutWidget, + getWidgetForPaintable().fieldContainer); + } else if (getWidgetForPaintable().lo != newLayoutWidget) { + // Layout has changed + client.unregisterPaintable(VPaintableMap.get(getConnection()) + .getPaintable(getWidgetForPaintable().lo)); + getWidgetForPaintable().remove(getWidgetForPaintable().lo); + getWidgetForPaintable().lo = newLayoutWidget; + getWidgetForPaintable().add(newLayoutWidget, + getWidgetForPaintable().fieldContainer); + } + newLayout.updateFromUIDL(layoutUidl, client); + + // also recalculates size of the footer if undefined size form - see + // #3710 + getWidgetForPaintable().updateSize(); + client.runDescendentsLayout(getWidgetForPaintable()); + + // We may have actions attached + if (uidl.getChildCount() > 1) { + UIDL childUidl = uidl.getChildByTagName("actions"); + if (childUidl != null) { + if (getWidgetForPaintable().shortcutHandler == null) { + getWidgetForPaintable().shortcutHandler = new ShortcutActionHandler( + getId(), client); + getWidgetForPaintable().keyDownRegistration = getWidgetForPaintable() + .addDomHandler(getWidgetForPaintable(), + KeyDownEvent.getType()); + } + getWidgetForPaintable().shortcutHandler + .updateActionMap(childUidl); + } + } else if (getWidgetForPaintable().shortcutHandler != null) { + getWidgetForPaintable().keyDownRegistration.removeHandler(); + getWidgetForPaintable().shortcutHandler = null; + getWidgetForPaintable().keyDownRegistration = null; + } + + getWidgetForPaintable().rendering = false; + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + // NOP form don't render caption for neither field layout nor footer + // layout + } + + @Override + public VForm getWidgetForPaintable() { + return (VForm) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VForm.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java index 82b3eabf40..e6e167f077 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java @@ -14,76 +14,58 @@ 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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; 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 class VGridLayout extends SimplePanel implements Container { public static final String CLASSNAME = "v-gridlayout"; private DivElement margin = Document.get().createDivElement(); - private final AbsolutePanel canvas = new AbsolutePanel(); + final AbsolutePanel canvas = new AbsolutePanel(); - private ApplicationConnection client; + ApplicationConnection client; protected HashMap<Widget, ChildComponentContainer> widgetToComponentContainer = new HashMap<Widget, ChildComponentContainer>(); - private HashMap<Paintable, Cell> paintableToCell = new HashMap<Paintable, Cell>(); + HashMap<Widget, Cell> widgetToCell = new HashMap<Widget, Cell>(); private int spacingPixelsHorizontal; private int spacingPixelsVertical; - private int[] columnWidths; - private int[] rowHeights; + int[] columnWidths; + int[] rowHeights; private String height; private String width; - private int[] colExpandRatioArray; + int[] colExpandRatioArray; - private int[] rowExpandRatioArray; + int[] rowExpandRatioArray; - private int[] minColumnWidths; + int[] minColumnWidths; private int[] minRowHeights; - private boolean rendering; + boolean rendering; - private HashMap<Widget, ChildComponentContainer> nonRenderedWidgets; + 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); - } - }; + boolean sizeChangedDuringRendering = false; public VGridLayout() { super(); @@ -133,125 +115,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { 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) { + static int[] cloneArray(int[] toBeCloned) { int[] clone = new int[toBeCloned.length]; for (int i = 0; i < clone.length; i++) { clone[i] = toBeCloned[i] * 1; @@ -259,7 +123,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { return clone; } - private void expandRows() { + void expandRows() { if (!"".equals(height)) { int usedSpace = minRowHeights[0]; for (int i = 1; i < minRowHeights.length; i++) { @@ -295,8 +159,8 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } else { expandRows(); layoutCells(); - for (Paintable c : paintableToCell.keySet()) { - client.handleComponentRelativeSize((Widget) c); + for (Widget w : widgetToCell.keySet()) { + client.handleComponentRelativeSize(w); } } } @@ -388,8 +252,8 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } layoutCells(); - for (Paintable c : paintableToCell.keySet()) { - client.handleComponentRelativeSize((Widget) c); + for (Widget w : widgetToCell.keySet()) { + client.handleComponentRelativeSize(w); } if (heightChanged && "".equals(height)) { Util.notifyParentOfSizeChange(this, false); @@ -398,7 +262,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } } - private void expandColumns() { + void expandColumns() { if (!"".equals(width)) { int usedSpace = minColumnWidths[0]; for (int i = 1; i < minColumnWidths.length; i++) { @@ -425,7 +289,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } } - private void layoutCells() { + void layoutCells() { int x = 0; int y = 0; for (int i = 0; i < cells.length; i++) { @@ -467,13 +331,13 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { return "".equals(width); } - private void renderRemainingComponents(LinkedList<Cell> pendingCells) { + void renderRemainingComponents(LinkedList<Cell> pendingCells) { for (Cell cell : pendingCells) { cell.render(); } } - private void detectRowHeights() { + void detectRowHeights() { // collect min rowheight from non-rowspanned cells for (int i = 0; i < cells.length; i++) { @@ -528,7 +392,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { l.cells.add(cell); } - private void renderRemainingComponentsWithNoRelativeHeight( + void renderRemainingComponentsWithNoRelativeHeight( LinkedList<Cell> pendingCells) { for (Iterator<Cell> iterator = pendingCells.iterator(); iterator @@ -546,7 +410,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { * Iterates colspanned cells, ensures cols have enough space to accommodate * them */ - private void distributeColSpanWidths() { + void distributeColSpanWidths() { for (SpanList list : colSpans) { for (Cell cell : list.cells) { // cells with relative content may return non 0 here if on @@ -640,7 +504,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } } - private void storeColSpannedCell(Cell cell) { + void storeColSpannedCell(Cell cell) { SpanList l = null; for (SpanList list : colSpans) { if (list.span < cell.colspan) { @@ -663,7 +527,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { l.cells.add(cell); } - private void detectSpacing(UIDL uidl) { + void detectSpacing(UIDL uidl) { DivElement spacingmeter = Document.get().createDivElement(); spacingmeter.setClassName(CLASSNAME + "-" + "spacing-" + (uidl.getBooleanAttribute("spacing") ? "on" : "off")); @@ -675,7 +539,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { canvas.getElement().removeChild(spacingmeter); } - private void handleMargins(UIDL uidl) { + void handleMargins(UIDL uidl) { final VMarginInfo margins = new VMarginInfo( uidl.getIntAttribute("margins")); @@ -699,7 +563,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } public boolean hasChildComponent(Widget component) { - return paintableToCell.containsKey(component); + return widgetToCell.containsKey(component); } public void replaceChildComponent(Widget oldComponent, Widget newComponent) { @@ -709,30 +573,14 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { return; } - componentContainer.setWidget(newComponent); + componentContainer.setPaintable(VPaintableMap.get(client).getPaintable( + newComponent)); widgetToComponentContainer.put(newComponent, componentContainer); - paintableToCell.put((Paintable) newComponent, - paintableToCell.get(oldComponent)); + widgetToCell.put(newComponent, widgetToCell.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) { + public boolean requestLayout(final Set<Widget> changedChildren) { boolean needsLayout = false; boolean reDistributeColSpanWidths = false; boolean reDistributeRowSpanHeights = false; @@ -743,9 +591,9 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } ArrayList<Integer> dirtyColumns = new ArrayList<Integer>(); ArrayList<Integer> dirtyRows = new ArrayList<Integer>(); - for (Paintable paintable : changedChildren) { + for (Widget widget : changedChildren) { - Cell cell = paintableToCell.get(paintable); + Cell cell = widgetToCell.get(widget); if (!cell.hasRelativeHeight() || !cell.hasRelativeWidth()) { // cell sizes will only stay still if only relatively // sized components @@ -886,17 +734,17 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } public RenderSpace getAllocatedSpace(Widget child) { - Cell cell = paintableToCell.get(child); + Cell cell = widgetToCell.get(child); assert cell != null; return cell.getAllocatedSpace(); } - private Cell[][] cells; + Cell[][] cells; /** * Private helper class. */ - private class Cell { + class Cell { private boolean relHeight = false; private boolean relWidth = false; private boolean widthCanAffectHeight = false; @@ -994,12 +842,13 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { protected void render() { assert childUidl != null; - Paintable paintable = client.getPaintable(childUidl); + VPaintableWidget paintable = client.getPaintable(childUidl); + Widget w = paintable.getWidgetForPaintable(); assert paintable != null; - if (cc == null || cc.getWidget() != paintable) { - if (widgetToComponentContainer.containsKey(paintable)) { + if (cc == null || cc.getWidget() != w) { + if (widgetToComponentContainer.containsKey(w)) { // Component moving from one place to another - cc = widgetToComponentContainer.get(paintable); + cc = widgetToComponentContainer.get(w); cc.setWidth(""); cc.setHeight(""); /* @@ -1007,23 +856,23 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { * and this layout has been hidden when moving out, see * #5372 */ - cc.setWidget((Widget) paintable); + cc.setPaintable(paintable); } else { // A new component - cc = new ChildComponentContainer((Widget) paintable, + cc = new ChildComponentContainer(paintable, CellBasedLayout.ORIENTATION_VERTICAL); - widgetToComponentContainer.put((Widget) paintable, cc); + widgetToComponentContainer.put(w, cc); cc.setWidth(""); canvas.add(cc, 0, 0); } - paintableToCell.put(paintable, this); + widgetToCell.put(w, this); } cc.renderChild(childUidl, client, -1); if (sizeChangedDuringRendering && Util.isCached(childUidl)) { client.handleComponentRelativeSize(cc.getWidget()); } cc.updateWidgetSize(); - nonRenderedWidgets.remove(paintable); + nonRenderedWidgets.remove(w); } public UIDL getChildUIDL() { @@ -1062,17 +911,19 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { // canvas later during the render phase cc = null; } else if (cc != null - && cc.getWidget() != client.getPaintable(c)) { + && cc.getWidget() != client.getPaintable(c) + .getWidgetForPaintable()) { // content has changed cc = null; - Paintable paintable = client.getPaintable(c); - if (widgetToComponentContainer.containsKey(paintable)) { + VPaintableWidget paintable = client.getPaintable(c); + Widget w = paintable.getWidgetForPaintable(); + if (widgetToComponentContainer.containsKey(w)) { // cc exist for this component (moved) use that for this // cell - cc = widgetToComponentContainer.get(paintable); + cc = widgetToComponentContainer.get(w); cc.setWidth(""); cc.setHeight(""); - paintableToCell.put(paintable, this); + widgetToCell.put(w, this); } } } @@ -1102,7 +953,7 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { } } - private Cell getCell(UIDL c) { + Cell getCell(UIDL c) { int row = c.getIntAttribute("y"); int col = c.getIntAttribute("x"); Cell cell = cells[col][row]; @@ -1125,8 +976,12 @@ public class VGridLayout extends SimplePanel implements Paintable, Container { * @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) { + VPaintableWidget getComponent(Element element) { return Util.getPaintableForElement(client, this, element); } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VGridLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VGridLayoutPaintable.java new file mode 100644 index 0000000000..639ac1f8de --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VGridLayoutPaintable.java @@ -0,0 +1,193 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; + +import com.google.gwt.core.client.GWT; +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.EventId; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.VGridLayout.Cell; +import com.vaadin.terminal.gwt.client.ui.layout.ChildComponentContainer; + +public class VGridLayoutPaintable extends VAbstractPaintableWidgetContainer { + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this, EventId.LAYOUT_CLICK) { + + @Override + protected VPaintableWidget getChildComponent(Element element) { + return getWidgetForPaintable().getComponent(element); + } + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + }; + + @SuppressWarnings("unchecked") + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + getWidgetForPaintable().client = client; + + if (client.updateComponent(this, uidl, true)) { + getWidgetForPaintable().rendering = false; + return; + } + clickEventHandler.handleEventHandlerRegistration(client); + + getWidgetForPaintable().canvas.setWidth("0px"); + + getWidgetForPaintable().handleMargins(uidl); + getWidgetForPaintable().detectSpacing(uidl); + + int cols = uidl.getIntAttribute("w"); + int rows = uidl.getIntAttribute("h"); + + getWidgetForPaintable().columnWidths = new int[cols]; + getWidgetForPaintable().rowHeights = new int[rows]; + + if (getWidgetForPaintable().cells == null) { + getWidgetForPaintable().cells = new Cell[cols][rows]; + } else if (getWidgetForPaintable().cells.length != cols + || getWidgetForPaintable().cells[0].length != rows) { + Cell[][] newCells = new Cell[cols][rows]; + for (int i = 0; i < getWidgetForPaintable().cells.length; i++) { + for (int j = 0; j < getWidgetForPaintable().cells[i].length; j++) { + if (i < cols && j < rows) { + newCells[i][j] = getWidgetForPaintable().cells[i][j]; + } + } + } + getWidgetForPaintable().cells = newCells; + } + + getWidgetForPaintable().nonRenderedWidgets = (HashMap<Widget, ChildComponentContainer>) getWidgetForPaintable().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 = getWidgetForPaintable().getCell(c); + if (cell.hasContent()) { + boolean rendered = cell.renderIfNoRelativeWidth(); + cell.alignment = alignments[alignmentIndex++]; + if (!rendered) { + pendingCells.add(cell); + } + + if (cell.colspan > 1) { + getWidgetForPaintable().storeColSpannedCell( + cell); + } else if (rendered) { + // strore non-colspanned widths to columnWidth + // array + if (getWidgetForPaintable().columnWidths[cell.col] < cell + .getWidth()) { + getWidgetForPaintable().columnWidths[cell.col] = cell + .getWidth(); + } + } + if (cell.hasRelativeHeight()) { + relativeHeighted.add(cell); + } + } + } + } + } + } + + getWidgetForPaintable().colExpandRatioArray = uidl + .getIntArrayAttribute("colExpand"); + getWidgetForPaintable().rowExpandRatioArray = uidl + .getIntArrayAttribute("rowExpand"); + getWidgetForPaintable().distributeColSpanWidths(); + + getWidgetForPaintable().minColumnWidths = VGridLayout + .cloneArray(getWidgetForPaintable().columnWidths); + getWidgetForPaintable().expandColumns(); + + getWidgetForPaintable().renderRemainingComponentsWithNoRelativeHeight( + pendingCells); + + getWidgetForPaintable().detectRowHeights(); + + getWidgetForPaintable().expandRows(); + + getWidgetForPaintable().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(); + } + + getWidgetForPaintable().layoutCells(); + + // clean non rendered components + for (Widget w : getWidgetForPaintable().nonRenderedWidgets.keySet()) { + ChildComponentContainer childComponentContainer = getWidgetForPaintable().widgetToComponentContainer + .get(w); + getWidgetForPaintable().widgetToCell.remove(w); + getWidgetForPaintable().widgetToComponentContainer.remove(w); + childComponentContainer.removeFromParent(); + VPaintableMap paintableMap = VPaintableMap.get(client); + paintableMap.unregisterPaintable(paintableMap.getPaintable(w)); + } + getWidgetForPaintable().nonRenderedWidgets = null; + + getWidgetForPaintable().rendering = false; + getWidgetForPaintable().sizeChangedDuringRendering = false; + + } + + public void updateCaption(VPaintableWidget paintable, UIDL uidl) { + Widget widget = paintable.getWidgetForPaintable(); + ChildComponentContainer cc = getWidgetForPaintable().widgetToComponentContainer + .get(widget); + if (cc != null) { + cc.updateCaption(uidl, getConnection()); + } + if (!getWidgetForPaintable().rendering) { + // ensure rel size details are updated + getWidgetForPaintable().widgetToCell.get(widget) + .updateRelSizeStatus(uidl); + /* + * This was a component-only update and the possible size change + * must be propagated to the layout + */ + getConnection().captionSizeUpdated(widget); + } + } + + @Override + public VGridLayout getWidgetForPaintable() { + return (VGridLayout) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VGridLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayoutPaintable.java new file mode 100644 index 0000000000..e5fb4e138b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayoutPaintable.java @@ -0,0 +1,17 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; + +public class VHorizontalLayoutPaintable extends VOrderedLayoutPaintable { + + @Override + public VHorizontalLayout getWidgetForPaintable() { + return (VHorizontalLayout) super.getWidgetForPaintable(); + } + + @Override + protected VHorizontalLayout createWidget() { + return GWT.create(VHorizontalLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VHorizontalSplitPanelPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VHorizontalSplitPanelPaintable.java new file mode 100644 index 0000000000..ebc8a5e523 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VHorizontalSplitPanelPaintable.java @@ -0,0 +1,13 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; + +public class VHorizontalSplitPanelPaintable extends + VAbstractSplitPanelPaintable { + + @Override + protected VAbstractSplitPanel createWidget() { + return GWT.create(VSplitPanelHorizontal.class); + } + +} 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 index b8030de421..df651c0a4d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VLink.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VLink.java @@ -12,42 +12,40 @@ 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 class VLink extends HTML implements 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; + protected static final int BORDER_STYLE_DEFAULT = 0; + protected static final int BORDER_STYLE_MINIMAL = 1; + protected static final int BORDER_STYLE_NONE = 2; - private String src; + protected String src; - private String target; + protected String target; - private int borderStyle = BORDER_STYLE_DEFAULT; + protected int borderStyle = BORDER_STYLE_DEFAULT; - private boolean enabled; + protected boolean enabled; - private boolean readonly; + protected boolean readonly; - private int targetWidth; + protected int targetWidth; - private int targetHeight; + protected int targetHeight; - private Element errorIndicatorElement; + protected Element errorIndicatorElement; - private final Element anchor = DOM.createAnchor(); + protected final Element anchor = DOM.createAnchor(); - private final Element captionElement = DOM.createSpan(); + protected final Element captionElement = DOM.createSpan(); - private Icon icon; + protected Icon icon; - private ApplicationConnection client; + protected ApplicationConnection client; public VLink() { super(); @@ -58,68 +56,6 @@ public class VLink extends HTML implements Paintable, ClickHandler { 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) { @@ -167,7 +103,7 @@ public class VLink extends HTML implements Paintable, ClickHandler { Util.notifyParentOfSizeChange(this, true); } if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } if (target == captionElement || target == anchor || (icon != null && target == icon.getElement())) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/VLinkPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VLinkPaintable.java new file mode 100644 index 0000000000..3fbd796c4b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VLinkPaintable.java @@ -0,0 +1,100 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; + +public class VLinkPaintable extends VAbstractPaintableWidget { + + 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; + } + + getWidgetForPaintable().client = client; + + getWidgetForPaintable().enabled = uidl.hasAttribute("disabled") ? false + : true; + getWidgetForPaintable().readonly = uidl.hasAttribute("readonly") ? true + : false; + + if (uidl.hasAttribute("name")) { + getWidgetForPaintable().target = uidl.getStringAttribute("name"); + getWidgetForPaintable().anchor.setAttribute("target", + getWidgetForPaintable().target); + } + if (uidl.hasAttribute("src")) { + getWidgetForPaintable().src = client.translateVaadinUri(uidl + .getStringAttribute("src")); + getWidgetForPaintable().anchor.setAttribute("href", + getWidgetForPaintable().src); + } + + if (uidl.hasAttribute("border")) { + if ("none".equals(uidl.getStringAttribute("border"))) { + getWidgetForPaintable().borderStyle = VLink.BORDER_STYLE_NONE; + } else { + getWidgetForPaintable().borderStyle = VLink.BORDER_STYLE_MINIMAL; + } + } else { + getWidgetForPaintable().borderStyle = VLink.BORDER_STYLE_DEFAULT; + } + + getWidgetForPaintable().targetHeight = uidl + .hasAttribute("targetHeight") ? uidl + .getIntAttribute("targetHeight") : -1; + getWidgetForPaintable().targetWidth = uidl.hasAttribute("targetWidth") ? uidl + .getIntAttribute("targetWidth") : -1; + + // Set link caption + getWidgetForPaintable().captionElement.setInnerText(uidl + .getStringAttribute("caption")); + + // handle error + if (uidl.hasAttribute("error")) { + if (getWidgetForPaintable().errorIndicatorElement == null) { + getWidgetForPaintable().errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty( + getWidgetForPaintable().errorIndicatorElement, + "className", "v-errorindicator"); + } + DOM.insertChild(getWidgetForPaintable().getElement(), + getWidgetForPaintable().errorIndicatorElement, 0); + } else if (getWidgetForPaintable().errorIndicatorElement != null) { + DOM.setStyleAttribute( + getWidgetForPaintable().errorIndicatorElement, "display", + "none"); + } + + if (uidl.hasAttribute("icon")) { + if (getWidgetForPaintable().icon == null) { + getWidgetForPaintable().icon = new Icon(client); + getWidgetForPaintable().anchor.insertBefore( + getWidgetForPaintable().icon.getElement(), + getWidgetForPaintable().captionElement); + } + getWidgetForPaintable().icon + .setUri(uidl.getStringAttribute("icon")); + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VLink.class); + } + + @Override + public VLink getWidgetForPaintable() { + return (VLink) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java index cebc4600a2..1ea2f4f705 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java @@ -10,8 +10,8 @@ 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.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.VTooltip; @@ -80,11 +80,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,7 +109,6 @@ public class VListSelect extends VOptionGroupBase { public void focus() { select.setFocus(true); } - } /** @@ -118,7 +117,7 @@ public class VListSelect extends VOptionGroupBase { */ class TooltipListBox extends ListBox { private ApplicationConnection client; - private Paintable pntbl; + private Widget widget; TooltipListBox(boolean isMultiselect) { super(isMultiselect); @@ -129,15 +128,16 @@ class TooltipListBox extends ListBox { this.client = client; } - public void setSelect(Paintable s) { - pntbl = s; + public void setSelect(Widget widget) { + this.widget = widget; } @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (client != null) { - client.handleTooltipEvent(event, pntbl); + client.handleWidgetTooltipEvent(event, widget); } } + }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/VListSelectPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VListSelectPaintable.java new file mode 100644 index 0000000000..b77bfa33e3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VListSelectPaintable.java @@ -0,0 +1,21 @@ +/* +@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 VListSelectPaintable extends VOptionGroupBasePaintable { + + @Override + protected Widget createWidget() { + return GWT.create(VListSelect.class); + } + + @Override + public VListSelect getWidgetForPaintable() { + return (VListSelect) super.getWidgetForPaintable(); + } +} 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/VMediaBasePaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VMediaBasePaintable.java new file mode 100644 index 0000000000..bef770f38b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VMediaBasePaintable.java @@ -0,0 +1,109 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; + +public abstract class VMediaBasePaintable extends VAbstractPaintableWidget { + + public static final String TAG_SOURCE = "src"; + + 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 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 void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + getWidgetForPaintable().setControls(shouldShowControls(uidl)); + getWidgetForPaintable().setAutoplay(shouldAutoplay(uidl)); + getWidgetForPaintable().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())) { + getWidgetForPaintable().addSource(getSourceUrl(child), + getSourceType(child)); + } + } + 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); + } + + private boolean allowHtmlContent(UIDL uidl) { + return uidl.getBooleanAttribute(ATTR_HTML); + } + + private void evalPlayCommand(UIDL uidl) { + if (uidl.hasAttribute(ATTR_PLAY)) { + getWidgetForPaintable().play(); + } + } + + private void evalPauseCommand(UIDL uidl) { + if (uidl.hasAttribute(ATTR_PAUSE)) { + getWidgetForPaintable().pause(); + } + } + + @Override + public VMediaBase getWidgetForPaintable() { + return (VMediaBase) super.getWidgetForPaintable(); + } + + /** + * @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(VMediaBasePaintable.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(VMediaBasePaintable.ATTR_RESOURCE_TYPE); + } + + private void setAltText(UIDL uidl) { + String alt = uidl.getStringAttribute(VMediaBasePaintable.ATTR_ALT_TEXT); + + if (alt == null || "".equals(alt)) { + alt = getWidgetForPaintable().getDefaultAltHtml(); + } else if (!allowHtmlContent(uidl)) { + alt = Util.escapeHTML(alt); + } + getWidgetForPaintable().setAltText(alt); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java index a0d7300f11..c39155d032 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java @@ -4,14 +4,11 @@ package com.vaadin.terminal.gwt.client.ui; 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; @@ -36,13 +33,12 @@ 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.TooltipInfo; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VTooltip; -public class VMenuBar extends SimpleFocusablePanel implements Paintable, +public class VMenuBar extends SimpleFocusablePanel implements CloseHandler<PopupPanel>, ContainerResizedListener, KeyPressHandler, KeyDownHandler, FocusHandler, SubPartAware { @@ -57,7 +53,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 @@ -83,7 +78,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 +90,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 @@ -157,24 +152,9 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, } 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); - } + + Util.setWidthExcludingPaddingAndBorder(this, width, 0); + if (!subMenu) { // Only needed for root level menu hideChildren(); @@ -184,141 +164,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 +177,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 +317,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 @@ -506,7 +342,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, // Handle tooltips if (targetItem == null && client != null) { // Handle root menubar tooltips - client.handleTooltipEvent(e, this); + client.handleWidgetTooltipEvent(e, this); } else if (targetItem != null) { // Handle item tooltips targetItem.onBrowserEvent(e); @@ -733,27 +569,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 +595,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 +768,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"); @@ -1113,7 +920,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, TooltipInfo info = new TooltipInfo(description); VMenuBar root = findRootMenu(); - client.registerTooltip(root, this, info); + client.registerWidgetTooltip(root, this, info); } } @@ -1121,7 +928,7 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (client != null) { - client.handleTooltipEvent(event, findRootMenu(), this); + client.handleWidgetTooltipEvent(event, findRootMenu(), this); } } @@ -1170,22 +977,9 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, 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); - } - // Used width includes the "more" item if present int usedWidth = getConsumedWidth(); int diff = availableWidth - usedWidth; @@ -1230,16 +1024,6 @@ public class VMenuBar extends SimpleFocusablePanel implements Paintable, } 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()); - } } } if (collapsedRootItems.getItems().size() > 0) { @@ -1636,31 +1420,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/VMenuBarPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VMenuBarPaintable.java new file mode 100644 index 0000000000..ef2a49b9d0 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VMenuBarPaintable.java @@ -0,0 +1,161 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.VMenuBar.CustomMenuItem; + +public class VMenuBarPaintable extends VAbstractPaintableWidget { + /** + * 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; + } + + getWidgetForPaintable().htmlContentAllowed = uidl + .hasAttribute(VMenuBar.HTML_CONTENT_ALLOWED); + + getWidgetForPaintable().openRootOnHover = uidl + .getBooleanAttribute(VMenuBar.OPEN_ROOT_MENU_ON_HOWER); + + getWidgetForPaintable().enabled = !uidl.getBooleanAttribute("disabled"); + + // For future connections + getWidgetForPaintable().client = client; + getWidgetForPaintable().uidlId = uidl.getId(); + + // Empty the menu every time it receives new information + if (!getWidgetForPaintable().getItems().isEmpty()) { + getWidgetForPaintable().clearItems(); + } + + UIDL options = uidl.getChildUIDL(0); + + 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); + + getWidgetForPaintable().moreItem = GWT.create(CustomMenuItem.class); + getWidgetForPaintable().moreItem.setHTML(itemHTML.toString()); + getWidgetForPaintable().moreItem.setCommand(VMenuBar.emptyCommand); + + getWidgetForPaintable().collapsedRootItems = new VMenuBar(true, + getWidgetForPaintable()); + getWidgetForPaintable().moreItem + .setSubMenu(getWidgetForPaintable().collapsedRootItems); + getWidgetForPaintable().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 = getWidgetForPaintable(); + + 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 = getWidgetForPaintable().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() { + getWidgetForPaintable().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 + + getWidgetForPaintable().iLayout(false); + + }// updateFromUIDL + + @Override + protected Widget createWidget() { + return GWT.create(VMenuBar.class); + } + + @Override + public VMenuBar getWidgetForPaintable() { + return (VMenuBar) super.getWidgetForPaintable(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java index 98d3b505ae..94e5cdf40c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java @@ -4,7 +4,6 @@ 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; @@ -13,28 +12,24 @@ 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, +public class VNativeButton extends Button implements ClickHandler, FocusHandler, BlurHandler { public static final String CLASSNAME = "v-nativebutton"; protected String width = null; - protected String id; + protected String paintableId; protected ApplicationConnection client; @@ -51,10 +46,10 @@ public class VNativeButton extends Button implements Paintable, ClickHandler, */ private boolean clickPending; - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; + protected HandlerRegistration focusHandlerRegistration; + protected HandlerRegistration blurHandlerRegistration; - private boolean disableOnClick = false; + protected boolean disableOnClick = false; public VNativeButton() { setStyleName(CLASSNAME); @@ -69,76 +64,6 @@ public class VNativeButton extends Button implements Paintable, ClickHandler, 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); @@ -164,30 +89,14 @@ public class VNativeButton extends Button implements Paintable, ClickHandler, } if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(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); - } } /* @@ -198,7 +107,7 @@ public class VNativeButton extends Button implements Paintable, ClickHandler, * .dom.client.ClickEvent) */ public void onClick(ClickEvent event) { - if (id == null || client == null) { + if (paintableId == null || client == null) { return; } @@ -207,24 +116,25 @@ public class VNativeButton extends Button implements Paintable, ClickHandler, } if (disableOnClick) { setEnabled(false); - client.updateVariable(id, "disabledOnClick", true, false); + client.updateVariable(paintableId, "disabledOnClick", true, false); } // Add mouse details MouseEventDetails details = new MouseEventDetails( event.getNativeEvent(), getElement()); - client.updateVariable(id, "mousedetails", details.serialize(), false); + client.updateVariable(paintableId, "mousedetails", details.serialize(), + false); - client.updateVariable(id, "state", true, true); + client.updateVariable(paintableId, "state", true, true); clickPending = false; } public void onFocus(FocusEvent arg0) { - client.updateVariable(id, EventId.FOCUS, "", true); + client.updateVariable(paintableId, EventId.FOCUS, "", true); } public void onBlur(BlurEvent arg0) { - client.updateVariable(id, EventId.BLUR, "", true); + client.updateVariable(paintableId, EventId.BLUR, "", true); } @Override @@ -234,5 +144,4 @@ public class VNativeButton extends Button implements Paintable, ClickHandler, setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled); } } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeButtonPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeButtonPaintable.java new file mode 100644 index 0000000000..ebf9cad264 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VNativeButtonPaintable.java @@ -0,0 +1,86 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +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.EventHelper; +import com.vaadin.terminal.gwt.client.UIDL; + +public class VNativeButtonPaintable extends VAbstractPaintableWidget { + + 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; + } + + getWidgetForPaintable().disableOnClick = uidl + .hasAttribute(VButton.ATTR_DISABLE_ON_CLICK); + + getWidgetForPaintable().focusHandlerRegistration = EventHelper + .updateFocusHandler(this, client, + getWidgetForPaintable().focusHandlerRegistration); + getWidgetForPaintable().blurHandlerRegistration = EventHelper + .updateBlurHandler(this, client, + getWidgetForPaintable().blurHandlerRegistration); + + // Save details + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getId(); + + // Set text + getWidgetForPaintable().setText(uidl.getStringAttribute("caption")); + + // handle error + if (uidl.hasAttribute("error")) { + if (getWidgetForPaintable().errorIndicatorElement == null) { + getWidgetForPaintable().errorIndicatorElement = DOM + .createSpan(); + getWidgetForPaintable().errorIndicatorElement + .setClassName("v-errorindicator"); + } + getWidgetForPaintable().getElement().insertBefore( + getWidgetForPaintable().errorIndicatorElement, + getWidgetForPaintable().captionElement); + + } else if (getWidgetForPaintable().errorIndicatorElement != null) { + getWidgetForPaintable().getElement().removeChild( + getWidgetForPaintable().errorIndicatorElement); + getWidgetForPaintable().errorIndicatorElement = null; + } + + if (uidl.hasAttribute("icon")) { + if (getWidgetForPaintable().icon == null) { + getWidgetForPaintable().icon = new Icon(client); + getWidgetForPaintable().getElement().insertBefore( + getWidgetForPaintable().icon.getElement(), + getWidgetForPaintable().captionElement); + } + getWidgetForPaintable().icon + .setUri(uidl.getStringAttribute("icon")); + } else { + if (getWidgetForPaintable().icon != null) { + getWidgetForPaintable().getElement().removeChild( + getWidgetForPaintable().icon.getElement()); + getWidgetForPaintable().icon = null; + } + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VNativeButton.class); + } + + @Override + public VNativeButton getWidgetForPaintable() { + return (VNativeButton) super.getWidgetForPaintable(); + } +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java index d1b2bd76ae..ec2f6e42a1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java @@ -8,9 +8,7 @@ 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; public class VNativeSelect extends VOptionGroupBase implements Field { @@ -58,11 +56,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 +73,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 +106,4 @@ public class VNativeSelect extends VOptionGroupBase implements Field { public void focus() { select.setFocus(true); } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeSelectPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeSelectPaintable.java new file mode 100644 index 0000000000..37defb605f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VNativeSelectPaintable.java @@ -0,0 +1,21 @@ +/* +@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 VNativeSelectPaintable extends VOptionGroupBasePaintable { + + @Override + protected Widget createWidget() { + return GWT.create(VNativeSelect.class); + } + + @Override + public VNativeSelect getWidgetForPaintable() { + return (VNativeSelect) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java index 662f195fcd..bf89a01a03 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java @@ -4,7 +4,6 @@ package com.vaadin.terminal.gwt.client.ui; -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,7 +26,6 @@ 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; @@ -40,21 +37,17 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, public static final String CLASSNAME = "v-select-optiongroup"; - private final Panel panel; + 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 +61,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 +69,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 +95,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"); } @@ -180,7 +136,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 +162,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 +181,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/VOptionGroupBase.java index 50a9f8cb98..bcd5cc891f 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBase.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBase.java @@ -19,35 +19,34 @@ 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; -abstract class VOptionGroupBase extends Composite implements Paintable, Field, +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 +57,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 +121,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/VOptionGroupBasePaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBasePaintable.java new file mode 100644 index 0000000000..76486ff8a3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBasePaintable.java @@ -0,0 +1,103 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; + +public abstract class VOptionGroupBasePaintable extends + VAbstractPaintableWidget { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // Save details + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getId(); + + if (client.updateComponent(this, uidl, true)) { + return; + } + + getWidgetForPaintable().selectedKeys = uidl + .getStringArrayVariableAsSet("selected"); + + getWidgetForPaintable().readonly = uidl.getBooleanAttribute("readonly"); + getWidgetForPaintable().disabled = uidl.getBooleanAttribute("disabled"); + getWidgetForPaintable().multiselect = "multi".equals(uidl + .getStringAttribute("selectmode")); + getWidgetForPaintable().immediate = uidl + .getBooleanAttribute("immediate"); + getWidgetForPaintable().nullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + getWidgetForPaintable().nullSelectionItemAvailable = uidl + .getBooleanAttribute("nullselectitem"); + + if (uidl.hasAttribute("cols")) { + getWidgetForPaintable().cols = uidl.getIntAttribute("cols"); + } + if (uidl.hasAttribute("rows")) { + getWidgetForPaintable().rows = uidl.getIntAttribute("rows"); + } + + final UIDL ops = uidl.getChildUIDL(0); + + if (getWidgetForPaintable().getColumns() > 0) { + getWidgetForPaintable().container.setWidth(getWidgetForPaintable() + .getColumns() + "em"); + if (getWidgetForPaintable().container != getWidgetForPaintable().optionsContainer) { + getWidgetForPaintable().optionsContainer.setWidth("100%"); + } + } + + getWidgetForPaintable().buildOptions(ops); + + if (uidl.getBooleanAttribute("allownewitem")) { + if (getWidgetForPaintable().newItemField == null) { + getWidgetForPaintable().newItemButton = new VNativeButton(); + getWidgetForPaintable().newItemButton.setText("+"); + getWidgetForPaintable().newItemButton + .addClickHandler(getWidgetForPaintable()); + getWidgetForPaintable().newItemField = new VTextField(); + getWidgetForPaintable().newItemField + .addKeyPressHandler(getWidgetForPaintable()); + } + getWidgetForPaintable().newItemField + .setEnabled(!getWidgetForPaintable().disabled + && !getWidgetForPaintable().readonly); + getWidgetForPaintable().newItemButton + .setEnabled(!getWidgetForPaintable().disabled + && !getWidgetForPaintable().readonly); + + if (getWidgetForPaintable().newItemField == null + || getWidgetForPaintable().newItemField.getParent() != getWidgetForPaintable().container) { + getWidgetForPaintable().container + .add(getWidgetForPaintable().newItemField); + getWidgetForPaintable().container + .add(getWidgetForPaintable().newItemButton); + final int w = getWidgetForPaintable().container + .getOffsetWidth() + - getWidgetForPaintable().newItemButton + .getOffsetWidth(); + getWidgetForPaintable().newItemField.setWidth(Math.max(w, 0) + + "px"); + } + } else if (getWidgetForPaintable().newItemField != null) { + getWidgetForPaintable().container + .remove(getWidgetForPaintable().newItemField); + getWidgetForPaintable().container + .remove(getWidgetForPaintable().newItemButton); + } + + getWidgetForPaintable().setTabIndex( + uidl.hasAttribute("tabindex") ? uidl + .getIntAttribute("tabindex") : 0); + + } + + @Override + public VOptionGroupBase getWidgetForPaintable() { + return (VOptionGroupBase) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupPaintable.java new file mode 100644 index 0000000000..f4ffc4f0da --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupPaintable.java @@ -0,0 +1,71 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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; + +public class VOptionGroupPaintable extends VOptionGroupBasePaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().htmlContentAllowed = uidl + .hasAttribute(VOptionGroup.HTML_CONTENT_ALLOWED); + + super.updateFromUIDL(uidl, client); + + getWidgetForPaintable().sendFocusEvents = client.hasEventListeners( + this, EventId.FOCUS); + getWidgetForPaintable().sendBlurEvents = client.hasEventListeners(this, + EventId.BLUR); + + if (getWidgetForPaintable().focusHandlers != null) { + for (HandlerRegistration reg : getWidgetForPaintable().focusHandlers) { + reg.removeHandler(); + } + getWidgetForPaintable().focusHandlers.clear(); + getWidgetForPaintable().focusHandlers = null; + + for (HandlerRegistration reg : getWidgetForPaintable().blurHandlers) { + reg.removeHandler(); + } + getWidgetForPaintable().blurHandlers.clear(); + getWidgetForPaintable().blurHandlers = null; + } + + if (getWidgetForPaintable().sendFocusEvents + || getWidgetForPaintable().sendBlurEvents) { + getWidgetForPaintable().focusHandlers = new ArrayList<HandlerRegistration>(); + getWidgetForPaintable().blurHandlers = new ArrayList<HandlerRegistration>(); + + // add focus and blur handlers to checkboxes / radio buttons + for (Widget wid : getWidgetForPaintable().panel) { + if (wid instanceof CheckBox) { + getWidgetForPaintable().focusHandlers.add(((CheckBox) wid) + .addFocusHandler(getWidgetForPaintable())); + getWidgetForPaintable().blurHandlers.add(((CheckBox) wid) + .addBlurHandler(getWidgetForPaintable())); + } + } + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VOptionGroup.class); + } + + @Override + public VOptionGroup getWidgetForPaintable() { + return (VOptionGroup) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayout.java index ecdb171ec4..9f5576dfaf 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayout.java @@ -9,20 +9,14 @@ 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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; 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; @@ -31,21 +25,18 @@ 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; + int orientation; /** * Size of the layout excluding any margins. */ - private Size activeLayoutSize = new Size(0, 0); + Size activeLayoutSize = new Size(0, 0); - private boolean isRendering = false; + boolean isRendering = false; private String width = ""; - private boolean sizeHasChangedDuringRendering = false; + boolean sizeHasChangedDuringRendering = false; private ValueMap expandRatios; @@ -55,24 +46,8 @@ public class VOrderedLayout extends CellBasedLayout { 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) { @@ -86,194 +61,7 @@ public class VOrderedLayout extends CellBasedLayout { 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() { + void layoutSizeMightHaveChanged() { Size oldSize = new Size(activeLayoutSize.getWidth(), activeLayoutSize.getHeight()); calculateLayoutDimensions(); @@ -286,7 +74,7 @@ public class VOrderedLayout extends CellBasedLayout { } } - private void updateWidgetSizes() { + void updateWidgetSizes() { for (ChildComponentContainer childComponentContainer : widgetToComponentContainer .values()) { @@ -297,7 +85,7 @@ public class VOrderedLayout extends CellBasedLayout { } } - private void recalculateLayout() { + void recalculateLayout() { /* Calculate space for relative size components */ int spaceForExpansion = calculateLayoutDimensions(); @@ -338,30 +126,13 @@ public class VOrderedLayout extends CellBasedLayout { } - 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() { + boolean updateRelativeSizesInNonMainDirection() { int updateDirection = 1 - orientation; if ((updateDirection == ORIENTATION_HORIZONTAL && !isDynamicWidth()) || (updateDirection == ORIENTATION_VERTICAL && !isDynamicHeight())) { @@ -466,7 +237,7 @@ public class VOrderedLayout extends CellBasedLayout { return widgetWidth; } - private void calculateAlignments() { + void calculateAlignments() { int w = 0; int h = 0; @@ -674,7 +445,7 @@ public class VOrderedLayout extends CellBasedLayout { * Updates the spacing between components. Needs to be done only when * components are added/removed. */ - private void updateContainerMargins() { + void updateContainerMargins() { ChildComponentContainer firstChildComponent = getFirstChildComponentContainer(); if (firstChildComponent != null) { firstChildComponent.setMarginLeft(0); @@ -695,15 +466,15 @@ public class VOrderedLayout extends CellBasedLayout { } } - private boolean isHorizontal() { + boolean isHorizontal() { return orientation == ORIENTATION_HORIZONTAL; } - private boolean isVertical() { + boolean isVertical() { return orientation == ORIENTATION_VERTICAL; } - private ChildComponentContainer createChildContainer(Widget child) { + ChildComponentContainer createChildContainer(VPaintableWidget child) { // Create a container DIV for the child ChildComponentContainer childComponent = new ChildComponentContainer( @@ -781,15 +552,15 @@ public class VOrderedLayout extends CellBasedLayout { setRootSize(); } - private void setRootSize() { + void setRootSize() { root.getStyle().setPropertyPx("width", activeLayoutSize.getWidth()); root.getStyle().setPropertyPx("height", activeLayoutSize.getHeight()); } - public boolean requestLayout(Set<Paintable> children) { - for (Paintable p : children) { + public boolean requestLayout(Set<Widget> children) { + for (Widget p : children) { /* Update widget size from DOM */ - ChildComponentContainer componentContainer = getComponentContainer((Widget) p); + ChildComponentContainer componentContainer = getComponentContainer(p); // This should no longer be needed (after #2563) // if (isDynamicWidth()) { // componentContainer.setUnlimitedContainerWidth(); @@ -898,7 +669,7 @@ public class VOrderedLayout extends CellBasedLayout { for (int i = 0; i < renderedWidgets.size(); i++) { Widget widget = renderedWidgets.get(i); - String pid = client.getPid(widget.getElement()); + String pid = VPaintableMap.get(client).getPid(widget); ChildComponentContainer container = getComponentContainer(widget); @@ -940,18 +711,6 @@ public class VOrderedLayout extends CellBasedLayout { } } - 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. @@ -962,8 +721,12 @@ public class VOrderedLayout extends CellBasedLayout { * @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) { + VPaintableWidget getComponent(Element element) { return Util.getPaintableForElement(client, this, element); } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayoutPaintable.java new file mode 100644 index 0000000000..5fa4f18efe --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VOrderedLayoutPaintable.java @@ -0,0 +1,254 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.Iterator; + +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.RenderInformation.FloatSize; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.layout.CellBasedLayoutPaintable; +import com.vaadin.terminal.gwt.client.ui.layout.ChildComponentContainer; + +public abstract class VOrderedLayoutPaintable extends CellBasedLayoutPaintable { + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this, EventId.LAYOUT_CLICK) { + + @Override + protected VPaintableWidget getChildComponent(Element element) { + return getWidgetForPaintable().getComponent(element); + } + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + }; + + public void updateCaption(VPaintableWidget paintable, UIDL uidl) { + Widget widget = paintable.getWidgetForPaintable(); + ChildComponentContainer componentContainer = getWidgetForPaintable() + .getComponentContainer(widget); + componentContainer.updateCaption(uidl, getConnection()); + if (!getWidgetForPaintable().isRendering) { + /* + * This was a component-only update and the possible size change + * must be propagated to the layout + */ + getConnection().captionSizeUpdated(widget); + } + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().isRendering = true; + super.updateFromUIDL(uidl, client); + + // Only non-cached, visible UIDL:s can introduce changes + if (uidl.getBooleanAttribute("cached") + || uidl.getBooleanAttribute("invisible")) { + getWidgetForPaintable().isRendering = false; + return; + } + + clickEventHandler.handleEventHandlerRegistration(client); + + // 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 VPaintableWidget childPaintable = client + .getPaintable(childUIDL); + Widget widget = childPaintable.getWidgetForPaintable(); + + // Create container for component + ChildComponentContainer childComponentContainer = getWidgetForPaintable() + .getComponentContainer(widget); + + if (childComponentContainer == null) { + // This is a new component + childComponentContainer = getWidgetForPaintable() + .createChildContainer(childPaintable); + } 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.setPaintable(childPaintable); + } + + getWidgetForPaintable().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(getWidgetForPaintable().orientation)) { + relativeSizeComponents.add(childComponentContainer); + relativeSizeComponentUIDL.add(childUIDL); + } else { + if (getWidgetForPaintable().isDynamicWidth()) { + childComponentContainer.renderChild(childUIDL, client, -1); + } else { + childComponentContainer + .renderChild(childUIDL, client, + getWidgetForPaintable().activeLayoutSize + .getWidth()); + } + if (getWidgetForPaintable().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 + */ + getWidgetForPaintable().removeChildrenAfter(pos); + + // w.mark("Old children removed"); + + /* Fetch alignments and expand ratio from UIDL */ + getWidgetForPaintable().updateAlignmentsAndExpandRatios(uidl, + uidlWidgets); + // w.mark("Alignments and expand ratios updated"); + + /* Fetch widget sizes from rendered components */ + getWidgetForPaintable().updateWidgetSizes(); + // w.mark("Widget sizes updated"); + + getWidgetForPaintable().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 (getWidgetForPaintable().isDynamicWidth()) { + childComponentContainer.renderChild(childUIDL, client, -1); + } else { + childComponentContainer.renderChild(childUIDL, client, + getWidgetForPaintable().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 : getWidgetForPaintable() + .getComponentContainers()) { + + /* 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 ((getWidgetForPaintable().isHorizontal() && getWidgetForPaintable() + .isDynamicHeight()) + || (getWidgetForPaintable().isVertical() && getWidgetForPaintable() + .isDynamicWidth())) { + getWidgetForPaintable().layoutSizeMightHaveChanged(); + } + // w.mark("Layout dimensions updated"); + + /* Update component spacing */ + getWidgetForPaintable().updateContainerMargins(); + + /* + * Update component sizes for components with relative size in non-main + * direction + */ + if (getWidgetForPaintable().updateRelativeSizesInNonMainDirection()) { + // Sizes updated - might affect the other dimension so we need to + // recheck the widget sizes and recalculate layout dimensions + getWidgetForPaintable().updateWidgetSizes(); + getWidgetForPaintable().layoutSizeMightHaveChanged(); + } + getWidgetForPaintable().calculateAlignments(); + // w.mark("recalculateComponentSizesAndAlignments done"); + + getWidgetForPaintable().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. + */ + getWidgetForPaintable().root.getStyle().setProperty("zoom", "1"); + } + + // w.mark("runDescendentsLayout done"); + getWidgetForPaintable().isRendering = false; + getWidgetForPaintable().sizeHasChangedDuringRendering = false; + } + + @Override + protected abstract VOrderedLayout createWidget(); + + @Override + public VOrderedLayout getWidgetForPaintable() { + return (VOrderedLayout) super.getWidgetForPaintable(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java b/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java index 413f30ad06..3d462fb0c6 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 @@ -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 diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VPanel.java index 036f4f0600..354a42c4d5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VPanel.java @@ -8,11 +8,8 @@ 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; @@ -22,38 +19,38 @@ 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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; 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(); + final Element captionNode = DOM.createDiv(); private final Element captionText = DOM.createSpan(); private Icon icon; - private final Element bottomDecoration = DOM.createDiv(); + final Element bottomDecoration = DOM.createDiv(); - private final Element contentNode = DOM.createDiv(); + final Element contentNode = DOM.createDiv(); private Element errorIndicatorElement; private String height; - private Paintable layout; + VPaintableWidget layout; ShortcutActionHandler shortcutHandler; @@ -61,9 +58,9 @@ public class VPanel extends SimplePanel implements Container, private Element geckoCaptionMeter; - private int scrollTop; + int scrollTop; - private int scrollLeft; + int scrollLeft; private RenderInformation renderInformation = new RenderInformation(); @@ -75,21 +72,12 @@ public class VPanel extends SimplePanel implements Container, private int captionMarginLeft = -1; - private boolean rendering; + 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() { @@ -156,129 +144,10 @@ public class VPanel extends SimplePanel implements Container, return contentNode; } - private void setCaption(String text) { + 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)) { @@ -288,7 +157,7 @@ public class VPanel extends SimplePanel implements Container, } } - private void handleError(UIDL uidl) { + void handleError(UIDL uidl) { if (uidl.hasAttribute("error")) { if (errorIndicatorElement == null) { errorIndicatorElement = DOM.createSpan(); @@ -304,7 +173,7 @@ public class VPanel extends SimplePanel implements Container, } } - private void setIconUri(UIDL uidl, ApplicationConnection client) { + void setIconUri(UIDL uidl, ApplicationConnection client) { final String iconUri = uidl.hasAttribute("icon") ? uidl .getStringAttribute("icon") : null; if (iconUri == null) { @@ -322,60 +191,20 @@ public class VPanel extends SimplePanel implements Container, } 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(""))) { + if ((BrowserInfo.get().isIE()) && (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 + * IE (what version??) 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() + int layoutWidth = layout.getWidgetForPaintable().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"); } @@ -433,7 +262,7 @@ public class VPanel extends SimplePanel implements Container, } } else if (captionNode.isOrHasChild(target)) { if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } } } @@ -509,7 +338,8 @@ public class VPanel extends SimplePanel implements Container, if (height.equals("")) { // Width change may affect height - Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this); + Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this, + this); } } @@ -548,12 +378,12 @@ public class VPanel extends SimplePanel implements Container, public void replaceChildComponent(Widget oldComponent, Widget newComponent) { // TODO This is untested as no layouts require this - if (oldComponent != layout) { + if (oldComponent != layout.getWidgetForPaintable()) { return; } setWidget(newComponent); - layout = (Paintable) newComponent; + layout = VPaintableMap.get(client).getPaintable(newComponent); } public RenderSpace getAllocatedSpace(Widget child) { @@ -577,10 +407,10 @@ public class VPanel extends SimplePanel implements Container, return new RenderSpace(w, h, true); } - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { // content size change might cause change to its available space // (scrollbars) - client.handleComponentRelativeSize((Widget) layout); + client.handleComponentRelativeSize(layout.getWidgetForPaintable()); if (height != null && height != "" && width != null && width != "") { /* * If the height and width has been specified the child components @@ -592,10 +422,6 @@ public class VPanel extends SimplePanel implements Container, 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(); @@ -606,4 +432,8 @@ public class VPanel extends SimplePanel implements Container, return shortcutHandler; } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPanelPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VPanelPaintable.java new file mode 100644 index 0000000000..c401c6db84 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VPanelPaintable.java @@ -0,0 +1,175 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; +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.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VPanelPaintable extends VAbstractPaintableWidgetContainer { + + public static final String CLICK_EVENT_IDENTIFIER = "click"; + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this, + CLICK_EVENT_IDENTIFIER) { + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + }; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + if (!uidl.hasAttribute("cached")) { + + // Handle caption displaying and style names, prior generics. + // Affects size + // calculations + + // Restore default stylenames + getWidgetForPaintable().contentNode.setClassName(VPanel.CLASSNAME + + "-content"); + getWidgetForPaintable().bottomDecoration + .setClassName(VPanel.CLASSNAME + "-deco"); + getWidgetForPaintable().captionNode.setClassName(VPanel.CLASSNAME + + "-caption"); + boolean hasCaption = false; + if (uidl.hasAttribute("caption") + && !uidl.getStringAttribute("caption").equals("")) { + getWidgetForPaintable().setCaption( + uidl.getStringAttribute("caption")); + hasCaption = true; + } else { + getWidgetForPaintable().setCaption(""); + getWidgetForPaintable().captionNode + .setClassName(VPanel.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 = 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; + for (int i = 0; i < styles.length; i++) { + captionClass += " " + captionBaseClass + "-" + styles[i]; + contentClass += " " + contentBaseClass + "-" + styles[i]; + decoClass += " " + decoBaseClass + "-" + styles[i]; + } + getWidgetForPaintable().captionNode.setClassName(captionClass); + getWidgetForPaintable().contentNode.setClassName(contentClass); + getWidgetForPaintable().bottomDecoration + .setClassName(decoClass); + + } + } + // Ensure correct implementation + if (client.updateComponent(this, uidl, false)) { + getWidgetForPaintable().rendering = false; + return; + } + + clickEventHandler.handleEventHandlerRegistration(client); + + getWidgetForPaintable().client = client; + getWidgetForPaintable().id = uidl.getId(); + + getWidgetForPaintable().setIconUri(uidl, client); + + getWidgetForPaintable().handleError(uidl); + + // Render content + final UIDL layoutUidl = uidl.getChildUIDL(0); + final VPaintableWidget newLayout = client.getPaintable(layoutUidl); + if (newLayout != getWidgetForPaintable().layout) { + if (getWidgetForPaintable().layout != null) { + client.unregisterPaintable(getWidgetForPaintable().layout); + } + getWidgetForPaintable() + .setWidget(newLayout.getWidgetForPaintable()); + getWidgetForPaintable().layout = newLayout; + } + getWidgetForPaintable().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 (getWidgetForPaintable().shortcutHandler == null) { + getWidgetForPaintable().shortcutHandler = new ShortcutActionHandler( + getId(), client); + } + getWidgetForPaintable().shortcutHandler + .updateActionMap(childUidl); + } + } + } + + if (uidl.hasVariable("scrollTop") + && uidl.getIntVariable("scrollTop") != getWidgetForPaintable().scrollTop) { + getWidgetForPaintable().scrollTop = uidl + .getIntVariable("scrollTop"); + getWidgetForPaintable().contentNode + .setScrollTop(getWidgetForPaintable().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 + getWidgetForPaintable().scrollTop = getWidgetForPaintable().contentNode + .getScrollTop(); + } + + if (uidl.hasVariable("scrollLeft") + && uidl.getIntVariable("scrollLeft") != getWidgetForPaintable().scrollLeft) { + getWidgetForPaintable().scrollLeft = uidl + .getIntVariable("scrollLeft"); + getWidgetForPaintable().contentNode + .setScrollLeft(getWidgetForPaintable().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 + getWidgetForPaintable().scrollLeft = getWidgetForPaintable().contentNode + .getScrollLeft(); + } + + // Must be run after scrollTop is set as Webkit overflow fix re-sets the + // scrollTop + getWidgetForPaintable().runHacks(false); + + // And apply tab index + if (uidl.hasVariable("tabindex")) { + getWidgetForPaintable().contentNode.setTabIndex(uidl + .getIntVariable("tabindex")); + } + + getWidgetForPaintable().rendering = false; + + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + // NOP: layouts caption, errors etc not rendered in Panel + } + + @Override + public VPanel getWidgetForPaintable() { + return (VPanel) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPanel.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPasswordFieldPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VPasswordFieldPaintable.java new file mode 100644 index 0000000000..98be1250a3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VPasswordFieldPaintable.java @@ -0,0 +1,28 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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 VPasswordFieldPaintable extends VTextFieldPaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPasswordField.class); + } + + @Override + public VPasswordField getWidgetForPaintable() { + return (VPasswordField) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java index 8bf2f6bfbf..1c0b937e05 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java @@ -21,16 +21,10 @@ 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; /** * Represents a date selection component with a text field and a popup date @@ -42,19 +36,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 +99,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 +120,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 +135,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 +172,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; diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendarPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendarPaintable.java new file mode 100644 index 0000000000..96e966a993 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendarPaintable.java @@ -0,0 +1,138 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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.VCalendarPanel.FocusChangeListener; +import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener; + +public class VPopupCalendarPaintable extends VTextualDatePaintable { + + /* + * (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 = getWidgetForPaintable().readonly; + boolean lastEnabledState = getWidgetForPaintable().isEnabled(); + + getWidgetForPaintable().parsable = uidl.getBooleanAttribute("parsable"); + + super.updateFromUIDL(uidl, client); + + String popupStyleNames = ApplicationConnection.getStyleName( + VPopupCalendar.POPUP_PRIMARY_STYLE_NAME, uidl, false); + popupStyleNames += " " + + VDateField.CLASSNAME + + "-" + + VPopupCalendar + .resolutionToString(getWidgetForPaintable().currentResolution); + getWidgetForPaintable().popup.setStyleName(popupStyleNames); + + getWidgetForPaintable().calendar + .setDateTimeService(getWidgetForPaintable() + .getDateTimeService()); + getWidgetForPaintable().calendar + .setShowISOWeekNumbers(getWidgetForPaintable() + .isShowISOWeekNumbers()); + if (getWidgetForPaintable().calendar.getResolution() != getWidgetForPaintable().currentResolution) { + getWidgetForPaintable().calendar + .setResolution(getWidgetForPaintable().currentResolution); + if (getWidgetForPaintable().calendar.getDate() != null) { + getWidgetForPaintable().calendar + .setDate((Date) getWidgetForPaintable() + .getCurrentDate().clone()); + // force re-render when changing resolution only + getWidgetForPaintable().calendar.renderCalendar(); + } + } + getWidgetForPaintable().calendarToggle + .setEnabled(getWidgetForPaintable().enabled); + + if (getWidgetForPaintable().currentResolution <= VPopupCalendar.RESOLUTION_MONTH) { + getWidgetForPaintable().calendar + .setFocusChangeListener(new FocusChangeListener() { + public void focusChanged(Date date) { + getWidgetForPaintable().updateValue(date); + getWidgetForPaintable().buildDate(); + Date date2 = getWidgetForPaintable().calendar + .getDate(); + date2.setYear(date.getYear()); + date2.setMonth(date.getMonth()); + } + }); + } else { + getWidgetForPaintable().calendar.setFocusChangeListener(null); + } + + if (getWidgetForPaintable().currentResolution > VPopupCalendar.RESOLUTION_DAY) { + getWidgetForPaintable().calendar + .setTimeChangeListener(new TimeChangeListener() { + public void changed(int hour, int min, int sec, int msec) { + Date d = getWidgetForPaintable().getDate(); + if (d == null) { + // date currently null, use the value from + // calendarPanel + // (~ client time at the init of the widget) + d = (Date) getWidgetForPaintable().calendar + .getDate().clone(); + } + d.setHours(hour); + d.setMinutes(min); + d.setSeconds(sec); + DateTimeService.setMilliseconds(d, msec); + + // Always update time changes to the server + getWidgetForPaintable().updateValue(d); + + // Update text field + getWidgetForPaintable().buildDate(); + } + }); + } + + if (getWidgetForPaintable().readonly) { + getWidgetForPaintable().calendarToggle + .addStyleName(VPopupCalendar.CLASSNAME + "-button-readonly"); + } else { + getWidgetForPaintable().calendarToggle + .removeStyleName(VPopupCalendar.CLASSNAME + + "-button-readonly"); + } + + if (lastReadOnlyState != getWidgetForPaintable().readonly + || lastEnabledState != getWidgetForPaintable().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) + getWidgetForPaintable().updateWidth(); + } + + getWidgetForPaintable().calendarToggle.setEnabled(true); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPopupCalendar.class); + } + + @Override + public VPopupCalendar getWidgetForPaintable() { + return (VPopupCalendar) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java index 907e11ac2d..34bf0ca619 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java @@ -25,13 +25,12 @@ 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.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.VPaintableWidget; import com.vaadin.terminal.gwt.client.VTooltip; import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; @@ -40,13 +39,13 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { 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 +81,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 +93,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(); } @@ -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 VPaintableWidget popupComponentPaintable = null; + Widget popupComponentWidget = null; + VCaptionWrapper captionWrapper = null; private boolean hasHadMouseOver = false; private boolean hideOnMouseOut = true; @@ -346,15 +290,13 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - Paintable newPopupComponent = client.getPaintable(uidl + VPaintableWidget newPopupComponent = client.getPaintable(uidl .getChildUIDL(0)); if (newPopupComponent != popupComponentPaintable) { - - setWidget((Widget) newPopupComponent); - - popupComponentWidget = (Widget) newPopupComponent; - + Widget newWidget = newPopupComponent.getWidgetForPaintable(); + setWidget(newWidget); + popupComponentWidget = newWidget; popupComponentPaintable = newPopupComponent; } @@ -443,35 +385,16 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { popup.popupComponentWidget = newComponent; } - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { 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); if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } } @@ -501,4 +424,8 @@ public class VPopupView extends HTML implements Container, Iterable<Widget> { }; } + public Widget getWidgetForPaintable() { + return this; + } + }// class VPopupView diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupViewPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupViewPaintable.java new file mode 100644 index 0000000000..e2399bafed --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VPopupViewPaintable.java @@ -0,0 +1,104 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.VCaption; +import com.vaadin.terminal.gwt.client.VCaptionWrapper; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VPopupViewPaintable extends VAbstractPaintableWidgetContainer { + + /** + * + * + * @see com.vaadin.terminal.gwt.client.VPaintableWidget#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 + getWidgetForPaintable().client = client; + getWidgetForPaintable().uidlId = uidl.getId(); + + getWidgetForPaintable().hostPopupVisible = uidl + .getBooleanVariable("popupVisibility"); + + getWidgetForPaintable().setHTML(uidl.getStringAttribute("html")); + + if (uidl.hasAttribute("hideOnMouseOut")) { + getWidgetForPaintable().popup.setHideOnMouseOut(uidl + .getBooleanAttribute("hideOnMouseOut")); + } + + // Render the popup if visible and show it. + if (getWidgetForPaintable().hostPopupVisible) { + UIDL popupUIDL = uidl.getChildUIDL(0); + + // showPopupOnTop(popup, hostReference); + getWidgetForPaintable().preparePopup(getWidgetForPaintable().popup); + getWidgetForPaintable().popup.updateFromUIDL(popupUIDL, client); + if (uidl.hasAttribute("style")) { + final String[] styles = uidl.getStringAttribute("style").split( + " "); + final StringBuffer styleBuf = new StringBuffer(); + final String primaryName = getWidgetForPaintable().popup + .getStylePrimaryName(); + styleBuf.append(primaryName); + for (int i = 0; i < styles.length; i++) { + styleBuf.append(" "); + styleBuf.append(primaryName); + styleBuf.append("-"); + styleBuf.append(styles[i]); + } + getWidgetForPaintable().popup.setStyleName(styleBuf.toString()); + } else { + getWidgetForPaintable().popup + .setStyleName(getWidgetForPaintable().popup + .getStylePrimaryName()); + } + getWidgetForPaintable().showPopup(getWidgetForPaintable().popup); + + // The popup shouldn't be visible, try to hide it. + } else { + getWidgetForPaintable().popup.hide(); + } + }// updateFromUIDL + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + if (VCaption.isNeeded(uidl)) { + if (getWidgetForPaintable().popup.captionWrapper != null) { + getWidgetForPaintable().popup.captionWrapper + .updateCaption(uidl); + } else { + getWidgetForPaintable().popup.captionWrapper = new VCaptionWrapper( + component, getConnection()); + getWidgetForPaintable().popup + .setWidget(getWidgetForPaintable().popup.captionWrapper); + getWidgetForPaintable().popup.captionWrapper + .updateCaption(uidl); + } + } else { + if (getWidgetForPaintable().popup.captionWrapper != null) { + getWidgetForPaintable().popup + .setWidget(getWidgetForPaintable().popup.popupComponentWidget); + } + } + } + + @Override + public VPopupView getWidgetForPaintable() { + return (VPopupView) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPopupView.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java b/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java index d5eac590ad..cff6bf89bd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java @@ -9,20 +9,18 @@ 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/VProgressIndicatorPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicatorPaintable.java new file mode 100644 index 0000000000..39ffe1ad96 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicatorPaintable.java @@ -0,0 +1,65 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; + +public class VProgressIndicatorPaintable extends VAbstractPaintableWidget { + + 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; + } + + // Save details + getWidgetForPaintable().client = client; + + getWidgetForPaintable().indeterminate = uidl + .getBooleanAttribute("indeterminate"); + + if (getWidgetForPaintable().indeterminate) { + String basename = VProgressIndicator.CLASSNAME + "-indeterminate"; + getWidgetForPaintable().addStyleName(basename); + if (uidl.getBooleanAttribute("disabled")) { + getWidgetForPaintable().addStyleName(basename + "-disabled"); + } else { + getWidgetForPaintable().removeStyleName(basename + "-disabled"); + } + } else { + try { + final float f = Float.parseFloat(uidl + .getStringAttribute("state")); + final int size = Math.round(100 * f); + DOM.setStyleAttribute(getWidgetForPaintable().indicator, + "width", size + "%"); + } catch (final Exception e) { + } + } + + if (!uidl.getBooleanAttribute("disabled")) { + getWidgetForPaintable().interval = uidl + .getIntAttribute("pollinginterval"); + getWidgetForPaintable().poller + .scheduleRepeating(getWidgetForPaintable().interval); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VProgressIndicator.class); + } + + @Override + public VProgressIndicator getWidgetForPaintable() { + return (VProgressIndicator) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java index f7d23d4453..24763745e3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -52,6 +52,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.RootPanel; import com.google.gwt.user.client.ui.UIObject; @@ -61,12 +62,13 @@ 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.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; 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.VConsole; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.VTooltip; import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; @@ -77,6 +79,7 @@ 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.label.VLabel; /** * VScrollTable @@ -101,17 +104,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"; @@ -160,10 +177,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>(); @@ -176,15 +193,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 @@ -195,7 +212,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 @@ -204,7 +221,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, private Timer scrollingVelocityTimer = null; - private String[] bodyActionKeys; + String[] bodyActionKeys; private boolean enableDebug = false; @@ -280,19 +297,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) { @@ -381,12 +397,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; @@ -401,9 +417,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,15 +434,15 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, 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 @@ -440,8 +456,8 @@ 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; public VScrollTable() { setMultiSelectMode(MULTISELECT_MODE_DEFAULT); @@ -808,211 +824,7 @@ 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; - - 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 (!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); @@ -1035,7 +847,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, scrollBody.restoreRowVisibility(); } - private void updateColumnProperties(UIDL uidl) { + void updateColumnProperties(UIDL uidl) { updateColumnOrder(uidl); updateCollapsedColumns(uidl); @@ -1070,7 +882,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")) { @@ -1107,7 +919,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"); @@ -1115,7 +927,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); @@ -1130,7 +942,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) { @@ -1145,7 +957,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"); @@ -1160,7 +972,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } - private void updateSelectionProperties(UIDL uidl) { + void updateSelectionProperties(UIDL uidl) { setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT); @@ -1169,18 +981,18 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (uidl.hasAttribute("selectmode")) { if (uidl.getBooleanAttribute("readonly")) { - selectMode = Table.SELECT_MODE_NONE; + 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()) { @@ -1217,7 +1029,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, 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(); @@ -1245,7 +1057,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) { @@ -1270,7 +1082,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) { @@ -1284,7 +1096,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); } @@ -1301,15 +1113,16 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * IScrollTableRows). This is done lazily as Table must survive from * "subtreecaching" logic. */ - private void purgeUnregistryBag() { + void purgeUnregistryBag() { for (Iterator<Panel> iterator = lazyUnregistryBag.iterator(); iterator .hasNext();) { - client.unregisterChildPaintables(iterator.next()); + VPaintableMap.get(client) + .unregisterChildPaintables(iterator.next()); } lazyUnregistryBag.clear(); } - private void updateActionMap(UIDL mainUidl) { + void updateActionMap(UIDL mainUidl) { UIDL actionsUidl = mainUidl.getChildByTagName("actions"); if (actionsUidl == null) { return; @@ -1411,7 +1224,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) { @@ -1428,7 +1241,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, discardRowsOutsideCacheWindow(); } - private void updateRowsInBody(UIDL partialRowUpdates) { + void updateRowsInBody(UIDL partialRowUpdates) { if (partialRowUpdates == null) { return; } @@ -1561,20 +1374,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; } @@ -1590,7 +1403,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) { @@ -1766,7 +1579,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } client.updateVariable(paintableId, "columnorder", columnOrder, false); - if (client.hasEventListeners(this, COLUMN_REORDER_EVENT_ID)) { + if (client.hasWidgetEventListeners(this, COLUMN_REORDER_EVENT_ID)) { client.sendPendingVariableChanges(); } } @@ -2105,19 +1918,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; @@ -2292,11 +2105,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(); @@ -2500,7 +2312,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The click event */ private void fireHeaderClickedEvent(Event event) { - if (client.hasEventListeners(VScrollTable.this, + if (client.hasWidgetEventListeners(VScrollTable.this, HEADER_CLICK_EVENT_ID)) { MouseEventDetails details = new MouseEventDetails(event); client.updateVariable(paintableId, "headerClickEvent", @@ -2756,8 +2568,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) { @@ -3024,12 +2835,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) { @@ -3548,7 +3354,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The click event */ private void fireFooterClickedEvent(Event event) { - if (client.hasEventListeners(VScrollTable.this, + if (client.hasWidgetEventListeners(VScrollTable.this, FOOTER_CLICK_EVENT_ID)) { MouseEventDetails details = new MouseEventDetails(event); client.updateVariable(paintableId, "footerClickEvent", @@ -3880,12 +3686,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); } /** @@ -4305,12 +4106,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows .get(index); // Unregister row tooltip - client.registerTooltip(VScrollTable.this, toBeRemoved.getElement(), - null); + client.registerWidgetTooltip(VScrollTable.this, + toBeRemoved.getElement(), null); for (int i = 0; i < toBeRemoved.getElement().getChildCount(); i++) { // Unregister cell tooltips Element td = toBeRemoved.getElement().getChild(i).cast(); - client.registerTooltip(VScrollTable.this, td, null); + client.registerWidgetTooltip(VScrollTable.this, td, null); } lazyUnregistryBag.add(toBeRemoved); tBodyElement.removeChild(toBeRemoved.getElement()); @@ -4569,10 +4370,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, String rowDescription = uidl.getStringAttribute("rowdescr"); if (rowDescription != null && !rowDescription.equals("")) { TooltipInfo info = new TooltipInfo(rowDescription); - client.registerTooltip(VScrollTable.this, rowElement, info); + client.registerWidgetTooltip(VScrollTable.this, rowElement, + info); } else { // Remove possibly previously set tooltip - client.registerTooltip(VScrollTable.this, rowElement, null); + client.registerWidgetTooltip(VScrollTable.this, rowElement, + null); } tHead.getColumnAlignments(); @@ -4650,11 +4453,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 VPaintableWidget cellContent = client .getPaintable((UIDL) cell); - addCell(uidl, (Widget) cellContent, aligns[col++], - style, sorted); + addCell(uidl, cellContent.getWidgetForPaintable(), + aligns[col++], style, sorted); paintComponent(cellContent, (UIDL) cell); } } @@ -4728,7 +4531,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return index; } - protected void paintComponent(Paintable p, UIDL uidl) { + protected void paintComponent(VPaintableWidget p, UIDL uidl) { if (isAttached()) { p.updateFromUIDL(uidl, client); } else { @@ -4744,7 +4547,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, super.onAttach(); if (pendingComponentPaints != null) { for (UIDL uidl : pendingComponentPaints) { - Paintable paintable = client.getPaintable(uidl); + VPaintableWidget paintable = (VPaintableWidget) VPaintableMap + .get(client).getPaintable(uidl.getId()); paintable.updateFromUIDL(uidl, client); } pendingComponentPaints.clear(); @@ -4807,10 +4611,10 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (description != null && !description.equals("")) { TooltipInfo info = new TooltipInfo(description); - client.registerTooltip(VScrollTable.this, td, info); + client.registerWidgetTooltip(VScrollTable.this, td, info); } else { // Remove possibly previously set tooltip - client.registerTooltip(VScrollTable.this, td, null); + client.registerWidgetTooltip(VScrollTable.this, td, null); } td.appendChild(container); @@ -4889,7 +4693,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, */ private boolean handleClickEvent(Event event, Element targetTdOrTr, boolean immediate) { - if (!client.hasEventListeners(VScrollTable.this, + if (!client.hasWidgetEventListeners(VScrollTable.this, ITEM_CLICK_EVENT_ID)) { // Don't send an event if nobody is listening return false; @@ -4933,22 +4737,24 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (!containsWidget) { // Only text nodes has tooltips - if (client.getTooltipTitleInfo(VScrollTable.this, - target) != null) { + if (VPaintableMap.get(client).getWidgetTooltipInfo( + VScrollTable.this, target) != null) { // Cell has description, use it - client.handleTooltipEvent(event, VScrollTable.this, - target); + client.handleWidgetTooltipEvent(event, + VScrollTable.this, target); } else { // Cell might have row description, use row // description - client.handleTooltipEvent(event, VScrollTable.this, + client.handleWidgetTooltipEvent(event, + VScrollTable.this, target.getParentElement()); } } } else { // Table row (tr) - client.handleTooltipEvent(event, VScrollTable.this, target); + client.handleWidgetTooltipEvent(event, VScrollTable.this, + target); } } @@ -4964,7 +4770,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, showContextMenu(event); if (enabled && (actionKeys != null || client - .hasEventListeners(VScrollTable.this, + .hasWidgetEventListeners( + VScrollTable.this, ITEM_CLICK_EVENT_ID))) { /* * Prevent browser context menu only if there are @@ -5253,7 +5060,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, Element targetTdOrTr) { mDown = true; VTransferable transferable = new VTransferable(); - transferable.setDragSource(VScrollTable.this); + transferable.setDragSource(VPaintableMap.get(client) + .getPaintable(VScrollTable.this)); transferable.setData("itemId", "" + rowKey); NodeList<TableCellElement> cells = rowElement.getCells(); for (int i = 0; i < cells.getLength(); i++) { @@ -5523,13 +5331,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } - public boolean requestLayout(Set<Paintable> children) { + public boolean requestLayout(Set<Widget> 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) { + public void updateCaption(VPaintableWidget component, UIDL uidl) { // NOP, not rendered } @@ -5538,6 +5346,10 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, // Component container interface faked here to get layouts // render properly } + + public Widget getWidgetForPaintable() { + return this; + } } protected class VScrollTableGeneratedRow extends VScrollTableRow { @@ -5671,7 +5483,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (!hasFocus) { scrollBodyPanel.setFocus(true); } + } + } /** @@ -5976,28 +5790,24 @@ 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; } @@ -6330,8 +6140,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } @Override - public Paintable getPaintable() { - return VScrollTable.this; + public VPaintableWidget getPaintable() { + return VPaintableMap.get(client).getPaintable(VScrollTable.this); } public ApplicationConnection getApplicationConnection() { @@ -6768,7 +6578,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * actions may need focus. * */ - private void setProperTabIndex() { + void setProperTabIndex() { int storedScrollTop = 0; int storedScrollLeft = 0; @@ -6891,4 +6701,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, VConsole.error(msg); } } + + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTablePaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTablePaintable.java new file mode 100644 index 0000000000..0c41ed1aa3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTablePaintable.java @@ -0,0 +1,254 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +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.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; + +public class VScrollTablePaintable extends VAbstractPaintableWidget { + + /* + * (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) { + getWidgetForPaintable().rendering = true; + + if (uidl.hasAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST)) { + getWidgetForPaintable().serverCacheFirst = uidl + .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST); + getWidgetForPaintable().serverCacheLast = uidl + .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_LAST); + } else { + getWidgetForPaintable().serverCacheFirst = -1; + getWidgetForPaintable().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")) { + getWidgetForPaintable().showColFooters = uidl + .getBooleanAttribute("colfooters"); + } + + getWidgetForPaintable().tFoot + .setVisible(getWidgetForPaintable().showColFooters); + + if (client.updateComponent(this, uidl, true)) { + getWidgetForPaintable().rendering = false; + return; + } + + getWidgetForPaintable().enabled = !uidl.hasAttribute("disabled"); + + if (BrowserInfo.get().isIE8() && !getWidgetForPaintable().enabled) { + /* + * The disabled shim will not cover the table body if it is relative + * in IE8. See #7324 + */ + getWidgetForPaintable().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.STATIC); + } else if (BrowserInfo.get().isIE8()) { + getWidgetForPaintable().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.RELATIVE); + } + + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getStringAttribute("id"); + getWidgetForPaintable().immediate = uidl + .getBooleanAttribute("immediate"); + + int previousTotalRows = getWidgetForPaintable().totalRows; + getWidgetForPaintable().updateTotalRows(uidl); + boolean totalRowsChanged = (getWidgetForPaintable().totalRows != previousTotalRows); + + getWidgetForPaintable().updateDragMode(uidl); + + getWidgetForPaintable().updateSelectionProperties(uidl); + + if (uidl.hasAttribute("alb")) { + getWidgetForPaintable().bodyActionKeys = uidl + .getStringArrayAttribute("alb"); + } else { + // Need to clear the actions if the action handlers have been + // removed + getWidgetForPaintable().bodyActionKeys = null; + } + + getWidgetForPaintable().setCacheRateFromUIDL(uidl); + + getWidgetForPaintable().recalcWidths = uidl + .hasAttribute("recalcWidths"); + if (getWidgetForPaintable().recalcWidths) { + getWidgetForPaintable().tHead.clear(); + getWidgetForPaintable().tFoot.clear(); + } + + getWidgetForPaintable().updatePageLength(uidl); + + getWidgetForPaintable().updateFirstVisibleAndScrollIfNeeded(uidl); + + getWidgetForPaintable().showRowHeaders = uidl + .getBooleanAttribute("rowheaders"); + getWidgetForPaintable().showColHeaders = uidl + .getBooleanAttribute("colheaders"); + + getWidgetForPaintable().updateSortingProperties(uidl); + + boolean keyboardSelectionOverRowFetchInProgress = getWidgetForPaintable() + .selectSelectedRows(uidl); + + getWidgetForPaintable().updateActionMap(uidl); + + getWidgetForPaintable().updateColumnProperties(uidl); + + UIDL ac = uidl.getChildByTagName("-ac"); + if (ac == null) { + if (getWidgetForPaintable().dropHandler != null) { + // remove dropHandler if not present anymore + getWidgetForPaintable().dropHandler = null; + } + } else { + if (getWidgetForPaintable().dropHandler == null) { + getWidgetForPaintable().dropHandler = getWidgetForPaintable().new VScrollTableDropHandler(); + } + getWidgetForPaintable().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 + getWidgetForPaintable().rowRequestHandler.cancel(); + + getWidgetForPaintable().updateRowsInBody(partialRowUpdates); + getWidgetForPaintable().addAndRemoveRows(partialRowAdditions); + } else { + UIDL rowData = uidl.getChildByTagName("rows"); + if (rowData != null) { + // we may have pending cache row fetch, cancel it. See #2136 + getWidgetForPaintable().rowRequestHandler.cancel(); + + if (!getWidgetForPaintable().recalcWidths + && getWidgetForPaintable().initializedAndAttached) { + getWidgetForPaintable().updateBody(rowData, + uidl.getIntAttribute("firstrow"), + uidl.getIntAttribute("rows")); + if (getWidgetForPaintable().headerChangedDuringUpdate) { + getWidgetForPaintable().triggerLazyColumnAdjustment( + true); + } else if (!getWidgetForPaintable() + .isScrollPositionVisible() + || totalRowsChanged + || getWidgetForPaintable().lastRenderedHeight != getWidgetForPaintable().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(getWidgetForPaintable().scrollBodyPanel + .getElement()); + } + }); + } + } else { + getWidgetForPaintable().initializeRows(uidl, rowData); + } + } + } + + if (!getWidgetForPaintable().isSelectable()) { + getWidgetForPaintable().scrollBody + .addStyleName(VScrollTable.CLASSNAME + "-body-noselection"); + } else { + getWidgetForPaintable().scrollBody + .removeStyleName(VScrollTable.CLASSNAME + + "-body-noselection"); + } + + getWidgetForPaintable().hideScrollPositionAnnotation(); + getWidgetForPaintable().purgeUnregistryBag(); + + // selection is no in sync with server, avoid excessive server visits by + // clearing to flag used during the normal operation + if (!keyboardSelectionOverRowFetchInProgress) { + getWidgetForPaintable().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 (getWidgetForPaintable().selectFirstItemInNextRender + || getWidgetForPaintable().focusFirstItemInNextRender) { + getWidgetForPaintable().selectFirstRenderedRowInViewPort( + getWidgetForPaintable().focusFirstItemInNextRender); + getWidgetForPaintable().selectFirstItemInNextRender = getWidgetForPaintable().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 (getWidgetForPaintable().selectLastItemInNextRender + || getWidgetForPaintable().focusLastItemInNextRender) { + getWidgetForPaintable().selectLastRenderedRowInViewPort( + getWidgetForPaintable().focusLastItemInNextRender); + getWidgetForPaintable().selectLastItemInNextRender = getWidgetForPaintable().focusLastItemInNextRender = false; + } + getWidgetForPaintable().multiselectPending = false; + + if (getWidgetForPaintable().focusedRow != null) { + if (!getWidgetForPaintable().focusedRow.isAttached() + && !getWidgetForPaintable().rowRequestHandler.isRunning()) { + // focused row has been orphaned, can't focus + getWidgetForPaintable().focusRowFromBody(); + } + } + + getWidgetForPaintable().tabIndex = uidl.hasAttribute("tabindex") ? uidl + .getIntAttribute("tabindex") : 0; + getWidgetForPaintable().setProperTabIndex(); + + getWidgetForPaintable().resizeSortedColumnForSortIndicator(); + + // Remember this to detect situations where overflow hack might be + // needed during scrolling + getWidgetForPaintable().lastRenderedHeight = getWidgetForPaintable().scrollBody + .getOffsetHeight(); + + getWidgetForPaintable().rendering = false; + getWidgetForPaintable().headerChangedDuringUpdate = false; + + } + + @Override + protected Widget createWidget() { + return GWT.create(VScrollTable.class); + } + + @Override + public VScrollTable getWidgetForPaintable() { + return (VScrollTable) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java b/src/com/vaadin/terminal/gwt/client/ui/VSlider.java index 4a46346613..6e2ff0930e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VSlider.java @@ -13,15 +13,14 @@ 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.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.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; -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 +35,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 +111,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 +134,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 +168,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 +216,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 +270,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(); } @@ -596,4 +510,8 @@ public class VSlider extends SimpleFocusablePanel implements Paintable, Field, protected int getNavigationRightKey() { return KeyCodes.KEY_RIGHT; } + + public Widget getWidgetForPaintable() { + return this; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSliderPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VSliderPaintable.java new file mode 100644 index 0000000000..c4e484a76b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VSliderPaintable.java @@ -0,0 +1,78 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; + +public class VSliderPaintable extends VAbstractPaintableWidget { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + getWidgetForPaintable().client = client; + getWidgetForPaintable().id = uidl.getId(); + + // Ensure correct implementation + if (client.updateComponent(this, uidl, true)) { + return; + } + + getWidgetForPaintable().immediate = uidl + .getBooleanAttribute("immediate"); + getWidgetForPaintable().disabled = uidl.getBooleanAttribute("disabled"); + getWidgetForPaintable().readonly = uidl.getBooleanAttribute("readonly"); + + getWidgetForPaintable().vertical = uidl.hasAttribute("vertical"); + + String style = ""; + if (uidl.hasAttribute("style")) { + style = uidl.getStringAttribute("style"); + } + + if (getWidgetForPaintable().vertical) { + getWidgetForPaintable().addStyleName( + VSlider.CLASSNAME + "-vertical"); + } else { + getWidgetForPaintable().removeStyleName( + VSlider.CLASSNAME + "-vertical"); + } + + getWidgetForPaintable().min = uidl.getDoubleAttribute("min"); + getWidgetForPaintable().max = uidl.getDoubleAttribute("max"); + getWidgetForPaintable().resolution = uidl.getIntAttribute("resolution"); + getWidgetForPaintable().value = new Double( + uidl.getDoubleVariable("value")); + + getWidgetForPaintable().setFeedbackValue(getWidgetForPaintable().value); + + getWidgetForPaintable().buildBase(); + + if (!getWidgetForPaintable().vertical) { + // Draw handle with a delay to allow base to gain maximum width + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + getWidgetForPaintable().buildHandle(); + getWidgetForPaintable().setValue( + getWidgetForPaintable().value, false); + } + }); + } else { + getWidgetForPaintable().buildHandle(); + getWidgetForPaintable().setValue(getWidgetForPaintable().value, + false); + } + } + + @Override + public VSlider getWidgetForPaintable() { + return (VSlider) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VSlider.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java index ff1e2e6b78..3902f064a5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java @@ -4,9 +4,9 @@ package com.vaadin.terminal.gwt.client.ui; -public class VSplitPanelHorizontal extends VSplitPanel { +public class VSplitPanelHorizontal extends VAbstractSplitPanel { public VSplitPanelHorizontal() { - super(VSplitPanel.ORIENTATION_HORIZONTAL); + super(VAbstractSplitPanel.ORIENTATION_HORIZONTAL); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java index dcf7622e50..e61f8cf5e5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java @@ -4,9 +4,9 @@ package com.vaadin.terminal.gwt.client.ui; -public class VSplitPanelVertical extends VSplitPanel { +public class VSplitPanelVertical extends VAbstractSplitPanel { public VSplitPanelVertical() { - super(VSplitPanel.ORIENTATION_VERTICAL); + super(VAbstractSplitPanel.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/VTabsheet.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java index d30d999d16..553a4d673e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java @@ -23,13 +23,15 @@ 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.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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.label.VLabel; public class VTabsheet extends VTabsheetBase { @@ -52,7 +54,7 @@ public class VTabsheet extends VTabsheetBase { /** * Representation of a single "tab" shown in the TabBar - * + * */ private static class Tab extends SimplePanel { private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell"; @@ -204,9 +206,10 @@ public class VTabsheet extends VTabsheetBase { if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { tooltipInfo.setErrorUidl(uidl.getErrors()); } - client.registerTooltip(getTabsheet(), getElement(), tooltipInfo); + client.registerWidgetTooltip(getTabsheet(), getElement(), + tooltipInfo); } else { - client.registerTooltip(getTabsheet(), getElement(), null); + client.registerWidgetTooltip(getTabsheet(), getElement(), null); } boolean ret = super.updateCaption(uidl); @@ -234,40 +237,13 @@ public class VTabsheet extends VTabsheetBase { if (event.getTypeInt() == Event.ONLOAD) { getTabsheet().tabSizeMightHaveChanged(getTab()); } - client.handleTooltipEvent(event, getTabsheet(), getElement()); + client.handleWidgetTooltipEvent(event, getTabsheet(), getElement()); } public Tab getTab() { 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) { @@ -494,7 +470,7 @@ public class VTabsheet extends VTabsheetBase { // Can't use "style" as it's already in use public static final String TAB_STYLE_NAME = "tabstyle"; - private final Element tabs; // tabbar and 'scroller' container + 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 @@ -505,15 +481,15 @@ public class VTabsheet extends VTabsheetBase { private int scrollerIndex = 0; private final TabBar tb = new TabBar(this); - private final VTabsheetPanel tp = new VTabsheetPanel(); + final VTabsheetPanel tp = new VTabsheetPanel(); private final Element contentNode, deco; private String height; private String width; - private boolean waitingForResponse; + boolean waitingForResponse; - private final RenderInformation renderInformation = new RenderInformation(); + final RenderInformation renderInformation = new RenderInformation(); /** * Previous visible widget is set invisible with CSS (not display: none, but @@ -522,7 +498,7 @@ public class VTabsheet extends VTabsheetBase { */ private Widget previousVisibleWidget; - private boolean rendering = false; + boolean rendering = false; private String currentStyle; @@ -570,11 +546,11 @@ public class VTabsheet extends VTabsheetBase { client.updateVariable(id, "close", tabKeys.get(tabIndex), true); } - private boolean isDynamicWidth() { + boolean isDynamicWidth() { return width == null || width.equals(""); } - private boolean isDynamicHeight() { + boolean isDynamicHeight() { return height == null || height.equals(""); } @@ -658,60 +634,7 @@ public class VTabsheet extends VTabsheetBase { 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()) { - // FIXME: This makes tab sheet tabs go to 1px width on every update - // and then back to original width - // update width later, in updateTabScroller(); - DOM.setStyleAttribute(tabs, "width", "1px"); - 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(); - } - - 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) { // Add proper stylenames for all elements (easier to prevent unwanted // style inheritance) if (uidl.hasAttribute("style")) { @@ -753,7 +676,7 @@ public class VTabsheet extends VTabsheetBase { } } - private void updateDynamicWidth() { + void updateDynamicWidth() { // Find width consumed by tabs TableCellElement spacerCell = ((TableElement) tb.getElement().cast()) .getRows().getItem(0).getCells().getItem(tb.getTabCount()); @@ -830,22 +753,24 @@ public class VTabsheet extends VTabsheetBase { tab.recalculateCaptionWidth(); UIDL tabContentUIDL = null; - Paintable tabContent = null; + VPaintableWidget tabContentPaintable = null; + Widget tabContentWidget = null; if (tabUidl.getChildCount() > 0) { tabContentUIDL = tabUidl.getChildUIDL(0); - tabContent = client.getPaintable(tabContentUIDL); + tabContentPaintable = client.getPaintable(tabContentUIDL); + tabContentWidget = tabContentPaintable.getWidgetForPaintable(); } - 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 */ @@ -869,10 +794,10 @@ public class VTabsheet extends VTabsheetBase { } 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); + tabContentPaintable.updateFromUIDL(tabContentUIDL, client); } else if (tp.getWidgetCount() <= index) { tp.add(new PlaceHolder()); } @@ -895,30 +820,32 @@ public class VTabsheet extends VTabsheetBase { } private void renderContent(final UIDL contentUIDL) { - final Paintable content = client.getPaintable(contentUIDL); + final VPaintableWidget content = client.getPaintable(contentUIDL); if (tp.getWidgetCount() > activeTabIndex) { Widget old = tp.getWidget(activeTabIndex); if (old != content) { tp.remove(activeTabIndex); - if (old instanceof Paintable) { - client.unregisterPaintable((Paintable) old); + VPaintableMap paintableMap = VPaintableMap.get(client); + if (paintableMap.isPaintable(old)) { + paintableMap.unregisterPaintable(paintableMap + .getPaintable(old)); } - tp.insert((Widget) content, activeTabIndex); + tp.insert(content.getWidgetForPaintable(), activeTabIndex); } } else { - tp.add((Widget) content); + tp.add(content.getWidgetForPaintable()); } tp.showWidget(activeTabIndex); VTabsheet.this.iLayout(); - (content).updateFromUIDL(contentUIDL, client); + 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.getWidgetForPaintable()); } updateOpenTabSize(); VTabsheet.this.removeStyleDependentName("loading"); @@ -944,7 +871,7 @@ public class VTabsheet extends VTabsheetBase { } } - private void updateContentNodeHeight() { + void updateContentNodeHeight() { if (height != null && !"".equals(height)) { int contentHeight = getOffsetHeight(); contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight"); @@ -1011,7 +938,7 @@ public class VTabsheet extends VTabsheetBase { * 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 @@ -1087,7 +1014,7 @@ public class VTabsheet extends VTabsheetBase { } - private void showAllTabs() { + void showAllTabs() { scrollerIndex = tb.getFirstVisibleTab(); for (int i = 0; i < tb.getTabCount(); i++) { Tab t = tb.getTab(i); @@ -1119,7 +1046,7 @@ public class VTabsheet extends VTabsheetBase { } @Override - protected Iterator getPaintableIterator() { + protected Iterator<Widget> getWidgetIterator() { return tp.iterator(); } @@ -1135,19 +1062,15 @@ public class VTabsheet extends VTabsheetBase { 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) { + public boolean requestLayout(Set<Widget> children) { 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); + for (Widget widget : children) { + client.handleComponentRelativeSize(widget); } return true; } @@ -1195,9 +1118,10 @@ public class VTabsheet extends VTabsheetBase { } @Override - protected Paintable getTab(int index) { + protected VPaintableWidget getTab(int index) { if (tp.getWidgetCount() > index) { - return (Paintable) tp.getWidget(index); + Widget widget = tp.getWidget(index); + return VPaintableMap.get(client).getPaintable(widget); } return null; } @@ -1214,4 +1138,7 @@ public class VTabsheet extends VTabsheetBase { } } + public Widget getWidgetForPaintable() { + return this; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java index 7304f62f41..271aed1859 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java @@ -13,8 +13,8 @@ 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; +import com.vaadin.terminal.gwt.client.VPaintableWidget; abstract class VTabsheetBase extends ComplexPanel implements Container { @@ -33,78 +33,6 @@ abstract class VTabsheetBase extends ComplexPanel implements Container { 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 * @@ -112,7 +40,7 @@ abstract class VTabsheetBase extends ComplexPanel implements Container { * {@link #updateFromUIDL(UIDL, ApplicationConnection)} checks if * instanceof Paintable. Therefore set to <Object> */ - abstract protected Iterator<Object> getPaintableIterator(); + abstract protected Iterator<Widget> getWidgetIterator(); /** * Clears current tabs and contents @@ -143,7 +71,7 @@ abstract class VTabsheetBase extends ComplexPanel implements Container { * Implement in extending classes. This method should return the Paintable * corresponding to the given index. */ - protected abstract Paintable getTab(int index); + protected abstract VPaintableWidget getTab(int index); /** * Implement in extending classes. This method should remove the rendered diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBasePaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBasePaintable.java new file mode 100644 index 0000000000..ec3b02de97 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBasePaintable.java @@ -0,0 +1,96 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public abstract class VTabsheetBasePaintable extends + VAbstractPaintableWidgetContainer { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().client = client; + + // Ensure correct implementation + getWidgetForPaintable().cachedUpdate = client.updateComponent(this, + uidl, true); + if (getWidgetForPaintable().cachedUpdate) { + return; + } + + // Update member references + getWidgetForPaintable().id = uidl.getId(); + getWidgetForPaintable().disabled = uidl.hasAttribute("disabled"); + + // Render content + final UIDL tabs = uidl.getChildUIDL(0); + + // Paintables in the TabSheet before update + ArrayList<Widget> oldWidgets = new ArrayList<Widget>(); + for (Iterator<Widget> iterator = getWidgetForPaintable() + .getWidgetIterator(); iterator.hasNext();) { + oldWidgets.add(iterator.next()); + } + + // Clear previous values + getWidgetForPaintable().tabKeys.clear(); + getWidgetForPaintable().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")) { + getWidgetForPaintable().disabledTabKeys.add(key); + } + + getWidgetForPaintable().tabKeys.add(key); + + if (selected) { + getWidgetForPaintable().activeTabIndex = index; + } + getWidgetForPaintable().renderTab(tab, index, selected, hidden); + index++; + } + + int tabCount = getWidgetForPaintable().getTabCount(); + while (tabCount-- > index) { + getWidgetForPaintable().removeTab(index); + } + + for (int i = 0; i < getWidgetForPaintable().getTabCount(); i++) { + VPaintableWidget p = getWidgetForPaintable().getTab(i); + // During the initial rendering the paintable might be null (this is + // weird...) + if (p != null) { + oldWidgets.remove(p.getWidgetForPaintable()); + } + } + + // Perform unregister for any paintables removed during update + for (Iterator<Widget> iterator = oldWidgets.iterator(); iterator + .hasNext();) { + Widget oldWidget = iterator.next(); + VPaintableWidget oldPaintable = VPaintableMap.get(client) + .getPaintable(oldWidget); + if (oldWidget.isAttached()) { + oldWidget.removeFromParent(); + } + VPaintableMap.get(client).unregisterPaintable(oldPaintable); + } + + } + + @Override + public VTabsheetBase getWidgetForPaintable() { + return (VTabsheetBase) super.getWidgetForPaintable(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPaintable.java new file mode 100644 index 0000000000..4bd91683d6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPaintable.java @@ -0,0 +1,82 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VTabsheetPaintable extends VTabsheetBasePaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + + if (!uidl.getBooleanAttribute("cached")) { + // Handle stylename changes before generics (might affect size + // calculations) + getWidgetForPaintable().handleStyleNames(uidl); + } + + super.updateFromUIDL(uidl, client); + if (getWidgetForPaintable().cachedUpdate) { + getWidgetForPaintable().rendering = false; + return; + } + + // tabs; push or not + if (!getWidgetForPaintable().isDynamicWidth()) { + // FIXME: This makes tab sheet tabs go to 1px width on every update + // and then back to original width + // update width later, in updateTabScroller(); + DOM.setStyleAttribute(getWidgetForPaintable().tabs, "width", "1px"); + DOM.setStyleAttribute(getWidgetForPaintable().tabs, "overflow", + "hidden"); + } else { + getWidgetForPaintable().showAllTabs(); + DOM.setStyleAttribute(getWidgetForPaintable().tabs, "width", ""); + DOM.setStyleAttribute(getWidgetForPaintable().tabs, "overflow", + "visible"); + getWidgetForPaintable().updateDynamicWidth(); + } + + if (!getWidgetForPaintable().isDynamicHeight()) { + // Must update height after the styles have been set + getWidgetForPaintable().updateContentNodeHeight(); + getWidgetForPaintable().updateOpenTabSize(); + } + + getWidgetForPaintable().iLayout(); + + // Re run relative size update to ensure optimal scrollbars + // TODO isolate to situation that visible tab has undefined height + try { + client.handleComponentRelativeSize(getWidgetForPaintable().tp + .getWidget(getWidgetForPaintable().tp.getVisibleWidget())); + } catch (Exception e) { + // Ignore, most likely empty tabsheet + } + + getWidgetForPaintable().renderInformation + .updateSize(getWidgetForPaintable().getElement()); + + getWidgetForPaintable().waitingForResponse = false; + getWidgetForPaintable().rendering = false; + } + + @Override + protected Widget createWidget() { + return GWT.create(VTabsheet.class); + } + + @Override + public VTabsheet getWidgetForPaintable() { + return (VTabsheet) super.getWidgetForPaintable(); + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + /* Tabsheet does not render its children's captions */ + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java b/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java index c6107e3b0e..2c8ed24693 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java @@ -9,8 +9,6 @@ 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; /** * This class represents a multiline textfield (textarea). @@ -29,20 +27,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/VTextAreaPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTextAreaPaintable.java new file mode 100644 index 0000000000..3c21a4ab2f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextAreaPaintable.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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; + +public class VTextAreaPaintable extends VTextFieldPaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Call parent renderer explicitly + super.updateFromUIDL(uidl, client); + + if (uidl.hasAttribute("rows")) { + getWidgetForPaintable().setRows(uidl.getIntAttribute("rows")); + } + + if (getWidgetForPaintable().getMaxLength() >= 0) { + getWidgetForPaintable().sinkEvents(Event.ONKEYUP); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextArea.class); + } + + @Override + public VTextArea getWidgetForPaintable() { + return (VTextArea) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java b/src/com/vaadin/terminal/gwt/client/ui/VTextField.java index 44ee7c11df..80b27e6c8c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextField.java @@ -4,7 +4,6 @@ package com.vaadin.terminal.gwt.client.ui; -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,8 @@ 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; /** * This class represents a basic text input field with one row. @@ -36,9 +31,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 +46,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 +59,20 @@ public class VTextField extends TextBoxBase implements Paintable, Field, */ private boolean valueBeforeEditIsSynced = true; - private boolean immediate = false; + protected boolean immediate = false; private int extraHorizontalPixels = -1; private int extraVerticalPixels = -1; 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 +83,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,14 +105,14 @@ 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 public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (client != null) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } if (listenTextChangeEvents @@ -163,7 +151,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 +173,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 +208,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; @@ -384,7 +262,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")) { @@ -420,20 +298,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)) { + if (blurred && client.hasWidgetEventListeners(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 +344,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 +366,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); + if (client.hasWidgetEventListeners(this, EventId.FOCUS)) { + client.updateVariable(paintableId, EventId.FOCUS, "", true); } } @@ -606,10 +480,6 @@ public class VTextField extends TextBoxBase implements Paintable, Field, } } - 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/VTextFieldPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTextFieldPaintable.java new file mode 100644 index 0000000000..e3eecd5a09 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextFieldPaintable.java @@ -0,0 +1,124 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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.UIDL; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; + +public class VTextFieldPaintable extends VAbstractPaintableWidget implements + BeforeShortcutActionListener { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Save details + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getId(); + + if (client.updateComponent(this, uidl, true)) { + return; + } + + if (uidl.getBooleanAttribute("readonly")) { + getWidgetForPaintable().setReadOnly(true); + } else { + getWidgetForPaintable().setReadOnly(false); + } + + getWidgetForPaintable().inputPrompt = uidl + .getStringAttribute(VTextField.ATTR_INPUTPROMPT); + + getWidgetForPaintable().setMaxLength( + uidl.hasAttribute("maxLength") ? uidl + .getIntAttribute("maxLength") : -1); + + getWidgetForPaintable().immediate = uidl + .getBooleanAttribute("immediate"); + + getWidgetForPaintable().listenTextChangeEvents = client + .hasEventListeners(this, "ie"); + if (getWidgetForPaintable().listenTextChangeEvents) { + getWidgetForPaintable().textChangeEventMode = uidl + .getStringAttribute(VTextField.ATTR_TEXTCHANGE_EVENTMODE); + if (getWidgetForPaintable().textChangeEventMode + .equals(VTextField.TEXTCHANGE_MODE_EAGER)) { + getWidgetForPaintable().textChangeEventTimeout = 1; + } else { + getWidgetForPaintable().textChangeEventTimeout = uidl + .getIntAttribute(VTextField.ATTR_TEXTCHANGE_TIMEOUT); + if (getWidgetForPaintable().textChangeEventTimeout < 1) { + // Sanitize and allow lazy/timeout with timeout set to 0 to + // work as eager + getWidgetForPaintable().textChangeEventTimeout = 1; + } + } + getWidgetForPaintable().sinkEvents(VTextField.TEXTCHANGE_EVENTS); + getWidgetForPaintable().attachCutEventListener( + getWidgetForPaintable().getElement()); + } + + if (uidl.hasAttribute("cols")) { + getWidgetForPaintable().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) + && getWidgetForPaintable().valueBeforeEdit != null && text + .equals(getWidgetForPaintable().valueBeforeEdit))) { + getWidgetForPaintable().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() { + getWidgetForPaintable().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) { + getWidgetForPaintable().setWordwrap(false); + } else { + getWidgetForPaintable().setWordwrap(true); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextField.class); + } + + @Override + public VTextField getWidgetForPaintable() { + return (VTextField) super.getWidgetForPaintable(); + } + + public void onBeforeShortcutAction(Event e) { + getWidgetForPaintable().valueChange(false); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java b/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java index 56cdf05ddb..bb808321b9 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java @@ -12,29 +12,24 @@ 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; -public class VTextualDate extends VDateField implements Paintable, Field, - ChangeHandler, ContainerResizedListener, Focusable, SubPartAware { +public class VTextualDate extends VDateField implements Field, ChangeHandler, + ContainerResizedListener, 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; @@ -42,11 +37,11 @@ public class VTextualDate extends VDateField implements Paintable, Field, 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() { @@ -67,8 +62,8 @@ public class VTextualDate extends VDateField implements Paintable, Field, setPrompting(false); } if (getClient() != null - && getClient().hasEventListeners(VTextualDate.this, - EventId.FOCUS)) { + && getClient().hasWidgetEventListeners( + VTextualDate.this, EventId.FOCUS)) { getClient() .updateVariable(getId(), EventId.FOCUS, "", true); } @@ -85,8 +80,8 @@ public class VTextualDate extends VDateField implements Paintable, Field, text.setText(readonly ? "" : inputPrompt); } if (getClient() != null - && getClient().hasEventListeners(VTextualDate.this, - EventId.BLUR)) { + && getClient().hasWidgetEventListeners( + VTextualDate.this, EventId.BLUR)) { getClient().updateVariable(getId(), EventId.BLUR, "", true); } } @@ -94,37 +89,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 +112,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 +261,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); - } } @@ -340,11 +297,8 @@ public class VTextualDate extends VDateField implements Paintable, Field, @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"); - } + if (!"".equals(newWidth) + && (isUndefinedWidth() || !newWidth.equals(width))) { needLayout = true; width = newWidth; super.setWidth(width); @@ -354,19 +308,15 @@ public class VTextualDate extends VDateField implements Paintable, Field, } } 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); + return width == null || "".equals(width); } /** @@ -378,10 +328,6 @@ public class VTextualDate extends VDateField implements Paintable, Field, 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; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextualDatePaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTextualDatePaintable.java new file mode 100644 index 0000000000..6a9da31269 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextualDatePaintable.java @@ -0,0 +1,58 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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 VTextualDatePaintable extends VDateFieldPaintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + int origRes = getWidgetForPaintable().currentResolution; + String oldLocale = getWidgetForPaintable().currentLocale; + super.updateFromUIDL(uidl, client); + if (origRes != getWidgetForPaintable().currentResolution + || oldLocale != getWidgetForPaintable().currentLocale) { + // force recreating format string + getWidgetForPaintable().formatStr = null; + } + if (uidl.hasAttribute("format")) { + getWidgetForPaintable().formatStr = uidl + .getStringAttribute("format"); + } + + getWidgetForPaintable().inputPrompt = uidl + .getStringAttribute(VTextualDate.ATTR_INPUTPROMPT); + + getWidgetForPaintable().lenient = !uidl.getBooleanAttribute("strict"); + + getWidgetForPaintable().buildDate(); + // not a FocusWidget -> needs own tabindex handling + if (uidl.hasAttribute("tabindex")) { + getWidgetForPaintable().text.setTabIndex(uidl + .getIntAttribute("tabindex")); + } + + if (getWidgetForPaintable().readonly) { + getWidgetForPaintable().text.addStyleDependentName("readonly"); + } else { + getWidgetForPaintable().text.removeStyleDependentName("readonly"); + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextualDate.class); + } + + @Override + public VTextualDate getWidgetForPaintable() { + return (VTextualDate) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTree.java b/src/com/vaadin/terminal/gwt/client/ui/VTree.java index 4344cec5f5..be337ae0cb 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTree.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTree.java @@ -40,10 +40,10 @@ 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.MouseEventDetails; -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.Util; +import com.vaadin.terminal.gwt.client.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.VTooltip; import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; @@ -58,9 +58,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"; @@ -71,17 +71,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>(); @@ -91,23 +91,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() { @@ -206,24 +206,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"); } @@ -232,99 +214,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) { - 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. @@ -371,7 +260,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; @@ -393,7 +282,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() { @@ -435,7 +324,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 @@ -457,7 +346,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; } } @@ -469,8 +358,8 @@ public class VTree extends FocusElementPanel implements Paintable, } @Override - public Paintable getPaintable() { - return VTree.this; + public VPaintableWidget getPaintable() { + return VPaintableMap.get(client).getPaintable(VTree.this); } public ApplicationConnection getApplicationConnection() { @@ -482,24 +371,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) @@ -568,33 +445,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 @@ -692,11 +562,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(); } @@ -764,14 +634,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) { @@ -779,20 +642,20 @@ public class VTree extends FocusElementPanel implements Paintable, } if (target == nodeCaptionSpan) { - client.handleTooltipEvent(event, VTree.this, key); + client.handleWidgetTooltipEvent(event, VTree.this, key); } final boolean inCaption = target == nodeCaptionSpan || (icon != null && target == icon.getElement()); if (inCaption - && client - .hasEventListeners(VTree.this, ITEM_CLICK_EVENT_ID) + && client.hasWidgetEventListeners(VTree.this, + ITEM_CLICK_EVENT_ID) && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) { fireClick(event); } if (type == Event.ONCLICK) { - if (getElement() == target || ie6compatnode == target) { + if (getElement() == target) { // state change toggleState(); } else if (!readonly && inCaption) { @@ -847,7 +710,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(VPaintableMap.get(client).getPaintable( + VTree.this)); t.setData("itemId", key); VDragEvent drag = VDragAndDropManager.get().startDrag( t, mouseDownEvent, true); @@ -878,7 +742,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 { @@ -939,15 +803,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 @@ -959,7 +814,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. @@ -972,76 +827,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) { @@ -1052,7 +837,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; } @@ -1083,43 +868,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; } @@ -1220,32 +976,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() @@ -1275,19 +1005,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; } @@ -1300,6 +1025,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() { @@ -2174,7 +1927,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(); @@ -2206,11 +1959,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; } @@ -2254,8 +2003,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; } @@ -2317,7 +2065,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/VTreePaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTreePaintable.java new file mode 100644 index 0000000000..d7f8fffbd7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTreePaintable.java @@ -0,0 +1,228 @@ +package com.vaadin.terminal.gwt.client.ui; + +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.TooltipInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.VTree.TreeNode; + +public class VTreePaintable extends VAbstractPaintableWidget { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Ensure correct implementation and let container manage caption + if (client.updateComponent(this, uidl, true)) { + return; + } + + getWidgetForPaintable().rendering = true; + + getWidgetForPaintable().client = client; + + if (uidl.hasAttribute("partialUpdate")) { + handleUpdate(uidl); + getWidgetForPaintable().rendering = false; + return; + } + + getWidgetForPaintable().paintableId = uidl.getId(); + + getWidgetForPaintable().immediate = uidl.hasAttribute("immediate"); + + getWidgetForPaintable().disabled = uidl.getBooleanAttribute("disabled"); + getWidgetForPaintable().readonly = uidl.getBooleanAttribute("readonly"); + + getWidgetForPaintable().dragMode = uidl.hasAttribute("dragMode") ? uidl + .getIntAttribute("dragMode") : 0; + + getWidgetForPaintable().isNullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + + if (uidl.hasAttribute("alb")) { + getWidgetForPaintable().bodyActionKeys = uidl + .getStringArrayAttribute("alb"); + } + + getWidgetForPaintable().body.clear(); + // clear out any references to nodes that no longer are attached + getWidgetForPaintable().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())) { + getWidgetForPaintable().updateDropHandler(childUidl); + continue; + } + childTree = getWidgetForPaintable().new TreeNode(); + updateNodeFromUIDL(childTree, childUidl); + getWidgetForPaintable().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"); + getWidgetForPaintable().selectable = !"none".equals(selectMode); + getWidgetForPaintable().isMultiselect = "multi".equals(selectMode); + + if (getWidgetForPaintable().isMultiselect) { + getWidgetForPaintable().multiSelectMode = uidl + .getIntAttribute("multiselectmode"); + } + + getWidgetForPaintable().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 (getWidgetForPaintable().lastSelection != null) { + getWidgetForPaintable().lastSelection = getWidgetForPaintable() + .getNodeByKey(getWidgetForPaintable().lastSelection.key); + } + if (getWidgetForPaintable().focusedNode != null) { + getWidgetForPaintable().setFocusedNode( + getWidgetForPaintable().getNodeByKey( + getWidgetForPaintable().focusedNode.key)); + } + + if (getWidgetForPaintable().lastSelection == null + && getWidgetForPaintable().focusedNode == null + && !getWidgetForPaintable().selectedIds.isEmpty()) { + getWidgetForPaintable().setFocusedNode( + getWidgetForPaintable().getNodeByKey( + getWidgetForPaintable().selectedIds.iterator() + .next())); + getWidgetForPaintable().focusedNode.setFocused(false); + } + + getWidgetForPaintable().rendering = false; + + } + + @Override + protected Widget createWidget() { + return GWT.create(VTree.class); + } + + @Override + public VTree getWidgetForPaintable() { + return (VTree) super.getWidgetForPaintable(); + } + + private void handleUpdate(UIDL uidl) { + final TreeNode rootNode = getWidgetForPaintable().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("caption"); + String iconUrl = null; + if (action.hasAttribute("icon")) { + iconUrl = getConnection().translateVaadinUri( + action.getStringAttribute("icon")); + } + getWidgetForPaintable().registerAction(key, caption, iconUrl); + } + + } + + public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) { + String nodeKey = uidl.getStringAttribute("key"); + treeNode.setText(uidl.getStringAttribute("caption")); + treeNode.key = nodeKey; + + getWidgetForPaintable().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("style")) { + treeNode.setNodeStyleName(uidl.getStringAttribute("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) + getWidgetForPaintable().selectedIds.add(nodeKey); + } + + treeNode.setIcon(uidl.getStringAttribute("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 = getWidgetForPaintable().new TreeNode(); + updateNodeFromUIDL(childTree, childUidl); + containerNode.add(childTree); + if (!i.hasNext()) { + childTree + .addStyleDependentName(childTree.isLeaf() ? "leaf-last" + : "last"); + childTree.childNodeContainer.addStyleDependentName("last"); + } + } + containerNode.childrenLoaded = true; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java index 8e55400f31..0eddca0ed3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java @@ -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; 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 { diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTreeTablePaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTreeTablePaintable.java new file mode 100644 index 0000000000..8c159f43de --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTreeTablePaintable.java @@ -0,0 +1,101 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +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.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.terminal.gwt.client.ui.VTreeTable.PendingNavigationEvent; + +public class VTreeTablePaintable extends VScrollTablePaintable { + public static final String ATTRIBUTE_HIERARCHY_COLUMN_INDEX = "hci"; + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + FocusableScrollPanel widget = null; + int scrollPosition = 0; + if (getWidgetForPaintable().collapseRequest) { + widget = (FocusableScrollPanel) getWidgetForPaintable() + .getWidget(1); + scrollPosition = widget.getScrollPosition(); + } + getWidgetForPaintable().animationsEnabled = uidl + .getBooleanAttribute("animate"); + getWidgetForPaintable().colIndexOfHierarchy = uidl + .hasAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl + .getIntAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) : 0; + int oldTotalRows = getWidgetForPaintable().getTotalRows(); + super.updateFromUIDL(uidl, client); + if (getWidgetForPaintable().collapseRequest) { + if (getWidgetForPaintable().collapsedRowKey != null + && getWidgetForPaintable().scrollBody != null) { + VScrollTableRow row = getWidgetForPaintable() + .getRenderedRowByKey( + getWidgetForPaintable().collapsedRowKey); + if (row != null) { + getWidgetForPaintable().setRowFocus(row); + getWidgetForPaintable().focus(); + } + } + + int scrollPosition2 = widget.getScrollPosition(); + if (scrollPosition != scrollPosition2) { + widget.setScrollPosition(scrollPosition); + } + + // check which rows are needed from the server and initiate a + // deferred fetch + getWidgetForPaintable().onScroll(null); + } + // Recalculate table size if collapse request, or if page length is zero + // (not sent by server) and row count changes (#7908). + if (getWidgetForPaintable().collapseRequest + || (!uidl.hasAttribute("pagelength") && getWidgetForPaintable() + .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. + */ + getWidgetForPaintable().triggerLazyColumnAdjustment(true); + + getWidgetForPaintable().collapseRequest = false; + } + if (uidl.hasAttribute("focusedRow")) { + String key = uidl.getStringAttribute("focusedRow"); + getWidgetForPaintable().setRowFocus( + getWidgetForPaintable().getRenderedRowByKey(key)); + getWidgetForPaintable().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 + getWidgetForPaintable().focusParentResponsePending = false; + } + + while (!getWidgetForPaintable().collapseRequest + && !getWidgetForPaintable().focusParentResponsePending + && !getWidgetForPaintable().pendingNavigationEvents.isEmpty()) { + // Keep replaying any queued events as long as we don't have any + // potential content changes pending + PendingNavigationEvent event = getWidgetForPaintable().pendingNavigationEvents + .removeFirst(); + getWidgetForPaintable().handleNavigation(event.keycode, event.ctrl, + event.shift); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTreeTable.class); + } + + @Override + public VTreeTable getWidgetForPaintable() { + return (VTreeTable) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java index b569dbc94e..e733b2e73b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java @@ -26,8 +26,7 @@ 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.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; @@ -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 @@ -297,7 +285,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 +294,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()); } @@ -385,16 +373,7 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, private void setInternalHeights() { int captionHeight = 0; - int totalHeight; - if (BrowserInfo.get().isIE6()) { - String o = getElement().getStyle().getOverflow(); - - getElement().getStyle().setOverflow(Overflow.HIDDEN); - totalHeight = getOffsetHeight(); - getElement().getStyle().setProperty("overflow", o); - } else { - totalHeight = getOffsetHeight(); - } + int totalHeight = getOffsetHeight(); if (optionsCaption != null) { captionHeight = Util.getRequiredHeight(optionsCaption); @@ -424,11 +403,6 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, 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(); @@ -642,4 +616,8 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, return null; } + + public Widget getWidgetForPaintable() { + return this; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelectPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelectPaintable.java new file mode 100644 index 0000000000..ce176113da --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelectPaintable.java @@ -0,0 +1,34 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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 VTwinColSelectPaintable extends VOptionGroupBasePaintable { + + @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")) { + getWidgetForPaintable().updateCaptions(uidl); + } + + super.updateFromUIDL(uidl, client); + } + + @Override + protected Widget createWidget() { + return GWT.create(VTwinColSelect.class); + } + + @Override + public VTwinColSelect getWidgetForPaintable() { + return (VTwinColSelect) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java b/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java index c7442e4436..ad702430f6 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponent.java @@ -6,18 +6,14 @@ 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; + protected String serverClassName = "unkwnown"; public VUnknownComponent() { panel = new VerticalPanel(); @@ -31,28 +27,6 @@ public class VUnknownComponent extends Composite implements Paintable { 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/VUnknownComponentPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponentPaintable.java new file mode 100644 index 0000000000..252d7528ca --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VUnknownComponentPaintable.java @@ -0,0 +1,52 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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.VUIDLBrowser; + +public class VUnknownComponentPaintable extends VAbstractPaintableWidget { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, false)) { + return; + } + getWidgetForPaintable().setCaption( + "Widgetset does not contain implementation for " + + getWidgetForPaintable().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 (getWidgetForPaintable().uidlTree != null) { + getWidgetForPaintable().uidlTree.removeFromParent(); + } + + getWidgetForPaintable().uidlTree = new VUIDLBrowser(uidl, + client.getConfiguration()); + getWidgetForPaintable().uidlTree.open(true); + getWidgetForPaintable().uidlTree.setText("Unrendered UIDL"); + getWidgetForPaintable().panel.add(getWidgetForPaintable().uidlTree); + } + + @Override + protected Widget createWidget() { + return GWT.create(VUnknownComponent.class); + } + + @Override + public VUnknownComponent getWidgetForPaintable() { + return (VUnknownComponent) super.getWidgetForPaintable(); + } + + public void setServerSideClassName(String serverClassName) { + getWidgetForPaintable().setServerSideClassName(serverClassName); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java b/src/com/vaadin/terminal/gwt/client/ui/VUpload.java index f3105b70a1..bc94ae317a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VUpload.java @@ -23,8 +23,6 @@ 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; @@ -34,7 +32,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 +70,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 +97,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()); @@ -136,7 +134,7 @@ public class VUpload extends SimplePanel implements Paintable { @Override public void onBrowserEvent(Event event) { if ((event.getTypeInt() & VTooltip.TOOLTIP_EVENTS) > 0) { - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } super.onBrowserEvent(event); } @@ -144,47 +142,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 +238,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 +276,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 +312,4 @@ public class VUpload extends SimplePanel implements Paintable { synthesizedFrame = null; } } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUploadPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VUploadPaintable.java new file mode 100644 index 0000000000..7fd59fc176 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VUploadPaintable.java @@ -0,0 +1,62 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +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 VUploadPaintable extends VAbstractPaintableWidget { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + if (uidl.hasAttribute("notStarted")) { + getWidgetForPaintable().t.schedule(400); + return; + } + if (uidl.hasAttribute("forceSubmit")) { + getWidgetForPaintable().submit(); + return; + } + getWidgetForPaintable().setImmediate( + uidl.getBooleanAttribute("immediate")); + getWidgetForPaintable().client = client; + getWidgetForPaintable().paintableId = uidl.getId(); + getWidgetForPaintable().nextUploadId = uidl.getIntAttribute("nextid"); + final String action = client.translateVaadinUri(uidl + .getStringVariable("action")); + getWidgetForPaintable().element.setAction(action); + if (uidl.hasAttribute("buttoncaption")) { + getWidgetForPaintable().submitButton.setText(uidl + .getStringAttribute("buttoncaption")); + getWidgetForPaintable().submitButton.setVisible(true); + } else { + getWidgetForPaintable().submitButton.setVisible(false); + } + getWidgetForPaintable().fu.setName(getWidgetForPaintable().paintableId + + "_file"); + + if (uidl.hasAttribute("disabled") || uidl.hasAttribute("readonly")) { + getWidgetForPaintable().disableUpload(); + } else if (!uidl.getBooleanAttribute("state")) { + // Enable the button only if an upload is not in progress + getWidgetForPaintable().enableUpload(); + getWidgetForPaintable().ensureTargetFrame(); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VUpload.class); + } + + @Override + public VUpload getWidgetForPaintable() { + return (VUpload) super.getWidgetForPaintable(); + } +} 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/VVerticalLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VVerticalLayoutPaintable.java new file mode 100644 index 0000000000..b911d0f013 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VVerticalLayoutPaintable.java @@ -0,0 +1,17 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; + +public class VVerticalLayoutPaintable extends VOrderedLayoutPaintable { + + @Override + public VVerticalLayout getWidgetForPaintable() { + return (VVerticalLayout) super.getWidgetForPaintable(); + } + + @Override + protected VVerticalLayout createWidget() { + return GWT.create(VVerticalLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VVerticalSplitPanelPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VVerticalSplitPanelPaintable.java new file mode 100644 index 0000000000..5b8f978b28 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VVerticalSplitPanelPaintable.java @@ -0,0 +1,12 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; + +public class VVerticalSplitPanelPaintable extends VAbstractSplitPanelPaintable { + + @Override + protected VAbstractSplitPanel createWidget() { + return GWT.create(VSplitPanelVertical.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VVideo.java b/src/com/vaadin/terminal/gwt/client/ui/VVideo.java index 92b93ac96b..36f17e1436 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VVideo.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VVideo.java @@ -8,12 +8,9 @@ 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; public class VVideo extends VMediaBase { - public static final String ATTR_POSTER = "poster"; private static String CLASSNAME = "v-video"; @@ -27,22 +24,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. @@ -74,4 +55,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/VVideoPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VVideoPaintable.java new file mode 100644 index 0000000000..ac4b735cae --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VVideoPaintable.java @@ -0,0 +1,38 @@ +package com.vaadin.terminal.gwt.client.ui; + +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 VVideoPaintable extends VMediaBasePaintable { + public static final String ATTR_POSTER = "poster"; + + @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)) { + getWidgetForPaintable().setPoster( + getConnection().translateVaadinUri( + uidl.getStringAttribute(ATTR_POSTER))); + } + } + + @Override + public VVideo getWidgetForPaintable() { + return (VVideo) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VVideo.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VView.java b/src/com/vaadin/terminal/gwt/client/ui/VView.java index 07ade6a8b1..7002c6279f 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VView.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VView.java @@ -5,8 +5,6 @@ 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; @@ -17,28 +15,28 @@ 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.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; 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.History; 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.VPaintableWidget; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; /** @@ -47,19 +45,21 @@ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHan public class VView extends SimplePanel implements Container, 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"; - private String theme; + String theme; - private Paintable layout; + VPaintableWidget layout; - private final LinkedHashSet<VWindow> subWindows = new LinkedHashSet<VWindow>(); + final LinkedHashSet<VWindow> subWindows = new LinkedHashSet<VWindow>(); - private String id; + String id; - private ShortcutActionHandler actionHandler; + ShortcutActionHandler actionHandler; /** stored width for IE resize optimization */ private int width; @@ -67,7 +67,10 @@ public class VView extends SimplePanel implements Container, ResizeHandler, /** stored height for IE resize optimization */ private int height; - private ApplicationConnection connection; + 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 @@ -77,32 +80,51 @@ public class VView extends SimplePanel implements Container, ResizeHandler, */ private Timer resizeTimer; - private int scrollTop; + int scrollTop; - private int scrollLeft; + int scrollLeft; - private boolean rendering; + boolean rendering; - private boolean scrollable; + boolean scrollable; - private boolean immediate; + boolean immediate; - private boolean resizeLazy = false; + boolean resizeLazy = false; + /** + * Attribute name for the lazy resize setting . + */ 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; + Element parentFrame; - private ClickEventHandler clickEventHandler = new ClickEventHandler(this, - VPanel.CLICK_EVENT_IDENTIFIER) { + private HandlerRegistration historyHandlerRegistration; - @Override - protected <H extends EventHandler> HandlerRegistration registerHandler( - H handler, Type<H> type) { - return addDomHandler(handler, type); + /** + * 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); + } } }; @@ -124,6 +146,21 @@ public class VView extends SimplePanel implements Container, ResizeHandler, 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. * @@ -160,7 +197,7 @@ public class VView extends SimplePanel implements Container, ResizeHandler, /** * Used to reload host page on theme changes. */ - private static native void reloadHostPage() + static native void reloadHostPage() /*-{ $wnd.location.reload(); }-*/; @@ -171,7 +208,7 @@ public class VView extends SimplePanel implements Container, ResizeHandler, * @param script * Script to be executed. */ - private static native void eval(String script) + static native void eval(String script) /*-{ try { if (script == null) return; @@ -192,217 +229,6 @@ public class VView extends SimplePanel implements Container, ResizeHandler, .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. @@ -413,9 +239,10 @@ public class VView extends SimplePanel implements Container, ResizeHandler, if (uidl.hasAttribute("scrollTo")) { Scheduler.get().scheduleDeferred(new Command() { public void execute() { - final Paintable paintable = uidl.getPaintableAttribute( - "scrollTo", connection); - ((Widget) paintable).getElement().scrollIntoView(); + final VPaintableWidget paintable = (VPaintableWidget) uidl + .getPaintableAttribute("scrollTo", connection); + paintable.getWidgetForPaintable().getElement() + .scrollIntoView(); } }); } @@ -465,7 +292,7 @@ public class VView extends SimplePanel implements Container, ResizeHandler, /** * Called when a resize event is received. */ - private void onResize() { + 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 @@ -476,9 +303,7 @@ public class VView extends SimplePanel implements Container, ResizeHandler, * 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(); + boolean lazy = resizeLazy || BrowserInfo.get().isIE8(); if (lazy) { delayedResizeExecutor.trigger(); @@ -509,10 +334,6 @@ public class VView extends SimplePanel implements Container, ResizeHandler, // 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() { @@ -563,16 +384,6 @@ public class VView extends SimplePanel implements Container, ResizeHandler, @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(); } @@ -602,7 +413,7 @@ public class VView extends SimplePanel implements Container, ResizeHandler, Element targetElement; if (appId.equals(ownAppId)) { // Only hide the contents of current application - targetElement = ((Widget) layout).getElement(); + targetElement = layout.getWidgetForPaintable().getElement(); } else { // Hide everything for other applications targetElement = Document.get().getElementById(appId); @@ -620,7 +431,7 @@ public class VView extends SimplePanel implements Container, ResizeHandler, String appId = vaadinApps.get(i); Element targetElement; if (appId.equals(ownAppId)) { - targetElement = ((Widget) layout).getElement(); + targetElement = layout.getWidgetForPaintable().getElement(); } else { targetElement = Document.get().getElementById(appId); } @@ -676,10 +487,10 @@ public class VView extends SimplePanel implements Container, ResizeHandler, } setWidget(newComponent); - layout = (Paintable) newComponent; + layout = (VPaintableWidget) newComponent; } - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { /* * Can never propagate further and we do not want need to re-layout the * layout which has caused this request. @@ -687,13 +498,13 @@ public class VView extends SimplePanel implements Container, ResizeHandler, updateParentFrameSize(); // layout size change may affect its available space (scrollbars) - connection.handleComponentRelativeSize((Widget) layout); + connection.handleComponentRelativeSize(layout.getWidgetForPaintable()); return true; } - private void updateParentFrameSize() { + void updateParentFrameSize() { if (parentFrame == null) { return; } @@ -705,7 +516,7 @@ public class VView extends SimplePanel implements Container, ResizeHandler, parentFrame.getStyle().setPropertyPx("height", childHeight); } - private static native Element getParentFrame() + static native Element getParentFrame() /*-{ try { var frameElement = $wnd.frameElement; @@ -720,10 +531,6 @@ public class VView extends SimplePanel implements Container, ResizeHandler, 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. @@ -738,36 +545,6 @@ public class VView extends SimplePanel implements Container, ResizeHandler, 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; } @@ -776,4 +553,8 @@ public class VView extends SimplePanel implements Container, ResizeHandler, getElement().focus(); } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VViewPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VViewPaintable.java new file mode 100644 index 0000000000..ccf7185c14 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VViewPaintable.java @@ -0,0 +1,331 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashSet; +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +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.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.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.Focusable; +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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; + +public class VViewPaintable extends VAbstractPaintableWidgetContainer { + + private static final String CLICK_EVENT_IDENTIFIER = VPanelPaintable.CLICK_EVENT_IDENTIFIER; + + public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().rendering = true; + + getWidgetForPaintable().id = uidl.getId(); + boolean firstPaint = getWidgetForPaintable().connection == null; + getWidgetForPaintable().connection = client; + + getWidgetForPaintable().immediate = uidl.hasAttribute("immediate"); + getWidgetForPaintable().resizeLazy = uidl + .hasAttribute(VView.RESIZE_LAZY); + String newTheme = uidl.getStringAttribute("theme"); + if (getWidgetForPaintable().theme != null + && !newTheme.equals(getWidgetForPaintable().theme)) { + // Complete page refresh is needed due css can affect layout + // calculations etc + getWidgetForPaintable().reloadHostPage(); + } else { + getWidgetForPaintable().theme = newTheme; + } + if (uidl.hasAttribute("style")) { + getWidgetForPaintable().setStyleName( + getWidgetForPaintable().getStylePrimaryName() + " " + + uidl.getStringAttribute("style")); + } + + clickEventHandler.handleEventHandlerRegistration(client); + + if (!getWidgetForPaintable().isEmbedded() + && uidl.hasAttribute("caption")) { + // 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() { + VView.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; + VView.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 + getWidgetForPaintable().rendering = false; + return; + } + + // Draw this application level window + UIDL childUidl = uidl.getChildUIDL(childIndex); + final VPaintableWidget lo = client.getPaintable(childUidl); + + if (getWidgetForPaintable().layout != null) { + if (getWidgetForPaintable().layout != lo) { + // remove old + client.unregisterPaintable(getWidgetForPaintable().layout); + // add new + getWidgetForPaintable().setWidget(lo.getWidgetForPaintable()); + getWidgetForPaintable().layout = lo; + } + } else { + getWidgetForPaintable().setWidget(lo.getWidgetForPaintable()); + getWidgetForPaintable().layout = lo; + } + + getWidgetForPaintable().layout.updateFromUIDL(childUidl, client); + if (!childUidl.getBooleanAttribute("cached")) { + getWidgetForPaintable().updateParentFrameSize(); + } + + // Save currently open subwindows to track which will need to be closed + final HashSet<VWindow> removedSubWindows = new HashSet<VWindow>( + getWidgetForPaintable().subWindows); + + // Handle other UIDL children + while ((childUidl = uidl.getChildUIDL(++childIndex)) != null) { + String tag = childUidl.getTag().intern(); + if (tag == "actions") { + if (getWidgetForPaintable().actionHandler == null) { + getWidgetForPaintable().actionHandler = new ShortcutActionHandler( + getWidgetForPaintable().id, client); + } + getWidgetForPaintable().actionHandler + .updateActionMap(childUidl); + } else if (tag == "execJS") { + String script = childUidl.getStringAttribute("script"); + VView.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 VPaintableWidget w = client.getPaintable(childUidl); + if (getWidgetForPaintable().subWindows.contains(w)) { + removedSubWindows.remove(w); + } else { + getWidgetForPaintable().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(VPaintableMap.get(getConnection()) + .getPaintable(w)); + getWidgetForPaintable().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() { + VPaintableWidget paintable = (VPaintableWidget) uidl + .getPaintableAttribute("focused", getConnection()); + + final Widget toBeFocused = paintable + .getWidgetForPaintable(); + /* + * 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(getWidgetForPaintable()); + Window.addResizeHandler(getWidgetForPaintable()); + } + + getWidgetForPaintable().onResize(); + + // finally set scroll position from UIDL + if (uidl.hasVariable("scrollTop")) { + getWidgetForPaintable().scrollable = true; + getWidgetForPaintable().scrollTop = uidl + .getIntVariable("scrollTop"); + DOM.setElementPropertyInt(getWidgetForPaintable().getElement(), + "scrollTop", getWidgetForPaintable().scrollTop); + getWidgetForPaintable().scrollLeft = uidl + .getIntVariable("scrollLeft"); + DOM.setElementPropertyInt(getWidgetForPaintable().getElement(), + "scrollLeft", getWidgetForPaintable().scrollLeft); + } else { + getWidgetForPaintable().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(getWidgetForPaintable().getElement()); + } + + getWidgetForPaintable().scrollIntoView(uidl); + + if (uidl.hasAttribute(VView.FRAGMENT_VARIABLE)) { + getWidgetForPaintable().currentFragment = uidl + .getStringAttribute(VView.FRAGMENT_VARIABLE); + if (!getWidgetForPaintable().currentFragment.equals(History + .getToken())) { + History.newItem(getWidgetForPaintable().currentFragment, true); + } + } else { + // Initial request for which the server doesn't yet have a fragment + // (and haven't shown any interest in getting one) + getWidgetForPaintable().currentFragment = History.getToken(); + + // Include current fragment in the next request + client.updateVariable(getWidgetForPaintable().id, + VView.FRAGMENT_VARIABLE, + getWidgetForPaintable().currentFragment, false); + } + + getWidgetForPaintable().rendering = false; + } + + public void init(String rootPanelId, + ApplicationConnection applicationConnection) { + DOM.sinkEvents(getWidgetForPaintable().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(getWidgetForPaintable().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"); + + String themeUri = applicationConnection.getConfiguration() + .getThemeUri(); + String themeName = themeUri.substring(themeUri.lastIndexOf('/')); + themeName = themeName.replaceAll("[^a-zA-Z0-9]", ""); + root.addStyleName("v-theme-" + themeName); + + root.add(getWidgetForPaintable()); + + 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). + getWidgetForPaintable().getElement().focus(); + } + + getWidgetForPaintable().parentFrame = VView.getParentFrame(); + } + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this, + CLICK_EVENT_IDENTIFIER) { + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + }; + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + // NOP The main view never draws caption for its layout + } + + @Override + public VView getWidgetForPaintable() { + return (VView) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VView.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VWindow.java b/src/com/vaadin/terminal/gwt/client/ui/VWindow.java index 7b9ece24c9..662b77a7b0 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VWindow.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VWindow.java @@ -7,28 +7,23 @@ package com.vaadin.terminal.gwt.client.ui; 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; @@ -38,11 +33,9 @@ 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.Util; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; /** @@ -52,7 +45,7 @@ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHan */ public class VWindow extends VOverlay implements Container, ShortcutActionHandlerOwner, ScrollHandler, KeyDownHandler, - FocusHandler, BlurHandler, BeforeShortcutActionListener, Focusable { + FocusHandler, BlurHandler, Focusable { /** * Minimum allowed height of a window. This refers to the content area, not @@ -89,9 +82,9 @@ public class VWindow extends VOverlay implements Container, public static final int Z_INDEX = 10000; - private Paintable layout; + VPaintableWidget layout; - private Element contents; + Element contents; private Element header; @@ -99,7 +92,7 @@ public class VWindow extends VOverlay implements Container, private Element resizeBox; - private final FocusableScrollPanel contentPanel = new FocusableScrollPanel(); + final FocusableScrollPanel contentPanel = new FocusableScrollPanel(); private boolean dragging; @@ -117,11 +110,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 +124,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; @@ -164,23 +157,13 @@ public class VWindow extends VOverlay implements Container, private String height; - private boolean immediate; + boolean immediate; private Element wrapper, wrapper2; - private ClickEventHandler clickEventHandler = new ClickEventHandler(this, - VPanel.CLICK_EVENT_IDENTIFIER) { + boolean visibilityChangesDisabled; - @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() { @@ -285,236 +268,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 +328,7 @@ public class VWindow extends VOverlay implements Container, } } - private void setDraggable(boolean draggable) { + void setDraggable(boolean draggable) { if (this.draggable == draggable) { return; } @@ -580,7 +339,7 @@ public class VWindow extends VOverlay implements Container, } private void setCursorProperties() { - if (!this.draggable) { + if (!draggable) { header.getStyle().setProperty("cursor", "default"); footer.getStyle().setProperty("cursor", "default"); } else { @@ -589,7 +348,7 @@ public class VWindow extends VOverlay implements Container, } } - private void setNaturalWidth() { + 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 @@ -599,19 +358,6 @@ public class VWindow extends VOverlay implements Container, 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(); @@ -622,10 +368,6 @@ public class VWindow extends VOverlay implements Container, int naturalWidth = (contentWidth > windowCaptionWidth ? contentWidth : windowCaptionWidth); - if (BrowserInfo.get().isIE6()) { - headerText.getStyle().setProperty("width", oldHeaderWidth); - } - setWidth(naturalWidth + "px"); } @@ -690,42 +432,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 @@ -736,7 +442,7 @@ public class VWindow extends VOverlay implements Container, super.hide(); } - private void setVaadinModality(boolean modality) { + void setVaadinModality(boolean modality) { vaadinModality = modality; if (vaadinModality) { if (isAttached()) { @@ -754,14 +460,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()) { @@ -781,15 +479,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); } } @@ -799,15 +493,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); } } @@ -843,7 +533,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"); @@ -908,7 +598,7 @@ public class VWindow extends VOverlay implements Container, if (client != null && header.isOrHasChild(target)) { // Handle window caption tooltips - client.handleTooltipEvent(event, this); + client.handleWidgetTooltipEvent(event, this); } if (resizing || resizeBox == target) { @@ -1063,8 +753,9 @@ 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.getWidgetForPaintable()); + client.runDescendentsLayout((HasWidgets) layout + .getWidgetForPaintable()); } Util.runWebkitOverflowAutoFix(contentPanel.getElement()); @@ -1100,10 +791,6 @@ public class VWindow extends VOverlay implements Container, // "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); @@ -1117,14 +804,6 @@ public class VWindow extends VOverlay implements Container, 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(); @@ -1175,7 +854,7 @@ public class VWindow extends VOverlay implements Container, private int extraH = 0; - private int getExtraHeight() { + int getExtraHeight() { extraH = header.getOffsetHeight() + footer.getOffsetHeight(); return extraH; } @@ -1264,7 +943,7 @@ public class VWindow extends VOverlay implements Container, while (w != null) { if (w instanceof Console) { return true; // allow debug-window clicks - } else if (w instanceof Paintable) { + } else if (w instanceof VPaintableWidget) { return false; } w = w.getParent(); @@ -1312,7 +991,7 @@ public class VWindow extends VOverlay implements Container, contentPanel.setWidget(newComponent); } - public boolean requestLayout(Set<Paintable> child) { + public boolean requestLayout(Set<Widget> children) { if (dynamicWidth && !layoutRelativeWidth) { setNaturalWidth(); } @@ -1321,14 +1000,10 @@ public class VWindow extends VOverlay implements Container, } updateShadowSizeAndPosition(); // layout size change may affect its available space (scrollbars) - client.handleComponentRelativeSize((Widget) layout); + client.handleComponentRelativeSize(layout.getWidgetForPaintable()); return true; } - public void updateCaption(Paintable component, UIDL uidl) { - // NOP, window has own caption, layout captio not rendered - } - public ShortcutActionHandler getShortcutActionHandler() { return shortcutHandler; } @@ -1350,24 +1025,23 @@ public class VWindow extends VOverlay implements Container, } public void onBlur(BlurEvent event) { - if (client.hasEventListeners(this, EventId.BLUR)) { + if (client.hasWidgetEventListeners(this, EventId.BLUR)) { client.updateVariable(id, EventId.BLUR, "", true); } } public void onFocus(FocusEvent event) { - if (client.hasEventListeners(this, EventId.FOCUS)) { + if (client.hasWidgetEventListeners(this, EventId.FOCUS)) { client.updateVariable(id, EventId.FOCUS, "", true); } } - public void onBeforeShortcutAction(Event e) { - // NOP, nothing to update just avoid workaround ( causes excess - // blur/focus ) - } - public void focus() { contentPanel.focus(); } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VWindowPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/VWindowPaintable.java new file mode 100644 index 0000000000..65ce4a672a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VWindowPaintable.java @@ -0,0 +1,296 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.GWT; +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.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Frame; +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.Util; +import com.vaadin.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; + +public class VWindowPaintable extends VAbstractPaintableWidgetContainer + implements BeforeShortcutActionListener { + + private static final String CLICK_EVENT_IDENTIFIER = VPanelPaintable.CLICK_EVENT_IDENTIFIER; + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this, + CLICK_EVENT_IDENTIFIER) { + + @Override + protected <H extends EventHandler> HandlerRegistration registerHandler( + H handler, Type<H> type) { + return getWidgetForPaintable().addDomHandler(handler, type); + } + }; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().id = uidl.getId(); + getWidgetForPaintable().client = client; + + // Workaround needed for Testing Tools (GWT generates window DOM + // slightly different in different browsers). + DOM.setElementProperty(getWidgetForPaintable().closeBox, "id", + getWidgetForPaintable().id + "_window_close"); + + if (uidl.hasAttribute("invisible")) { + getWidgetForPaintable().hide(); + return; + } + + if (!uidl.hasAttribute("cached")) { + if (uidl.getBooleanAttribute("modal") != getWidgetForPaintable().vaadinModality) { + getWidgetForPaintable().setVaadinModality( + !getWidgetForPaintable().vaadinModality); + } + if (!getWidgetForPaintable().isAttached()) { + getWidgetForPaintable().setVisible(false); // hide until + // possible centering + getWidgetForPaintable().show(); + } + if (uidl.getBooleanAttribute("resizable") != getWidgetForPaintable().resizable) { + getWidgetForPaintable().setResizable( + !getWidgetForPaintable().resizable); + } + getWidgetForPaintable().resizeLazy = uidl + .hasAttribute(VView.RESIZE_LAZY); + + getWidgetForPaintable().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. + getWidgetForPaintable().setCaption( + uidl.getStringAttribute("caption"), + uidl.getStringAttribute("icon")); + } + + getWidgetForPaintable().visibilityChangesDisabled = true; + if (client.updateComponent(this, uidl, false)) { + return; + } + getWidgetForPaintable().visibilityChangesDisabled = false; + + clickEventHandler.handleEventHandlerRegistration(client); + + getWidgetForPaintable().immediate = uidl.hasAttribute("immediate"); + + getWidgetForPaintable().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; + } + getWidgetForPaintable().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); + getWidgetForPaintable().contentPanel.setWidget(frame); + showingUrl = true; + } else { + final String target = childUidl.getStringAttribute("name"); + Window.open(parsedUri, target, ""); + } + childUidl = uidl.getChildUIDL(childIndex++); + } + + final VPaintableWidget lo = client.getPaintable(childUidl); + if (getWidgetForPaintable().layout != null) { + if (getWidgetForPaintable().layout != lo) { + // remove old + client.unregisterPaintable(getWidgetForPaintable().layout); + getWidgetForPaintable().contentPanel + .remove(getWidgetForPaintable().layout + .getWidgetForPaintable()); + // add new + if (!showingUrl) { + getWidgetForPaintable().contentPanel.setWidget(lo + .getWidgetForPaintable()); + } + getWidgetForPaintable().layout = lo; + } + } else if (!showingUrl) { + getWidgetForPaintable().contentPanel.setWidget(lo + .getWidgetForPaintable()); + getWidgetForPaintable().layout = lo; + } + + getWidgetForPaintable().dynamicWidth = !uidl.hasAttribute("width"); + getWidgetForPaintable().dynamicHeight = !uidl.hasAttribute("height"); + + getWidgetForPaintable().layoutRelativeWidth = uidl + .hasAttribute("layoutRelativeWidth"); + getWidgetForPaintable().layoutRelativeHeight = uidl + .hasAttribute("layoutRelativeHeight"); + + if (getWidgetForPaintable().dynamicWidth + && getWidgetForPaintable().layoutRelativeWidth) { + /* + * Relative layout width, fix window width before rendering (width + * according to caption) + */ + getWidgetForPaintable().setNaturalWidth(); + } + + getWidgetForPaintable().layout.updateFromUIDL(childUidl, client); + if (!getWidgetForPaintable().dynamicHeight + && getWidgetForPaintable().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(getWidgetForPaintable()); + } + + /* + * No explicit width is set and the layout does not have relative width + * so fix the size according to the layout. + */ + if (getWidgetForPaintable().dynamicWidth + && !getWidgetForPaintable().layoutRelativeWidth) { + getWidgetForPaintable().setNaturalWidth(); + } + + if (getWidgetForPaintable().dynamicHeight + && getWidgetForPaintable().layoutRelativeHeight) { + // Prevent resizing until height has been fixed + getWidgetForPaintable().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 (getWidgetForPaintable().shortcutHandler == null) { + getWidgetForPaintable().shortcutHandler = new ShortcutActionHandler( + getId(), client); + } + getWidgetForPaintable().shortcutHandler + .updateActionMap(childUidl); + } + } + + } + + // setting scrollposition must happen after children is rendered + getWidgetForPaintable().contentPanel.setScrollPosition(uidl + .getIntVariable("scrollTop")); + getWidgetForPaintable().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 + getWidgetForPaintable().centered = true; + getWidgetForPaintable().center(); + } else { + // don't try to center the window anymore + getWidgetForPaintable().centered = false; + } + getWidgetForPaintable().updateShadowSizeAndPosition(); + getWidgetForPaintable().setVisible(true); + + boolean sizeReduced = false; + // ensure window is not larger than browser window + if (getWidgetForPaintable().getOffsetWidth() > Window.getClientWidth()) { + getWidgetForPaintable().setWidth(Window.getClientWidth() + "px"); + sizeReduced = true; + } + if (getWidgetForPaintable().getOffsetHeight() > Window + .getClientHeight()) { + getWidgetForPaintable().setHeight(Window.getClientHeight() + "px"); + sizeReduced = true; + } + + if (getWidgetForPaintable().dynamicHeight + && getWidgetForPaintable().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 = getWidgetForPaintable().contents.getOffsetHeight() + + getWidgetForPaintable().getExtraHeight(); + int w = getWidgetForPaintable().getElement().getOffsetWidth(); + + client.updateVariable(getId(), "height", h, false); + client.updateVariable(getId(), "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(getWidgetForPaintable()); + } + + Util.runWebkitOverflowAutoFix(getWidgetForPaintable().contentPanel + .getElement()); + + client.getView().getWidgetForPaintable().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. + */ + getWidgetForPaintable().contentPanel.focus(); + getWidgetForPaintable().bringToFrontSequence = uidl + .getIntAttribute("bringToFront"); + VWindow.deferOrdering(); + } + } + + public void updateCaption(VPaintableWidget component, UIDL uidl) { + // NOP, window has own caption, layout captio not rendered + } + + public void onBeforeShortcutAction(Event e) { + // NOP, nothing to update just avoid workaround ( causes excess + // blur/focus ) + } + + @Override + public VWindow getWidgetForPaintable() { + return (VWindow) super.getWidgetForPaintable(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VWindow.class); + } + +} 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..4d752d4527 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.VPaintableWidget; 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 VPaintableWidget getPaintable(); } 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 dbeff243a8..b86a404678 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java @@ -24,9 +24,9 @@ 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.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.VPaintableWidget; import com.vaadin.terminal.gwt.client.ValueMap; /** @@ -333,10 +333,10 @@ public class VDragAndDropManager { } private void addActiveDragSourceStyleName() { - Paintable dragSource = currentDrag.getTransferable() + VPaintableWidget dragSource = currentDrag.getTransferable() .getDragSource(); - ((Widget) dragSource) - .addStyleName(ACTIVE_DRAG_SOURCE_STYLENAME); + dragSource.getWidgetForPaintable().addStyleName( + ACTIVE_DRAG_SOURCE_STYLENAME); } }; @@ -499,8 +499,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 VPaintableWidget dragSource = currentDrag + .getTransferable().getDragSource(); final ApplicationConnection client = currentDropHandler .getApplicationConnection(); Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { @@ -543,8 +543,9 @@ public class VDragAndDropManager { } - private void removeActiveDragSourceStyleName(Paintable dragSource) { - ((Widget) dragSource).removeStyleName(ACTIVE_DRAG_SOURCE_STYLENAME); + private void removeActiveDragSourceStyleName(VPaintableWidget dragSource) { + dragSource.getWidgetForPaintable().removeStyleName( + ACTIVE_DRAG_SOURCE_STYLENAME); } private void clearDragElement() { @@ -578,7 +579,7 @@ public class VDragAndDropManager { if (currentDropHandler == null) { return; } - Paintable paintable = currentDropHandler.getPaintable(); + VPaintableWidget paintable = currentDropHandler.getPaintable(); ApplicationConnection client = currentDropHandler .getApplicationConnection(); /* 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..2be75ae3d9 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java @@ -3,8 +3,9 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.VPaintableMap; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; /** * TODO Javadoc! @@ -16,13 +17,15 @@ final public class VDragSourceIs extends VAcceptCriterion { @Override protected boolean accept(VDragEvent drag, UIDL configuration) { try { - Paintable component = drag.getTransferable().getDragSource(); + VPaintableWidget 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() + VDropHandler currentDropHandler = VDragAndDropManager.get() + .getCurrentDropHandler(); + VPaintableWidget paintable = (VPaintableWidget) VPaintableMap + .get(currentDropHandler.getApplicationConnection()) .getPaintable(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..d84dea4833 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.VPaintableWidget; /** * Vaadin Widgets that want to receive something via drag and drop implement @@ -61,7 +61,7 @@ public interface VDropHandler { /** * Returns the Paintable into which this DragHandler is associated */ - public Paintable getPaintable(); + public VPaintableWidget getPaintable(); /** * 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..48f748a4f3 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.VPaintableWidget; /** * 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 VPaintableWidget}. * */ 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..3ea7d1a482 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java @@ -6,8 +6,9 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.VPaintableMap; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; final public class VIsOverId extends VAcceptCriterion { @@ -16,10 +17,13 @@ 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(); + VPaintableWidget paintable = currentDropHandler.getPaintable(); + VPaintableMap paintableMap = VPaintableMap.get(currentDropHandler + .getApplicationConnection()); + + String pid2 = paintableMap.getPid(paintable); 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..16de428d9c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java @@ -6,8 +6,9 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.VPaintableMap; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VPaintableWidget; final public class VItemIdIs extends VAcceptCriterion { @@ -15,9 +16,13 @@ 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); + VPaintableWidget dragSource = drag.getTransferable() + .getDragSource(); + VDropHandler currentDropHandler = VDragAndDropManager.get() + .getCurrentDropHandler(); + String pid2 = VPaintableMap.get( + currentDropHandler.getApplicationConnection()).getPid( + dragSource); 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..55c1110546 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java @@ -6,15 +6,15 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; -import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.VPaintableWidget; 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() + VPaintableWidget dragSource = drag.getTransferable().getDragSource(); + VPaintableWidget paintable = VDragAndDropManager.get().getCurrentDropHandler() .getPaintable(); return paintable == dragSource; 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..f81567f906 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.VPaintableWidget; /** * 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 VPaintableWidget 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 VPaintableWidget getDragSource() { return component; } @@ -41,7 +41,7 @@ public class VTransferable { * @param component * the component to set */ - public void setDragSource(Paintable component) { + public void setDragSource(VPaintableWidget component) { this.component = component; } 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..8828582b57 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/label/VLabel.java @@ -0,0 +1,70 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.label; + +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.HTML; +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 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.stopPropagation(); + return; + } + } + + @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().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); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/label/VLabelPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/label/VLabelPaintable.java new file mode 100644 index 0000000000..c57f705c75 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/label/VLabelPaintable.java @@ -0,0 +1,71 @@ +/* +@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.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.VAbstractPaintableWidget; + +public class VLabelPaintable extends VAbstractPaintableWidget { + public VLabelPaintable() { + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + boolean sinkOnloads = false; + + final String mode = uidl.getStringAttribute("mode"); + if (mode == null || "text".equals(mode)) { + getWidgetForPaintable().setText(uidl.getChildString(0)); + } else if ("pre".equals(mode)) { + PreElement preElement = Document.get().createPreElement(); + preElement.setInnerText(uidl.getChildUIDL(0).getChildString(0)); + // clear existing content + getWidgetForPaintable().setHTML(""); + // add preformatted text to dom + getWidgetForPaintable().getElement().appendChild(preElement); + } else if ("uidl".equals(mode)) { + getWidgetForPaintable().setHTML(uidl.getChildrenAsXML()); + } else if ("xhtml".equals(mode)) { + UIDL content = uidl.getChildUIDL(0).getChildUIDL(0); + if (content.getChildCount() > 0) { + getWidgetForPaintable().setHTML(content.getChildString(0)); + } else { + getWidgetForPaintable().setHTML(""); + } + sinkOnloads = true; + } else if ("xml".equals(mode)) { + getWidgetForPaintable().setHTML( + uidl.getChildUIDL(0).getChildString(0)); + } else if ("raw".equals(mode)) { + getWidgetForPaintable().setHTML( + uidl.getChildUIDL(0).getChildString(0)); + sinkOnloads = true; + } else { + getWidgetForPaintable().setText(""); + } + if (sinkOnloads) { + Util.sinkOnloadForImages(getWidgetForPaintable().getElement()); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VLabel.class); + } + + @Override + public VLabel getWidgetForPaintable() { + return (VLabel) super.getWidgetForPaintable(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayout.java index 2ca58a38c2..9b38ba23ae 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayout.java @@ -3,6 +3,7 @@ */ package com.vaadin.terminal.gwt.client.ui.layout; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -17,8 +18,8 @@ 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.VPaintableMap; +import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.ui.VMarginInfo; public abstract class CellBasedLayout extends ComplexPanel implements Container { @@ -27,7 +28,7 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container protected ApplicationConnection client = null; - protected DivElement root; + public DivElement root; public static final int ORIENTATION_VERTICAL = 0; public static final int ORIENTATION_HORIZONTAL = 1; @@ -39,15 +40,15 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container protected final Spacing spacingFromCSS = new Spacing(12, 12); protected final Spacing activeSpacing = new Spacing(0, 0); - private boolean dynamicWidth; + boolean dynamicWidth; - private boolean dynamicHeight; + boolean dynamicHeight; private final DivElement clearElement = Document.get().createDivElement(); private String lastStyleName = ""; - private boolean marginsNeedsRecalculation = false; + boolean marginsNeedsRecalculation = false; protected String STYLENAME_SPACING = ""; protected String STYLENAME_MARGIN_TOP = ""; @@ -104,32 +105,6 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container 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); @@ -158,27 +133,6 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container } } - 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); @@ -194,7 +148,7 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container } } - protected void addOrMoveChild(ChildComponentContainer childComponent, + public void addOrMoveChild(ChildComponentContainer childComponent, int position) { if (childComponent.getParent() == this) { if (getWidgetIndex(childComponent) != position) { @@ -234,33 +188,22 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container } - protected ChildComponentContainer getComponentContainer(Widget child) { + public Collection<ChildComponentContainer> getComponentContainers() { + return widgetToComponentContainer.values(); + } + + public ChildComponentContainer getComponentContainer(Widget child) { return widgetToComponentContainer.get(child); } - protected boolean isDynamicWidth() { + public boolean isDynamicWidth() { return dynamicWidth; } - protected boolean isDynamicHeight() { + public 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; @@ -341,7 +284,7 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container return (ChildComponentContainer) getChildren().get(0); } - protected void removeChildrenAfter(int pos) { + public void removeChildrenAfter(int pos) { // Remove all children after position "pos" but leave the clear element // in place @@ -375,8 +318,9 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container widgetToComponentContainer.remove(widget); remove(child); if (!relocated) { - Paintable p = (Paintable) widget; - client.unregisterPaintable(p); + VPaintableMap paintableMap = VPaintableMap.get(client); + VPaintableWidget p = paintableMap.getPaintable(widget); + paintableMap.unregisterPaintable(p); } } @@ -389,8 +333,10 @@ public abstract class CellBasedLayout extends ComplexPanel implements Container return; } - componentContainer.setWidget(newComponent); - client.unregisterPaintable((Paintable) oldComponent); + componentContainer.setPaintable(VPaintableMap.get(client).getPaintable( + newComponent)); + client.unregisterPaintable(VPaintableMap.get(client).getPaintable( + oldComponent)); widgetToComponentContainer.put(newComponent, componentContainer); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayoutPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayoutPaintable.java new file mode 100644 index 0000000000..5fc74c056e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/CellBasedLayoutPaintable.java @@ -0,0 +1,81 @@ +package com.vaadin.terminal.gwt.client.ui.layout; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.VAbstractPaintableWidgetContainer; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; + +public abstract class CellBasedLayoutPaintable extends + VAbstractPaintableWidgetContainer { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().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); + } + + private void handleDynamicDimensions(UIDL uidl) { + String w = uidl.hasAttribute("width") ? uidl + .getStringAttribute("width") : ""; + + String h = uidl.hasAttribute("height") ? uidl + .getStringAttribute("height") : ""; + + if (w.equals("")) { + getWidgetForPaintable().dynamicWidth = true; + } else { + getWidgetForPaintable().dynamicWidth = false; + } + + if (h.equals("")) { + getWidgetForPaintable().dynamicHeight = true; + } else { + getWidgetForPaintable().dynamicHeight = false; + } + + } + + void updateMarginAndSpacingInfo(UIDL uidl) { + if (!uidl.hasAttribute("invisible")) { + int bitMask = uidl.getIntAttribute("margins"); + if (getWidgetForPaintable().activeMarginsInfo.getBitMask() != bitMask) { + getWidgetForPaintable().activeMarginsInfo = new VMarginInfo( + bitMask); + getWidgetForPaintable().marginsNeedsRecalculation = true; + } + boolean spacing = uidl.getBooleanAttribute("spacing"); + if (spacing != getWidgetForPaintable().spacingEnabled) { + getWidgetForPaintable().marginsNeedsRecalculation = true; + getWidgetForPaintable().spacingEnabled = spacing; + } + } + } + + @Override + protected abstract CellBasedLayout createWidget(); + + @Override + public CellBasedLayout getWidgetForPaintable() { + return (CellBasedLayout) super.getWidgetForPaintable(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/ChildComponentContainer.java b/src/com/vaadin/terminal/gwt/client/ui/layout/ChildComponentContainer.java index 36d18306fa..92464cd459 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/layout/ChildComponentContainer.java +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/ChildComponentContainer.java @@ -9,20 +9,17 @@ 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.VPaintableWidget; import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; public class ChildComponentContainer extends Panel { @@ -71,38 +68,22 @@ public class ChildComponentContainer extends Panel { private VCaption caption = null; private DivElement containerDIV; private DivElement widgetDIV; + private VPaintableWidget paintable; private Widget widget; private FloatSize relativeSize = null; - public ChildComponentContainer(Widget widget, int orientation) { + public ChildComponentContainer(VPaintableWidget child, 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"); - } + + setFloat(widgetDIV, "left"); + setElement(containerDIV); + containerDIV.getStyle().setProperty("height", "0"); + containerDIV.getStyle().setProperty("width", "0px"); + containerDIV.getStyle().setProperty("overflow", "hidden"); if (BrowserInfo.get().isIE()) { /* @@ -119,11 +100,15 @@ public class ChildComponentContainer extends Panel { setOrientation(orientation); - setWidget(widget); + setPaintable(child); + } + public void setPaintable(VPaintableWidget childPaintable) { + paintable = childPaintable; + setWidget(childPaintable.getWidgetForPaintable()); } - public void setWidget(Widget w) { + private void setWidget(Widget w) { // Validate if (w == widget) { return; @@ -207,7 +192,7 @@ public class ChildComponentContainer extends Panel { if (fixedWidth < 0 && BrowserInfo.get().isOpera()) { setUnlimitedContainerWidth(); } - ((Paintable) widget).updateFromUIDL(childUIDL, client); + paintable.updateFromUIDL(childUIDL, client); } public void setUnlimitedContainerWidth() { @@ -224,36 +209,6 @@ public class ChildComponentContainer extends Panel { * 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); @@ -454,7 +409,7 @@ public class ChildComponentContainer extends Panel { VCaption newCaption = caption; if (newCaption == null) { - newCaption = new VCaption((Paintable) widget, client); + newCaption = new VCaption(paintable, client); // Set initial height to avoid Safari flicker newCaption.setHeight("18px"); // newCaption.setHeight(newCaption.getHeight()); // This might 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..a586929d13 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.Util; +import com.vaadin.terminal.gwt.client.VPaintableMap; 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()), + VPaintableMap.get(client).getPaintable(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(); } @@ -394,4 +346,8 @@ public class VRichTextArea extends Composite implements Paintable, Field, rta.setTabIndex(index); } + public Widget getWidgetForPaintable() { + return this; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/richtextarea/VRichTextAreaPaintable.java b/src/com/vaadin/terminal/gwt/client/ui/richtextarea/VRichTextAreaPaintable.java new file mode 100644 index 0000000000..1dadf8cc17 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/richtextarea/VRichTextAreaPaintable.java @@ -0,0 +1,76 @@ +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.UIDL; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.terminal.gwt.client.ui.VAbstractPaintableWidget; + +public class VRichTextAreaPaintable extends VAbstractPaintableWidget implements + BeforeShortcutActionListener { + + public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { + getWidgetForPaintable().client = client; + getWidgetForPaintable().id = uidl.getId(); + + if (uidl.hasVariable("text")) { + getWidgetForPaintable().currentValue = uidl + .getStringVariable("text"); + if (getWidgetForPaintable().rta.isAttached()) { + getWidgetForPaintable().rta + .setHTML(getWidgetForPaintable().currentValue); + } else { + getWidgetForPaintable().html + .setHTML(getWidgetForPaintable().currentValue); + } + } + if (!uidl.hasAttribute("cached")) { + getWidgetForPaintable().setEnabled( + !uidl.getBooleanAttribute("disabled")); + } + + if (client.updateComponent(this, uidl, true)) { + return; + } + + getWidgetForPaintable().setReadOnly( + uidl.getBooleanAttribute("readonly")); + getWidgetForPaintable().immediate = uidl + .getBooleanAttribute("immediate"); + int newMaxLength = uidl.hasAttribute("maxLength") ? uidl + .getIntAttribute("maxLength") : -1; + if (newMaxLength >= 0) { + if (getWidgetForPaintable().maxLength == -1) { + getWidgetForPaintable().keyPressHandler = getWidgetForPaintable().rta + .addKeyPressHandler(getWidgetForPaintable()); + } + getWidgetForPaintable().maxLength = newMaxLength; + } else if (getWidgetForPaintable().maxLength != -1) { + getWidgetForPaintable().getElement().setAttribute("maxlength", ""); + getWidgetForPaintable().maxLength = -1; + getWidgetForPaintable().keyPressHandler.removeHandler(); + } + + if (uidl.hasAttribute("selectAll")) { + getWidgetForPaintable().selectAll(); + } + + } + + public void onBeforeShortcutAction(Event e) { + getWidgetForPaintable().synchronizeContentToServer(); + } + + @Override + public VRichTextArea getWidgetForPaintable() { + return (VRichTextArea) super.getWidgetForPaintable(); + }; + + @Override + protected Widget createWidget() { + return GWT.create(VRichTextArea.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index acae039e8b..57e257a7cd 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; @@ -51,11 +44,13 @@ import com.liferay.portal.kernel.util.PortalClassInvoker; import com.liferay.portal.kernel.util.PropsUtil; import com.vaadin.Application; 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 +66,250 @@ 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"; + } + } + }; + @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 +332,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 +436,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 +466,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,6 +502,27 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet protected void handleRequest(PortletRequest request, PortletResponse response) throws PortletException, IOException { + 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()); + RequestType requestType = getRequestType(request); if (requestType == RequestType.UNKNOWN) { @@ -355,10 +552,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 @@ -373,8 +572,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 @@ -399,20 +598,32 @@ 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: + 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 } @@ -422,37 +633,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 { /* @@ -463,8 +676,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) { @@ -485,16 +698,25 @@ 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); } } } } } + private DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + private void handleUnknownRequest(PortletRequest request, PortletResponse response) { logger.warning("Unknown request type"); @@ -517,37 +739,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) { @@ -558,126 +761,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(); @@ -766,7 +855,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(null, applicationProperties, context, + isProductionMode()); } } @@ -780,9 +870,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); @@ -797,10 +889,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)); @@ -870,436 +962,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; @@ -1307,6 +969,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); @@ -1454,162 +1117,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 3957c84a71..b28cb361b0 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -18,12 +18,10 @@ import java.net.URL; 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; @@ -38,13 +36,14 @@ import javax.servlet.http.HttpSession; import com.vaadin.Application; 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 +63,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 +133,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 +140,37 @@ 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; + } + }; + static final String UPLOAD_URL_PREFIX = "APP/UPLOAD/"; /** @@ -201,17 +189,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 +199,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 +240,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 +325,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,10 +381,17 @@ 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 { + service(createWrappedRequest(request), createWrappedResponse(response)); + } + + private void service(WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws ServletException, + IOException { + AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( + this); RequestType requestType = getRequestType(request); if (!ensureCookiesEnabled(requestType, request, response)) { @@ -445,6 +436,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (application == null) { return; } + Application.setCurrentApplication(application); /* * Get or create a WebApplicationContext and an ApplicationManager @@ -456,7 +448,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 @@ -468,7 +460,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,10 +476,16 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements 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; } @@ -498,34 +496,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 @@ -544,9 +518,14 @@ 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); } } @@ -554,6 +533,36 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } } + 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. @@ -587,23 +596,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( @@ -812,7 +804,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; @@ -858,101 +850,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. @@ -996,42 +893,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. @@ -1064,34 +925,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 { @@ -1200,7 +1033,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements Locale locale = request.getLocale(); application.setLocale(locale); application.start(applicationUrl, applicationProperties, - webApplicationContext); + webApplicationContext, isProductionMode()); } } @@ -1435,12 +1268,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)) { @@ -1454,6 +1289,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/")) { @@ -1520,13 +1360,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); @@ -1562,15 +1414,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); } @@ -1653,473 +1496,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?" + new Date().getTime(); - - // 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"); - } - - /** - * @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 @@ -2247,52 +1623,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> @@ -2362,75 +1692,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 e96f2d2b56..86f2c7a2de 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -14,9 +14,9 @@ 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.security.GeneralSecurityException; import java.text.CharacterIterator; import java.text.DateFormat; @@ -42,32 +42,32 @@ 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.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.CombinedRequest; 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.VariableOwner; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; +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.Window; +import com.vaadin.ui.Root; /** * This is a common base class for the server-side implementations of the @@ -78,7 +78,7 @@ import com.vaadin.ui.Window; * 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 + * {@link com.vaadin.terminal.gwt.client.VPaintableWidget#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)}). @@ -94,177 +94,20 @@ public abstract class AbstractCommunicationManager implements 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(); - - } - - /** - * 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(); + private static final RequestHandler APP_RESOURCE_HANDLER = new ApplicationResourceHandler(); - } + private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler(); /** - * 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(); - - } - - /** - * 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 { @@ -306,7 +149,7 @@ public abstract class AbstractCommunicationManager implements public static final char VAR_ESCAPE_CHARACTER = '\u001b'; - private final HashMap<String, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<String, OpenWindowCache>(); + private final HashMap<Integer, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<Integer, OpenWindowCache>(); private static final int MAX_BUFFER_SIZE = 64 * 1024; @@ -325,10 +168,6 @@ public abstract class AbstractCommunicationManager implements 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,8 +180,6 @@ public abstract class AbstractCommunicationManager implements private int maxInactiveInterval; - private static int nextUnusedWindowSuffix = 1; - /** * TODO New constructor - document me! * @@ -350,6 +187,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()); } @@ -390,8 +230,8 @@ public abstract class AbstractCommunicationManager implements * @param boundary * @throws IOException */ - protected void doHandleSimpleMultipartFileUpload(Request request, - Response response, StreamVariable streamVariable, + protected void doHandleSimpleMultipartFileUpload(WrappedRequest request, + WrappedResponse response, StreamVariable streamVariable, String variableName, VariableOwner owner, String boundary) throws IOException { // multipart parsing, supports only one file for request, but that is @@ -489,9 +329,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, VariableOwner owner, int contentLength) + throws IOException { // These are unknown in filexhr ATM, maybe add to Accept header that // is accessible in portlets @@ -628,17 +469,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 +491,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 +506,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 +520,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; @@ -734,11 +564,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 +577,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,12 +608,8 @@ public abstract class AbstractCommunicationManager implements } paintAfterVariableChanges(request, response, callback, repaintAll, - outWriter, window, analyzeLayouts); + outWriter, root, analyzeLayouts); - if (closingWindowName != null) { - currentlyOpenWindowsInClient.remove(closingWindowName); - closingWindowName = null; - } } outWriter.close(); @@ -864,13 +688,13 @@ 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 { + private void paintAfterVariableChanges(WrappedRequest request, + WrappedResponse response, Callback callback, boolean repaintAll, + final PrintWriter outWriter, Root root, boolean analyzeLayouts) + throws PaintException, IOException { if (repaintAll) { - makeAllPaintablesDirty(window); + makeAllPaintablesDirty(root); } // Removes application if it has stopped during variable changes @@ -886,44 +710,54 @@ 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; - } - - writeUidlResponce(callback, repaintAll, outWriter, window, - analyzeLayouts); + writeUidlResponce(repaintAll, outWriter, root, analyzeLayouts); - } closeJsonMessage(outWriter); outWriter.close(); } - public void writeUidlResponce(Callback callback, boolean repaintAll, - final PrintWriter outWriter, Window window, boolean analyzeLayouts) + /** + * 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 ""; + } + } + + /** + * 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; + } + + public void writeUidlResponce(boolean repaintAll, + final PrintWriter outWriter, Root root, boolean analyzeLayouts) throws PaintException { outWriter.print("\"changes\":["); @@ -933,17 +767,18 @@ public abstract class AbstractCommunicationManager implements JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, !repaintAll); - OpenWindowCache windowCache = currentlyOpenWindowsInClient.get(window - .getName()); + OpenWindowCache windowCache = currentlyOpenWindowsInClient.get(Integer + .valueOf(root.getRootId())); if (windowCache == null) { windowCache = new OpenWindowCache(); - currentlyOpenWindowsInClient.put(window.getName(), windowCache); + currentlyOpenWindowsInClient.put(Integer.valueOf(root.getRootId()), + windowCache); } // Paints components if (repaintAll) { paintables = new ArrayList<Paintable>(); - paintables.add(window); + paintables.add(root); // Reset sent locales locales = null; @@ -973,7 +808,7 @@ public abstract class AbstractCommunicationManager implements dirtyPaintables.remove(p); } } - paintables = getDirtyVisibleComponents(window); + paintables = getDirtyVisibleComponents(root); } if (paintables != null) { @@ -1008,13 +843,13 @@ public abstract class AbstractCommunicationManager implements .hasNext();) { final Paintable p = i.next(); - // TODO CLEAN - if (p instanceof Window) { - final Window w = (Window) p; - if (w.getTerminal() == null) { - w.setTerminal(application.getMainWindow().getTerminal()); - } - } + // // TODO CLEAN + // if (p instanceof Root) { + // final Root r = (Root) p; + // if (r.getTerminal() == null) { + // r.setTerminal(application.getRoot().getTerminal()); + // } + // } /* * This does not seem to happen in tk5, but remember this case: * else if (p instanceof Component) { if (((Component) @@ -1038,20 +873,20 @@ public abstract class AbstractCommunicationManager implements paintablePainted(p); if (analyzeLayouts) { - Window w = (Window) p; + Root w = (Root) 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); - } - } + // // Also check any existing subwindows + // if (w.getChildWindows() != null) { + // for (Window subWindow : w.getChildWindows()) { + // invalidComponentRelativeSizes = ComponentSizeValidator + // .validateComponentRelativeSizes( + // subWindow.getContent(), + // invalidComponentRelativeSizes, null); + // } + // } } } } @@ -1140,8 +975,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.", @@ -1207,12 +1041,15 @@ public abstract class AbstractCommunicationManager implements } } + 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) { @@ -1228,19 +1065,19 @@ public abstract class AbstractCommunicationManager implements return requestThemeName; } - public void makeAllPaintablesDirty(Window window) { + public void makeAllPaintablesDirty(Root root) { // 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)) { + if (isChildOf(root, c)) { it.remove(); paintableIdMap.remove(c); } } // clean WindowCache OpenWindowCache openWindowCache = currentlyOpenWindowsInClient - .get(window.getName()); + .get(Integer.valueOf(root.getRootId())); if (openWindowCache != null) { openWindowCache.clear(); } @@ -1257,6 +1094,18 @@ public abstract class AbstractCommunicationManager implements } /** + * Returns false if the cross site request forgery protection is turned off. + * + * @param application + * @return false if the XSRF is turned off, true otherwise + */ + public boolean isXSRFEnabled(Application application) { + return !"true" + .equals(application + .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); + } + + /** * TODO document * * If this method returns false, something was submitted that we did not @@ -1265,9 +1114,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); @@ -1279,9 +1129,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. @@ -1290,8 +1138,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( @@ -1319,7 +1167,7 @@ public abstract class AbstractCommunicationManager implements new CharArrayWriter()); paintAfterVariableChanges(request, response, callback, - true, outWriter, window, false); + true, outWriter, root, false); } @@ -1331,7 +1179,7 @@ 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, @@ -1385,16 +1233,6 @@ public abstract class AbstractCommunicationManager implements } 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(); - } - } } catch (Exception e) { if (owner instanceof Component) { handleChangeVariablesError(app, (Component) owner, e, m); @@ -1462,7 +1300,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) { @@ -1526,7 +1365,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 @@ -1825,108 +1664,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 @@ -1942,8 +1679,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) { @@ -1974,7 +1712,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 @@ -2035,7 +1774,7 @@ public abstract class AbstractCommunicationManager implements * root window for which dirty components is to be fetched * @return */ - private ArrayList<Paintable> getDirtyVisibleComponents(Window w) { + private ArrayList<Paintable> getDirtyVisibleComponents(Root r) { final ArrayList<Paintable> resultset = new ArrayList<Paintable>( dirtyPaintables); @@ -2054,18 +1793,18 @@ public abstract class AbstractCommunicationManager implements resultset.remove(p); i.remove(); } else { - Window componentsRoot = component.getWindow(); + Root componentsRoot = component.getRoot(); 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) { + // if (componentsRoot.getParent() != null) { + // // this is a subwindow + // componentsRoot = componentsRoot.getParent(); + // } + if (componentsRoot != r) { resultset.remove(p); } else if (component.getParent() != null && !component.getParent().isVisible()) { @@ -2107,41 +1846,6 @@ public abstract class AbstractCommunicationManager implements } /** - * 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; - } - } - - /** * Queues a locale to be sent to the client (browser) for date and time * entry etc. All locale specific information is derived from server-side * {@link Locale} instances and sent to the client when needed, eliminating @@ -2209,80 +1913,11 @@ 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 int nextTypeKey = 0; + private BootstrapHandler bootstrapHandler; + String getTagForType(Class<? extends Paintable> class1) { Integer object = typeToKey.get(class1); if (object == null) { @@ -2323,6 +1958,101 @@ public abstract class AbstractCommunicationManager implements abstract protected void cleanStreamVariable(VariableOwner 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); + } + response.getWriter().write(params.toString()); + } 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 writeUidlResponCe()? + makeAllPaintablesDirty(root); + StringWriter sWriter = new StringWriter(); + PrintWriter pWriter = new PrintWriter(sWriter); + pWriter.print("{"); + if (isXSRFEnabled(root.getApplication())) { + pWriter.print(getSecurityKeyUIDL(request)); + } + writeUidlResponce(true, pWriter, root, false); + pWriter.print("}"); + String initialUIDL = sWriter.toString(); + return initialUIDL; + } + + /** * Stream that extracts content from another stream until the boundary * string is encountered. * 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 index a9eaa1bb82..064d2b16af 100644 --- a/src/com/vaadin/terminal/gwt/server/ApplicationRunnerServlet.java +++ b/src/com/vaadin/terminal/gwt/server/ApplicationRunnerServlet.java @@ -15,10 +15,29 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.ui.Root; @SuppressWarnings("serial") public class ApplicationRunnerServlet extends AbstractApplicationServlet { + /** + * Internal implementation of an application with a dynamically selected + * Root implementation; + */ + private static class RootRunnerApplication extends Application { + private final Class<?> runnableClass; + + private RootRunnerApplication(Class<?> runnableClass) { + this.runnableClass = runnableClass; + } + + @Override + protected String getRootClassName(WrappedRequest request) { + return runnableClass.getCanonicalName(); + } + } + private static final Logger logger = Logger .getLogger(ApplicationRunnerServlet.class.getName()); @@ -43,8 +62,11 @@ public class ApplicationRunnerServlet extends AbstractApplicationServlet { protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.request.set(request); - super.service(request, response); - this.request.set(null); + try { + super.service(request, response); + } finally { + this.request.set(null); + } } @Override @@ -65,8 +87,15 @@ public class ApplicationRunnerServlet extends AbstractApplicationServlet { // Creates a new application instance try { - final Application application = getApplicationClass().newInstance(); - return application; + final Class<?> classToRun = getClassToRun(); + if (Root.class.isAssignableFrom(classToRun)) { + return new RootRunnerApplication(classToRun); + } else if (Application.class.isAssignableFrom(classToRun)) { + return (Application) classToRun.newInstance(); + } else { + throw new ServletException(classToRun.getCanonicalName() + + " is neither an Application nor a Root"); + } } catch (final IllegalAccessException e) { throw new ServletException(e); } catch (final InstantiationException e) { @@ -150,28 +179,37 @@ public class ApplicationRunnerServlet extends AbstractApplicationServlet { return uris; } - @SuppressWarnings("unchecked") @Override protected Class<? extends Application> getApplicationClass() throws ClassNotFoundException { + Class<?> classToRun = getClassToRun(); + if (Root.class.isAssignableFrom(classToRun)) { + return RootRunnerApplication.class; + } else if (Application.class.isAssignableFrom(classToRun)) { + return classToRun.asSubclass(Application.class); + } else { + throw new ClassCastException(classToRun.getCanonicalName() + + " is not an Application nor a Root"); + } + } + + private Class<?> getClassToRun() throws ClassNotFoundException { // TODO use getClassLoader() ? - Class<? extends Application> appClass = null; + Class<?> appClass = null; String baseName = getApplicationRunnerApplicationClassName(request .get()); try { - appClass = (Class<? extends Application>) getClass() - .getClassLoader().loadClass(baseName); + appClass = 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); + appClass = getClass().getClassLoader().loadClass( + defaultPackages[i] + "." + baseName); } catch (ClassNotFoundException ee) { // Ignore as this is expected for many packages } catch (Exception e2) { @@ -215,4 +253,16 @@ public class ApplicationRunnerServlet extends AbstractApplicationServlet { return staticFilesPath; } + @Override + protected WrappedHttpServletRequest createWrappedRequest( + HttpServletRequest request) { + return new WrappedHttpServletRequest(request, + getDeploymentConfiguration()) { + @Override + public String getRequestPathInfo() { + return ApplicationRunnerServlet.this.getRequestPathInfo(this); + } + }; + } + } 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/CommunicationManager.java b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java index 9c67a03bb3..3a9b8ff2db 100644 --- a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java @@ -6,24 +6,22 @@ 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.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.PaintException; import com.vaadin.terminal.Paintable; import com.vaadin.terminal.StreamVariable; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; import com.vaadin.ui.Component; -import com.vaadin.ui.Window; +import com.vaadin.ui.Root; /** * Application manager processes changes and paints for single application @@ -42,150 +40,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 @@ -215,15 +69,15 @@ public class CommunicationManager extends AbstractCommunicationManager { * @throws IOException * @throws InvalidUIDLSecurityKeyException */ - public void handleFileUpload(HttpServletRequest request, - HttpServletResponse response) throws IOException, + public void handleFileUpload(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) @@ -240,20 +94,16 @@ public class CommunicationManager extends AbstractCommunicationManager { VariableOwner source = getVariableOwner(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,82 +112,6 @@ 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 */ @@ -411,4 +185,72 @@ public class CommunicationManager extends AbstractCommunicationManager { } } + @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..d3095512bb 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; @@ -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"; } @@ -426,9 +417,7 @@ public class ComponentSizeValidator implements Serializable { 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 +449,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 +477,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 +493,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) { @@ -528,11 +517,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 +552,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/JsonPaintTarget.java b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java index d6c53a2da6..91c15df5b2 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java +++ b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java @@ -37,6 +37,7 @@ import com.vaadin.ui.Alignment; import com.vaadin.ui.ClientWidget; import com.vaadin.ui.Component; import com.vaadin.ui.CustomLayout; +import com.vaadin.ui.Root; /** * User Interface Description Language Target. @@ -1038,6 +1039,9 @@ public class JsonPaintTarget implements PaintTarget { } private boolean hasClientWidgetMapping(Class<? extends Paintable> class1) { + if (Root.class == class1) { + return true; + } try { return class1.isAnnotationPresent(ClientWidget.class); } catch (NoClassDefFoundError e) { 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..49bcb7a79c 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java @@ -5,29 +5,30 @@ 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.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.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.DeploymentConfiguration; +import com.vaadin.terminal.PaintException; import com.vaadin.terminal.Paintable; import com.vaadin.terminal.StreamVariable; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.WrappedResponse; import com.vaadin.ui.Component; -import com.vaadin.ui.Window; +import com.vaadin.ui.Root; /** * TODO document me! @@ -40,160 +41,12 @@ 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"); @@ -202,13 +55,11 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { variableOwner).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, variableOwner, + contentType.split("boundary=")[1]); } else { - doHandleXhrFilePost(new PortletRequestWrapper(request), - new PortletResponseWrapper(response), streamVariable, name, + doHandleXhrFilePost(request, response, streamVariable, name, variableOwner, request.getContentLength()); } @@ -222,52 +73,16 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { } } - 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; @Override @@ -302,4 +117,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/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..2c9828b66b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java @@ -0,0 +1,175 @@ +/* +@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.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() { + // No browserDetails available for normal requests + return null; + } + + 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..ae744aa4f8 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java @@ -33,6 +33,7 @@ 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.ui.Root; /** * Utility class to collect widgetset related information from classpath. @@ -95,7 +96,7 @@ public class ClassPathExplorer { /** * Finds server side widgets with {@link ClientWidget} annotation on the * class path (entries that can contain widgets/widgetsets - see - * {@link #getRawClasspathEntries()}). + * getRawClasspathEntries()). * * As a side effect, also accept criteria are searched under the same class * path entries and added into the acceptCriterion collection. @@ -563,7 +564,7 @@ public class ClassPathExplorer { Class<?> c = Class.forName(fullclassName); - if (c.getAnnotation(ClientWidget.class) != null) { + if (c.getAnnotation(ClientWidget.class) != null || Root.class == c) { paintables.add((Class<? extends Paintable>) c); // System.out.println("Found paintable " + fullclassName); } else if (c.getAnnotation(ClientCriterion.class) != null) { diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java index c1f9134e7b..2150748b54 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java @@ -23,9 +23,11 @@ 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.terminal.gwt.client.VPaintableWidget; +import com.vaadin.terminal.gwt.client.ui.VViewPaintable; import com.vaadin.ui.ClientWidget; import com.vaadin.ui.ClientWidget.LoadStyle; +import com.vaadin.ui.Root; /** * WidgetMapGenerator's are GWT generator to build WidgetMapImpl dynamically @@ -69,6 +71,8 @@ import com.vaadin.ui.ClientWidget.LoadStyle; */ public class WidgetMapGenerator extends Generator { + private static String paintableClassName = VPaintableWidget.class.getName(); + private String packageName; private String className; @@ -161,12 +165,10 @@ public class WidgetMapGenerator extends Generator { .iterator(); iterator.hasNext();) { Class<? extends Paintable> class1 = iterator.next(); - ClientWidget annotation = class1.getAnnotation(ClientWidget.class); - - if (typeOracle.findType(annotation.value().getName()) == null) { + Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget> clientClass = getClientClass(class1); + if (typeOracle.findType(clientClass.getName()) == null) { // GWT widget not inherited - logger.log(Type.WARN, "Widget class " - + annotation.value().getName() + logger.log(Type.WARN, "Widget class " + clientClass.getName() + " was not found. The component " + class1.getName() + " will not be included in the widgetset."); iterator.remove(); @@ -225,6 +227,9 @@ public class WidgetMapGenerator extends Generator { * the client side engine */ protected LoadStyle getLoadStyle(Class<? extends Paintable> paintableType) { + if (Root.class == paintableType) { + return LoadStyle.EAGER; + } ClientWidget annotation = paintableType .getAnnotation(ClientWidget.class); return annotation.loadStyle(); @@ -239,23 +244,21 @@ public class WidgetMapGenerator extends Generator { // 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 " + + paintableClassName + "> 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>>(); - + + HashSet<Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget>> widgetsWithInstantiator = new HashSet<Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget>>(); + 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)) { + Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget> clientClass = getClientClass(class1); + if (widgetsWithInstantiator.contains(clientClass)) { continue; } - if (clientClass == VView.class) { + if (clientClass == VViewPaintable.class) { // VView's are not instantiated by widgetset continue; } @@ -267,7 +270,8 @@ public class WidgetMapGenerator extends Generator { sourceWriter.print("if( classType == " + clientClass.getName() + ".class) {"); - String instantiator = "new WidgetInstantiator() {\n public Paintable get() {\n return GWT.create(" + String instantiator = "new WidgetInstantiator() {\n public " + + paintableClassName + " get() {\n return GWT.create(" + clientClass.getName() + ".class );\n}\n}\n"; LoadStyle loadStyle = getLoadStyle(class1); @@ -302,8 +306,8 @@ public class WidgetMapGenerator extends Generator { sourceWriter.println("}"); - sourceWriter - .println("public Class<? extends Paintable>[] getDeferredLoadedWidgets() {"); + sourceWriter.println("public Class<? extends " + paintableClassName + + ">[] getDeferredLoadedWidgets() {"); sourceWriter.println("return new Class[] {"); first = true; @@ -313,7 +317,7 @@ public class WidgetMapGenerator extends Generator { } first = false; ClientWidget annotation = class2.getAnnotation(ClientWidget.class); - Class<? extends com.vaadin.terminal.gwt.client.Paintable> value = annotation + Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget> value = annotation .value(); sourceWriter.print(value.getName() + ".class"); } @@ -328,11 +332,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 " + paintableClassName + + " instantiate(Class<? extends " + paintableClassName + + "> classType) {"); sourceWriter.indent(); - sourceWriter - .println("Paintable p = super.instantiate(classType); if(p!= null) return p;"); + sourceWriter.println(paintableClassName + + " p = super.instantiate(classType); if(p!= null) return p;"); sourceWriter.println("return instmap.get(classType).get();"); sourceWriter.outdent(); @@ -350,16 +355,16 @@ public class WidgetMapGenerator extends Generator { SourceWriter sourceWriter, Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) { sourceWriter - .println("public Class<? extends Paintable> " + .println("public Class<? extends " + + paintableClassName + + "> " + "getImplementationByServerSideClassName(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(); + Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget> clientClass = getClientClass(class1); sourceWriter.print("if ( fullyQualifiedName == \""); sourceWriter.print(class1.getName()); sourceWriter.print("\" ) { ensureInstantiator(" @@ -369,9 +374,21 @@ public class WidgetMapGenerator extends Generator { sourceWriter.print("else "); } sourceWriter - .println("return com.vaadin.terminal.gwt.client.ui.VUnknownComponent.class;"); + .println("return com.vaadin.terminal.gwt.client.ui.VUnknownComponentPaintable.class;"); sourceWriter.outdent(); sourceWriter.println("}"); } + + private static Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget> getClientClass( + Class<? extends Paintable> class1) { + Class<? extends com.vaadin.terminal.gwt.client.VPaintableWidget> clientClass; + if (Root.class == class1) { + clientClass = VViewPaintable.class; + } else { + ClientWidget annotation = class1.getAnnotation(ClientWidget.class); + clientClass = annotation.value(); + } + return clientClass; + } } 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..c75073c02b 100644 --- a/src/com/vaadin/ui/AbsoluteLayout.java +++ b/src/com/vaadin/ui/AbsoluteLayout.java @@ -17,7 +17,7 @@ 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.ui.VAbsoluteLayoutPaintable; /** * AbsoluteLayout is a layout implementation that mimics html absolute @@ -25,7 +25,7 @@ import com.vaadin.terminal.gwt.client.ui.VAbsoluteLayout; * */ @SuppressWarnings("serial") -@ClientWidget(VAbsoluteLayout.class) +@ClientWidget(VAbsoluteLayoutPaintable.class) public class AbsoluteLayout extends AbstractLayout implements LayoutClickNotifier { @@ -176,10 +176,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 +193,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 +215,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 +241,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 +248,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 +275,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 +291,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 +307,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 +323,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 +440,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 +451,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 +462,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 +473,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 +484,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 +495,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 +506,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 +517,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(); } diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java index 0ca9cc6bd4..059076790b 100644 --- a/src/com/vaadin/ui/AbstractComponent.java +++ b/src/com/vaadin/ui/AbstractComponent.java @@ -19,8 +19,10 @@ 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; @@ -138,13 +140,19 @@ public abstract class AbstractComponent implements Component, MethodEventSource 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; + /* Constructor */ /** @@ -319,6 +327,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(); } @@ -609,11 +619,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(); } } @@ -635,6 +645,9 @@ public abstract class AbstractComponent implements Component, MethodEventSource if (delayedFocus) { focus(); } + if (actionManager != null) { + actionManager.setViewer(getRoot()); + } } /* @@ -642,6 +655,9 @@ public abstract class AbstractComponent implements Component, MethodEventSource * we use the default documentation from implemented interface. */ public void detach() { + if (actionManager != null) { + actionManager.setViewer((Root) null); + } } /** @@ -651,7 +667,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; @@ -727,13 +743,13 @@ public abstract class AbstractComponent implements Component, MethodEventSource // Only paint content of visible components. if (isVisible()) { if (getHeight() >= 0 - && (getHeightUnits() != UNITS_PERCENTAGE || ComponentSizeValidator + && (getHeightUnits() != Unit.PERCENTAGE || ComponentSizeValidator .parentCanDefineHeight(this))) { target.addAttribute("height", "" + getCSSHeight()); } if (getWidth() >= 0 - && (getWidthUnits() != UNITS_PERCENTAGE || ComponentSizeValidator + && (getWidthUnits() != Unit.PERCENTAGE || ComponentSizeValidator .parentCanDefineWidth(this))) { target.addAttribute("width", "" + getCSSWidth()); } @@ -791,10 +807,10 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @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,10 +820,10 @@ 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(); } } @@ -1283,7 +1299,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * * @see com.vaadin.terminal.Sizeable#getHeightUnits() */ - public int getHeightUnits() { + public Unit getHeightUnits() { return heightUnit; } @@ -1301,36 +1317,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 +1342,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 +1352,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); + setWidth(-1, Unit.PIXELS); + setHeight(-1, Unit.PIXELS); } /* * (non-Javadoc) * - * @see com.vaadin.terminal.Sizeable#setWidth(float) + * @see com.vaadin.terminal.Sizeable#setWidth(float, Unit) */ - @Deprecated - public void setWidth(float width) { - setWidth(width, getWidthUnits()); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.Sizeable#setWidthUnits(int) - */ - @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 +1377,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 +1391,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 +1506,34 @@ 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(); + if (getRoot() != null) { + actionManager.setViewer(getRoot()); + } + } + return actionManager; + } + + public void addShortcutListener(ShortcutListener shortcut) { + getActionManager().addAction(shortcut); + } + + public void removeShortcutListener(ShortcutListener shortcut) { + if (actionManager != null) { + actionManager.removeAction(shortcut); + } + } + +} diff --git a/src/com/vaadin/ui/AbstractComponentContainer.java b/src/com/vaadin/ui/AbstractComponentContainer.java index 5d5218307a..bbe2c20ea5 100644 --- a/src/com/vaadin/ui/AbstractComponentContainer.java +++ b/src/com/vaadin/ui/AbstractComponentContainer.java @@ -226,7 +226,7 @@ public abstract class AbstractComponentContainer extends AbstractComponent } @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 +237,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 +326,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 +337,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 diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java index 9ea9bbba2e..4217f5f6fe 100644 --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@ -6,19 +6,24 @@ 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.List; import java.util.Map; +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.CompositeErrorMessage; @@ -53,21 +58,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. @@ -127,15 +140,14 @@ public abstract class AbstractField extends AbstractComponent implements Field, 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; @@ -185,11 +197,16 @@ public abstract class AbstractField extends AbstractComponent implements Field, && getErrorMessage() != 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 @@ -237,23 +254,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; } @@ -266,15 +281,14 @@ public abstract class AbstractField extends AbstractComponent implements Field, boolean repaintNeeded = false; // The abstract field is not modified anymore - if (modified) { - modified = false; + if (isModified()) { + setModified(false); repaintNeeded = true; } // If successful, remove set the buffering state to be ok - if (currentBufferedSourceException != null) { - currentBufferedSourceException = null; - repaintNeeded = true; + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); } if (valueWasModifiedByDataSourceDuringCommit) { @@ -294,19 +308,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( @@ -318,22 +331,46 @@ 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 @@ -343,6 +380,10 @@ public abstract class AbstractField extends AbstractComponent implements Field, return modified; } + private void setModified(boolean modified) { + this.modified = modified; + } + /* * Tests if the field is in write-through mode. Don't add a JavaDoc comment * here, we use the default documentation from the implemented interface. @@ -351,11 +392,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) { @@ -375,38 +433,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(); } /** @@ -414,67 +530,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 " + + getClass().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()) { @@ -483,24 +595,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 @@ -510,10 +622,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) { @@ -530,9 +643,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) { @@ -549,6 +661,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 */ /** @@ -591,7 +710,7 @@ 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(); // Stops listening the old data source changes if (dataSource != null @@ -609,17 +728,32 @@ public abstract class AbstractField extends AbstractComponent implements Field, // Sets the new data source dataSource = newDataSource; + // 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); } // Listens the new data source if possible @@ -644,12 +778,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 */ /** @@ -698,102 +979,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); } /** @@ -857,13 +1135,13 @@ 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 }); + validationError, getCurrentBufferedSourceException() }); } @@ -949,8 +1227,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. @@ -1014,10 +1292,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 @@ -1040,7 +1316,7 @@ public abstract class AbstractField extends AbstractComponent implements Field, } private void readValueFromProperty(Property.ValueChangeEvent event) { - setInternalValue(event.getProperty().getValue()); + setInternalValue(convertFromDataSource(event.getProperty().getValue())); } @Override @@ -1056,25 +1332,6 @@ 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) * @@ -1095,15 +1352,34 @@ public abstract class AbstractField extends AbstractComponent implements Field, } /** + * 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(); @@ -1111,27 +1387,6 @@ public abstract class AbstractField extends AbstractComponent implements Field, } /** - * Notifies the component that it is connected to an application. - * - * @see com.vaadin.ui.Component#attach() - */ - @Override - public void attach() { - super.attach(); - if (actionManager != null) { - actionManager.setViewer(getWindow()); - } - } - - @Override - public void detach() { - super.detach(); - if (actionManager != null) { - actionManager.setViewer((Window) null); - } - } - - /** * Is this field required. Required fields must filled by the user. * * If the field is required, it is visually indicated in the user interface. @@ -1190,13 +1445,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); } /** @@ -1244,34 +1522,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; } /** @@ -1329,4 +1586,28 @@ public abstract class AbstractField extends AbstractComponent implements Field, focusable.focus(); } } -}
\ No newline at end of file + + /** + * 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(); + } + +} diff --git a/src/com/vaadin/ui/AbstractMedia.java b/src/com/vaadin/ui/AbstractMedia.java index 9117bce997..369ef773b9 100644 --- a/src/com/vaadin/ui/AbstractMedia.java +++ b/src/com/vaadin/ui/AbstractMedia.java @@ -12,7 +12,7 @@ import java.util.List; 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.gwt.client.ui.VMediaBasePaintable; /** * Abstract base class for the HTML5 media components. @@ -203,25 +203,27 @@ public class AbstractMedia extends AbstractComponent { @Override public void paintContent(PaintTarget target) throws PaintException { super.paintContent(target); - target.addAttribute(VMediaBase.ATTR_CONTROLS, isShowControls()); + target.addAttribute(VMediaBasePaintable.ATTR_CONTROLS, isShowControls()); if (getAltText() != null) { - target.addAttribute(VMediaBase.ATTR_ALT_TEXT, getAltText()); + target.addAttribute(VMediaBasePaintable.ATTR_ALT_TEXT, getAltText()); } - target.addAttribute(VMediaBase.ATTR_HTML, isHtmlContentAllowed()); - target.addAttribute(VMediaBase.ATTR_AUTOPLAY, isAutoplay()); + target.addAttribute(VMediaBasePaintable.ATTR_HTML, + isHtmlContentAllowed()); + target.addAttribute(VMediaBasePaintable.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.startTag(VMediaBasePaintable.TAG_SOURCE); + target.addAttribute(VMediaBasePaintable.ATTR_RESOURCE, r); + target.addAttribute(VMediaBasePaintable.ATTR_RESOURCE_TYPE, + r.getMIMEType()); + target.endTag(VMediaBasePaintable.TAG_SOURCE); } - target.addAttribute(VMediaBase.ATTR_MUTED, isMuted()); + target.addAttribute(VMediaBasePaintable.ATTR_MUTED, isMuted()); if (play) { - target.addAttribute(VMediaBase.ATTR_PLAY, true); + target.addAttribute(VMediaBasePaintable.ATTR_PLAY, true); play = false; } if (pause) { - target.addAttribute(VMediaBase.ATTR_PAUSE, true); + target.addAttribute(VMediaBasePaintable.ATTR_PAUSE, true); pause = false; } } diff --git a/src/com/vaadin/ui/AbstractSelect.java b/src/com/vaadin/ui/AbstractSelect.java index bb49626741..5e086f0b8d 100644 --- a/src/com/vaadin/ui/AbstractSelect.java +++ b/src/com/vaadin/ui/AbstractSelect.java @@ -55,46 +55,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 { + 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 @@ -174,7 +219,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. @@ -515,16 +560,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()); @@ -614,8 +652,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 +678,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 +766,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); } @@ -1045,11 +1082,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 +1095,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 +1130,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 +1165,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 +1207,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 +1242,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 +1256,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 +1733,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 +1746,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 +1757,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 adb84f9d9d..e03e73a781 100644 --- a/src/com/vaadin/ui/AbstractSplitPanel.java +++ b/src/com/vaadin/ui/AbstractSplitPanel.java @@ -15,7 +15,7 @@ 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.VAbstractSplitPanelPaintable; import com.vaadin.tools.ReflectTools; /** @@ -37,13 +37,13 @@ public abstract class AbstractSplitPanel extends AbstractLayout { private int pos = 50; - private int posUnit = UNITS_PERCENTAGE; + private Unit posUnit = Unit.PERCENTAGE; private boolean posReversed = false; private boolean locked = false; - private static final String SPLITTER_CLICK_EVENT = VSplitPanel.SPLITTER_CLICK_EVENT_IDENTIFIER; + private static final String SPLITTER_CLICK_EVENT = VAbstractSplitPanelPaintable.SPLITTER_CLICK_EVENT_IDENTIFIER; /** * Modifiable and Serializable Iterator for the components, used by @@ -209,7 +209,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { public void paintContent(PaintTarget target) throws PaintException { super.paintContent(target); - final String position = pos + UNIT_SYMBOLS[posUnit]; + final String position = pos + posUnit.getSymbol(); target.addAttribute("position", position); @@ -278,7 +278,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * @param unit * the unit (from {@link Sizeable}) in which the size is given. */ - public void setSplitPosition(int pos, int unit) { + public void setSplitPosition(int pos, Unit unit) { setSplitPosition(pos, unit, true, false); } @@ -294,7 +294,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * second region else it is measured by the first region * */ - public void setSplitPosition(int pos, int unit, boolean reverse) { + public void setSplitPosition(int pos, Unit unit, boolean reverse) { setSplitPosition(pos, unit, true, reverse); } @@ -313,7 +313,7 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * * @return unit of position of the splitter */ - public int getSplitPositionUnit() { + public Unit getSplitPositionUnit() { return posUnit; } @@ -329,9 +329,9 @@ public abstract class AbstractSplitPanel extends AbstractLayout { * position info has come from the client side, thus it already * knows the position. */ - private void setSplitPosition(int pos, int unit, boolean repaintNeeded, + private void setSplitPosition(int pos, Unit unit, boolean repaintNeeded, boolean reverse) { - if (unit != UNITS_PERCENTAGE && unit != UNITS_PIXELS) { + if (unit != Unit.PERCENTAGE && unit != Unit.PIXELS) { throw new IllegalArgumentException( "Only percentage and pixel units are allowed"); } diff --git a/src/com/vaadin/ui/AbstractTextField.java b/src/com/vaadin/ui/AbstractTextField.java index 346d370bd5..96d8a11410 100644 --- a/src/com/vaadin/ui/AbstractTextField.java +++ b/src/com/vaadin/ui/AbstractTextField.java @@ -20,7 +20,7 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.gwt.client.ui.VTextField; -public abstract class AbstractTextField extends AbstractField implements +public abstract class AbstractTextField extends AbstractField<String> implements BlurNotifier, FocusNotifier, TextChangeNotifier { /** @@ -173,8 +173,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; } @@ -252,7 +252,7 @@ public abstract class AbstractTextField extends AbstractField implements } @Override - public Class getType() { + public Class<String> getType() { return String.class; } @@ -375,7 +375,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 +463,7 @@ public abstract class AbstractTextField extends AbstractField implements } @Override - protected void setInternalValue(Object newValue) { + protected void setInternalValue(String newValue) { if (changingVariables && !textChangeEventPending) { /* @@ -505,8 +505,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 +514,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 +752,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..4ee75326ff 100644 --- a/src/com/vaadin/ui/Accordion.java +++ b/src/com/vaadin/ui/Accordion.java @@ -3,7 +3,7 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VAccordion; +import com.vaadin.terminal.gwt.client.ui.VAccordionPaintable; /** * An accordion is a component similar to a {@link TabSheet}, but with a @@ -16,8 +16,7 @@ import com.vaadin.terminal.gwt.client.ui.VAccordion; * * @see TabSheet */ -@SuppressWarnings("serial") -@ClientWidget(VAccordion.class) +@ClientWidget(VAccordionPaintable.class) public class Accordion extends TabSheet { } diff --git a/src/com/vaadin/ui/Audio.java b/src/com/vaadin/ui/Audio.java index 574c1f4186..048ef81c10 100644 --- a/src/com/vaadin/ui/Audio.java +++ b/src/com/vaadin/ui/Audio.java @@ -5,7 +5,7 @@ package com.vaadin.ui; import com.vaadin.terminal.Resource; -import com.vaadin.terminal.gwt.client.ui.VAudio; +import com.vaadin.terminal.gwt.client.ui.VAudioPaintable; /** * The Audio component translates into an HTML5 <audio> element and as @@ -28,7 +28,7 @@ import com.vaadin.terminal.gwt.client.ui.VAudio; * @author Vaadin Ltd * @since 6.7.0 */ -@ClientWidget(VAudio.class) +@ClientWidget(VAudioPaintable.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 795a13e41a..8f677a9775 100644 --- a/src/com/vaadin/ui/Button.java +++ b/src/com/vaadin/ui/Button.java @@ -9,7 +9,7 @@ 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; @@ -23,8 +23,10 @@ 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.terminal.gwt.client.ui.VButtonPaintable; +import com.vaadin.tools.ReflectTools; import com.vaadin.ui.ClientWidget.LoadStyle; -import com.vaadin.ui.themes.BaseTheme; +import com.vaadin.ui.Component.Focusable; /** * A generic button component. @@ -35,29 +37,23 @@ 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 { +@ClientWidget(value = VButtonPaintable.class, loadStyle = LoadStyle.EAGER) +public class Button extends AbstractComponent implements + FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Focusable, + Action.ShortcutNotifier { /* Private members */ - boolean switchMode = false; - boolean disableOnClick = false; /** - * 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); } /** - * 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 +64,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,57 +77,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 @@ -145,11 +90,6 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, 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); } @@ -176,46 +116,14 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, } 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(); - } - } + // Send click events when the button is pushed + if (variables.containsKey("mousedetails")) { + fireClick(MouseEventDetails.deSerialize((String) variables + .get("mousedetails"))); } 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); - } + // for compatibility with custom implementations which + // don't send mouse details + fireClick(); } } @@ -228,92 +136,6 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, } /** - * 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 +306,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 +328,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 +339,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); } /** @@ -538,16 +366,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); @@ -573,6 +391,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/> @@ -685,4 +505,18 @@ public class Button extends AbstractField implements FieldEvents.BlurNotifier, 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(); + } } diff --git a/src/com/vaadin/ui/CheckBox.java b/src/com/vaadin/ui/CheckBox.java index 00a248cdf3..9dc96bb55d 100644 --- a/src/com/vaadin/ui/CheckBox.java +++ b/src/com/vaadin/ui/CheckBox.java @@ -4,110 +4,130 @@ package com.vaadin.ui; -import java.lang.reflect.Method; +import java.util.Map; import com.vaadin.data.Property; +import com.vaadin.event.FieldEvents.BlurEvent; +import com.vaadin.event.FieldEvents.BlurListener; +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.VCheckBox; -@ClientWidget(com.vaadin.terminal.gwt.client.ui.VCheckBox.class) -public class CheckBox extends Button { +@ClientWidget(com.vaadin.terminal.gwt.client.ui.VCheckBoxPaintable.class) +public class CheckBox extends AbstractField<Boolean> { /** - * Creates a new switch button. + * Creates a new checkbox. */ public CheckBox() { - setSwitchMode(true); + setValue(Boolean.FALSE); } /** - * Creates a new switch button with a caption and a set initial state. + * Creates a new checkbox with a set caption. * * @param caption - * the caption of the switch button - * @param initialState - * the initial state of the switch button + * the Checkbox caption. */ - @SuppressWarnings("deprecation") - public CheckBox(String caption, boolean initialState) { - super(caption, initialState); - } - - /** - * Creates a new switch button with a caption and a click listener. - * - * @param caption - * the caption of the switch button - * @param listener - * the click listener - */ - 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 void paintContent(PaintTarget target) throws PaintException { + super.paintContent(target); + + Boolean value = getValue(); + boolean booleanValue = (value != null) ? value : false; + target.addVariable(this, VCheckBox.VARIABLE_STATE, booleanValue); } - @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)"); + public void changeVariables(Object source, Map<String, Object> variables) { + super.changeVariables(source, variables); + + if (!isReadOnly() && variables.containsKey(VCheckBox.VARIABLE_STATE)) { + // Gets the new and old states + final Boolean newValue = (Boolean) variables + .get(VCheckBox.VARIABLE_STATE); + final Boolean oldValue = getValue(); + + // The event is only sent if the switch state is changed + if (newValue != null && !newValue.equals(oldValue)) { + setValue(newValue); + } + } + + if (variables.containsKey(FocusEvent.EVENT_ID)) { + fireEvent(new FocusEvent(this)); + } + if (variables.containsKey(BlurEvent.EVENT_ID)) { + fireEvent(new BlurEvent(this)); } - super.setSwitchMode(true); } - @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/ClientWidget.java b/src/com/vaadin/ui/ClientWidget.java index 8817f8f776..d944e8b3c8 100644 --- a/src/com/vaadin/ui/ClientWidget.java +++ b/src/com/vaadin/ui/ClientWidget.java @@ -11,7 +11,7 @@ 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.VPaintableWidget; import com.vaadin.terminal.gwt.widgetsetutils.CustomWidgetMapGenerator; import com.vaadin.terminal.gwt.widgetsetutils.EagerWidgetMapGenerator; import com.vaadin.terminal.gwt.widgetsetutils.LazyWidgetMapGenerator; @@ -36,7 +36,7 @@ public @interface ClientWidget { /** * @return the client side counterpart for the annotated component */ - Class<? extends Paintable> value(); + Class<? extends VPaintableWidget> value(); /** * Depending on the used WidgetMap generator, these optional hints may be diff --git a/src/com/vaadin/ui/ComboBox.java b/src/com/vaadin/ui/ComboBox.java index bc7ab6f994..013fe6ab85 100644 --- a/src/com/vaadin/ui/ComboBox.java +++ b/src/com/vaadin/ui/ComboBox.java @@ -10,6 +10,7 @@ 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.VFilterSelectPaintable; /** * A filtering dropdown single-select. Suitable for newItemsAllowed, but it's @@ -20,7 +21,7 @@ import com.vaadin.terminal.gwt.client.ui.VFilterSelect; * */ @SuppressWarnings("serial") -@ClientWidget(VFilterSelect.class) +@ClientWidget(VFilterSelectPaintable.class) public class ComboBox extends Select { private String inputPrompt = null; diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java index b32aad2fca..1289b57bd9 100644 --- a/src/com/vaadin/ui/Component.java +++ b/src/com/vaadin/ui/Component.java @@ -560,7 +560,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 +593,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: @@ -648,7 +648,7 @@ 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 + * The {@link #getApplication()} and {@link #getRoot()} methods might * return <code>null</code> after this method is called. * </p> * diff --git a/src/com/vaadin/ui/CssLayout.java b/src/com/vaadin/ui/CssLayout.java index b9432df6b6..ebfee5a787 100644 --- a/src/com/vaadin/ui/CssLayout.java +++ b/src/com/vaadin/ui/CssLayout.java @@ -14,7 +14,7 @@ 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.ui.VCssLayoutPaintable; /** * CssLayout is a layout component that can be used in browser environment only. @@ -57,7 +57,7 @@ import com.vaadin.terminal.gwt.client.ui.VCssLayout; * @since 6.1 brought in from "FastLayouts" incubator project * */ -@ClientWidget(VCssLayout.class) +@ClientWidget(VCssLayoutPaintable.class) public class CssLayout extends AbstractLayout implements LayoutClickNotifier { private static final String CLICK_EVENT = EventId.LAYOUT_CLICK; diff --git a/src/com/vaadin/ui/CustomComponent.java b/src/com/vaadin/ui/CustomComponent.java index 21eda08909..7aba34b6bb 100644 --- a/src/com/vaadin/ui/CustomComponent.java +++ b/src/com/vaadin/ui/CustomComponent.java @@ -9,7 +9,7 @@ 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.terminal.gwt.client.ui.VCustomComponentPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -27,7 +27,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VCustomComponent.class, loadStyle = LoadStyle.EAGER) +@ClientWidget(value = VCustomComponentPaintable.class, loadStyle = LoadStyle.EAGER) public class CustomComponent extends AbstractComponentContainer { /** @@ -36,11 +36,6 @@ public class CustomComponent extends AbstractComponentContainer { private Component root = null; /** - * Type of the component. - */ - private String componentType = null; - - /** * Constructs a new custom component. * * <p> @@ -115,43 +110,9 @@ public class CustomComponent extends AbstractComponentContainer { + " 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..73f9050363 --- /dev/null +++ b/src/com/vaadin/ui/CustomField.java @@ -0,0 +1,252 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Iterator; + +import com.vaadin.data.Property; +import com.vaadin.terminal.PaintException; +import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.gwt.client.ui.VCustomComponentPaintable; + +/** + * 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 + */ +@ClientWidget(VCustomComponentPaintable.class) +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(); + } + + @Override + public void paintContent(PaintTarget target) throws PaintException { + if (getContent() == null) { + throw new IllegalStateException( + "Content component or layout of the field must be set before the " + + getClass().getName() + " can be painted"); + } + + getContent().paint(target); + } + + /** + * 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(); + + private void requestContentRepaint() { + if (getParent() == null) { + // skip repaint - not yet attached + return; + } + if (getContent() instanceof ComponentContainer) { + ((ComponentContainer) getContent()).requestRepaintAll(); + } else { + getContent().requestRepaint(); + } + } + + // 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); + requestContentRepaint(); + } + + @Override + public void setWidth(float height, Unit unit) { + super.setWidth(height, unit); + requestContentRepaint(); + } + + // ComponentContainer methods + + private class ComponentIterator implements Iterator<Component>, + Serializable { + boolean first = getContent() != 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 int getComponentCount() { + return (null != getContent()) ? 1 : 0; + } + + public void requestRepaintAll() { + requestRepaint(); + + requestContentRepaint(); + } + + /** + * 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 + } + +} diff --git a/src/com/vaadin/ui/CustomLayout.java b/src/com/vaadin/ui/CustomLayout.java index dc473fb549..fb0c369969 100644 --- a/src/com/vaadin/ui/CustomLayout.java +++ b/src/com/vaadin/ui/CustomLayout.java @@ -12,7 +12,7 @@ import java.util.Iterator; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VCustomLayout; +import com.vaadin.terminal.gwt.client.ui.VCustomLayoutPaintable; /** * <p> @@ -44,7 +44,7 @@ import com.vaadin.terminal.gwt.client.ui.VCustomLayout; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VCustomLayout.class) +@ClientWidget(VCustomLayoutPaintable.class) public class CustomLayout extends AbstractLayout { private static final int BUFFER_SIZE = 10000; diff --git a/src/com/vaadin/ui/DateField.java b/src/com/vaadin/ui/DateField.java index ef67345aab..9589414f4d 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; @@ -24,7 +27,7 @@ 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.gwt.client.ui.VPopupCalendarPaintable; /** * <p> @@ -47,56 +50,129 @@ import com.vaadin.terminal.gwt.client.ui.VPopupCalendar; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VPopupCalendar.class) -public class DateField extends AbstractField implements +@ClientWidget(VPopupCalendarPaintable.class) +public class DateField extends AbstractField<Date> implements FieldEvents.BlurNotifier, FieldEvents.FocusNotifier { - /* Private members */ - /** - * 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 +205,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 */ /** @@ -228,51 +314,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); } } @@ -298,10 +354,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 +365,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 +424,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 +492,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 +500,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 +516,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 +526,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 +560,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 +575,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 +595,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 +619,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 +653,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 +775,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/DragAndDropWrapper.java b/src/com/vaadin/ui/DragAndDropWrapper.java index c6522f15c7..512b70e118 100644 --- a/src/com/vaadin/ui/DragAndDropWrapper.java +++ b/src/com/vaadin/ui/DragAndDropWrapper.java @@ -22,11 +22,12 @@ import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.StreamVariable; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper; +import com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapperPaintable; import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; @SuppressWarnings("serial") -@ClientWidget(VDragAndDropWrapper.class) +@ClientWidget(VDragAndDropWrapperPaintable.class) public class DragAndDropWrapper extends CustomComponent implements DropTarget, DragSource { diff --git a/src/com/vaadin/ui/Embedded.java b/src/com/vaadin/ui/Embedded.java index dc14cc6ef8..f655b55711 100644 --- a/src/com/vaadin/ui/Embedded.java +++ b/src/com/vaadin/ui/Embedded.java @@ -14,7 +14,7 @@ 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.VEmbedded; +import com.vaadin.terminal.gwt.client.ui.VEmbeddedPaintable; /** * Component for embedding external objects. @@ -25,10 +25,10 @@ import com.vaadin.terminal.gwt.client.ui.VEmbedded; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VEmbedded.class) +@ClientWidget(VEmbeddedPaintable.class) public class Embedded extends AbstractComponent { - private static final String CLICK_EVENT = VEmbedded.CLICK_EVENT_IDENTIFIER; + private static final String CLICK_EVENT = VEmbeddedPaintable.CLICK_EVENT_IDENTIFIER; /** * General object type. 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..6cc11daf08 100644 --- a/src/com/vaadin/ui/Field.java +++ b/src/com/vaadin/ui/Field.java @@ -9,30 +9,20 @@ 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 +70,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..c79804c7e7 100644 --- a/src/com/vaadin/ui/Form.java +++ b/src/com/vaadin/ui/Form.java @@ -17,6 +17,7 @@ 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; @@ -26,7 +27,7 @@ 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.gwt.client.ui.VFormPaintable; /** * Form component provides easy way of creating and managing sets fields. @@ -58,11 +59,13 @@ 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 { +@ClientWidget(VFormPaintable.class) +@Deprecated +public class Form extends AbstractField<Object> implements Item.Editor, + Buffered, Item, Validatable, Action.Notifier { private Object propertyValue; @@ -99,12 +102,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. @@ -242,7 +245,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, 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( @@ -321,7 +324,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 +411,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 +425,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 +435,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 +490,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 +521,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 +541,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; } @@ -599,13 +604,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 +625,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 +642,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); @@ -750,9 +755,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); @@ -828,7 +833,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, if (layout != 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, @@ -875,7 +880,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."); @@ -952,7 +957,7 @@ 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); @@ -994,21 +999,7 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item, } /** - * 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 +1046,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 +1071,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 +1112,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 +1205,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 +1231,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 +1243,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; } } 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..c5c211924e 100644 --- a/src/com/vaadin/ui/FormLayout.java +++ b/src/com/vaadin/ui/FormLayout.java @@ -4,7 +4,7 @@ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VFormLayout; +import com.vaadin.terminal.gwt.client.ui.VFormLayoutPaintable; /** * FormLayout is used by {@link Form} to layout fields. It may also be used @@ -21,14 +21,14 @@ import com.vaadin.terminal.gwt.client.ui.VFormLayout; * bottom are by default on. * */ -@SuppressWarnings({ "deprecation", "serial" }) -@ClientWidget(VFormLayout.class) -public class FormLayout extends OrderedLayout { +@ClientWidget(VFormLayoutPaintable.class) +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 24a57d462b..4588328345 100644 --- a/src/com/vaadin/ui/GridLayout.java +++ b/src/com/vaadin/ui/GridLayout.java @@ -18,7 +18,7 @@ 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.ui.VGridLayout; +import com.vaadin.terminal.gwt.client.ui.VGridLayoutPaintable; /** * <p> @@ -41,7 +41,7 @@ import com.vaadin.terminal.gwt.client.ui.VGridLayout; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VGridLayout.class) +@ClientWidget(VGridLayoutPaintable.class) public class GridLayout extends AbstractLayout implements Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier { diff --git a/src/com/vaadin/ui/HorizontalLayout.java b/src/com/vaadin/ui/HorizontalLayout.java index ed1cad8184..ba685ec410 100644 --- a/src/com/vaadin/ui/HorizontalLayout.java +++ b/src/com/vaadin/ui/HorizontalLayout.java @@ -3,7 +3,7 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VHorizontalLayout; +import com.vaadin.terminal.gwt.client.ui.VHorizontalLayoutPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -18,7 +18,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 5.3 */ @SuppressWarnings("serial") -@ClientWidget(value = VHorizontalLayout.class, loadStyle = LoadStyle.EAGER) +@ClientWidget(value = VHorizontalLayoutPaintable.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..d4a1e7cc0e 100644 --- a/src/com/vaadin/ui/HorizontalSplitPanel.java +++ b/src/com/vaadin/ui/HorizontalSplitPanel.java @@ -3,7 +3,7 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VSplitPanelHorizontal; +import com.vaadin.terminal.gwt.client.ui.VHorizontalSplitPanelPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -29,7 +29,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @VERSION@ * @since 6.5 */ -@ClientWidget(value = VSplitPanelHorizontal.class, loadStyle = LoadStyle.EAGER) +@ClientWidget(value = VHorizontalSplitPanelPaintable.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..b4160604ff 100644 --- a/src/com/vaadin/ui/InlineDateField.java +++ b/src/com/vaadin/ui/InlineDateField.java @@ -7,7 +7,7 @@ package com.vaadin.ui; import java.util.Date; import com.vaadin.data.Property; -import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar; +import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendarPaintable; /** * <p> @@ -22,7 +22,7 @@ import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar; * @VERSION@ * @since 5.0 */ -@ClientWidget(VDateFieldCalendar.class) +@ClientWidget(VDateFieldCalendarPaintable.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..576bbd967f 100644 --- a/src/com/vaadin/ui/Label.java +++ b/src/com/vaadin/ui/Label.java @@ -10,14 +10,13 @@ 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.terminal.gwt.client.ui.label.VLabelPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** * 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,166 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VLabel.class, loadStyle = LoadStyle.EAGER) +@ClientWidget(value = VLabelPaintable.class, loadStyle = LoadStyle.EAGER) +// TODO generics for interface Property public class Label extends AbstractComponent implements Property, Property.Viewer, Property.ValueChangeListener, Property.ValueChangeNotifier, Comparable<Object> { /** - * 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 +212,7 @@ public class Label extends AbstractComponent implements Property, * @param content */ public Label(String content) { - this(content, CONTENT_DEFAULT); + this(content, ContentMode.DEFAULT); } /** @@ -124,7 +222,7 @@ public class Label extends AbstractComponent implements Property, * @param contentSource */ public Label(Property contentSource) { - this(contentSource, CONTENT_DEFAULT); + this(contentSource, ContentMode.DEFAULT); } /** @@ -133,7 +231,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 +242,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 @@ -190,30 +260,11 @@ public class Label extends AbstractComponent implements Property, */ @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 +297,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 +378,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 +428,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 +518,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(); } diff --git a/src/com/vaadin/ui/Link.java b/src/com/vaadin/ui/Link.java index ebea47118a..0b11151be7 100644 --- a/src/com/vaadin/ui/Link.java +++ b/src/com/vaadin/ui/Link.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.VLink; +import com.vaadin.terminal.gwt.client.ui.VLinkPaintable; /** * Link is used to create external or internal URL links. @@ -18,17 +18,17 @@ import com.vaadin.terminal.gwt.client.ui.VLink; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VLink.class) +@ClientWidget(VLinkPaintable.class) public class Link extends AbstractComponent { /* 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; diff --git a/src/com/vaadin/ui/ListSelect.java b/src/com/vaadin/ui/ListSelect.java index 5c879f00f5..cf0e6773f2 100644 --- a/src/com/vaadin/ui/ListSelect.java +++ b/src/com/vaadin/ui/ListSelect.java @@ -9,14 +9,14 @@ 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; +import com.vaadin.terminal.gwt.client.ui.VListSelectPaintable; /** * This is a simple list select without, for instance, support for new items, * lazyloading, and other advanced features. */ @SuppressWarnings("serial") -@ClientWidget(VListSelect.class) +@ClientWidget(VListSelectPaintable.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..7eaeb824c9 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 " @@ -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..54a094a4e7 100644 --- a/src/com/vaadin/ui/MenuBar.java +++ b/src/com/vaadin/ui/MenuBar.java @@ -14,6 +14,7 @@ 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.terminal.gwt.client.ui.VMenuBarPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -24,7 +25,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * </p> */ @SuppressWarnings("serial") -@ClientWidget(value = VMenuBar.class, loadStyle = LoadStyle.LAZY) +@ClientWidget(value = VMenuBarPaintable.class, loadStyle = LoadStyle.LAZY) public class MenuBar extends AbstractComponent { // Items of the top-level menu @@ -33,20 +34,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; @@ -68,10 +55,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()); @@ -193,7 +176,6 @@ public class MenuBar extends AbstractComponent { */ public MenuBar() { menuItems = new ArrayList<MenuItem>(); - setCollapse(true); setMoreMenuItem(null); } @@ -311,54 +293,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. diff --git a/src/com/vaadin/ui/NativeButton.java b/src/com/vaadin/ui/NativeButton.java index 369b40b93a..b7b7fcb38c 100644 --- a/src/com/vaadin/ui/NativeButton.java +++ b/src/com/vaadin/ui/NativeButton.java @@ -3,11 +3,10 @@ */ package com.vaadin.ui; -import com.vaadin.data.Property; -import com.vaadin.terminal.gwt.client.ui.VNativeButton; +import com.vaadin.terminal.gwt.client.ui.VNativeButtonPaintable; @SuppressWarnings("serial") -@ClientWidget(VNativeButton.class) +@ClientWidget(VNativeButtonPaintable.class) public class NativeButton extends Button { public NativeButton() { @@ -22,34 +21,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..b0070426ad 100644 --- a/src/com/vaadin/ui/NativeSelect.java +++ b/src/com/vaadin/ui/NativeSelect.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.VNativeSelect; +import com.vaadin.terminal.gwt.client.ui.VNativeSelectPaintable; /** * This is a simple drop-down select without, for instance, support for @@ -18,7 +18,7 @@ import com.vaadin.terminal.gwt.client.ui.VNativeSelect; * better choice. */ @SuppressWarnings("serial") -@ClientWidget(VNativeSelect.class) +@ClientWidget(VNativeSelectPaintable.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..ddacc31554 100644 --- a/src/com/vaadin/ui/OptionGroup.java +++ b/src/com/vaadin/ui/OptionGroup.java @@ -18,12 +18,13 @@ 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.VOptionGroupPaintable; /** * Configures select to be used as an option group. */ @SuppressWarnings("serial") -@ClientWidget(VOptionGroup.class) +@ClientWidget(VOptionGroupPaintable.class) public class OptionGroup extends AbstractSelect implements FieldEvents.BlurNotifier, FieldEvents.FocusNotifier { 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..390279a62f 100644 --- a/src/com/vaadin/ui/Panel.java +++ b/src/com/vaadin/ui/Panel.java @@ -16,7 +16,7 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Scrollable; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.VPanel; +import com.vaadin.terminal.gwt.client.ui.VPanelPaintable; import com.vaadin.ui.Component.Focusable; import com.vaadin.ui.themes.Reindeer; import com.vaadin.ui.themes.Runo; @@ -30,12 +30,12 @@ import com.vaadin.ui.themes.Runo; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VPanel.class) +@ClientWidget(VPanelPaintable.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; + private static final String CLICK_EVENT = VPanelPaintable.CLICK_EVENT_IDENTIFIER; /** * Removes extra decorations from the Panel. @@ -559,6 +559,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, /* * ACTIONS */ + @Override protected ActionManager getActionManager() { if (actionManager == null) { actionManager = new ActionManager(this); diff --git a/src/com/vaadin/ui/PasswordField.java b/src/com/vaadin/ui/PasswordField.java index 99874147d5..d5be4f378d 100644 --- a/src/com/vaadin/ui/PasswordField.java +++ b/src/com/vaadin/ui/PasswordField.java @@ -4,13 +4,13 @@ package com.vaadin.ui; import com.vaadin.data.Property; -import com.vaadin.terminal.gwt.client.ui.VPasswordField; +import com.vaadin.terminal.gwt.client.ui.VPasswordFieldPaintable; /** * A field that is used to enter secret text information like passwords. The * entered text is not displayed on the screen. */ -@ClientWidget(VPasswordField.class) +@ClientWidget(VPasswordFieldPaintable.class) public class PasswordField extends AbstractTextField { /** diff --git a/src/com/vaadin/ui/PopupView.java b/src/com/vaadin/ui/PopupView.java index fcad727510..5637ef69d7 100644 --- a/src/com/vaadin/ui/PopupView.java +++ b/src/com/vaadin/ui/PopupView.java @@ -10,7 +10,7 @@ import java.util.Map; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.VPopupView; +import com.vaadin.terminal.gwt.client.ui.VPopupViewPaintable; /** * @@ -22,7 +22,7 @@ import com.vaadin.terminal.gwt.client.ui.VPopupView; * @author Vaadin Ltd. */ @SuppressWarnings("serial") -@ClientWidget(VPopupView.class) +@ClientWidget(VPopupViewPaintable.class) public class PopupView extends AbstractComponentContainer { private Content content; diff --git a/src/com/vaadin/ui/ProgressIndicator.java b/src/com/vaadin/ui/ProgressIndicator.java index 405ff2b52f..9152a3adab 100644 --- a/src/com/vaadin/ui/ProgressIndicator.java +++ b/src/com/vaadin/ui/ProgressIndicator.java @@ -8,7 +8,7 @@ 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.gwt.client.ui.VProgressIndicatorPaintable; /** * <code>ProgressIndicator</code> is component that shows user state of a @@ -25,8 +25,8 @@ import com.vaadin.terminal.gwt.client.ui.VProgressIndicator; * @since 4 */ @SuppressWarnings("serial") -@ClientWidget(VProgressIndicator.class) -public class ProgressIndicator extends AbstractField implements Property, +@ClientWidget(VProgressIndicatorPaintable.class) +public class ProgressIndicator extends AbstractField<Number> implements Property.Viewer, Property.ValueChangeListener { /** @@ -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"); } diff --git a/src/com/vaadin/ui/RichTextArea.java b/src/com/vaadin/ui/RichTextArea.java index f4d88edc78..240063caf3 100644 --- a/src/com/vaadin/ui/RichTextArea.java +++ b/src/com/vaadin/ui/RichTextArea.java @@ -10,7 +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.terminal.gwt.client.ui.richtextarea.VRichTextAreaPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -20,8 +20,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 { +@ClientWidget(value = VRichTextAreaPaintable.class, loadStyle = LoadStyle.LAZY) +public class RichTextArea extends AbstractField<String> { /** * Value formatter used to format the string contents. @@ -129,6 +129,7 @@ public class RichTextArea extends AbstractField { 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 +176,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; } @@ -221,7 +222,7 @@ public class RichTextArea extends AbstractField { } @Override - public Class getType() { + public Class<String> getType() { return String.class; } @@ -342,7 +343,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..7fd4a79458 --- /dev/null +++ b/src/com/vaadin/ui/Root.java @@ -0,0 +1,1539 @@ +/* +@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.WrappedRequest; +import com.vaadin.terminal.WrappedRequest.BrowserDetails; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.VView; +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 + */ +// @ClientWidget(VView.class) - Can't have annotation because of eager +// classloaders in application servers and hard coded logic in client side code +public abstract class Root extends AbstractComponentContainer implements + Action.Container, Action.Notifier { + + /** + * 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 container in which the component hierarchy of the root starts. + */ + private ComponentContainer content; + + /** + * 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 = VView.CLICK_EVENT_ID; + + /** + * Creates a new empty root without a caption. This root will have a + * {@link VerticalLayout} with margins enabled as its content. + */ + public Root() { + // Nothing to do here? + } + + /** + * 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) { + 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) { + 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); + } + + /** + * 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; + } + + @Override + 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(); + } + } + + ComponentContainer content = getContent(); + if (content != null) { + content.paint(target); + } + + // Paint subwindows + for (final Iterator<Window> i = windows.iterator(); i.hasNext();) { + final Window w = i.next(); + w.paint(target); + } + + // 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.getDescription() != null) { + target.addAttribute("message", n.getDescription()); + } + 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; + } + + // 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(VView.FRAGMENT_VARIABLE, fragment); + } + + if (isResizeLazy()) { + target.addAttribute(VView.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") + @Override + public void changeVariables(Object source, Map<String, Object> variables) { + super.changeVariables(source, 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(VView.FRAGMENT_VARIABLE)) { + String fragment = (String) variables.get(VView.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)); + } + } + + public Iterator<Component> getComponentIterator() { + return Collections.singleton((Component) getContent()).iterator(); + } + + /** + * 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() { + if (content == null) { + setContent(createDefaultLayout()); + } + return content; + } + + /** + * 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 (this.content != null) { + super.removeComponent(this.content); + } + this.content = 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(); + } + } + +} diff --git a/src/com/vaadin/ui/Select.java b/src/com/vaadin/ui/Select.java index 0ea331dc40..38785f3ab9 100644 --- a/src/com/vaadin/ui/Select.java +++ b/src/com/vaadin/ui/Select.java @@ -23,7 +23,7 @@ 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; +import com.vaadin.terminal.gwt.client.ui.VFilterSelectPaintable; /** * <p> @@ -44,7 +44,7 @@ import com.vaadin.terminal.gwt.client.ui.VFilterSelect; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(VFilterSelect.class) +@ClientWidget(VFilterSelectPaintable.class) public class Select extends AbstractSelect implements AbstractSelect.Filtering, FieldEvents.BlurNotifier, FieldEvents.FocusNotifier { diff --git a/src/com/vaadin/ui/Slider.java b/src/com/vaadin/ui/Slider.java index 1d67a6b12c..aad1b60f87 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.gwt.client.ui.VSliderPaintable; /** * A component for selecting a numerical value within a range. @@ -46,22 +46,13 @@ import com.vaadin.terminal.gwt.client.ui.VSlider; * * @author Vaadin Ltd. */ -@ClientWidget(VSlider.class) -public class Slider extends AbstractField { +@ClientWidget(VSliderPaintable.class) +public class Slider extends AbstractField<Double> { 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 +71,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 +160,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 +184,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 +247,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,97 +264,19 @@ 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); + super.setValue(newValue, repaintIsNotNeeded); } - /** - * 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; - } - - /** - * 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; - } - /** - * 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(); + super.setValue(newFieldValue); } @Override @@ -426,30 +292,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); - } - } /** @@ -491,7 +342,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 +368,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 a13c336943..e256c51cfd 100644 --- a/src/com/vaadin/ui/TabSheet.java +++ b/src/com/vaadin/ui/TabSheet.java @@ -20,6 +20,7 @@ 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.client.ui.VTabsheetPaintable; import com.vaadin.terminal.gwt.server.CommunicationManager; import com.vaadin.ui.themes.Reindeer; import com.vaadin.ui.themes.Runo; @@ -53,8 +54,7 @@ import com.vaadin.ui.themes.Runo; * @VERSION@ * @since 3.0 */ -@SuppressWarnings("serial") -@ClientWidget(VTabsheet.class) +@ClientWidget(VTabsheetPaintable.class) public class TabSheet extends AbstractComponentContainer { /** diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index 199a6805f6..003ef6978c 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -20,11 +20,13 @@ 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; @@ -45,6 +47,7 @@ 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.VScrollTablePaintable; import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; /** @@ -70,8 +73,8 @@ import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; * @VERSION@ * @since 3.0 */ -@SuppressWarnings({ "serial", "deprecation" }) -@ClientWidget(VScrollTable.class) +@SuppressWarnings({ "deprecation" }) +@ClientWidget(VScrollTablePaintable.class) public class Table extends AbstractSelect implements Action.Container, Container.Ordered, Container.Sortable, ItemClickSource, ItemClickNotifier, DragSource, DropTarget { @@ -113,91 +116,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. @@ -252,7 +379,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 +415,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 +436,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. @@ -404,10 +531,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 +637,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 +781,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 +809,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++) { @@ -1030,13 +1149,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(); } @@ -1069,9 +1188,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; } /** @@ -1079,8 +1198,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 @@ -1088,17 +1207,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); @@ -1393,7 +1503,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @return the Value of property columnHeaderMode. */ - public int getColumnHeaderMode() { + public ColumnHeaderMode getColumnHeaderMode() { return columnHeaderMode; } @@ -1403,10 +1513,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(); } @@ -1718,13 +1830,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>(); } @@ -1746,7 +1858,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]) @@ -1767,7 +1879,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: @@ -1784,7 +1896,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 @@ -1904,8 +2016,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)) { @@ -1945,7 +2057,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)) { @@ -1970,7 +2082,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 @@ -1983,8 +2095,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); @@ -2017,8 +2129,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) { @@ -2066,17 +2178,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(); } /** @@ -2085,9 +2197,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; } /** @@ -2924,7 +3035,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 { @@ -2955,8 +3066,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"); @@ -3402,8 +3514,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 @@ -3457,11 +3569,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 */ @@ -3704,7 +3832,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; } @@ -4034,44 +4162,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 @@ -5152,6 +5242,62 @@ 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) { 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..a5e6bb5630 100644 --- a/src/com/vaadin/ui/TextArea.java +++ b/src/com/vaadin/ui/TextArea.java @@ -7,12 +7,12 @@ 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; +import com.vaadin.terminal.gwt.client.ui.VTextAreaPaintable; /** * A text field that supports multi line editing. */ -@ClientWidget(VTextArea.class) +@ClientWidget(VTextAreaPaintable.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..a3d873336c 100644 --- a/src/com/vaadin/ui/TextField.java +++ b/src/com/vaadin/ui/TextField.java @@ -5,9 +5,7 @@ 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.terminal.gwt.client.ui.VTextFieldPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -31,30 +29,10 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VTextField.class, loadStyle = LoadStyle.EAGER) +@ClientWidget(value = VTextFieldPaintable.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 +84,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 +92,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..4ea66cc6bf 100644 --- a/src/com/vaadin/ui/Tree.java +++ b/src/com/vaadin/ui/Tree.java @@ -45,6 +45,7 @@ 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.VTreePaintable; 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; @@ -60,7 +61,7 @@ import com.vaadin.tools.ReflectTools; * @since 3.0 */ @SuppressWarnings({ "serial", "deprecation" }) -@ClientWidget(VTree.class) +@ClientWidget(VTreePaintable.class) public class Tree extends AbstractSelect implements Container.Hierarchical, Action.Container, ItemClickSource, ItemClickNotifier, DragSource, DropTarget { diff --git a/src/com/vaadin/ui/TreeTable.java b/src/com/vaadin/ui/TreeTable.java index 43bc7a80fe..09417e1e16 100644 --- a/src/com/vaadin/ui/TreeTable.java +++ b/src/com/vaadin/ui/TreeTable.java @@ -22,7 +22,7 @@ import com.vaadin.data.util.HierarchicalContainer; 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.VTreeTablePaintable; import com.vaadin.ui.Tree.CollapseEvent; import com.vaadin.ui.Tree.CollapseListener; import com.vaadin.ui.Tree.ExpandEvent; @@ -48,7 +48,7 @@ import com.vaadin.ui.treetable.HierarchicalContainerOrderedWrapper; * share UI state in the container. */ @SuppressWarnings({ "serial" }) -@ClientWidget(VTreeTable.class) +@ClientWidget(VTreeTablePaintable.class) public class TreeTable extends Table implements Hierarchical { private static final Logger logger = Logger.getLogger(TreeTable.class @@ -454,7 +454,8 @@ public class TreeTable extends Table implements Hierarchical { Object object = visibleColumns2[i]; if (hierarchyColumnId.equals(object)) { target.addAttribute( - VTreeTable.ATTRIBUTE_HIERARCHY_COLUMN_INDEX, i); + VTreeTablePaintable.ATTRIBUTE_HIERARCHY_COLUMN_INDEX, + i); break; } } diff --git a/src/com/vaadin/ui/TwinColSelect.java b/src/com/vaadin/ui/TwinColSelect.java index 1c1fe07a5c..fbdd825a66 100644 --- a/src/com/vaadin/ui/TwinColSelect.java +++ b/src/com/vaadin/ui/TwinColSelect.java @@ -10,13 +10,14 @@ 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.VTwinColSelectPaintable; /** * Multiselect component with two lists: left side for available items and right * side for selected items. */ @SuppressWarnings("serial") -@ClientWidget(VTwinColSelect.class) +@ClientWidget(VTwinColSelectPaintable.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..08a8023ad5 100644 --- a/src/com/vaadin/ui/Upload.java +++ b/src/com/vaadin/ui/Upload.java @@ -15,7 +15,7 @@ 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.gwt.client.ui.VUploadPaintable; import com.vaadin.terminal.gwt.server.NoInputStreamException; import com.vaadin.terminal.gwt.server.NoOutputStreamException; import com.vaadin.ui.ClientWidget.LoadStyle; @@ -61,7 +61,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 3.0 */ @SuppressWarnings("serial") -@ClientWidget(value = VUpload.class, loadStyle = LoadStyle.LAZY) +@ClientWidget(value = VUploadPaintable.class, loadStyle = LoadStyle.LAZY) public class Upload extends AbstractComponent implements Component.Focusable { /** 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..0c5de43bbd 100644 --- a/src/com/vaadin/ui/VerticalLayout.java +++ b/src/com/vaadin/ui/VerticalLayout.java @@ -3,7 +3,7 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VVerticalLayout; +import com.vaadin.terminal.gwt.client.ui.VVerticalLayoutPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -19,7 +19,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * @since 5.3 */ @SuppressWarnings("serial") -@ClientWidget(value = VVerticalLayout.class, loadStyle = LoadStyle.EAGER) +@ClientWidget(value = VVerticalLayoutPaintable.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..46e9d681e8 100644 --- a/src/com/vaadin/ui/VerticalSplitPanel.java +++ b/src/com/vaadin/ui/VerticalSplitPanel.java @@ -3,7 +3,7 @@ */ package com.vaadin.ui; -import com.vaadin.terminal.gwt.client.ui.VSplitPanelVertical; +import com.vaadin.terminal.gwt.client.ui.VVerticalSplitPanelPaintable; import com.vaadin.ui.ClientWidget.LoadStyle; /** @@ -23,7 +23,7 @@ import com.vaadin.ui.ClientWidget.LoadStyle; * </pre> * */ -@ClientWidget(value = VSplitPanelVertical.class, loadStyle = LoadStyle.EAGER) +@ClientWidget(value = VVerticalSplitPanelPaintable.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..a2e71b1120 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.VVideoPaintable; /** * The Video component translates into an HTML5 <video> element and as @@ -30,7 +30,7 @@ import com.vaadin.terminal.gwt.client.ui.VVideo; * @author Vaadin Ltd * @since 6.7.0 */ -@ClientWidget(VVideo.class) +@ClientWidget(VVideoPaintable.class) public class Video extends AbstractMedia { private Resource poster; @@ -80,7 +80,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(VVideoPaintable.ATTR_POSTER, getPoster()); } } } diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java index 7e33f91d31..a6cf51e80f 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; @@ -27,16 +19,11 @@ 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.gwt.client.ui.VWindowPaintable; /** * A component that represents an application (browser native) window or a sub @@ -84,79 +71,8 @@ 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 { - - /** - * <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>(); - - /** - * <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; +@ClientWidget(VWindowPaintable.class) +public class Window extends Panel implements FocusNotifier, BlurNotifier { /** * <b>Sub window only</b>. Top offset in pixels for the sub window (relative @@ -171,13 +87,6 @@ public class Window extends Panel implements URIHandler, ParameterHandler, 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; @@ -205,25 +114,6 @@ public class Window extends Panel implements URIHandler, ParameterHandler, 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; - - /** * Creates a new unnamed window with a default layout. */ public Window() { @@ -269,288 +159,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) * @@ -560,14 +170,6 @@ public class Window extends Panel implements URIHandler, ParameterHandler, 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); } @@ -594,17 +196,6 @@ public class Window extends Panel implements URIHandler, ParameterHandler, 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); @@ -614,450 +205,15 @@ public class Window extends Panel implements URIHandler, ParameterHandler, } } - // 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 +224,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 +294,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); } } @@ -1413,128 +568,45 @@ 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. * @@ -1609,533 +681,6 @@ public class Window extends Panel implements URIHandler, ParameterHandler, } /** - * 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); - - requestRepaint(); - } - - /** * Returns the closable status of the sub window. If a sub window is * closable it typically shows an X in the upper right corner. Clicking on * the X sends a close event to the server. Setting closable to false will @@ -2344,40 +889,13 @@ 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(); - } - } - - /** - * 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(); - } + /* + * 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(); } /** |