diff options
Diffstat (limited to 'src/com/vaadin/terminal')
87 files changed, 3109 insertions, 1285 deletions
diff --git a/src/com/vaadin/terminal/AbstractClientConnector.java b/src/com/vaadin/terminal/AbstractClientConnector.java index a2bf4a0be8..6a87f58c71 100644 --- a/src/com/vaadin/terminal/AbstractClientConnector.java +++ b/src/com/vaadin/terminal/AbstractClientConnector.java @@ -31,7 +31,12 @@ import com.vaadin.ui.HasComponents; import com.vaadin.ui.Root; /** - * + * An abstract base class for ClientConnector implementations. This class + * provides all the basic functionality required for connectors. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 */ public abstract class AbstractClientConnector implements ClientConnector { /** @@ -115,6 +120,7 @@ public abstract class AbstractClientConnector implements ClientConnector { throw new RuntimeException( "Use registerRpc(T implementation, Class<T> rpcInterfaceType) if the Rpc implementation implements more than one interface"); } + @SuppressWarnings("unchecked") Class<T> type = (Class<T>) interfaces[0]; registerRpc(implementation, type); } @@ -210,7 +216,7 @@ public abstract class AbstractClientConnector implements ClientConnector { public Iterator<ClientConnector> iterator() { CombinedIterator<ClientConnector> iterator = new CombinedIterator<ClientConnector>(); - iterator.addIterator(connector.getExtensionIterator()); + iterator.addIterator(connector.getExtensions().iterator()); if (connector instanceof HasComponents) { HasComponents hasComponents = (HasComponents) connector; @@ -371,35 +377,42 @@ public abstract class AbstractClientConnector implements ClientConnector { } } + /** + * Get an Iterable for iterating over all child connectors, including both + * extensions and child components. + * + * @param connector + * the connector to get children for + * @return an Iterable giving all child connectors. + */ public static Iterable<ClientConnector> getAllChildrenIterable( final ClientConnector connector) { return new AllChildrenIterable(connector); } - public Iterator<Extension> getExtensionIterator() { - return Collections.unmodifiableList(extensions).iterator(); + public Collection<Extension> getExtensions() { + return Collections.unmodifiableCollection(extensions); } + /** + * Add an extension to this connector. This method is protected to allow + * extensions to select which targets they can extend. + * + * @param extension + * the extension to add + */ protected void addExtension(Extension extension) { - addExtensionAtIndex(extension, extensions.size()); - } - - protected void addExtensionAtIndex(Extension extension, int index) { ClientConnector previousParent = extension.getParent(); if (previousParent == this) { - int oldIndex = extensions.indexOf(extension); - if (oldIndex < index) { - index--; - } - extensions.remove(oldIndex); - extensions.add(index, extension); - } else { - if (previousParent != null) { - previousParent.removeExtension(extension); - } - extensions.add(index, extension); - extension.setParent(this); + // Nothing to do, already attached + return; + } else if (previousParent != null) { + throw new IllegalStateException( + "Moving an extension from one parent to another is not supported"); } + + extensions.add(extension); + extension.setParent(this); requestRepaint(); } @@ -447,6 +460,14 @@ public abstract class AbstractClientConnector implements ClientConnector { } } + /** + * {@inheritDoc} + * + * <p> + * The {@link #getApplication()} and {@link #getRoot()} methods might return + * <code>null</code> after this method is called. + * </p> + */ public void detach() { for (ClientConnector connector : getAllChildrenIterable(this)) { connector.detach(); diff --git a/src/com/vaadin/terminal/AbstractExtension.java b/src/com/vaadin/terminal/AbstractExtension.java index 353db85a18..33a60e39ef 100644 --- a/src/com/vaadin/terminal/AbstractExtension.java +++ b/src/com/vaadin/terminal/AbstractExtension.java @@ -6,26 +6,69 @@ package com.vaadin.terminal; import com.vaadin.terminal.gwt.server.ClientConnector; +/** + * An extension is an entity that is attached to a Component or another + * Extension and independently communicates between client and server. + * <p> + * Extensions can use shared state and RPC in the same way as components. + * <p> + * AbstractExtension adds a mechanism for adding the extension to any Connector + * (extend). To let the Extension determine what kind target it can be added to, + * the extend method is declared as protected. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public abstract class AbstractExtension extends AbstractClientConnector implements Extension { + private boolean previouslyAttached = false; - protected Class<? extends ClientConnector> getAcceptedParentType() { + /** + * Gets a type that the parent must be an instance of. Override this if the + * extension only support certain targets, e.g. if only TextFields can be + * extended. + * + * @return a type that the parent must be an instance of + */ + protected Class<? extends ClientConnector> getSupportedParentType() { return ClientConnector.class; } - protected void attachTo(AbstractClientConnector parent) { - parent.addExtension(this); + /** + * Add this extension to the target connector. This method is protected to + * allow subclasses to require a more specific type of target. + * + * @param target + * the connector to attach this extension to + */ + protected void extend(AbstractClientConnector target) { + target.addExtension(this); + } + + /** + * Remove this extension from its target. After an extension has been + * removed, it can not be attached again. + */ + public void removeFromTarget() { + getParent().removeExtension(this); } @Override public void setParent(ClientConnector parent) { - Class<? extends ClientConnector> acceptedParentType = getAcceptedParentType(); - if (parent == null || acceptedParentType.isInstance(parent)) { + if (previouslyAttached && parent != null) { + throw new IllegalStateException( + "An extension can not be set to extend a new target after getting detached from the previous."); + } + + Class<? extends ClientConnector> supportedParentType = getSupportedParentType(); + if (parent == null || supportedParentType.isInstance(parent)) { super.setParent(parent); + previouslyAttached = true; } else { throw new IllegalArgumentException(getClass().getName() - + " can only be attached to parents of type " - + acceptedParentType.getName() + " but attach to " + + " can only be attached to targets of type " + + supportedParentType.getName() + " but attach to " + parent.getClass().getName() + " was attempted."); } } diff --git a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java new file mode 100644 index 0000000000..bdcd948c74 --- /dev/null +++ b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java @@ -0,0 +1,158 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import com.vaadin.terminal.gwt.client.JavaScriptExtensionState; +import com.vaadin.ui.JavaScriptCallback; + +/** + * Base class for Extensions with all client-side logic implemented using + * JavaScript. + * <p> + * When a new JavaScript extension is initialized in the browser, the framework + * will look for a globally defined JavaScript function that will initialize the + * extension. The name of the initialization function is formed by replacing . + * with _ in the name of the server-side class. If no such function is defined, + * each super class is used in turn until a match is found. The framework will + * thus first attempt with <code>com_example_MyExtension</code> for the + * server-side + * <code>com.example.MyExtension extends AbstractJavaScriptExtension</code> + * class. If MyExtension instead extends <code>com.example.SuperExtension</code> + * , then <code>com_example_SuperExtension</code> will also be attempted if + * <code>com_example_MyExtension</code> has not been defined. + * <p> + * + * The initialization function will be called with <code>this</code> pointing to + * a connector wrapper object providing integration to Vaadin with the following + * functions: + * <ul> + * <li><code>getConnectorId()</code> - returns a string with the id of the + * connector.</li> + * <li><code>getParentId([connectorId])</code> - returns a string with the id of + * the connector's parent. If <code>connectorId</code> is provided, the id of + * the parent of the corresponding connector with the passed id is returned + * instead.</li> + * <li><code>getWidgetElement([connectorId])</code> - returns the DOM Element + * that is the root of a connector's widget. <code>null</code> is returned if + * the connector can not be found or if the connector doesn't have a widget. If + * <code>connectorId</code> is not provided, the connector id of the current + * connector will be used.</li> + * <li><code>getState()</code> - returns an object corresponding to the shared + * state defined on the server. The scheme for conversion between Java and + * JavaScript types is described bellow.</li> + * <li><code>registerRpc([name, ] rpcObject)</code> - registers the + * <code>rpcObject</code> as a RPC handler. <code>rpcObject</code> should be an + * object with field containing functions for all eligible RPC functions. If + * <code>name</code> is provided, the RPC handler will only used for RPC calls + * for the RPC interface with the same fully qualified Java name. If no + * <code>name</code> is provided, the RPC handler will be used for all incoming + * RPC invocations where the RPC method name is defined as a function field in + * the handler. The scheme for conversion between Java types in the RPC + * interface definition and the JavaScript values passed as arguments to the + * handler functions is described bellow.</li> + * <li><code>getRpcProxy([name])</code> - returns an RPC proxy object. If + * <code>name</code> is provided, the proxy object will contain functions for + * all methods in the RPC interface with the same fully qualified name, provided + * a RPC handler has been registered by the server-side code. If no + * <code>name</code> is provided, the returned RPC proxy object will contain + * functions for all methods in all RPC interfaces registered for the connector + * on the server. If the same method name is present in multiple registered RPC + * interfaces, the corresponding function in the RPC proxy object will throw an + * exception when called. The scheme for conversion between Java types in the + * RPC interface and the JavaScript values that should be passed to the + * functions is described bellow.</li> + * </ul> + * The connector wrapper also supports these special functions: + * <ul> + * <li><code>onStateChange</code> - If the JavaScript code assigns a function to + * the field, that function is called whenever the contents of the shared state + * is changed.</li> + * <li>Any field name corresponding to a call to + * {@link #registerCallback(String, JavaScriptCallback)} on the server will + * automatically be present as a function that triggers the registered callback + * on the server.</li> + * <li>Any field name referred to using + * {@link #invokeCallback(String, Object...)} on the server will be called if a + * function has been assigned to the field.</li> + * </ul> + * <p> + * + * Values in the Shared State and in RPC calls are converted between Java and + * JavaScript using the following conventions: + * <ul> + * <li>Primitive Java numbers (byte, char, int, long, float, double) and their + * boxed types (Byte, Character, Integer, Long, Float, Double) are represented + * by JavaScript numbers.</li> + * <li>The primitive Java boolean and the boxed Boolean are represented by + * JavaScript booleans.</li> + * <li>Java Strings are represented by JavaScript strings.</li> + * <li>List, Set and all arrays in Java are represented by JavaScript arrays.</li> + * <li>Map<String, ?> in Java is represented by JavaScript object with fields + * corresponding to the map keys.</li> + * <li>Any other Java Map is represented by a JavaScript array containing two + * arrays, the first contains the keys and the second contains the values in the + * same order.</li> + * <li>A Java Bean is represented by a JavaScript object with fields + * corresponding to the bean's properties.</li> + * <li>A Java Connector is represented by a JavaScript string containing the + * connector's id.</li> + * <li>A pluggable serialization mechanism is provided for types not described + * here. Please refer to the documentation for specific types for serialization + * information.</li> + * </ul> + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public abstract class AbstractJavaScriptExtension extends AbstractExtension { + private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( + this); + + @Override + protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) { + super.registerRpc(implementation, rpcInterfaceType); + callbackHelper.registerRpc(rpcInterfaceType); + } + + /** + * Register a {@link JavaScriptCallback} that can be called from the + * JavaScript using the provided name. A JavaScript function with the + * provided name will be added to the connector wrapper object (initially + * available as <code>this</code>). Calling that JavaScript function will + * cause the call method in the registered {@link JavaScriptCallback} to be + * invoked with the same arguments. + * + * @param functionName + * the name that should be used for client-side callback + * @param javaScriptCallback + * the callback object that will be invoked when the JavaScript + * function is called + */ + protected void registerCallback(String functionName, + JavaScriptCallback javaScriptCallback) { + callbackHelper.registerCallback(functionName, javaScriptCallback); + } + + /** + * Invoke a named function that the connector JavaScript has added to the + * JavaScript connector wrapper object. The arguments should only contain + * data types that can be represented in JavaScript, including primitive + * boxing types, arrays, String, List, Set, Map, Connector and JavaBeans. + * + * @param name + * the name of the function + * @param arguments + * function arguments + */ + protected void invokeCallback(String name, Object... arguments) { + callbackHelper.invokeCallback(name, arguments); + } + + @Override + public JavaScriptExtensionState getState() { + return (JavaScriptExtensionState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/AbstractJavascriptExtension.java b/src/com/vaadin/terminal/AbstractJavascriptExtension.java deleted file mode 100644 index e741e2af1e..0000000000 --- a/src/com/vaadin/terminal/AbstractJavascriptExtension.java +++ /dev/null @@ -1,20 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal; - -import com.vaadin.ui.JavascriptCallback; - -public class AbstractJavascriptExtension extends AbstractExtension { - private JavascriptRpcHelper rpcHelper = new JavascriptRpcHelper(this); - - protected void registerCallback(String functionName, - JavascriptCallback javascriptCallback) { - rpcHelper.registerCallback(functionName, javascriptCallback); - } - - protected void invokeCallback(String name, Object... arguments) { - rpcHelper.invokeCallback(name, arguments); - } -} diff --git a/src/com/vaadin/terminal/ClassResource.java b/src/com/vaadin/terminal/ClassResource.java index fa196e90d9..e7419576f1 100644 --- a/src/com/vaadin/terminal/ClassResource.java +++ b/src/com/vaadin/terminal/ClassResource.java @@ -60,13 +60,7 @@ public class ClassResource implements ApplicationResource, Serializable { * the application this resource will be added to. */ public ClassResource(String resourceName, Application application) { - associatedClass = application.getClass(); - this.resourceName = resourceName; - this.application = application; - if (resourceName == null) { - throw new NullPointerException(); - } - application.addResource(this); + this(application.getClass(), resourceName, application); } /** diff --git a/src/com/vaadin/terminal/Extension.java b/src/com/vaadin/terminal/Extension.java index 7769423f0d..ef5bb4cf8d 100644 --- a/src/com/vaadin/terminal/Extension.java +++ b/src/com/vaadin/terminal/Extension.java @@ -6,6 +6,22 @@ package com.vaadin.terminal; import com.vaadin.terminal.gwt.server.ClientConnector; +/** + * An extension is an entity that is attached to a Component or another + * Extension and independently communicates between client and server. + * <p> + * An extension can only be attached once. It is not supported to move an + * extension from one target to another. + * <p> + * Extensions can use shared state and RPC in the same way as components. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public interface Extension extends ClientConnector { - + /* + * Currently just an empty marker interface to distinguish between + * extensions and other connectors, e.g. components + */ } diff --git a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java new file mode 100644 index 0000000000..01db0267d9 --- /dev/null +++ b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java @@ -0,0 +1,115 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.AbstractJavaScriptComponent; +import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; +import com.vaadin.ui.JavaScriptCallback; + +/** + * Internal helper class used to implement functionality common to + * {@link AbstractJavaScriptComponent} and {@link AbstractJavaScriptExtension}. + * Corresponding support in client-side code is in + * {@link JavaScriptConnectorHelper}. + * <p> + * You should most likely no use this class directly. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public class JavaScriptCallbackHelper implements Serializable { + + private static final Method CALL_METHOD = ReflectTools.findMethod( + JavaScriptCallbackRpc.class, "call", String.class, JSONArray.class); + private AbstractClientConnector connector; + + private Map<String, JavaScriptCallback> callbacks = new HashMap<String, JavaScriptCallback>(); + private JavaScriptCallbackRpc javascriptCallbackRpc; + + public JavaScriptCallbackHelper(AbstractClientConnector connector) { + this.connector = connector; + } + + public void registerCallback(String functionName, + JavaScriptCallback javaScriptCallback) { + callbacks.put(functionName, javaScriptCallback); + JavaScriptConnectorState state = getConnectorState(); + if (state.getCallbackNames().add(functionName)) { + connector.requestRepaint(); + } + ensureRpc(); + } + + private JavaScriptConnectorState getConnectorState() { + JavaScriptConnectorState state = (JavaScriptConnectorState) connector + .getState(); + return state; + } + + private void ensureRpc() { + if (javascriptCallbackRpc == null) { + javascriptCallbackRpc = new JavaScriptCallbackRpc() { + public void call(String name, JSONArray arguments) { + JavaScriptCallback callback = callbacks.get(name); + try { + callback.call(arguments); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + }; + connector.registerRpc(javascriptCallbackRpc); + } + } + + public void invokeCallback(String name, Object... arguments) { + if (callbacks.containsKey(name)) { + throw new IllegalStateException( + "Can't call callback " + + name + + " on the client because a callback with the same name is registered on the server."); + } + JSONArray args = new JSONArray(Arrays.asList(arguments)); + connector.addMethodInvocationToQueue( + JavaScriptCallbackRpc.class.getName(), CALL_METHOD, + new Object[] { name, args }); + connector.requestRepaint(); + } + + public void registerRpc(Class<?> rpcInterfaceType) { + if (rpcInterfaceType == JavaScriptCallbackRpc.class) { + // Ignore + return; + } + Map<String, Set<String>> rpcInterfaces = getConnectorState() + .getRpcInterfaces(); + String interfaceName = rpcInterfaceType.getName(); + if (!rpcInterfaces.containsKey(interfaceName)) { + Set<String> methodNames = new HashSet<String>(); + + for (Method method : rpcInterfaceType.getMethods()) { + methodNames.add(method.getName()); + } + + rpcInterfaces.put(interfaceName, methodNames); + connector.requestRepaint(); + } + } + +} diff --git a/src/com/vaadin/terminal/JavascriptRpcHelper.java b/src/com/vaadin/terminal/JavascriptRpcHelper.java deleted file mode 100644 index b566462833..0000000000 --- a/src/com/vaadin/terminal/JavascriptRpcHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import com.vaadin.external.json.JSONArray; -import com.vaadin.external.json.JSONException; -import com.vaadin.tools.ReflectTools; -import com.vaadin.ui.JavascriptCallback; -import com.vaadin.ui.JavascriptManager.JavascriptCallbackRpc; - -public class JavascriptRpcHelper { - - private static final Method CALL_METHOD = ReflectTools.findMethod( - JavascriptCallbackRpc.class, "call", String.class, JSONArray.class); - private AbstractClientConnector connector; - - private Map<String, JavascriptCallback> callbacks = new HashMap<String, JavascriptCallback>(); - private JavascriptCallbackRpc javascriptCallbackRpc; - - public JavascriptRpcHelper(AbstractClientConnector connector) { - this.connector = connector; - } - - public void registerCallback(String functionName, - JavascriptCallback javascriptCallback) { - callbacks.put(functionName, javascriptCallback); - ensureRpc(); - } - - private void ensureRpc() { - if (javascriptCallbackRpc == null) { - javascriptCallbackRpc = new JavascriptCallbackRpc() { - public void call(String name, JSONArray arguments) { - JavascriptCallback callback = callbacks.get(name); - try { - callback.call(arguments); - } catch (JSONException e) { - throw new IllegalArgumentException(e); - } - } - }; - connector.registerRpc(javascriptCallbackRpc); - } - } - - public void invokeCallback(String name, Object... arguments) { - JSONArray args = new JSONArray(Arrays.asList(arguments)); - connector.addMethodInvocationToQueue( - JavascriptCallbackRpc.class.getName(), CALL_METHOD, - new Object[] { name, args }); - connector.requestRepaint(); - } - -} diff --git a/src/com/vaadin/terminal/Page.java b/src/com/vaadin/terminal/Page.java new file mode 100644 index 0000000000..65fa500d27 --- /dev/null +++ b/src/com/vaadin/terminal/Page.java @@ -0,0 +1,630 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.EventObject; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.vaadin.event.EventRouter; +import com.vaadin.terminal.WrappedRequest.BrowserDetails; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; +import com.vaadin.terminal.gwt.client.ui.root.PageClientRpc; +import com.vaadin.terminal.gwt.client.ui.root.VRoot; +import com.vaadin.terminal.gwt.server.WebApplicationContext; +import com.vaadin.terminal.gwt.server.WebBrowser; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.JavaScript; +import com.vaadin.ui.Notification; +import com.vaadin.ui.Root; + +public class Page implements Serializable { + + /** + * 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 EventObject { + + 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(Page source, int width, int height) { + super(source); + this.width = width; + this.height = height; + } + + @Override + public Page getSource() { + return (Page) 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 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"); + } + } + + private static final Method BROWSWER_RESIZE_METHOD = ReflectTools + .findMethod(BrowserWindowResizeListener.class, + "browserWindowResized", BrowserWindowResizeEvent.class); + + /** + * 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; + + /** + * Listener that listens changes in URI fragment. + */ + public interface FragmentChangedListener extends Serializable { + public void fragmentChanged(FragmentChangedEvent event); + } + + private static final Method FRAGMENT_CHANGED_METHOD = ReflectTools + .findMethod(Page.FragmentChangedListener.class, "fragmentChanged", + FragmentChangedEvent.class); + + /** + * 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>(); + + /** + * 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; + + /** + * Event fired when uri fragment changes. + */ + public class FragmentChangedEvent extends EventObject { + + /** + * 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(Page 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 Page getPage() { + return (Page) getSource(); + } + + /** + * Get the new fragment + * + * @return the new fragment + */ + public String getFragment() { + return fragment; + } + } + + private EventRouter eventRouter; + + /** + * The current URI fragment. + */ + private String fragment; + + private final Root root; + + private int browserWindowWidth = -1; + private int browserWindowHeight = -1; + + private JavaScript javaScript; + + public Page(Root root) { + this.root = root; + } + + private void addListener(Class<?> eventType, Object target, Method method) { + if (eventRouter == null) { + eventRouter = new EventRouter(); + } + eventRouter.addListener(eventType, target, method); + } + + private void removeListener(Class<?> eventType, Object target, Method method) { + if (eventRouter != null) { + eventRouter.removeListener(eventType, target, method); + } + } + + public void addListener(Page.FragmentChangedListener listener) { + addListener(FragmentChangedEvent.class, listener, + FRAGMENT_CHANGED_METHOD); + } + + public void removeListener(Page.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 Page.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)); + } + root.requestRepaint(); + } + } + + private void fireEvent(EventObject event) { + if (eventRouter != null) { + eventRouter.fireEvent(event); + } + } + + /** + * Sets URI fragment. This method fires a {@link FragmentChangedEvent} + * + * @param newFragment + * id of the new fragment + * @see FragmentChangedEvent + * @see Page.FragmentChangedListener + */ + public void setFragment(String newFragment) { + setFragment(newFragment, true); + } + + /** + * Gets currently set URI fragment. + * <p> + * To listen changes in fragment, hook a + * {@link Page.FragmentChangedListener}. + * + * @return the current fragment in browser uri or null if not known + */ + public String getFragment() { + return fragment; + } + + public void init(WrappedRequest request) { + BrowserDetails browserDetails = request.getBrowserDetails(); + if (browserDetails != null) { + fragment = browserDetails.getUriFragment(); + } + } + + public WebBrowser getWebBrowser() { + return ((WebApplicationContext) root.getApplication().getContext()) + .getBrowser(); + } + + public void setBrowserWindowSize(Integer width, Integer height) { + boolean fireEvent = false; + + if (width != null) { + int newWidth = width.intValue(); + if (newWidth != browserWindowWidth) { + browserWindowWidth = newWidth; + fireEvent = true; + } + } + + if (height != null) { + int newHeight = height.intValue(); + if (newHeight != browserWindowHeight) { + browserWindowHeight = newHeight; + fireEvent = true; + } + } + + if (fireEvent) { + fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth, + browserWindowHeight)); + } + + } + + /** + * 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; + } + + public JavaScript getJavaScript() { + if (javaScript == null) { + // Create and attach on first use + javaScript = new JavaScript(); + javaScript.extend(root); + } + + return javaScript; + } + + public void paintContent(PaintTarget target) throws PaintException { + if (!openList.isEmpty()) { + for (final Iterator<OpenResource> i = openList.iterator(); i + .hasNext();) { + (i.next()).paintContent(target); + } + openList.clear(); + } + + // Paint notifications + if (notifications != null) { + target.startTag("notifications"); + for (final Iterator<Notification> it = notifications.iterator(); it + .hasNext();) { + final Notification n = it.next(); + target.startTag("notification"); + if (n.getCaption() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_CAPTION, + n.getCaption()); + } + if (n.getDescription() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_MESSAGE, + n.getDescription()); + } + if (n.getIcon() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_ICON, + n.getIcon()); + } + if (!n.isHtmlContentAllowed()) { + target.addAttribute( + VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED, true); + } + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_POSITION, + n.getPosition()); + target.addAttribute(VNotification.ATTRIBUTE_NOTIFICATION_DELAY, + n.getDelayMsec()); + if (n.getStyleName() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_STYLE, + n.getStyleName()); + } + target.endTag("notification"); + } + target.endTag("notifications"); + notifications = null; + } + + if (fragment != null) { + target.addAttribute(VRoot.FRAGMENT_VARIABLE, fragment); + } + + } + + /** + * 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) { + openList.add(new OpenResource(resource, null, -1, -1, BORDER_DEFAULT)); + root.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) { + openList.add(new OpenResource(resource, windowName, -1, -1, + BORDER_DEFAULT)); + root.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) { + openList.add(new OpenResource(resource, windowName, width, height, + border)); + root.requestRepaint(); + } + + /** + * 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); + root.requestRepaint(); + } + + /** + * 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); + } + + public static Page getCurrent() { + Root currentRoot = Root.getCurrentRoot(); + if (currentRoot == null) { + return null; + } + return currentRoot.getPage(); + } + + public void setTitle(String title) { + root.getRpcProxy(PageClientRpc.class).setTitle(title); + } + +} diff --git a/src/com/vaadin/terminal/UserError.java b/src/com/vaadin/terminal/UserError.java index baaf331fa0..a7a4fd89e2 100644 --- a/src/com/vaadin/terminal/UserError.java +++ b/src/com/vaadin/terminal/UserError.java @@ -44,6 +44,16 @@ public class UserError extends AbstractErrorMessage { super(textErrorMessage); } + /** + * Creates an 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, ContentMode contentMode, ErrorLevel errorLevel) { super(message); diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java index 244f310c1a..960b0a8b0e 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java @@ -18,6 +18,7 @@ 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.google.gwt.user.client.Window; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class ApplicationConfiguration implements EntryPoint { @@ -346,6 +347,7 @@ public class ApplicationConfiguration implements EntryPoint { */ public static void startApplication(final String applicationId) { Scheduler.get().scheduleDeferred(new ScheduledCommand() { + public void execute() { ApplicationConfiguration appConf = getConfigFromDOM(applicationId); ApplicationConnection a = GWT @@ -568,6 +570,7 @@ public class ApplicationConfiguration implements EntryPoint { * GWT hosted mode. */ GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { /* * Note in case of null console (without ?debug) we eat @@ -602,12 +605,15 @@ public class ApplicationConfiguration implements EntryPoint { * * @return true if client side is currently been debugged */ - public native static boolean isDebugMode() + public static boolean isDebugMode() { + return isDebugAvailable() + && Window.Location.getParameter("debug") != null; + } + + private native static boolean isDebugAvailable() /*-{ if($wnd.vaadin.debug) { - var parameters = $wnd.location.search; - var re = /debug[^\/]*$/; - return re.test(parameters); + return true; } else { return false; } @@ -618,12 +624,11 @@ public class ApplicationConfiguration implements EntryPoint { * * @return <code>true</code> if debug logging should be quiet */ - public native static boolean isQuietDebugMode() - /*-{ - var uri = $wnd.location; - var re = /debug=q[^\/]*$/; - return re.test(uri); - }-*/; + public static boolean isQuietDebugMode() { + String debugParameter = Window.Location.getParameter("debug"); + return isDebugAvailable() && debugParameter != null + && debugParameter.startsWith("q"); + } /** * Checks whether information from the web browser (e.g. uri fragment and diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 93a51e6e96..f0470c8ee8 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -40,7 +40,7 @@ import com.google.gwt.user.client.Timer; 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.communication.HasJavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; import com.vaadin.terminal.gwt.client.communication.JsonDecoder; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; import com.vaadin.terminal.gwt.client.communication.MethodInvocation; @@ -50,6 +50,7 @@ import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; import com.vaadin.terminal.gwt.client.communication.Type; import com.vaadin.terminal.gwt.client.communication.UidlValue; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; import com.vaadin.terminal.gwt.client.ui.VContextMenu; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; @@ -370,6 +371,25 @@ public class ApplicationConnection { }-*/; /** + * If on Liferay and logged in, ask the client side session management + * JavaScript to extend the session duration. + * + * Otherwise, Liferay client side JavaScript will explicitly expire the + * session even though the server side considers the session to be active. + * See ticket #8305 for more information. + */ + protected native void extendLiferaySession() + /*-{ + if ($wnd.Liferay && $wnd.Liferay.Session) { + $wnd.Liferay.Session.extend(); + // if the extend banner is visible, hide it + if ($wnd.Liferay.Session.banner) { + $wnd.Liferay.Session.banner.remove(); + } + } + }-*/; + + /** * Get the active Console for writing debug messages. May return an actual * logging console, or the NullConsole if debugging is not turned on. * @@ -851,6 +871,14 @@ public class ApplicationConnection { public void execute() { if (!hasActiveRequest()) { hideLoadingIndicator(); + + // If on Liferay and session expiration management is in + // use, extend session duration on each request. + // Doing it here rather than before the request to improve + // responsiveness. + // Postponed until the end of the next request if other + // requests still pending. + extendLiferaySession(); } } }); @@ -1415,8 +1443,8 @@ public class ApplicationConnection { JSONObject stateJson = new JSONObject( states.getJavaScriptObject(connectorId)); - if (connector instanceof HasJavascriptConnectorHelper) { - ((HasJavascriptConnectorHelper) connector) + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) .getJavascriptConnectorHelper() .setNativeState( stateJson.getJavaScriptObject()); @@ -1491,6 +1519,10 @@ public class ApplicationConnection { if (childConnector instanceof ComponentConnector) { newComponents .add((ComponentConnector) childConnector); + } else if (!(childConnector instanceof AbstractExtensionConnector)) { + throw new IllegalStateException( + Util.getConnectorString(childConnector) + + " is not a ComponentConnector nor an AbstractExtensionConnector"); } if (childConnector.getParent() != parentConnector) { // Avoid extra calls to setParent diff --git a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java index e12c002930..82cf925ec1 100644 --- a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java +++ b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java @@ -31,6 +31,9 @@ public class BrowserInfo { private static final String OS_ANDROID = "android"; private static final String OS_IOS = "ios"; + // Common CSS class for all touch devices + private static final String UI_TOUCH = "touch"; + private static BrowserInfo instance; private static String cssClass = null; @@ -169,7 +172,9 @@ public class BrowserInfo { if (osClass != null) { cssClass = cssClass + " " + prefix + osClass; } - + if (isTouchDevice()) { + cssClass = cssClass + " " + prefix + UI_TOUCH; + } } return cssClass; @@ -344,15 +349,14 @@ public class BrowserInfo { if (!isTouchDevice()) { return false; } - - if (isAndroid() && isWebkit() && getWebkitVersion() < 534) { - return true; + if (isAndroid() && isWebkit() && getWebkitVersion() >= 534) { + return false; } - // if (isIOS() && isWebkit() && getWebkitVersion() < ???) { - // return true; + // Cannot enable native touch scrolling on iOS 5 until #8792 is resolved + // if (isIOS() && isWebkit() && getWebkitVersion() >= 534) { + // return false; // } - - return false; + return true; } /** diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java new file mode 100644 index 0000000000..bd62a759cb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java @@ -0,0 +1,372 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.user.client.Element; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; + +public class JavaScriptConnectorHelper { + + public interface JavaScriptConnectorState { + public Set<String> getCallbackNames(); + + public Map<String, Set<String>> getRpcInterfaces(); + } + + private final ServerConnector connector; + private final JavaScriptObject nativeState = JavaScriptObject + .createObject(); + private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); + + private final Map<String, JavaScriptObject> rpcObjects = new HashMap<String, JavaScriptObject>(); + private final Map<String, Set<String>> rpcMethods = new HashMap<String, Set<String>>(); + + private JavaScriptObject connectorWrapper; + private int tag; + + private boolean inited = false; + + public JavaScriptConnectorHelper(ServerConnector connector) { + this.connector = connector; + + // Wildcard rpc object + rpcObjects.put("", JavaScriptObject.createObject()); + } + + public void init() { + connector.addStateChangeHandler(new StateChangeHandler() { + public void onStateChanged(StateChangeEvent stateChangeEvent) { + JavaScriptObject wrapper = getConnectorWrapper(); + JavaScriptConnectorState state = getConnectorState(); + + for (String callback : state.getCallbackNames()) { + ensureCallback(JavaScriptConnectorHelper.this, wrapper, + callback); + } + + for (Entry<String, Set<String>> entry : state + .getRpcInterfaces().entrySet()) { + String rpcName = entry.getKey(); + String jsName = getJsInterfaceName(rpcName); + if (!rpcObjects.containsKey(jsName)) { + Set<String> methods = entry.getValue(); + rpcObjects.put(jsName, + createRpcObject(rpcName, methods)); + + // Init all methods for wildcard rpc + for (String method : methods) { + JavaScriptObject wildcardRpcObject = rpcObjects + .get(""); + Set<String> interfaces = rpcMethods.get(method); + if (interfaces == null) { + interfaces = new HashSet<String>(); + rpcMethods.put(method, interfaces); + attachRpcMethod(wildcardRpcObject, null, method); + } + interfaces.add(rpcName); + } + } + } + + // Init after setting up callbacks & rpc + if (!inited) { + initJavaScript(); + inited = true; + } + + fireNativeStateChange(wrapper); + } + }); + } + + private static String getJsInterfaceName(String rpcName) { + return rpcName.replace('$', '.'); + } + + protected JavaScriptObject createRpcObject(String iface, Set<String> methods) { + JavaScriptObject object = JavaScriptObject.createObject(); + + for (String method : methods) { + attachRpcMethod(object, iface, method); + } + + return object; + } + + private boolean initJavaScript() { + ApplicationConfiguration conf = connector.getConnection() + .getConfiguration(); + ArrayList<String> attemptedNames = new ArrayList<String>(); + Integer tag = Integer.valueOf(this.tag); + while (tag != null) { + String serverSideClassName = conf.getServerSideClassNameForTag(tag); + String initFunctionName = serverSideClassName + .replaceAll("\\.", "_"); + if (tryInitJs(initFunctionName, getConnectorWrapper())) { + VConsole.log("JavaScript connector initialized using " + + initFunctionName); + return true; + } else { + VConsole.log("No JavaScript function " + initFunctionName + + " found"); + attemptedNames.add(initFunctionName); + tag = conf.getParentTag(tag.intValue()); + } + } + VConsole.log("No JavaScript init for connector not found"); + showInitProblem(attemptedNames); + return false; + } + + protected void showInitProblem(ArrayList<String> attemptedNames) { + // Default does nothing + } + + private static native boolean tryInitJs(String initFunctionName, + JavaScriptObject connectorWrapper) + /*-{ + if (typeof $wnd[initFunctionName] == 'function') { + $wnd[initFunctionName].apply(connectorWrapper); + return true; + } else { + return false; + } + }-*/; + + private JavaScriptObject getConnectorWrapper() { + if (connectorWrapper == null) { + connectorWrapper = createConnectorWrapper(this, nativeState, + rpcMap, connector.getConnectorId(), rpcObjects); + } + + return connectorWrapper; + } + + private static native void fireNativeStateChange( + JavaScriptObject connectorWrapper) + /*-{ + if (typeof connectorWrapper.onStateChange == 'function') { + connectorWrapper.onStateChange(); + } + }-*/; + + private static native JavaScriptObject createConnectorWrapper( + JavaScriptConnectorHelper h, JavaScriptObject nativeState, + JavaScriptObject registeredRpc, String connectorId, + Map<String, JavaScriptObject> rpcObjects) + /*-{ + return { + 'getConnectorId': function() { + return connectorId; + }, + 'getParentId': $entry(function(connectorId) { + return h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::getParentId(Ljava/lang/String;)(connectorId); + }), + 'getState': function() { + return nativeState; + }, + 'getRpcProxy': $entry(function(iface) { + if (!iface) { + iface = ''; + } + return rpcObjects.@java.util.Map::get(Ljava/lang/Object;)(iface); + }), + 'getWidgetElement': $entry(function(connectorId) { + return h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::getWidgetElement(Ljava/lang/String;)(connectorId); + }), + 'registerRpc': function(iface, rpcHandler) { + //registerRpc(handler) -> registerRpc('', handler); + if (!rpcHandler) { + rpcHandler = iface; + iface = ''; + } + if (!registeredRpc[iface]) { + registeredRpc[iface] = []; + } + registeredRpc[iface].push(rpcHandler); + }, + }; + }-*/; + + private native void attachRpcMethod(JavaScriptObject rpc, String iface, + String method) + /*-{ + var self = this; + rpc[method] = $entry(function() { + self.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); + }); + }-*/; + + private String getParentId(String connectorId) { + ServerConnector target = getConnector(connectorId); + if (target == null) { + return null; + } + ServerConnector parent = target.getParent(); + if (parent == null) { + return null; + } else { + return parent.getConnectorId(); + } + } + + private Element getWidgetElement(String connectorId) { + ServerConnector target = getConnector(connectorId); + if (target instanceof ComponentConnector) { + return ((ComponentConnector) target).getWidget().getElement(); + } else { + return null; + } + } + + private ServerConnector getConnector(String connectorId) { + if (connectorId == null || connectorId.length() == 0) { + return connector; + } + + return ConnectorMap.get(connector.getConnection()) + .getConnector(connectorId); + } + + private void fireRpc(String iface, String method, + JsArray<JavaScriptObject> arguments) { + if (iface == null) { + iface = findWildcardInterface(method); + } + + JSONArray argumentsArray = new JSONArray(arguments); + Object[] parameters = new Object[arguments.length()]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = argumentsArray.get(i); + } + connector.getConnection().addMethodInvocationToQueue( + new MethodInvocation(connector.getConnectorId(), iface, method, + parameters), true); + } + + private String findWildcardInterface(String method) { + Set<String> interfaces = rpcMethods.get(method); + if (interfaces.size() == 1) { + return interfaces.iterator().next(); + } else { + // TODO Resolve conflicts using argument count and types + String interfaceList = ""; + for (String iface : interfaces) { + if (interfaceList.length() != 0) { + interfaceList += ", "; + } + interfaceList += getJsInterfaceName(iface); + } + + throw new IllegalStateException( + "Can not call method " + + method + + " for wildcard rpc proxy because the function is defined for multiple rpc interfaces: " + + interfaceList + + ". Retrieve a rpc proxy for a specific interface using getRpcProxy(interfaceName) to use the function."); + } + } + + private void fireCallback(String name, JsArray<JavaScriptObject> arguments) { + MethodInvocation invocation = new MethodInvocation( + connector.getConnectorId(), + "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call", + new Object[] { name, new JSONArray(arguments) }); + connector.getConnection().addMethodInvocationToQueue(invocation, true); + } + + public void setNativeState(JavaScriptObject state) { + updateNativeState(nativeState, state); + } + + private static native void updateNativeState(JavaScriptObject state, + JavaScriptObject input) + /*-{ + // Copy all fields to existing state object + for(var key in state) { + if (state.hasOwnProperty(key)) { + delete state[key]; + } + } + + for(var key in input) { + if (input.hasOwnProperty(key)) { + state[key] = input[key]; + } + } + }-*/; + + public Object[] decodeRpcParameters(JSONArray parametersJson) { + return new Object[] { parametersJson.getJavaScriptObject() }; + } + + public void setTag(int tag) { + this.tag = tag; + } + + public void invokeJsRpc(MethodInvocation invocation, + JSONArray parametersJson) { + String iface = invocation.getInterfaceName(); + String method = invocation.getMethodName(); + if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(iface) + && "call".equals(method)) { + String callbackName = parametersJson.get(0).isString() + .stringValue(); + JavaScriptObject arguments = parametersJson.get(1).isArray() + .getJavaScriptObject(); + invokeCallback(getConnectorWrapper(), callbackName, arguments); + } else { + JavaScriptObject arguments = parametersJson.getJavaScriptObject(); + invokeJsRpc(rpcMap, iface, method, arguments); + // Also invoke wildcard interface + invokeJsRpc(rpcMap, "", method, arguments); + } + } + + private static native void invokeCallback(JavaScriptObject connector, + String name, JavaScriptObject arguments) + /*-{ + connector[name].apply(connector, arguments); + }-*/; + + private static native void invokeJsRpc(JavaScriptObject rpcMap, + String interfaceName, String methodName, JavaScriptObject parameters) + /*-{ + var targets = rpcMap[interfaceName]; + if (!targets) { + return; + } + for(var i = 0; i < targets.length; i++) { + var target = targets[i]; + target[methodName].apply(target, parameters); + } + }-*/; + + private static native void ensureCallback(JavaScriptConnectorHelper h, + JavaScriptObject connector, String name) + /*-{ + connector[name] = $entry(function() { + var args = Array.prototype.slice.call(arguments, 0); + h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireCallback(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + private JavaScriptConnectorState getConnectorState() { + return (JavaScriptConnectorState) connector.getState(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java new file mode 100644 index 0000000000..2a97e4a770 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java @@ -0,0 +1,32 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import com.vaadin.terminal.AbstractJavaScriptExtension; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; + +@Connect(AbstractJavaScriptExtension.class) +public final class JavaScriptExtension extends AbstractExtensionConnector + implements HasJavaScriptConnectorHelper { + private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( + this); + + @Override + protected void init() { + super.init(); + helper.init(); + } + + public JavaScriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } + + @Override + public JavaScriptExtensionState getState() { + return (JavaScriptExtensionState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java new file mode 100644 index 0000000000..e7bfbc4bb2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java @@ -0,0 +1,36 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class JavaScriptExtensionState extends SharedState implements + JavaScriptConnectorState { + + private Set<String> callbackNames = new HashSet<String>(); + private Map<String, Set<String>> rpcInterfaces = new HashMap<String, Set<String>>(); + + public Set<String> getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set<String> callbackNames) { + this.callbackNames = callbackNames; + } + + public Map<String, Set<String>> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map<String, Set<String>> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java deleted file mode 100644 index ab0e62222c..0000000000 --- a/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java +++ /dev/null @@ -1,205 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client; - -import java.util.ArrayList; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; -import com.google.gwt.json.client.JSONArray; -import com.vaadin.terminal.gwt.client.communication.MethodInvocation; - -public class JavascriptConnectorHelper { - - private final ServerConnector connector; - private final JavaScriptObject nativeState = JavaScriptObject - .createObject(); - private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); - - private JavaScriptObject connectorWrapper; - private int tag; - - public JavascriptConnectorHelper(ServerConnector connector) { - this.connector = connector; - } - - public boolean init() { - ApplicationConfiguration conf = connector.getConnection() - .getConfiguration(); - ArrayList<String> attemptedNames = new ArrayList<String>(); - Integer tag = Integer.valueOf(this.tag); - while (tag != null) { - String serverSideClassName = conf.getServerSideClassNameForTag(tag); - String initFunctionName = serverSideClassName - .replaceAll("\\.", "_"); - if (tryInitJs(initFunctionName, getConnectorWrapper())) { - VConsole.log("Javascript connector initialized using " - + initFunctionName); - return true; - } else { - VConsole.log("No javascript function " + initFunctionName - + " found"); - attemptedNames.add(initFunctionName); - tag = conf.getParentTag(tag.intValue()); - } - } - VConsole.log("No javascript init for connector not found"); - showInitProblem(attemptedNames); - return false; - } - - protected void showInitProblem(ArrayList<String> attemptedNames) { - // Default does nothing - } - - private static native boolean tryInitJs(String initFunctionName, - JavaScriptObject connectorWrapper) - /*-{ - if (typeof $wnd[initFunctionName] == 'function') { - $wnd[initFunctionName].apply(connectorWrapper); - return true; - } else { - return false; - } - }-*/; - - private JavaScriptObject getConnectorWrapper() { - if (connectorWrapper == null) { - connectorWrapper = createConnectorWrapper(); - } - - return connectorWrapper; - } - - protected JavaScriptObject createConnectorWrapper() { - return createConnectorWrapper(this, nativeState, rpcMap, - connector.getConnectorId()); - } - - public void fireNativeStateChange() { - fireNativeStateChange(getConnectorWrapper()); - } - - private static native void fireNativeStateChange( - JavaScriptObject connectorWrapper) - /*-{ - if (typeof connectorWrapper.onStateChange == 'function') { - connectorWrapper.onStateChange(); - } - }-*/; - - private static native JavaScriptObject createConnectorWrapper( - JavascriptConnectorHelper h, JavaScriptObject nativeState, - JavaScriptObject registeredRpc, String connectorId) - /*-{ - return { - 'getConnectorId': function() { - return connectorId; - }, - 'getState': function() { - return nativeState; - }, - 'getRpcProxyFunction': function(iface, method) { - return $entry(function() { - h.@com.vaadin.terminal.gwt.client.JavascriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); - }); - }, - 'getCallback': function(name) { - return $entry(function() { - var args = [name, Array.prototype.slice.call(arguments, 0)]; - var iface = "com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc"; - var method = "call"; - h.@com.vaadin.terminal.gwt.client.JavascriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, args); - }); - }, - 'registerCallback': function(name, callback) { - //TODO maintain separate map - if (!registeredRpc[name]) { - registeredRpc[name] = []; - } - registeredRpc[name].push(callback); - }, - 'registerRpc': function(iface, rpcHandler) { - if (!registeredRpc[iface]) { - registeredRpc[iface] = []; - } - registeredRpc[iface].push(rpcHandler); - }, - }; - }-*/; - - private void fireRpc(String iface, String method, - JsArray<JavaScriptObject> arguments) { - JSONArray argumentsArray = new JSONArray(arguments); - Object[] parameters = new Object[arguments.length()]; - for (int i = 0; i < parameters.length; i++) { - parameters[i] = argumentsArray.get(i); - } - connector.getConnection().addMethodInvocationToQueue( - new MethodInvocation(connector.getConnectorId(), iface, method, - parameters), true); - } - - public void setNativeState(JavaScriptObject state) { - updateNativeState(nativeState, state); - } - - private static native void updateNativeState(JavaScriptObject state, - JavaScriptObject input) - /*-{ - // Copy all fields to existing state object - for(var key in state) { - if (state.hasOwnProperty(key)) { - delete state[key]; - } - } - - for(var key in input) { - if (input.hasOwnProperty(key)) { - state[key] = input[key]; - } - } - }-*/; - - public Object[] decodeRpcParameters(JSONArray parametersJson) { - return new Object[] { parametersJson.getJavaScriptObject() }; - } - - public void setTag(int tag) { - this.tag = tag; - } - - public void invokeJsRpc(MethodInvocation invocation, - JSONArray parametersJson) { - if ("com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc" - .equals(invocation.getInterfaceName()) - && "call".equals(invocation.getMethodName())) { - invokeJsRpc(rpcMap, parametersJson.get(0).isString().stringValue(), - null, parametersJson.get(1).isArray().getJavaScriptObject()); - } else { - invokeJsRpc(rpcMap, invocation.getInterfaceName(), - invocation.getMethodName(), - parametersJson.getJavaScriptObject()); - } - } - - private static native void invokeJsRpc(JavaScriptObject rpcMap, - String interfaceName, String methodName, JavaScriptObject parameters) - /*-{ - var targets = rpcMap[interfaceName]; - if (!targets) { - return; - } - for(var i = 0; i < targets.length; i++) { - var target = targets[i]; - if (methodName === null && typeof target === 'function') { - target.apply($wnd, parameters); - } else { - target[methodName].apply(target, parameters); - } - } - }-*/; - -} diff --git a/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java deleted file mode 100644 index 6c098a52f6..0000000000 --- a/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java +++ /dev/null @@ -1,33 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client; - -import com.vaadin.terminal.AbstractJavascriptExtension; -import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.AbstractConnector; -import com.vaadin.terminal.gwt.client.ui.Connect; - -@Connect(AbstractJavascriptExtension.class) -public class JavascriptExtension extends AbstractConnector implements - HasJavascriptConnectorHelper { - private final JavascriptConnectorHelper helper = new JavascriptConnectorHelper( - this); - - @Override - protected void init() { - helper.init(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - helper.fireNativeStateChange(); - } - - public JavascriptConnectorHelper getJavascriptConnectorHelper() { - return helper; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/Util.java b/src/com/vaadin/terminal/gwt/client/Util.java index 87bf27fc27..d3cb54160d 100644 --- a/src/com/vaadin/terminal/gwt/client/Util.java +++ b/src/com/vaadin/terminal/gwt/client/Util.java @@ -911,6 +911,22 @@ public class Util { } /** + * Find the element corresponding to the coordinates in the passed mouse + * event. Please note that this is not always the same as the target of the + * event e.g. if event capture is used. + * + * @param event + * the mouse event to get coordinates from + * @return the element at the coordinates of the event + */ + public static Element getElementUnderMouse(NativeEvent event) { + int pageX = getTouchOrMouseClientX(event); + int pageY = getTouchOrMouseClientY(event); + + return getElementFromPoint(pageX, pageY); + } + + /** * A helper method to return the client position from an event. Returns * position from either first changed touch (if touch event) or from the * event itself. diff --git a/src/com/vaadin/terminal/gwt/client/WidgetSet.java b/src/com/vaadin/terminal/gwt/client/WidgetSet.java index ecbfb0ecc9..3d7e838c62 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetSet.java @@ -5,7 +5,7 @@ package com.vaadin.terminal.gwt.client; import com.google.gwt.core.client.GWT; -import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class WidgetSet { @@ -54,8 +54,8 @@ public class WidgetSet { * let the auto generated code instantiate this type */ ServerConnector connector = widgetMap.instantiate(classType); - if (connector instanceof HasJavascriptConnectorHelper) { - ((HasJavascriptConnectorHelper) connector) + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) .getJavascriptConnectorHelper().setTag(tag); } return connector; diff --git a/src/com/vaadin/terminal/gwt/client/communication/HasJavaScriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/communication/HasJavaScriptConnectorHelper.java new file mode 100644 index 0000000000..a5191a5fed --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/HasJavaScriptConnectorHelper.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; + +public interface HasJavaScriptConnectorHelper { + public JavaScriptConnectorHelper getJavascriptConnectorHelper(); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java deleted file mode 100644 index 74bc75da66..0000000000 --- a/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java +++ /dev/null @@ -1,11 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.communication; - -import com.vaadin.terminal.gwt.client.JavascriptConnectorHelper; - -public interface HasJavascriptConnectorHelper { - public JavascriptConnectorHelper getJavascriptConnectorHelper(); -} diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java index e0ffb40125..07d6292ce2 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java @@ -89,8 +89,8 @@ public class RpcManager { MethodInvocation invocation = new MethodInvocation(connectorId, interfaceName, methodName); - if (connector instanceof HasJavascriptConnectorHelper) { - ((HasJavascriptConnectorHelper) connector) + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) .getJavascriptConnectorHelper().invokeJsRpc(invocation, parametersJson); } else { diff --git a/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java new file mode 100644 index 0000000000..bcefcf05cb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java @@ -0,0 +1,27 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions; + +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractConnector; + +public abstract class AbstractExtensionConnector extends AbstractConnector { + @Override + public void setParent(ServerConnector parent) { + ServerConnector oldParent = getParent(); + if (oldParent != null && oldParent != parent) { + throw new IllegalStateException( + "An extension can not be moved from one parent to another."); + } + + super.setParent(parent); + + extend(parent); + } + + protected void extend(ServerConnector target) { + // Default does nothing + } +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java new file mode 100644 index 0000000000..f1185586d5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import com.vaadin.terminal.gwt.client.communication.ClientRpc; + +public interface ExecuteJavaScriptRpc extends ClientRpc { + public void executeJavaScript(String script); +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java new file mode 100644 index 0000000000..8656783a86 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java @@ -0,0 +1,119 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import java.util.HashSet; +import java.util.Set; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.json.client.JSONArray; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.JavaScript; + +@Connect(JavaScript.class) +public class JavaScriptManagerConnector extends AbstractExtensionConnector { + private Set<String> currentNames = new HashSet<String>(); + + @Override + protected void init() { + registerRpc(ExecuteJavaScriptRpc.class, new ExecuteJavaScriptRpc() { + public void executeJavaScript(String Script) { + eval(Script); + } + }); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + Set<String> newNames = getState().getNames(); + + // Current names now only contains orphan callbacks + currentNames.removeAll(newNames); + + for (String name : currentNames) { + removeCallback(name); + } + + currentNames = new HashSet<String>(newNames); + for (String name : newNames) { + addCallback(name); + } + } + + // TODO Ensure we don't overwrite anything (important) in $wnd + private native void addCallback(String name) + /*-{ + var m = this; + var target = $wnd; + var parts = name.split('.'); + + for(var i = 0; i < parts.length - 1; i++) { + var part = parts[i]; + if (target[part] === undefined) { + target[part] = {}; + } + target = target[part]; + } + + target[parts[parts.length - 1]] = $entry(function() { + //Must make a copy because arguments is an array-like object (not instanceof Array), causing suboptimal JSON encoding + var args = Array.prototype.slice.call(arguments, 0); + m.@com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavaScriptManagerConnector::sendRpc(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + // TODO only remove what we actually added + // TODO We might leave empty objects behind, but there's no good way of + // knowing whether they are unused + private native void removeCallback(String name) + /*-{ + var target = $wnd; + var parts = name.split('.'); + + for(var i = 0; i < parts.length - 1; i++) { + var part = parts[i]; + if (target[part] === undefined) { + $wnd.console.log(part,'not defined in',target); + // No longer attached -> nothing more to do + return; + } + target = target[part]; + } + + $wnd.console.log('removing',parts[parts.length - 1],'from',target); + delete target[parts[parts.length - 1]]; + }-*/; + + private static native void eval(String script) + /*-{ + if(script) { + (new $wnd.Function(script)).apply($wnd); + } + }-*/; + + public void sendRpc(String name, JsArray<JavaScriptObject> arguments) { + Object[] parameters = new Object[] { name, new JSONArray(arguments) }; + + /* + * Must invoke manually as the RPC interface can't be used in GWT + * because of the JSONArray parameter + */ + getConnection().addMethodInvocationToQueue( + new MethodInvocation(getConnectorId(), + "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", + "call", parameters), true); + } + + @Override + public JavaScriptManagerState getState() { + return (JavaScriptManagerState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerState.java index 77794ffdca..fc246aff04 100644 --- a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerState.java @@ -9,7 +9,7 @@ import java.util.Set; import com.vaadin.terminal.gwt.client.communication.SharedState; -public class JavascriptManagerState extends SharedState { +public class JavaScriptManagerState extends SharedState { private Set<String> names = new HashSet<String>(); public Set<String> getNames() { diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java deleted file mode 100644 index 979a6d22c4..0000000000 --- a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java +++ /dev/null @@ -1,78 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; - -import java.util.HashSet; -import java.util.Set; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; -import com.google.gwt.json.client.JSONArray; -import com.vaadin.terminal.gwt.client.communication.MethodInvocation; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.AbstractConnector; -import com.vaadin.terminal.gwt.client.ui.Connect; -import com.vaadin.ui.JavascriptManager; - -@Connect(JavascriptManager.class) -public class JavascriptManagerConnector extends AbstractConnector { - private Set<String> currentNames = new HashSet<String>(); - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - Set<String> newNames = getState().getNames(); - - // Current names now only contains orphan callbacks - currentNames.removeAll(newNames); - - for (String name : currentNames) { - removeCallback(name); - } - - currentNames = new HashSet<String>(newNames); - for (String name : newNames) { - addCallback(name); - } - } - - // TODO Ensure we don't overwrite anything (important) in $wnd - private native void addCallback(String name) - /*-{ - var m = this; - $wnd[name] = $entry(function() { - //Must make a copy because arguments is an array-like object (not instanceof Array), causing suboptimal JSON encoding - var args = Array.prototype.slice.call(arguments, 0); - m.@com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavascriptManagerConnector::sendRpc(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); - }); - }-*/; - - // TODO only remove what we actually added - private native void removeCallback(String name) - /*-{ - delete $wnd[name]; - }-*/; - - public void sendRpc(String name, JsArray<JavaScriptObject> arguments) { - Object[] parameters = new Object[] { name, new JSONArray(arguments) }; - - /* - * Must invoke manually as the RPC interface can't be used in GWT - * because of the JSONArray parameter - */ - getConnection() - .addMethodInvocationToQueue( - new MethodInvocation( - getConnectorId(), - "com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc", - "call", parameters), true); - } - - @Override - public JavascriptManagerState getState() { - return (JavascriptManagerState) super.getState(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java index 31204aa0c6..e0ca798682 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java @@ -3,29 +3,76 @@ */ package com.vaadin.terminal.gwt.client.ui; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.dom.client.ContextMenuHandler; import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.event.dom.client.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.Util; -public abstract class AbstractClickEventHandler implements DoubleClickHandler, - ContextMenuHandler, MouseUpHandler { +public abstract class AbstractClickEventHandler implements MouseDownHandler, + MouseUpHandler, DoubleClickHandler, ContextMenuHandler { - private HandlerRegistration doubleClickHandlerRegistration; + private HandlerRegistration mouseDownHandlerRegistration; private HandlerRegistration mouseUpHandlerRegistration; + private HandlerRegistration doubleClickHandlerRegistration; private HandlerRegistration contextMenuHandlerRegistration; protected ComponentConnector connector; private String clickEventIdentifier; + /** + * The element where the last mouse down event was registered. + */ + private JavaScriptObject lastMouseDownTarget; + + /** + * Set to true by {@link #mouseUpPreviewHandler} if it gets a mouseup at the + * same element as {@link #lastMouseDownTarget}. + */ + private boolean mouseUpPreviewMatched = false; + + private HandlerRegistration mouseUpEventPreviewRegistration; + + /** + * Previews events after a mousedown to detect where the following mouseup + * hits. + */ + private final NativePreviewHandler mouseUpPreviewHandler = new NativePreviewHandler() { + + public void onPreviewNativeEvent(NativePreviewEvent event) { + if (event.getTypeInt() == Event.ONMOUSEUP) { + mouseUpEventPreviewRegistration.removeHandler(); + + // Event's reported target not always correct if event + // capture is in use + Element elementUnderMouse = Util.getElementUnderMouse(event + .getNativeEvent()); + if (lastMouseDownTarget != null + && elementUnderMouse.cast() == lastMouseDownTarget) { + mouseUpPreviewMatched = true; + } else { + System.out.println("Ignoring mouseup from " + + elementUnderMouse + " when mousedown was on " + + lastMouseDownTarget); + } + } + } + }; + public AbstractClickEventHandler(ComponentConnector connector, String clickEventIdentifier) { this.connector = connector; @@ -36,25 +83,28 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, // Handle registering/unregistering of click handler depending on if // server side listeners have been added or removed. if (hasEventListener()) { - if (mouseUpHandlerRegistration == null) { + if (mouseDownHandlerRegistration == null) { + mouseDownHandlerRegistration = registerHandler(this, + MouseDownEvent.getType()); mouseUpHandlerRegistration = registerHandler(this, MouseUpEvent.getType()); - contextMenuHandlerRegistration = registerHandler(this, - ContextMenuEvent.getType()); doubleClickHandlerRegistration = registerHandler(this, DoubleClickEvent.getType()); + contextMenuHandlerRegistration = registerHandler(this, + ContextMenuEvent.getType()); } } else { - if (mouseUpHandlerRegistration != null) { + if (mouseDownHandlerRegistration != null) { // Remove existing handlers - doubleClickHandlerRegistration.removeHandler(); + mouseDownHandlerRegistration.removeHandler(); mouseUpHandlerRegistration.removeHandler(); + doubleClickHandlerRegistration.removeHandler(); contextMenuHandlerRegistration.removeHandler(); - contextMenuHandlerRegistration = null; + mouseDownHandlerRegistration = null; mouseUpHandlerRegistration = null; doubleClickHandlerRegistration = null; - + contextMenuHandlerRegistration = null; } } @@ -93,6 +143,7 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, * Event handler for context menu. Prevents the browser context menu from * popping up if there is a listener for right clicks. */ + public void onContextMenu(ContextMenuEvent event) { if (hasEventListener() && shouldFireEvent(event)) { // Prevent showing the browser's context menu when there is a right @@ -101,18 +152,34 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, } } - /** - * Event handler for mouse up. This is used to detect all single click - * events. - */ + public void onMouseDown(MouseDownEvent event) { + /* + * When getting a mousedown event, we must detect where the + * corresponding mouseup event if it's on a different part of the page. + */ + lastMouseDownTarget = event.getNativeEvent().getEventTarget(); + mouseUpPreviewMatched = false; + mouseUpEventPreviewRegistration = Event + .addNativePreviewHandler(mouseUpPreviewHandler); + } + public void onMouseUp(MouseUpEvent event) { - // TODO For perfect accuracy we should check that a mousedown has - // occured on this element before this mouseup and that no mouseup - // has occured anywhere after that. - if (hasEventListener() && shouldFireEvent(event)) { + /* + * Only fire a click if the mouseup hits the same element as the + * corresponding mousedown. This is first checked in the event preview + * but we can't fire the even there as the event might get canceled + * before it gets here. + */ + if (hasEventListener() + && mouseUpPreviewMatched + && lastMouseDownTarget != null + && Util.getElementUnderMouse(event.getNativeEvent()) == lastMouseDownTarget + && shouldFireEvent(event)) { // "Click" with left, right or middle button fireClick(event.getNativeEvent()); } + mouseUpPreviewMatched = false; + lastMouseDownTarget = null; } /** @@ -140,6 +207,7 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, * that browsers typically fail to prevent the second click event so a * double click will result in two click events and one double click event. */ + public void onDoubleClick(DoubleClickEvent event) { if (hasEventListener() && shouldFireEvent(event)) { fireClick(event.getNativeEvent()); diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java index 67dbd2d9d9..f0b9d518ca 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java @@ -131,7 +131,22 @@ public abstract class AbstractComponentConnector extends AbstractConnector public void setWidgetEnabled(boolean widgetEnabled) { if (getWidget() instanceof HasEnabled) { + // set widget specific enabled state ((HasEnabled) getWidget()).setEnabled(widgetEnabled); + // add or remove v-disabled style name from the widget + getWidget().setStyleName(ApplicationConnection.DISABLED_CLASSNAME, + !widgetEnabled); + // make sure the caption has or has not v-disabled style + if (delegateCaptionHandling()) { + ServerConnector parent = getParent(); + if (parent instanceof ComponentContainerConnector) { + ((ComponentContainerConnector) parent).updateCaption(this); + } else if (parent == null && !(this instanceof RootConnector)) { + VConsole.error("Parent of connector " + + Util.getConnectorString(this) + + " is null. This is typically an indication of a broken component hierarchy"); + } + } } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java index cffdb1e68a..758f798ef2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java @@ -28,6 +28,7 @@ public abstract class ClickEventHandler extends AbstractClickEventHandler { * @param event * The native event that caused this click event */ + @Override protected void fireClick(NativeEvent event) { MouseEventDetails mouseDetails = MouseEventDetailsBuilder .buildMouseEventDetails(event, getRelativeToElement()); diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 62697c4d98..ef1ea8521b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -34,8 +34,8 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements public FocusableScrollPanel() { // Prevent IE standard mode bug when a AbsolutePanel is contained. + TouchScrollDelegate.enableTouchScrolling(this, getElement()); Style style = getElement().getStyle(); - style.setOverflow(Overflow.AUTO); style.setProperty("zoom", "1"); style.setPosition(Position.RELATIVE); } @@ -153,7 +153,8 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * the new vertical scroll position, in pixels */ public void setScrollPosition(int position) { - if (BrowserInfo.get().isAndroidWithBrokenScrollTop()) { + if (BrowserInfo.get().isAndroidWithBrokenScrollTop() + && BrowserInfo.get().requiresTouchScrollDelegate()) { ArrayList<com.google.gwt.dom.client.Element> elements = TouchScrollDelegate .getElements(getElement()); for (com.google.gwt.dom.client.Element el : elements) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java new file mode 100644 index 0000000000..bb062a6677 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java @@ -0,0 +1,42 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; +import com.vaadin.ui.AbstractJavaScriptComponent; + +@Connect(AbstractJavaScriptComponent.class) +public final class JavaScriptComponentConnector extends + AbstractComponentConnector implements HasJavaScriptConnectorHelper { + + private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( + this) { + @Override + protected void showInitProblem( + java.util.ArrayList<String> attemptedNames) { + getWidget().showNoInitFound(attemptedNames); + } + }; + + @Override + public JavaScriptWidget getWidget() { + return (JavaScriptWidget) super.getWidget(); + } + + @Override + protected void init() { + super.init(); + helper.init(); + } + + public JavaScriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } + + @Override + public JavaScriptComponentState getState() { + return (JavaScriptComponentState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java new file mode 100644 index 0000000000..6728f85ec9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java @@ -0,0 +1,37 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; + +public class JavaScriptComponentState extends ComponentState implements + JavaScriptConnectorState { + + private Set<String> callbackNames = new HashSet<String>(); + private Map<String, Set<String>> rpcInterfaces = new HashMap<String, Set<String>>(); + + public Set<String> getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set<String> callbackNames) { + this.callbackNames = callbackNames; + } + + public Map<String, Set<String>> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map<String, Set<String>> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptWidget.java index 93a4417b1c..e6c3323893 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptWidget.java @@ -8,13 +8,13 @@ import java.util.ArrayList; import com.google.gwt.dom.client.Document; import com.google.gwt.user.client.ui.Widget; -public class JavascriptWidget extends Widget { - public JavascriptWidget() { +public class JavaScriptWidget extends Widget { + public JavaScriptWidget() { setElement(Document.get().createDivElement()); } public void showNoInitFound(ArrayList<String> attemptedNames) { - String message = "Could not initialize JavascriptConnector because no javascript init function was found. Make sure one of these functions are defined: <ul>"; + String message = "Could not initialize JavaScriptConnector because no JavaScript init function was found. Make sure one of these functions are defined: <ul>"; for (String name : attemptedNames) { message += "<li>" + name + "</li>"; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java deleted file mode 100644 index 57e65e91c6..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java +++ /dev/null @@ -1,60 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.user.client.Element; -import com.vaadin.terminal.gwt.client.JavascriptConnectorHelper; -import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.ui.AbstractJavascriptComponent; - -@Connect(AbstractJavascriptComponent.class) -public class JavascriptComponentConnector extends AbstractComponentConnector - implements HasJavascriptConnectorHelper { - - private final JavascriptConnectorHelper helper = new JavascriptConnectorHelper( - this) { - @Override - protected void showInitProblem( - java.util.ArrayList<String> attemptedNames) { - getWidget().showNoInitFound(attemptedNames); - } - - @Override - protected JavaScriptObject createConnectorWrapper() { - JavaScriptObject connectorWrapper = super.createConnectorWrapper(); - addGetWidgetElement(connectorWrapper, getWidget().getElement()); - return connectorWrapper; - } - }; - - @Override - protected void init() { - helper.init(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - helper.fireNativeStateChange(); - } - - private static native void addGetWidgetElement( - JavaScriptObject connectorWrapper, Element element) - /*-{ - connectorWrapper.getWidgetElement = function() { - return element; - }; - }-*/; - - @Override - public JavascriptWidget getWidget() { - return (JavascriptWidget) super.getWidget(); - } - - public JavascriptConnectorHelper getJavascriptConnectorHelper() { - return helper; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java index 8b2248aff6..7302f9f2ac 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java @@ -4,6 +4,8 @@ package com.vaadin.terminal.gwt.client.ui; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import com.google.gwt.animation.client.Animation; import com.google.gwt.core.client.Duration; @@ -15,10 +17,12 @@ import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.VConsole; @@ -68,7 +72,7 @@ public class TouchScrollDelegate implements NativePreviewHandler { private static final double DECELERATION = 0.002; private static final int MAX_DURATION = 1500; private int origY; - private Element[] scrollableElements; + private HashSet<Element> scrollableElements; private Element scrolledElement; private int origScrollTop; private HandlerRegistration handlerRegistration; @@ -86,8 +90,107 @@ public class TouchScrollDelegate implements NativePreviewHandler { private static final boolean androidWithBrokenScrollTop = BrowserInfo.get() .isAndroidWithBrokenScrollTop(); + /** + * A helper class for making a widget scrollable. Uses native scrolling if + * supported by the browser, otherwise registers a touch start handler + * delegating to a TouchScrollDelegate instance. + */ + public static class TouchScrollHandler implements TouchStartHandler { + + private static final String SCROLLABLE_CLASSNAME = "v-scrollable"; + + private final TouchScrollDelegate delegate; + private final boolean requiresDelegate = BrowserInfo.get() + .requiresTouchScrollDelegate(); + + /** + * Constructs a scroll handler for the given widget. + * + * @param widget + * The widget that contains scrollable elements + * @param scrollables + * The elements of the widget that should be scrollable. + */ + public TouchScrollHandler(Widget widget, Element... scrollables) { + if (requiresDelegate) { + delegate = new TouchScrollDelegate(); + widget.addDomHandler(this, TouchStartEvent.getType()); + } else { + delegate = null; + } + setElements(scrollables); + } + + public void onTouchStart(TouchStartEvent event) { + assert delegate != null; + delegate.onTouchStart(event); + } + + public void debug(Element e) { + VConsole.log("Classes: " + e.getClassName() + " overflow: " + + e.getStyle().getProperty("overflow") + " w-o-s: " + + e.getStyle().getProperty("WebkitOverflowScrolling")); + } + + /** + * Registers the given element as scrollable. + */ + public void addElement(Element scrollable) { + scrollable.addClassName(SCROLLABLE_CLASSNAME); + if (requiresDelegate) { + delegate.scrollableElements.add(scrollable); + } + } + + /** + * Unregisters the given element as scrollable. Should be called when a + * previously-registered element is removed from the DOM to prevent + * memory leaks. + */ + public void removeElement(Element scrollable) { + scrollable.removeClassName(SCROLLABLE_CLASSNAME); + if (requiresDelegate) { + delegate.scrollableElements.remove(scrollable); + } + } + + /** + * Registers the given elements as scrollable, removing previously + * registered scrollables from this handler. + * + * @param scrollables + * The elements that should be scrollable + */ + public void setElements(Element... scrollables) { + if (requiresDelegate) { + for (Element e : delegate.scrollableElements) { + e.removeClassName(SCROLLABLE_CLASSNAME); + } + delegate.scrollableElements.clear(); + } + for (Element e : scrollables) { + addElement(e); + } + } + } + + /** + * Makes the given elements scrollable, either natively or by using a + * TouchScrollDelegate, depending on platform capabilities. + * + * @param widget + * The widget that contains scrollable elements + * @param scrollables + * The elements inside the widget that should be scrollable + * @return A scroll handler for the given widget. + */ + public static TouchScrollHandler enableTouchScrolling(Widget widget, + Element... scrollables) { + return new TouchScrollHandler(widget, scrollables); + } + public TouchScrollDelegate(Element... elements) { - scrollableElements = elements; + setElements(elements); } public void setScrollHandler(ScrollHandler scrollHandler) { @@ -535,8 +638,8 @@ public class TouchScrollDelegate implements NativePreviewHandler { } } - public void setElements(com.google.gwt.user.client.Element[] elements) { - scrollableElements = elements; + public void setElements(Element[] elements) { + scrollableElements = new HashSet<Element>(Arrays.asList(elements)); } /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java b/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java index dcd520bbb3..b83d5afb00 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java +++ b/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java @@ -20,6 +20,8 @@ import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; import com.vaadin.terminal.gwt.client.ui.tabsheet.TabsheetBaseConnector; import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheetBase; @@ -35,8 +37,11 @@ public class VAccordion extends VTabsheetBase { int selectedUIDLItemIndex = -1; + private final TouchScrollHandler touchScrollHandler; + public VAccordion() { super(CLASSNAME); + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); } @Override @@ -145,7 +150,6 @@ public class VAccordion extends VTabsheetBase { } } } - if (!alreadyOpen) { item.open(); activeTabIndex = itemIndex; @@ -342,11 +346,13 @@ public class VAccordion extends VTabsheetBase { DOM.appendChild(captionNode, caption.getElement()); DOM.appendChild(getElement(), captionNode); DOM.appendChild(getElement(), content); - setStyleName(CLASSNAME + "-item"); - DOM.setElementProperty(content, "className", CLASSNAME - + "-item-content"); - DOM.setElementProperty(captionNode, "className", CLASSNAME - + "-item-caption"); + + getElement().addClassName(CLASSNAME + "-item"); + captionNode.addClassName(CLASSNAME + "-item-caption"); + content.addClassName(CLASSNAME + "-item-content"); + + touchScrollHandler.addElement(getContainerElement()); + close(); } @@ -488,6 +494,7 @@ public class VAccordion extends VTabsheetBase { protected void removeTab(int index) { StackItem item = getStackItem(index); remove(item); + touchScrollHandler.removeElement(item.getContainerElement()); } @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java b/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java index e5e7dbba8b..0cd8bc54f4 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java @@ -10,6 +10,7 @@ import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Accessibility; @@ -61,7 +62,9 @@ public class VButton extends FocusWidget implements ClickHandler { private boolean isCapturing; /** - * If <code>true</code>, this widget has focus with the space bar down. + * If <code>true</code>, this widget has focus with the space bar down. This + * means that we will get events when the button is released, but we should + * trigger the button only if the button is still focused at that point. */ private boolean isFocusing; @@ -74,6 +77,14 @@ public class VButton extends FocusWidget implements ClickHandler { protected int clickShortcut = 0; + private HandlerRegistration focusHandlerRegistration; + private HandlerRegistration blurHandlerRegistration; + + /** + * If caption should be rendered in HTML + */ + protected boolean htmlCaption = false; + public VButton() { super(DOM.createDiv()); setTabIndex(0); @@ -229,37 +240,28 @@ public class VButton extends FocusWidget implements ClickHandler { if ((event.getTypeInt() & Event.KEYEVENTS) != 0) { switch (type) { case Event.ONKEYDOWN: + // Stop propagation when the user starts pressing a button that + // we are handling to prevent actions from getting triggered if (event.getKeyCode() == 32 /* space */) { isFocusing = true; event.preventDefault(); + event.stopPropagation(); + } else if (event.getKeyCode() == KeyCodes.KEY_ENTER) { + event.stopPropagation(); } break; case Event.ONKEYUP: if (isFocusing && event.getKeyCode() == 32 /* space */) { isFocusing = false; - - /* - * If click shortcut is space then the shortcut handler will - * take care of the click. - */ - if (clickShortcut != 32 /* space */) { - onClick(); - } - + onClick(); + event.stopPropagation(); event.preventDefault(); } break; case Event.ONKEYPRESS: if (event.getKeyCode() == KeyCodes.KEY_ENTER) { - - /* - * If click shortcut is enter then the shortcut handler will - * take care of the click. - */ - if (clickShortcut != KeyCodes.KEY_ENTER) { - onClick(); - } - + onClick(); + event.stopPropagation(); event.preventDefault(); } break; @@ -336,12 +338,10 @@ public class VButton extends FocusWidget implements ClickHandler { Accessibility.removeState(getElement(), Accessibility.STATE_PRESSED); super.setTabIndex(-1); - addStyleName(ApplicationConnection.DISABLED_CLASSNAME); } else { Accessibility.setState(getElement(), Accessibility.STATE_PRESSED, "false"); super.setTabIndex(tabIndex); - removeStyleName(ApplicationConnection.DISABLED_CLASSNAME); } } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java b/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java index d29eda0d6a..8c5d521445 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java @@ -69,7 +69,8 @@ import com.vaadin.terminal.gwt.client.ui.menubar.MenuItem; */ @SuppressWarnings("deprecation") public class VFilterSelect extends Composite implements Field, KeyDownHandler, - KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable { + KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable, + SubPartAware { /** * Represents a suggestion in the suggestion popup box @@ -100,6 +101,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * contains an image tag with the rows icon (if an icon has been * specified) and the caption of the item */ + public String getDisplayString() { final StringBuffer sb = new StringBuffer(); if (iconUri != null) { @@ -122,6 +124,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Get a string that represents this item. This is used in the text box. */ + public String getReplacementString() { return caption; } @@ -147,6 +150,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Executes a selection of this item. */ + public void execute() { onSuggestionSelected(this); } @@ -392,6 +396,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt * .user.client.Event) */ + @Override public void onBrowserEvent(Event event) { if (event.getTypeInt() == Event.ONCLICK) { @@ -449,6 +454,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition * (int, int) */ + public void setPosition(int offsetWidth, int offsetHeight) { int top = -1; @@ -541,6 +547,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google * .gwt.event.logical.shared.CloseEvent) */ + @Override public void onClose(CloseEvent<PopupPanel> event) { if (event.isAutoClosed()) { @@ -824,6 +831,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.TextBoxBase#onBrowserEvent(com.google * .gwt.user.client.Event) */ + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -832,8 +840,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } } - @Override // Overridden to avoid selecting text when text input is disabled + @Override public void setSelectionRange(int pos, int length) { if (textInputEnabled) { super.setSelectionRange(pos, length); @@ -857,6 +865,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt * .user.client.Event) */ + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -951,6 +960,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, public VFilterSelect() { selectedItemIcon.setStyleName("v-icon"); selectedItemIcon.addLoadHandler(new LoadHandler() { + public void onLoad(LoadEvent event) { if (BrowserInfo.get().isIE8()) { // IE8 needs some help to discover it should reposition the @@ -1203,6 +1213,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt * .event.dom.client.KeyDownEvent) */ + public void onKeyDown(KeyDownEvent event) { if (enabled && !readonly) { int keyCode = event.getNativeKeyCode(); @@ -1364,6 +1375,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * @param event * The KeyUpEvent of the key depressed */ + public void onKeyUp(KeyUpEvent event) { if (enabled && !readonly) { switch (event.getNativeKeyCode()) { @@ -1411,6 +1423,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Listener for popupopener */ + public void onClick(ClickEvent event) { if (textInputEnabled && event.getNativeEvent().getEventTarget().cast() == tb @@ -1474,6 +1487,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event * .dom.client.FocusEvent) */ + public void onFocus(FocusEvent event) { /* @@ -1567,6 +1581,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * * @see com.vaadin.terminal.gwt.client.Focusable#focus() */ + public void focus() { focused = true; if (prompting && !readonly) { @@ -1674,4 +1689,22 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, super.onDetach(); suggestionPopup.hide(); } + + public Element getSubPartElement(String subPart) { + if ("textbox".equals(subPart)) { + return this.tb.getElement(); + } else if ("button".equals(subPart)) { + return this.popupOpener.getElement(); + } + return null; + } + + public String getSubPartName(Element subElement) { + if (tb.getElement().isOrHasChild(subElement)) { + return "textbox"; + } else if (popupOpener.getElement().isOrHasChild(subElement)) { + return "button"; + } + return null; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java new file mode 100644 index 0000000000..32dac10170 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java @@ -0,0 +1,33 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.dd; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation type used to point the server side counterpart for client side + * a {@link VAcceptCriterion} class. + * <p> + * Annotations are used at GWT compilation phase, so remember to rebuild your + * widgetset if you do changes for {@link AcceptCriterion} mappings. + * + * Prior to Vaadin 7, the mapping was done with an annotation on server side + * classes. + * + * @since 7.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AcceptCriterion { + /** + * @return the fully qualified class name of the server side counterpart for + * the annotated criterion + */ + String value(); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java b/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java index 02c1fe5061..97f5eb86fd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Window; import com.vaadin.terminal.gwt.client.Util; public class DDUtil { @@ -43,8 +44,11 @@ public class DDUtil { public static VerticalDropLocation getVerticalDropLocation(Element element, int offsetHeight, int clientY, double topBottomRatio) { - int absoluteTop = element.getAbsoluteTop(); - int fromTop = clientY - absoluteTop; + // Event coordinates are relative to the viewport, element absolute + // position is relative to the document. Make element position relative + // to viewport by adjusting for viewport scrolling. See #6021 + int elementTop = element.getAbsoluteTop() - Window.getScrollTop(); + int fromTop = clientY - elementTop; float percentageFromTop = (fromTop / (float) offsetHeight); if (percentageFromTop < topBottomRatio) { @@ -74,14 +78,17 @@ public class DDUtil { public static HorizontalDropLocation getHorizontalDropLocation( Element element, int clientX, double leftRightRatio) { - int absoluteLeft = element.getAbsoluteLeft(); + // Event coordinates are relative to the viewport, element absolute + // position is relative to the document. Make element position relative + // to viewport by adjusting for viewport scrolling. See #6021 + int elementLeft = element.getAbsoluteLeft() - Window.getScrollLeft(); int offsetWidth = element.getOffsetWidth(); - int fromTop = clientX - absoluteLeft; + int fromLeft = clientX - elementLeft; - float percentageFromTop = (fromTop / (float) offsetWidth); - if (percentageFromTop < leftRightRatio) { + float percentageFromLeft = (fromLeft / (float) offsetWidth); + if (percentageFromLeft < leftRightRatio) { return HorizontalDropLocation.LEFT; - } else if (percentageFromTop > 1 - leftRightRatio) { + } else if (percentageFromLeft > 1 - leftRightRatio) { return HorizontalDropLocation.RIGHT; } else { return HorizontalDropLocation.CENTER; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java index 51782c002d..07e931fb02 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java @@ -8,6 +8,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.AcceptAll") final public class VAcceptAll extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java index 65c67e6b8a..727c30075c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java @@ -8,6 +8,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.And") final public class VAnd extends VAcceptCriterion implements VAcceptCallback { private boolean b1; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java index 4a95034d4a..5786068174 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java @@ -8,6 +8,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.ContainsDataFlavor") final public class VContainsDataFlavor extends VAcceptCriterion { @Override 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 aabbf58b24..58550af918 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java @@ -12,6 +12,7 @@ import com.vaadin.terminal.gwt.client.UIDL; * * @since 6.3 */ +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.SourceIs") final public class VDragSourceIs extends VAcceptCriterion { @Override 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 90e2b033c9..3fc54e6fd3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java @@ -10,6 +10,7 @@ import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.ui.AbstractSelect.TargetItemIs") final public class VIsOverId extends VAcceptCriterion { @Override 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 eb55c1a91c..5f1fe978b5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java @@ -9,6 +9,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.ui.AbstractSelect.AcceptItem") final public class VItemIdIs extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java index b824c1cb65..e972371b9f 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java @@ -13,11 +13,23 @@ import com.vaadin.terminal.gwt.client.UIDL; /** * */ -final public class VLazyInitItemIdentifiers extends VAcceptCriterion { +public class VLazyInitItemIdentifiers extends VAcceptCriterion { private boolean loaded = false; private HashSet<String> hashSet; private VDragEvent lastDragEvent; + @AcceptCriterion("com.vaadin.ui.Table.TableDropCriterion") + final public static class VTableLazyInitItemIdentifiers extends + VLazyInitItemIdentifiers { + // all logic in superclass + } + + @AcceptCriterion("com.vaadin.ui.Tree.TreeDropCriterion") + final public static class VTreeLazyInitItemIdentifiers extends + VLazyInitItemIdentifiers { + // all logic in superclass + } + @Override public void accept(final VDragEvent drag, UIDL configuration, final VAcceptCallback callback) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java index f4ba868497..e91ad6149a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java @@ -13,6 +13,7 @@ import com.vaadin.terminal.gwt.client.VConsole; * TODO implementation could now be simplified/optimized * */ +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.Not") final public class VNot extends VAcceptCriterion { private boolean b1; private VAcceptCriterion crit1; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java index e47447b7fc..3664326568 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java @@ -11,6 +11,7 @@ import com.vaadin.terminal.gwt.client.UIDL; /** * */ +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.Or") final public class VOr extends VAcceptCriterion implements VAcceptCallback { private boolean accepted; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java index bccb6656aa..e679b64369 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java @@ -8,6 +8,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.ServerSideCriterion") final public class VServerAccept extends VAcceptCriterion { @Override public void accept(final VDragEvent drag, UIDL 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 430b422b34..9bbabe9d29 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java @@ -9,6 +9,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.SourceIsTarget") final public class VSourceIsTarget extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java index 713a0d6646..7d92359f7d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java @@ -8,6 +8,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion("com.vaadin.event.dd.acceptcriteria.TargetDetailIs") final public class VTargetDetailIs extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java index f69fa85290..3db44f3162 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java @@ -11,6 +11,7 @@ import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.ui.tree.VTree; import com.vaadin.terminal.gwt.client.ui.tree.VTree.TreeNode; +@AcceptCriterion("com.vaadin.ui.Tree.TargetInSubtree") final public class VTargetInSubtree extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java index 57f6835bcc..2a78cc4433 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java @@ -57,6 +57,9 @@ public class DragAndDropWrapperConnector extends CustomComponentConnector getWidget().initDragStartMode(); getWidget().html5DataFlavors = uidl .getMapAttribute(VDragAndDropWrapper.HTML5_DATA_FLAVORS); + + // Used to prevent wrapper from stealing tooltips when not defined + getWidget().hasTooltip = getState().hasDescription(); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java index d09b81e1e1..4c36e92bbb 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java @@ -60,6 +60,8 @@ public class VDragAndDropWrapper extends VCustomComponent implements private static final String CLASSNAME = "v-ddwrapper"; protected static final String DRAGGABLE = "draggable"; + boolean hasTooltip = false; + public VDragAndDropWrapper() { super(); sinkEvents(VTooltip.TOOLTIP_EVENTS); @@ -67,6 +69,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements hookHtml5Events(getElement()); setStyleName(CLASSNAME); addDomHandler(new MouseDownHandler() { + public void onMouseDown(MouseDownEvent event) { if (startDrag(event.getNativeEvent())) { event.preventDefault(); // prevent text selection @@ -75,6 +78,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements }, MouseDownEvent.getType()); addDomHandler(new TouchStartHandler() { + public void onTouchStart(TouchStartEvent event) { if (startDrag(event.getNativeEvent())) { /* @@ -92,7 +96,8 @@ public class VDragAndDropWrapper extends VCustomComponent implements public void onBrowserEvent(Event event) { super.onBrowserEvent(event); - if (client != null) { + if (hasTooltip && client != null) { + // Override child tooltips if the wrapper has a tooltip defined client.handleTooltipEvent(event, this); } } @@ -172,6 +177,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements private boolean uploading; private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() { + public void onReadyStateChange(XMLHttpRequest xhr) { if (xhr.getReadyState() == XMLHttpRequest.DONE) { // visit server for possible @@ -269,6 +275,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements try { dragleavetimer = new Timer() { + @Override public void run() { // Yes, dragleave happens before drop. Makes no sense to me. @@ -455,6 +462,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements if (detailsChanged) { currentlyValid = false; validate(new VAcceptCallback() { + public void accepted(VDragEvent event) { dragAccepted(drag); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java index e3a0c9b321..81f24a8e7e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java +++ b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java @@ -4,7 +4,6 @@ package com.vaadin.terminal.gwt.client.ui.form; -import com.google.gwt.dom.client.Style.Display; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.shared.HandlerRegistration; @@ -27,7 +26,6 @@ public class VForm extends ComplexPanel implements KeyDownHandler { Widget lo; Element legend = DOM.createLegend(); Element caption = DOM.createSpan(); - private Element errorIndicatorElement = DOM.createDiv(); Element desc = DOM.createDiv(); Icon icon; VErrorMessage errorMessage = new VErrorMessage(); @@ -52,9 +50,6 @@ public class VForm extends ComplexPanel implements KeyDownHandler { setStyleName(CLASSNAME); fieldSet.appendChild(legend); legend.appendChild(caption); - errorIndicatorElement.setClassName("v-errorindicator"); - errorIndicatorElement.getStyle().setDisplay(Display.NONE); - errorIndicatorElement.setInnerText(" "); // needed for IE desc.setClassName("v-form-description"); fieldSet.appendChild(desc); // Adding description for initial padding // measurements, removed later if no diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java index 01ac4fab6a..dd6e741126 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java @@ -127,11 +127,4 @@ public class VNativeButton extends Button implements ClickHandler { clickPending = false; } - @Override - public void setEnabled(boolean enabled) { - if (isEnabled() != enabled) { - super.setEnabled(enabled); - setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled); - } - } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java index 0d222044ba..fb853b8a55 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java +++ b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java @@ -43,6 +43,8 @@ public class VNotification extends VOverlay { public static final String STYLE_SYSTEM = "system"; private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps + private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>(); + private int startOpacity = 90; private int fadeMsec = 400; private int delayMsec = 1000; @@ -159,6 +161,7 @@ public class VNotification extends VOverlay { addStyleDependentName(style); } super.show(); + notifications.add(this); setPosition(position); /** * Android 4 fails to render notifications correctly without a little @@ -180,6 +183,7 @@ public class VNotification extends VOverlay { temporaryStyle = null; } super.hide(); + notifications.remove(this); fireEvent(new HideEvent(this)); } @@ -435,4 +439,19 @@ public class VNotification extends VOverlay { public interface EventListener extends java.util.EventListener { public void notificationHidden(HideEvent event); } + + /** + * Moves currently visible notifications to the top of the event preview + * stack. Can be called when opening other overlays such as subwindows to + * ensure the notifications receive the events they need and don't linger + * indefinitely. See #7136. + * + * TODO Should this be a generic Overlay feature instead? + */ + public static void bringNotificationsToFront() { + for (VNotification notification : notifications) { + DOM.removeEventPreview(notification); + DOM.addEventPreview(notification); + } + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java index 5b97fc110f..d9096526f3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java @@ -111,6 +111,8 @@ public class PanelConnector extends AbstractComponentContainerConnector getWidget().captionNode.setClassName(captionClass); getWidget().contentNode.setClassName(contentClass); getWidget().bottomDecoration.setClassName(decoClass); + + getWidget().makeScrollable(); } if (!isRealUpdate(uidl)) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java index e2d3d443a0..6a06367acd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java @@ -6,8 +6,6 @@ package com.vaadin.terminal.gwt.client.ui.panel; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; @@ -18,6 +16,7 @@ import com.vaadin.terminal.gwt.client.ui.Icon; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, Focusable { @@ -46,7 +45,7 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, int scrollLeft; - private TouchScrollDelegate touchScrollDelegate; + private TouchScrollHandler touchScrollHandler; public VPanel() { super(); @@ -74,11 +73,11 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, setStyleName(CLASSNAME); DOM.sinkEvents(getElement(), Event.ONKEYDOWN); DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); - addHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); + + contentNode.getStyle().setProperty("position", "relative"); + getElement().getStyle().setProperty("overflow", "hidden"); + + makeScrollable(); } /** @@ -100,6 +99,7 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, * * @see com.vaadin.terminal.gwt.client.Focusable#focus() */ + public void focus() { setFocus(true); @@ -174,16 +174,17 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, } } - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(contentNode); - } - return touchScrollDelegate; - - } - public ShortcutActionHandler getShortcutActionHandler() { return shortcutHandler; } + /** + * Ensures the panel is scrollable eg. after style name changes + */ + void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(contentNode); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/PageClientRpc.java b/src/com/vaadin/terminal/gwt/client/ui/root/PageClientRpc.java new file mode 100644 index 0000000000..a02ecc8ded --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/root/PageClientRpc.java @@ -0,0 +1,13 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.root; + +import com.vaadin.terminal.gwt.client.communication.ClientRpc; + +public interface PageClientRpc extends ClientRpc { + + public void setTitle(String title); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java index 9be41a9623..1a62e566ad 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java @@ -61,6 +61,11 @@ public class RootConnector extends AbstractComponentContainerConnector @Override protected void init() { super.init(); + registerRpc(PageClientRpc.class, new PageClientRpc() { + public void setTitle(String title) { + com.google.gwt.user.client.Window.setTitle(title); + } + }); } public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { @@ -93,12 +98,9 @@ public class RootConnector extends AbstractComponentContainerConnector } getWidget().setStyleName(styles.trim()); - clickEventHandler.handleEventHandlerRegistration(); + getWidget().makeScrollable(); - if (!getWidget().isEmbedded() && getState().getCaption() != null) { - // only change window title if we're in charge of the whole page - com.google.gwt.user.client.Window.setTitle(getState().getCaption()); - } + clickEventHandler.handleEventHandlerRegistration(); // Process children int childIndex = 0; @@ -168,9 +170,6 @@ public class RootConnector extends AbstractComponentContainerConnector getWidget().id, client); } getWidget().actionHandler.updateActionMap(childUidl); - } else if (tag == "execJS") { - String script = childUidl.getStringAttribute("script"); - VRoot.eval(script); } else if (tag == "notifications") { for (final Iterator<?> it = childUidl.getChildIterator(); it .hasNext();) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java index 12a69d5556..0af8919280 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java @@ -7,6 +7,7 @@ package com.vaadin.terminal.gwt.client.ui.root; import java.util.ArrayList; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; @@ -26,6 +27,8 @@ import com.vaadin.terminal.gwt.client.Focusable; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; @@ -37,21 +40,36 @@ public class VRoot extends SimplePanel implements ResizeHandler, public static final String FRAGMENT_VARIABLE = "fragment"; + public static final String BROWSER_HEIGHT_VAR = "browserHeight"; + + public static final String BROWSER_WIDTH_VAR = "browserWidth"; + private static final String CLASSNAME = "v-view"; public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain"; + private static int MONITOR_PARENT_TIMER_INTERVAL = 1000; + String theme; String id; ShortcutActionHandler actionHandler; - /** stored width for IE resize optimization */ - private int width; + /* + * Last known window size used to detect whether VView should be layouted + * again. Detection must check window size, because the VView size might be + * fixed and thus not automatically adapt to changed window sizes. + */ + private int windowWidth; + private int windowHeight; - /** stored height for IE resize optimization */ - private int height; + /* + * Last know view size used to detect whether new dimensions should be sent + * to the server. + */ + private int viewWidth; + private int viewHeight; ApplicationConnection connection; @@ -59,13 +77,19 @@ public class VRoot extends SimplePanel implements ResizeHandler, public static final String CLICK_EVENT_ID = "click"; /** - * We are postponing resize process with IE. IE bugs with scrollbars in some - * situations, that causes false onWindowResized calls. With Timer we will - * give IE some time to decide if it really wants to keep current size - * (scrollbars). + * Keep track of possible parent size changes when an embedded application. + * + * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to + * keep track of when there is a real change. */ private Timer resizeTimer; + /** stored width of parent for embedded application auto-resize */ + private int parentWidth; + + /** stored height of parent for embedded application auto-resize */ + private int parentHeight; + int scrollTop; int scrollLeft; @@ -85,6 +109,8 @@ public class VRoot extends SimplePanel implements ResizeHandler, private HandlerRegistration historyHandlerRegistration; + private TouchScrollHandler touchScrollHandler; + /** * The current URI fragment, used to avoid sending updates if nothing has * changed. @@ -96,6 +122,7 @@ public class VRoot extends SimplePanel implements ResizeHandler, * whenever the value changes. */ private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() { + public void onValueChange(ValueChangeEvent<String> event) { String newFragment = event.getValue(); @@ -110,9 +137,9 @@ public class VRoot extends SimplePanel implements ResizeHandler, private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200, new ScheduledCommand() { + public void execute() { - windowSizeMaybeChanged(Window.getClientWidth(), - Window.getClientHeight()); + performSizeCheck(); } }); @@ -124,6 +151,29 @@ public class VRoot extends SimplePanel implements ResizeHandler, // Allow focusing the view by using the focus() method, the view // should not be in the document focus flow getElement().setTabIndex(-1); + makeScrollable(); + } + + /** + * Start to periodically monitor for parent element resizes if embedded + * application (e.g. portlet). + */ + @Override + protected void onLoad() { + super.onLoad(); + if (isMonitoringParentSize()) { + resizeTimer = new Timer() { + + @Override + public void run() { + // trigger check to see if parent size has changed, + // recalculate layouts + performSizeCheck(); + resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); + } + }; + resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); + } } @Override @@ -142,32 +192,95 @@ public class VRoot extends SimplePanel implements ResizeHandler, } /** - * Called when the window might have been resized. + * Stop monitoring for parent element resizes. + */ + + @Override + protected void onUnload() { + if (resizeTimer != null) { + resizeTimer.cancel(); + resizeTimer = null; + } + super.onUnload(); + } + + /** + * Called when the window or parent div might have been resized. * - * @param newWidth + * This immediately checks the sizes of the window and the parent div (if + * monitoring it) and triggers layout recalculation if they have changed. + */ + protected void performSizeCheck() { + windowSizeMaybeChanged(Window.getClientWidth(), + Window.getClientHeight()); + } + + /** + * Called when the window or parent div might have been resized. + * + * This immediately checks the sizes of the window and the parent div (if + * monitoring it) and triggers layout recalculation if they have changed. + * + * @param newWindowWidth * The new width of the window - * @param newHeight + * @param newWindowHeight * The new height of the window + * + * @deprecated use {@link #performSizeCheck()} */ - protected void windowSizeMaybeChanged(int newWidth, int newHeight) { + @Deprecated + protected void windowSizeMaybeChanged(int newWindowWidth, + int newWindowHeight) { boolean changed = false; ComponentConnector connector = ConnectorMap.get(connection) .getConnector(this); - if (width != newWidth) { - width = newWidth; + if (windowWidth != newWindowWidth) { + windowWidth = newWindowWidth; changed = true; - connector.getLayoutManager().reportOuterWidth(connector, newWidth); - VConsole.log("New window width: " + width); + connector.getLayoutManager().reportOuterWidth(connector, + newWindowWidth); + VConsole.log("New window width: " + windowWidth); } - if (height != newHeight) { - height = newHeight; + if (windowHeight != newWindowHeight) { + windowHeight = newWindowHeight; changed = true; - connector.getLayoutManager() - .reportOuterHeight(connector, newHeight); - VConsole.log("New window height: " + height); + connector.getLayoutManager().reportOuterHeight(connector, + newWindowHeight); + VConsole.log("New window height: " + windowHeight); + } + Element parentElement = getElement().getParentElement(); + if (isMonitoringParentSize() && parentElement != null) { + // check also for parent size changes + int newParentWidth = parentElement.getClientWidth(); + int newParentHeight = parentElement.getClientHeight(); + if (parentWidth != newParentWidth) { + parentWidth = newParentWidth; + changed = true; + VConsole.log("New parent width: " + parentWidth); + } + if (parentHeight != newParentHeight) { + parentHeight = newParentHeight; + changed = true; + VConsole.log("New parent height: " + parentHeight); + } } if (changed) { - VConsole.log("Running layout functions due to window resize"); + /* + * If the window size has changed, layout the VView again and send + * new size to the server if the size changed. (Just checking VView + * size would cause us to ignore cases when a relatively sized VView + * should shrink as the content's size is fixed and would thus not + * automatically shrink.) + */ + VConsole.log("Running layout functions due to window or parent resize"); + + // update size to avoid (most) redundant re-layout passes + // there can still be an extra layout recalculation if webkit + // overflow fix updates the size in a deferred block + if (isMonitoringParentSize() && parentElement != null) { + parentWidth = parentElement.getClientWidth(); + parentHeight = parentElement.getClientHeight(); + } sendClientResized(); @@ -188,21 +301,6 @@ public class VRoot extends SimplePanel implements ResizeHandler, }-*/; /** - * Evaluate the given script in the browser document. - * - * @param script - * Script to be executed. - */ - static native void eval(String script) - /*-{ - try { - if (script == null) return; - $wnd.eval(script); - } catch (e) { - } - }-*/; - - /** * Returns true if the body is NOT generated, i.e if someone else has made * the page that we're running in. Otherwise we're in charge of the whole * page. @@ -214,6 +312,17 @@ public class VRoot extends SimplePanel implements ResizeHandler, .contains(ApplicationConnection.GENERATED_BODY_CLASSNAME); } + /** + * Returns true if the size of the parent should be checked periodically and + * the application should react to its changes. + * + * @return true if size of parent should be tracked + */ + protected boolean isMonitoringParentSize() { + // could also perform a more specific check (Liferay portlet) + return isEmbedded(); + } + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -251,14 +360,19 @@ public class VRoot extends SimplePanel implements ResizeHandler, * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google * .gwt.event.logical.shared.ResizeEvent) */ + public void onResize(ResizeEvent event) { - onResize(); + triggerSizeChangeCheck(); } /** * Called when a resize event is received. + * + * This may trigger a lazy refresh or perform the size check immediately + * depending on the browser used and whether the server side requests + * resizes to be lazy. */ - void onResize() { + private void triggerSizeChangeCheck() { /* * IE (pre IE9 at least) will give us some false resize events due to * problems with scrollbars. Firefox 3 might also produce some extra @@ -274,8 +388,7 @@ public class VRoot extends SimplePanel implements ResizeHandler, if (lazy) { delayedResizeExecutor.trigger(); } else { - windowSizeMaybeChanged(Window.getClientWidth(), - Window.getClientHeight()); + performSizeCheck(); } } @@ -283,8 +396,19 @@ public class VRoot extends SimplePanel implements ResizeHandler, * Send new dimensions to the server. */ private void sendClientResized() { - connection.updateVariable(id, "height", height, false); - connection.updateVariable(id, "width", width, immediate); + Element parentElement = getElement().getParentElement(); + int viewHeight = parentElement.getClientHeight(); + int viewWidth = parentElement.getClientWidth(); + + connection.updateVariable(id, "height", viewHeight, false); + connection.updateVariable(id, "width", viewWidth, false); + + int windowWidth = Window.getClientWidth(); + int windowHeight = Window.getClientHeight(); + + connection.updateVariable(id, BROWSER_WIDTH_VAR, windowWidth, false); + connection.updateVariable(id, BROWSER_HEIGHT_VAR, windowHeight, + immediate); } public native static void goTo(String url) @@ -319,4 +443,13 @@ public class VRoot extends SimplePanel implements ResizeHandler, getElement().focus(); } + /** + * Ensures the root is scrollable eg. after style name changes. + */ + void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(getElement()); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java index 9ff614252d..5c7ee7a784 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java +++ b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java @@ -18,6 +18,7 @@ import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.ContainerResizedListener; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.VTooltip; import com.vaadin.terminal.gwt.client.ui.Field; import com.vaadin.terminal.gwt.client.ui.SimpleFocusablePanel; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; @@ -51,6 +52,7 @@ public class VSlider extends SimpleFocusablePanel implements Field, private final HTML feedback = new HTML("", false); private final VOverlay feedbackPopup = new VOverlay(true, false, true) { + @Override public void show() { super.show(); @@ -112,6 +114,8 @@ public class VSlider extends SimpleFocusablePanel implements Field, feedbackPopup.addStyleName(CLASSNAME + "-feedback"); feedbackPopup.setWidget(feedback); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); } void setFeedbackValue(double value) { @@ -139,8 +143,12 @@ public class VSlider extends SimpleFocusablePanel implements Field, void buildBase() { final String styleAttribute = vertical ? "height" : "width"; + final String oppositeStyleAttribute = vertical ? "width" : "height"; final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + // clear unnecessary opposite style attribute + DOM.setStyleAttribute(base, oppositeStyleAttribute, ""); + final Element p = DOM.getParent(getElement()); if (DOM.getElementPropertyInt(p, domProperty) > 50) { if (vertical) { @@ -153,6 +161,7 @@ public class VSlider extends SimpleFocusablePanel implements Field, // (supposedly) been drawn completely. DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px"); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { final Element p = DOM.getParent(getElement()); if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) { @@ -173,9 +182,14 @@ public class VSlider extends SimpleFocusablePanel implements Field, void buildHandle() { final String handleAttribute = vertical ? "marginTop" : "marginLeft"; + final String oppositeHandleAttribute = vertical ? "marginLeft" + : "marginTop"; DOM.setStyleAttribute(handle, handleAttribute, "0"); + // clear unnecessary opposite handle attribute + DOM.setStyleAttribute(handle, oppositeHandleAttribute, ""); + // Restore visibility DOM.setStyleAttribute(handle, "visibility", "visible"); @@ -277,6 +291,9 @@ public class VSlider extends SimpleFocusablePanel implements Field, event.preventDefault(); // avoid simulated events event.stopPropagation(); } + if (client != null) { + client.handleTooltipEvent(event, this); + } } private void processMouseWheelEvent(final Event event) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java index 10e5dbe37a..e33755bc9b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java @@ -127,6 +127,12 @@ public abstract class AbstractSplitPanelConnector extends getWidget().setStylenames(); + getWidget().minimumPosition = splitterState.getMinPosition() + + splitterState.getMinPositionUnit(); + + getWidget().maximumPosition = splitterState.getMaxPosition() + + splitterState.getMaxPositionUnit(); + getWidget().position = splitterState.getPosition() + splitterState.getPositionUnit(); @@ -136,6 +142,7 @@ public abstract class AbstractSplitPanelConnector extends getLayoutManager().setNeedsLayout(this); + getWidget().makeScrollable(); } public void layout() { diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java index 8b80eed840..db3a39d3a5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java @@ -49,6 +49,10 @@ public class AbstractSplitPanelState extends ComponentState { public static class SplitterState implements Serializable { private float position; private String positionUnit; + private float minPosition; + private String minPositionUnit; + private float maxPosition; + private String maxPositionUnit; private boolean positionReversed = false; private boolean locked = false; @@ -68,6 +72,38 @@ public class AbstractSplitPanelState extends ComponentState { this.positionUnit = positionUnit; } + public float getMinPosition() { + return minPosition; + } + + public void setMinPosition(float minPosition) { + this.minPosition = minPosition; + } + + public String getMinPositionUnit() { + return minPositionUnit; + } + + public void setMinPositionUnit(String minPositionUnit) { + this.minPositionUnit = minPositionUnit; + } + + public float getMaxPosition() { + return maxPosition; + } + + public void setMaxPosition(float maxPosition) { + this.maxPosition = maxPosition; + } + + public String getMaxPositionUnit() { + return maxPositionUnit; + } + + public void setMaxPositionUnit(String maxPositionUnit) { + this.maxPositionUnit = maxPositionUnit; + } + public boolean isPositionReversed() { return positionReversed; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java index 166e79e92e..e2f30c6676 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java @@ -4,6 +4,7 @@ package com.vaadin.terminal.gwt.client.ui.splitpanel; +import java.util.Collections; import java.util.List; import com.google.gwt.dom.client.Node; @@ -31,6 +32,7 @@ import com.vaadin.terminal.gwt.client.LayoutManager; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; import com.vaadin.terminal.gwt.client.ui.VOverlay; import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; @@ -76,7 +78,7 @@ public class VAbstractSplitPanel extends ComplexPanel { private boolean positionReversed = false; - List<String> componentStyleNames; + List<String> componentStyleNames = Collections.emptyList(); private Element draggingCurtain; @@ -87,12 +89,16 @@ public class VAbstractSplitPanel extends ComplexPanel { /* The current position of the split handle in either percentages or pixels */ String position; + String maximumPosition; + + String minimumPosition; + + private TouchScrollHandler touchScrollHandler; + protected Element scrolledContainer; protected int origScrollTop; - private TouchScrollDelegate touchScrollDelegate; - public VAbstractSplitPanel() { this(ORIENTATION_HORIZONTAL); } @@ -116,6 +122,8 @@ public class VAbstractSplitPanel extends ComplexPanel { setOrientation(orientation); sinkEvents(Event.MOUSEEVENTS); + makeScrollable(); + addDomHandler(new TouchCancelHandler() { public void onTouchCancel(TouchCancelEvent event) { // TODO When does this actually happen?? @@ -127,11 +135,8 @@ public class VAbstractSplitPanel extends ComplexPanel { Node target = event.getTouches().get(0).getTarget().cast(); if (splitter.isOrHasChild(target)) { onMouseDown(Event.as(event.getNativeEvent())); - } else { - getTouchScrollDelegate().onTouchStart(event); } } - }, TouchStartEvent.getType()); addDomHandler(new TouchMoveHandler() { public void onTouchMove(TouchMoveEvent event) { @@ -150,14 +155,6 @@ public class VAbstractSplitPanel extends ComplexPanel { } - private TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(firstContainer, - secondContainer); - } - return touchScrollDelegate; - } - protected void constructDom() { DOM.appendChild(splitter, DOM.createDiv()); // for styling DOM.appendChild(getElement(), wrapper); @@ -172,9 +169,7 @@ public class VAbstractSplitPanel extends ComplexPanel { DOM.setStyleAttribute(splitter, "position", "absolute"); DOM.setStyleAttribute(secondContainer, "position", "absolute"); - DOM.setStyleAttribute(firstContainer, "overflow", "auto"); - DOM.setStyleAttribute(secondContainer, "overflow", "auto"); - + setStylenames(); } private void setOrientation(int orientation) { @@ -190,11 +185,6 @@ public class VAbstractSplitPanel extends ComplexPanel { DOM.setStyleAttribute(firstContainer, "width", "100%"); DOM.setStyleAttribute(secondContainer, "width", "100%"); } - - DOM.setElementProperty(firstContainer, "className", CLASSNAME - + "-first-container"); - DOM.setElementProperty(secondContainer, "className", CLASSNAME - + "-second-container"); } @Override @@ -232,11 +222,109 @@ public class VAbstractSplitPanel extends ComplexPanel { } } + /** + * Converts given split position string (in pixels or percentage) to a + * floating point pixel value. + * + * @param pos + * @return + */ + private float convertToPixels(String pos) { + float posAsFloat; + if (pos.indexOf("%") > 0) { + posAsFloat = Math.round(Float.parseFloat(pos.substring(0, + pos.length() - 1)) + / 100 + * (orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() + : getOffsetHeight())); + } else { + posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 2)); + } + return posAsFloat; + } + + /** + * Converts given split position string (in pixels or percentage) to a float + * percentage value. + * + * @param pos + * @return + */ + private float convertToPercentage(String pos) { + float posAsFloat = 0; + + if (pos.indexOf("px") > 0) { + int posAsInt = Integer.parseInt(pos.substring(0, pos.length() - 2)); + int offsetLength = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() + : getOffsetHeight(); + + // 100% needs special handling + if (posAsInt + getSplitterSize() >= offsetLength) { + posAsInt = offsetLength; + } + posAsFloat = ((float) posAsInt / (float) offsetLength * 100); + + } else { + posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 1)); + } + return posAsFloat; + } + + /** + * Returns the given position clamped to the range between current minimum + * and maximum positions. + * + * TODO Should this be in the connector? + * + * @param pos + * Position of the splitter as a CSS string, either pixels or a + * percentage. + * @return minimumPosition if pos is less than minimumPosition; + * maximumPosition if pos is greater than maximumPosition; pos + * otherwise. + */ + private String checkSplitPositionLimits(String pos) { + float positionAsFloat = convertToPixels(pos); + + if (maximumPosition != null + && convertToPixels(maximumPosition) < positionAsFloat) { + pos = maximumPosition; + } else if (minimumPosition != null + && convertToPixels(minimumPosition) > positionAsFloat) { + pos = minimumPosition; + } + return pos; + } + + /** + * Converts given string to the same units as the split position is. + * + * @param pos + * position to be converted + * @return converted position string + */ + private String convertToPositionUnits(String pos) { + if (position.indexOf("%") != -1 && pos.indexOf("%") == -1) { + // position is in percentage, pos in pixels + pos = convertToPercentage(pos) + "%"; + } else if (position.indexOf("px") > 0 && pos.indexOf("px") == -1) { + // position is in pixels and pos in percentage + pos = convertToPixels(pos) + "px"; + } + + return pos; + } + void setSplitPosition(String pos) { if (pos == null) { return; } + pos = checkSplitPositionLimits(pos); + if (!pos.equals(position)) { + position = convertToPositionUnits(pos); + } + // Convert percentage values to pixels if (pos.indexOf("%") > 0) { int size = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() @@ -375,7 +463,6 @@ public class VAbstractSplitPanel extends ComplexPanel { } break; } - } void setFirstWidget(Widget w) { @@ -481,16 +568,7 @@ public class VAbstractSplitPanel extends ComplexPanel { } if (position.indexOf("%") > 0) { - float pos = newX; - // 100% needs special handling - if (newX + getSplitterSize() >= getOffsetWidth()) { - pos = getOffsetWidth(); - } - // Reversed position - if (positionReversed) { - pos = getOffsetWidth() - pos - getSplitterSize(); - } - position = (pos / getOffsetWidth() * 100) + "%"; + position = convertToPositionUnits(newX + "px"); } else { // Reversed position if (positionReversed) { @@ -523,16 +601,7 @@ public class VAbstractSplitPanel extends ComplexPanel { } if (position.indexOf("%") > 0) { - float pos = newY; - // 100% needs special handling - if (newY + getSplitterSize() >= getOffsetHeight()) { - pos = getOffsetHeight(); - } - // Reversed position - if (positionReversed) { - pos = getOffsetHeight() - pos - getSplitterSize(); - } - position = pos / getOffsetHeight() * 100 + "%"; + position = convertToPositionUnits(newY + "px"); } else { // Reversed position if (positionReversed) { @@ -659,30 +728,24 @@ public class VAbstractSplitPanel extends ComplexPanel { } void setStylenames() { - final String splitterSuffix = (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" - : "-vsplitter"); - final String firstContainerSuffix = "-first-container"; - final String secondContainerSuffix = "-second-container"; - String lockedSuffix = ""; - - String splitterStyle = CLASSNAME + splitterSuffix; - String firstStyle = CLASSNAME + firstContainerSuffix; - String secondStyle = CLASSNAME + secondContainerSuffix; - - if (locked) { - splitterStyle = CLASSNAME + splitterSuffix + "-locked"; - lockedSuffix = "-locked"; - } - for (String style : componentStyleNames) { - splitterStyle += " " + CLASSNAME + splitterSuffix + "-" + style - + lockedSuffix; - firstStyle += " " + CLASSNAME + firstContainerSuffix + "-" + style; - secondStyle += " " + CLASSNAME + secondContainerSuffix + "-" - + style; - } - DOM.setElementProperty(splitter, "className", splitterStyle); - DOM.setElementProperty(firstContainer, "className", firstStyle); - DOM.setElementProperty(secondContainer, "className", secondStyle); + final String splitterClass = CLASSNAME + + (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" + : "-vsplitter"); + final String firstContainerClass = CLASSNAME + "-first-container"; + final String secondContainerClass = CLASSNAME + "-second-container"; + final String lockedSuffix = locked ? "-locked" : ""; + + splitter.setClassName(splitterClass + lockedSuffix); + firstContainer.setClassName(firstContainerClass); + secondContainer.setClassName(secondContainerClass); + + for (String styleName : componentStyleNames) { + splitter.addClassName(splitterClass + "-" + styleName + + lockedSuffix); + firstContainer.addClassName(firstContainerClass + "-" + styleName); + secondContainer + .addClassName(secondContainerClass + "-" + styleName); + } } public void setEnabled(boolean enabled) { @@ -693,4 +756,14 @@ public class VAbstractSplitPanel extends ComplexPanel { return enabled; } + /** + * Ensures the panels are scrollable eg. after style name changes + */ + void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(firstContainer); + touchScrollHandler.addElement(secondContainer); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java index c45c26c4ac..c4a57f5c8b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java @@ -44,8 +44,6 @@ import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.user.client.Command; @@ -236,6 +234,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private boolean enableDebug = false; + private static final boolean hasNativeTouchScrolling = BrowserInfo.get() + .isTouchDevice() + && !BrowserInfo.get().requiresTouchScrollDelegate(); + + private Set<String> noncollapsibleColumns; + /** * Represents a select range of rows */ @@ -268,6 +272,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see java.lang.Object#toString() */ + @Override public String toString() { return startRow.getKey() + "-" + length; @@ -321,6 +326,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(true); private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { + public void onKeyPress(KeyPressEvent keyPressEvent) { // This is used for Firefox only, since Firefox auto-repeat // works correctly only if we use a key press handler, other @@ -489,12 +495,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public VScrollTable() { setMultiSelectMode(MULTISELECT_MODE_DEFAULT); - scrollBodyPanel.setStyleName(CLASSNAME + "-body-wrapper"); + scrollBodyPanel.addStyleName(CLASSNAME + "-body-wrapper"); scrollBodyPanel.addFocusHandler(this); scrollBodyPanel.addBlurHandler(this); scrollBodyPanel.addScrollHandler(this); - scrollBodyPanel.setStyleName(CLASSNAME + "-body"); + scrollBodyPanel.addStyleName(CLASSNAME + "-body"); /* * Firefox auto-repeat works correctly only if we use a key press @@ -509,14 +515,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, scrollBodyPanel.addKeyUpHandler(navKeyUpHandler); scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS); - scrollBodyPanel.addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU); scrollBodyPanel.addDomHandler(new ContextMenuHandler() { + public void onContextMenu(ContextMenuEvent event) { handleBodyContextMenu(event); } @@ -536,22 +538,13 @@ public class VScrollTable extends FlowPanel implements HasWidgets, // Add a handler to clear saved context menu details when the menu // closes. See #8526. client.getContextMenu().addCloseHandler(new CloseHandler<PopupPanel>() { + public void onClose(CloseEvent<PopupPanel> event) { contextMenu = null; } }); } - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate( - scrollBodyPanel.getElement()); - touchScrollDelegate.setScrollHandler(this); - } - return touchScrollDelegate; - - } - private void handleBodyContextMenu(ContextMenuEvent event) { if (enabled && bodyActionKeys != null) { int left = Util.getTouchOrMouseClientX(event.getNativeEvent()); @@ -896,6 +889,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, updateHeader(uidl.getStringArrayAttribute("vcolorder")); updateFooter(uidl.getStringArrayAttribute("vcolorder")); + if (uidl.hasVariable("noncollapsiblecolumns")) { + noncollapsibleColumns = uidl + .getStringArrayVariableAsSet("noncollapsiblecolumns"); + } } private void updateCollapsedColumns(UIDL uidl) { @@ -1848,12 +1845,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, isNewBody = false; if (firstvisible > 0) { - // FIXME #7607 - // Originally deferred due to Firefox oddities which should not - // occur any more. Currently deferring breaks Webkit scrolling with - // relative-height tables, but not deferring instead breaks tables - // with explicit page length. + // Deferred due to some Firefox oddities Scheduler.get().scheduleDeferred(new Command() { + public void execute() { scrollBodyPanel .setScrollPosition(measureRowHeightOffset(firstvisible)); @@ -1888,6 +1882,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, */ scrollBody.reLayoutComponents(); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); } @@ -2218,6 +2213,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, setWidth(tdWidth + "px"); } else { Scheduler.get().scheduleDeferred(new Command() { + public void execute() { int tdWidth = width + scrollBody.getCellExtraWidth(); @@ -2271,6 +2267,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Handle column reordering. */ + @Override public void onBrowserEvent(Event event) { if (enabled && event != null) { @@ -2814,6 +2811,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, final int newWidth = width; Scheduler.get().scheduleDeferred( new ScheduledCommand() { + public void execute() { setColWidth(colIx, newWidth, true); } @@ -2841,6 +2839,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (refreshContentWidths) { // Recalculate the column sizings if any column has changed Scheduler.get().scheduleDeferred(new ScheduledCommand() { + public void execute() { triggerLazyColumnAdjustment(true); } @@ -3029,6 +3028,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, String colKey; private boolean collapsed; + private boolean noncollapsible = false; private VScrollTableRow currentlyFocusedRow; public VisibleColumnAction(String colKey) { @@ -3040,6 +3040,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, @Override public void execute() { + if (noncollapsible) { + return; + } client.getContextMenu().hide(); // toggle selected column if (collapsedColumns.contains(colKey)) { @@ -3063,17 +3066,28 @@ public class VScrollTable extends FlowPanel implements HasWidgets, collapsed = b; } + public void setNoncollapsible(boolean b) { + noncollapsible = b; + } + /** * Override default method to distinguish on/off columns */ + @Override public String getHTML() { final StringBuffer buf = new StringBuffer(); + buf.append("<span class=\""); if (collapsed) { - buf.append("<span class=\"v-off\">"); + buf.append("v-off"); } else { - buf.append("<span class=\"v-on\">"); + buf.append("v-on"); + } + if (noncollapsible) { + buf.append(" v-disabled"); } + buf.append("\">"); + buf.append(super.getHTML()); buf.append("</span>"); @@ -3085,6 +3099,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * Returns columns as Action array for column select popup */ + public Action[] getActions() { Object[] cols; if (columnReordering && columnOrder != null) { @@ -3115,6 +3130,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (!c.isEnabled()) { a.setCollapsed(true); } + if (noncollapsibleColumns.contains(cid)) { + a.setNoncollapsible(true); + } actions[i] = a; } return actions; @@ -3200,6 +3218,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * The text in the footer */ public void setText(String footerText) { + if (footerText == null || footerText.equals("")) { + footerText = " "; + } + DOM.setInnerHTML(captionContainer, footerText); } @@ -3295,6 +3317,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, setWidth(tdWidth + "px"); } else { Scheduler.get().scheduleDeferred(new Command() { + public void execute() { int borderWidths = 1; int tdWidth = width @@ -3541,6 +3564,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client * .ui.Widget) */ + @Override public boolean remove(Widget w) { if (visibleCells.contains(w)) { @@ -3557,6 +3581,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see com.google.gwt.user.client.ui.HasWidgets#iterator() */ + public Iterator<Widget> iterator() { return visibleCells.iterator(); } @@ -3835,7 +3860,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, DOM.appendChild(container, preSpacer); DOM.appendChild(container, table); DOM.appendChild(container, postSpacer); - if (BrowserInfo.get().isTouchDevice()) { + if (BrowserInfo.get().requiresTouchScrollDelegate()) { NodeList<Node> childNodes = container.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Element item = (Element) childNodes.getItem(i); @@ -4388,7 +4413,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private String[] actionKeys = null; private final TableRowElement rowElement; - private boolean mDown; private int index; private Event touchStart; private static final String ROW_CLASSNAME_EVEN = CLASSNAME + "-row"; @@ -4396,8 +4420,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, + "-row-odd"; private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; private Timer contextTouchTimeout; + private Timer dragTouchTimeout; private int touchStartY; private int touchStartX; + private boolean isDragging = false; private VScrollTableRow(int rowKey) { this.rowKey = rowKey; @@ -4785,12 +4811,128 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } } + /** + * Special handler for touch devices that support native scrolling + * + * @return Whether the event was handled by this method. + */ + private boolean handleTouchEvent(final Event event) { + + boolean touchEventHandled = false; + + if (enabled && hasNativeTouchScrolling) { + final Element targetTdOrTr = getEventTargetTdOrTr(event); + final int type = event.getTypeInt(); + + switch (type) { + case Event.ONTOUCHSTART: + touchEventHandled = true; + touchStart = event; + isDragging = false; + Touch touch = event.getChangedTouches().get(0); + // save position to fields, touches in events are same + // instance during the operation. + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + + if (dragmode != 0) { + if (dragTouchTimeout == null) { + dragTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Start a drag if a finger is held + // in place long enough, then moved + isDragging = true; + } + } + }; + } + dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT); + } + + if (actionKeys != null) { + if (contextTouchTimeout == null) { + contextTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Open the context menu if finger + // is held in place long enough. + showContextMenu(touchStart); + event.preventDefault(); + touchStart = null; + } + } + }; + } + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + } + break; + case Event.ONTOUCHMOVE: + touchEventHandled = true; + if (isSignificantMove(event)) { + if (contextTouchTimeout != null) { + // Moved finger before the context menu timer + // expired, so let the browser handle this as a + // scroll. + contextTouchTimeout.cancel(); + contextTouchTimeout = null; + } + if (!isDragging && dragTouchTimeout != null) { + // Moved finger before the drag timer expired, + // so let the browser handle this as a scroll. + dragTouchTimeout.cancel(); + dragTouchTimeout = null; + } + + if (dragmode != 0 && touchStart != null + && isDragging) { + event.preventDefault(); + event.stopPropagation(); + startRowDrag(touchStart, type, targetTdOrTr); + } + touchStart = null; + } + break; + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + touchEventHandled = true; + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + } + if (dragTouchTimeout != null) { + dragTouchTimeout.cancel(); + } + if (touchStart != null) { + event.preventDefault(); + event.stopPropagation(); + if (!BrowserInfo.get().isAndroid()) { + Util.simulateClickFromTouchEvent(touchStart, + this); + } + touchStart = null; + } + isDragging = false; + break; + } + } + return touchEventHandled; + } + /* * React on click that occur on content cells only */ + @Override public void onBrowserEvent(final Event event) { - if (enabled) { + + final boolean touchEventHandled = handleTouchEvent(event); + + if (enabled && !touchEventHandled) { final int type = event.getTypeInt(); final Element targetTdOrTr = getEventTargetTdOrTr(event); if (type == Event.ONCONTEXTMENU) { @@ -4823,7 +4965,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, break; case Event.ONMOUSEUP: if (targetCellOrRowFound) { - mDown = false; /* * Queue here, send at the same time as the * corresponding value change event - see #7127 @@ -4983,6 +5124,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, touchStart.preventDefault(); if (dragmode != 0 || actionKeys != null) { new Timer() { + @Override public void run() { TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate @@ -5021,6 +5163,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (contextTouchTimeout == null && actionKeys != null) { contextTouchTimeout = new Timer() { + @Override public void run() { if (touchStart != null) { @@ -5066,9 +5209,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } break; case Event.ONMOUSEOUT: - if (targetCellOrRowFound) { - mDown = false; - } break; default: break; @@ -5098,7 +5238,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, protected void startRowDrag(Event event, final int type, Element targetTdOrTr) { - mDown = true; VTransferable transferable = new VTransferable(); transferable.setDragSource(ConnectorMap.get(client) .getConnector(VScrollTable.this)); @@ -5290,6 +5429,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see com.vaadin.terminal.gwt.client.ui.IActionOwner#getActions () */ + public Action[] getActions() { if (actionKeys == null) { return new Action[] {}; @@ -5299,6 +5439,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, final String actionKey = actionKeys[i]; final TreeAction a = new TreeAction(this, String.valueOf(rowKey), actionKey) { + @Override public void execute() { super.execute(); @@ -5586,6 +5727,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * column widths "optimally". Doing this lazily to avoid expensive * calculation when resizing is not yet finished. */ + @Override public void run() { if (scrollBody == null) { @@ -5682,7 +5824,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (isDynamicHeight() && totalRows == pageLength) { // fix body height (may vary if lazy loading is offhorizontal // scrollbar appears/disappears) - int bodyHeight = scrollBody.getRequiredHeight(); + int bodyHeight = Util.getRequiredHeight(scrollBody); boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth); if (needsSpaceForHorizontalScrollbar) { bodyHeight += Util.getNativeScrollbarSize(); @@ -5695,6 +5837,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } scrollBody.reLayoutComponents(); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); } @@ -5804,15 +5947,26 @@ public class VScrollTable extends FlowPanel implements HasWidgets, void updateHeight() { setContainerHeight(); - updatePageLength(); - + if (initializedAndAttached) { + updatePageLength(); + } if (!rendering) { // Webkit may sometimes get an odd rendering bug (white space // between header and body), see bug #3875. Running // overflow hack here to shake body element a bit. - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + // We must run the fix as a deferred command to prevent it from + // overwriting the scroll position with an outdated value, see + // #7607. + Scheduler.get().scheduleDeferred(new Command() { + + public void execute() { + Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + } + }); } + triggerLazyColumnAdjustment(false); + /* * setting height may affect wheter the component has scrollbars -> * needs scrolling or not @@ -5826,6 +5980,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * lost). Example ITabPanel just set contained components invisible and back * when changing tabs. */ + @Override public void setVisible(boolean visible) { if (isVisible() != visible) { @@ -5833,6 +5988,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (initializedAndAttached) { if (visible) { Scheduler.get().scheduleDeferred(new Command() { + public void execute() { scrollBodyPanel .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); @@ -5866,6 +6022,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * This method has logic which rows needs to be requested from server when * user scrolls */ + public void onScroll(ScrollEvent event) { scrollLeft = scrollBodyPanel.getElement().getScrollLeft(); scrollTop = scrollBodyPanel.getScrollPosition(); @@ -5894,6 +6051,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, // correct // value available soon. Scheduler.get().scheduleDeferred(new Command() { + public void execute() { onScroll(null); } @@ -5994,7 +6152,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return false; } - // @Override + // // public int hashCode() { // return overkey; // } @@ -6053,6 +6211,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, deEmphasis(); final TableDDDetails newDetails = dropDetails; VAcceptCallback cb = new VAcceptCallback() { + public void accepted(VDragEvent event) { if (newDetails.equals(dropDetails)) { dragAccepted(event); @@ -6459,6 +6618,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event * .dom.client.FocusEvent) */ + public void onFocus(FocusEvent event) { if (isFocusable()) { hasFocus = true; @@ -6479,6 +6639,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event * .dom.client.BlurEvent) */ + public void onBlur(BlurEvent event) { hasFocus = false; navKeyDown = false; @@ -6551,6 +6712,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see com.vaadin.terminal.gwt.client.Focusable#focus() */ + public void focus() { if (isFocusable()) { scrollBodyPanel.focus(); @@ -6594,6 +6756,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public void startScrollingVelocityTimer() { if (scrollingVelocityTimer == null) { scrollingVelocityTimer = new Timer() { + @Override public void run() { scrollingVelocity++; @@ -6630,6 +6793,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) { Scheduler.get().scheduleFinally(new ScheduledCommand() { + public void execute() { if (currentlyFocusedRow != null) { setRowFocus(currentlyFocusedRow); diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java index c97ede1252..aba5a41f9a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java @@ -331,6 +331,11 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } return width; } + + public Element getCloseButton() { + return closeButton; + } + } static class TabBar extends ComplexPanel implements ClickHandler, @@ -393,7 +398,14 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } public void onClick(ClickEvent event) { - Widget caption = (Widget) event.getSource(); + TabCaption caption = (TabCaption) event.getSource(); + Element targetElement = event.getNativeEvent().getEventTarget() + .cast(); + // the tab should not be focused if the close button was clicked + if (targetElement == caption.getCloseButton()) { + return; + } + int index = getWidgetIndex(caption.getParent()); // IE needs explicit focus() if (BrowserInfo.get().isIE()) { @@ -1021,6 +1033,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, final Style style = scroller.getStyle(); style.setProperty("whiteSpace", "normal"); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { style.setProperty("whiteSpace", ""); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java index f2b37c3a1c..bd6cddb682 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java @@ -4,16 +4,12 @@ package com.vaadin.terminal.gwt.client.ui.tabsheet; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; /** * A panel that displays all of its child widgets in a 'deck', where only one @@ -27,38 +23,15 @@ import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; public class VTabsheetPanel extends ComplexPanel { private Widget visibleWidget; - private TouchScrollDelegate touchScrollDelegate; + + private final TouchScrollHandler touchScrollHandler; /** * Creates an empty tabsheet panel. */ public VTabsheetPanel() { setElement(DOM.createDiv()); - sinkEvents(Event.TOUCHEVENTS); - addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - /* - * All container elements needs to be scrollable by one finger. - * Update the scrollable element list of touch delegate on each - * touch start. - */ - NodeList<Node> childNodes = getElement().getChildNodes(); - Element[] elements = new Element[childNodes.getLength()]; - for (int i = 0; i < elements.length; i++) { - elements[i] = (Element) childNodes.getItem(i); - } - getTouchScrollDelegate().setElements(elements); - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); - } - - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(); - } - return touchScrollDelegate; - + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); } /** @@ -77,8 +50,8 @@ public class VTabsheetPanel extends ComplexPanel { private Element createContainerElement() { Element el = DOM.createDiv(); DOM.setStyleAttribute(el, "position", "absolute"); - DOM.setStyleAttribute(el, "overflow", "auto"); hide(el); + touchScrollHandler.addElement(el); return el; } @@ -122,6 +95,7 @@ public class VTabsheetPanel extends ComplexPanel { if (parent != null) { DOM.removeChild(getElement(), parent); } + touchScrollHandler.removeElement(parent); } return removed; } @@ -141,6 +115,8 @@ public class VTabsheetPanel extends ComplexPanel { hide(DOM.getParent(visibleWidget.getElement())); } visibleWidget = newVisible; + touchScrollHandler.setElements(visibleWidget.getElement() + .getParentElement()); } // Always ensure the selected tab is visible. If server prevents a tab // change we might end up here with visibleWidget == newVisible but its diff --git a/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java index f7cd9d133e..9a8e0e9ce1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java @@ -379,26 +379,34 @@ public class VTreeTable extends VScrollTable { rowsToDelete.add(row); } } - RowCollapseAnimation anim = new RowCollapseAnimation(rowsToDelete) { - @Override - protected void onComplete() { - super.onComplete(); - // Actually unlink the rows and update the cache after the - // animation is done. - unlinkAndReindexRows(firstIndex, rows); - discardRowsOutsideCacheWindow(); - ensureCacheFilled(); - } - }; - anim.run(150); + if (!rowsToDelete.isEmpty()) { + // #8810 Only animate if there's something to animate + RowCollapseAnimation anim = new RowCollapseAnimation( + rowsToDelete) { + @Override + protected void onComplete() { + super.onComplete(); + // Actually unlink the rows and update the cache after + // the + // animation is done. + unlinkAndReindexRows(firstIndex, rows); + discardRowsOutsideCacheWindow(); + ensureCacheFilled(); + } + }; + anim.run(150); + } } protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData, int firstIndex, int rows) { List<VScrollTableRow> insertedRows = insertAndReindexRows(rowData, firstIndex, rows); - RowExpandAnimation anim = new RowExpandAnimation(insertedRows); - anim.run(150); + if (!insertedRows.isEmpty()) { + // Only animate if there's something to animate (#8810) + RowExpandAnimation anim = new RowExpandAnimation(insertedRows); + anim.run(150); + } return insertedRows; } @@ -521,6 +529,10 @@ public class VTreeTable extends VScrollTable { private Element cloneTable; private AnimationPreparator preparator; + /** + * @param rows + * List of rows to animate. Must not be empty. + */ public RowExpandAnimation(List<VScrollTableRow> rows) { this.rows = rows; buildAndInsertAnimatingDiv(); @@ -641,6 +653,10 @@ public class VTreeTable extends VScrollTable { private final List<VScrollTableRow> rows; + /** + * @param rows + * List of rows to animate. Must not be empty. + */ public RowCollapseAnimation(List<VScrollTableRow> rows) { super(rows); this.rows = rows; diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java b/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java index d08387fc6d..8fd84a9ea6 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java +++ b/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java @@ -40,6 +40,7 @@ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; /** * "Sub window" component. @@ -264,8 +265,10 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, if (!orderingDefered) { orderingDefered = true; Scheduler.get().scheduleFinally(new Command() { + public void execute() { doServerSideOrdering(); + VNotification.bringNotificationsToFront(); } }); } @@ -275,6 +278,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, orderingDefered = false; VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]); Arrays.sort(array, new Comparator<VWindow>() { + public int compare(VWindow o1, VWindow o2) { /* * Order by modality, then by bringtofront sequence. diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index 5b2be308a3..bf29144cc1 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -64,9 +64,6 @@ import com.vaadin.ui.Root; public abstract class AbstractApplicationPortlet extends GenericPortlet implements Constants { - private static final Logger logger = Logger - .getLogger(AbstractApplicationPortlet.class.getName()); - public static class WrappedHttpAndPortletRequest extends WrappedPortletRequest { @@ -203,6 +200,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet */ public static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme"; + public static final String WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE = "writeAjaxPageScriptWidgetsetShouldWrite"; + // TODO some parts could be shared with AbstractApplicationServlet // TODO Can we close the application when the portlet is removed? Do we know @@ -213,6 +212,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet private boolean productionMode = false; private DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration() { + public String getConfiguredWidgetset(WrappedRequest request) { String widgetset = getApplicationOrSystemProperty( @@ -271,6 +271,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * @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( @@ -329,7 +330,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * Print an information/warning message about running with xsrf * protection disabled */ - logger.warning(WARNING_XSRF_PROTECTION_DISABLED); + getLogger().warning(WARNING_XSRF_PROTECTION_DISABLED); } } @@ -345,7 +346,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet if (!productionMode) { /* Print an information/warning message about running in debug mode */ // TODO Maybe we need a different message for portlets? - logger.warning(NOT_PRODUCTION_MODE_INFO); + getLogger().warning(NOT_PRODUCTION_MODE_INFO); } } @@ -665,11 +666,12 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } catch (final SessionExpiredException e) { // TODO Figure out a better way to deal with // SessionExpiredExceptions - logger.finest("A user session has expired"); + getLogger().finest("A user session has expired"); } catch (final GeneralSecurityException e) { // TODO Figure out a better way to deal with // GeneralSecurityExceptions - logger.fine("General security exception, the security key was probably incorrect."); + getLogger() + .fine("General security exception, the security key was probably incorrect."); } catch (final Throwable e) { handleServiceException(request, response, application, e); } finally { @@ -690,9 +692,11 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet Root.setCurrentRoot(null); Application.setCurrentApplication(null); - requestTimer - .stop((AbstractWebApplicationContext) application - .getContext()); + PortletSession session = request + .getPortletSession(false); + if (session != null) { + requestTimer.stop(getApplicationContext(session)); + } } } } @@ -729,7 +733,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet private void handleUnknownRequest(PortletRequest request, PortletResponse response) { - logger.warning("Unknown request type"); + getLogger().warning("Unknown request type"); } /** @@ -795,8 +799,9 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet os.write(buffer, 0, bytes); } } else { - logger.info("Requested resource [" + resourceID - + "] could not be found"); + getLogger().info( + "Requested resource [" + resourceID + + "] could not be found"); response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(HttpServletResponse.SC_NOT_FOUND)); } @@ -1141,4 +1146,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet return PortletApplicationContext2.getApplicationContext(portletSession); } + private static final Logger getLogger() { + return Logger.getLogger(AbstractApplicationPortlet.class.getName()); + } + } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 905cfe7e3c..2179761d31 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -87,9 +87,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // TODO Move some (all?) of the constants to a separate interface (shared // with portlet) - private static final Logger logger = Logger - .getLogger(AbstractApplicationServlet.class.getName()); - private Properties applicationProperties; private boolean productionMode = false; @@ -99,6 +96,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements private int resourceCacheTime = 3600; private DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration() { + public String getStaticFileLocation(WrappedRequest request) { HttpServletRequest servletRequest = WrappedHttpServletRequest .cast(request); @@ -149,8 +147,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * if an exception has occurred that interferes with the * servlet's normal operation. */ - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public void init(javax.servlet.ServletConfig servletConfig) throws javax.servlet.ServletException { super.init(servletConfig); @@ -186,7 +184,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * Print an information/warning message about running with xsrf * protection disabled */ - logger.warning(WARNING_XSRF_PROTECTION_DISABLED); + getLogger().warning(WARNING_XSRF_PROTECTION_DISABLED); } } @@ -200,7 +198,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (!productionMode) { /* Print an information/warning message about running in debug mode */ - logger.warning(NOT_PRODUCTION_MODE_INFO); + getLogger().warning(NOT_PRODUCTION_MODE_INFO); } } @@ -214,7 +212,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } catch (NumberFormatException nfe) { // Default is 1h resourceCacheTime = 3600; - logger.warning(WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC); + getLogger().warning(WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC); } } @@ -333,6 +331,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * @throws IOException * if the request for the TRACE cannot be handled. */ + @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -478,9 +477,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements Root.setCurrentRoot(null); Application.setCurrentApplication(null); - requestTimer - .stop((AbstractWebApplicationContext) application - .getContext()); + HttpSession session = request.getSession(false); + if (session != null) { + requestTimer.stop(getApplicationContext(session)); + } } } @@ -796,8 +796,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements resultPath = url.getFile(); } catch (final Exception e) { // FIXME: Handle exception - logger.log(Level.INFO, "Could not find resource path " + path, - e); + getLogger().log(Level.INFO, + "Could not find resource path " + path, e); } } return resultPath; @@ -1054,10 +1054,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (resourceUrl == null) { // cannot serve requested file - logger.info("Requested resource [" - + filename - + "] not found from filesystem or through class loader." - + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder."); + getLogger() + .info("Requested resource [" + + filename + + "] not found from filesystem or through class loader." + + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder."); response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } @@ -1065,9 +1066,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // security check: do not permit navigation out of the VAADIN // directory if (!isAllowedVAADINResourceUrl(request, resourceUrl)) { - logger.info("Requested resource [" - + filename - + "] not accessible in the VAADIN directory or access to it is forbidden."); + getLogger() + .info("Requested resource [" + + filename + + "] not accessible in the VAADIN directory or access to it is forbidden."); response.setStatus(HttpServletResponse.SC_FORBIDDEN); return; } @@ -1090,10 +1092,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } } catch (Exception e) { // Failed to find out last modified timestamp. Continue without it. - logger.log( - Level.FINEST, - "Failed to find out last modified timestamp. Continuing without it.", - e); + getLogger() + .log(Level.FINEST, + "Failed to find out last modified timestamp. Continuing without it.", + e); } finally { if (connection instanceof URLConnection) { try { @@ -1105,7 +1107,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements is.close(); } } catch (IOException e) { - logger.log(Level.INFO, + getLogger().log(Level.INFO, "Error closing URLConnection input stream", e); } } @@ -1130,7 +1132,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * parameter in web.xml */ response.setHeader("Cache-Control", - "max-age: " + String.valueOf(resourceCacheTime)); + "max-age= " + String.valueOf(resourceCacheTime)); } // Write the resource to the client. @@ -1173,12 +1175,14 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // loader sees it. if (!resourceUrl.getPath().contains("!/VAADIN/")) { - logger.info("Blocked attempt to access a JAR entry not starting with /VAADIN/: " - + resourceUrl); + getLogger().info( + "Blocked attempt to access a JAR entry not starting with /VAADIN/: " + + resourceUrl); return false; } - logger.fine("Accepted access to a JAR entry using a class loader: " - + resourceUrl); + getLogger().fine( + "Accepted access to a JAR entry using a class loader: " + + resourceUrl); return true; } else { // Some servers such as GlassFish extract files from JARs (file:) @@ -1188,11 +1192,13 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // "/../" if (!resourceUrl.getPath().contains("/VAADIN/") || resourceUrl.getPath().contains("/../")) { - logger.info("Blocked attempt to access file: " + resourceUrl); + getLogger().info( + "Blocked attempt to access file: " + resourceUrl); return false; } - logger.fine("Accepted access to a file using a class loader: " - + resourceUrl); + getLogger().fine( + "Accepted access to a file using a class loader: " + + resourceUrl); return true; } } @@ -1733,4 +1739,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements c > 96 && c < 123 // a-z ; } + + private static final Logger getLogger() { + return Logger.getLogger(AbstractApplicationServlet.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 494ca7e28e..e04857f800 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -92,9 +92,6 @@ public abstract class AbstractCommunicationManager implements Serializable { private static final String DASHDASH = "--"; - private static final Logger logger = Logger - .getLogger(AbstractCommunicationManager.class.getName()); - private static final RequestHandler APP_RESOURCE_HANDLER = new ApplicationResourceHandler(); private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler(); @@ -539,7 +536,7 @@ public abstract class AbstractCommunicationManager implements Serializable { if (root == null) { // This should not happen, no windows exists but // application is still open. - logger.warning("Could not get root for application"); + getLogger().warning("Could not get root for application"); return; } } else { @@ -562,7 +559,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // FIXME: Handle exception // Not critical, but something is still wrong; print // stacktrace - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e2); } if (ci != null) { @@ -604,8 +601,9 @@ public abstract class AbstractCommunicationManager implements Serializable { } if (!Version.getFullVersion().equals(widgetsetVersion)) { - logger.warning(String.format(Constants.WIDGETSET_MISMATCH_INFO, - Version.getFullVersion(), widgetsetVersion)); + getLogger().warning( + String.format(Constants.WIDGETSET_MISMATCH_INFO, + Version.getFullVersion(), widgetsetVersion)); } } @@ -638,7 +636,7 @@ public abstract class AbstractCommunicationManager implements Serializable { printHighlightedComponentHierarchy(sb, component); } - logger.info(sb.toString()); + getLogger().info(sb.toString()); } protected void printHighlightedComponentHierarchy(StringBuilder sb, @@ -767,7 +765,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // Paints components DirtyConnectorTracker rootConnectorTracker = root .getDirtyConnectorTracker(); - logger.log(Level.FINE, "* Creating response to client"); + getLogger().log(Level.FINE, "* Creating response to client"); if (repaintAll) { getClientCache(root).clear(); rootConnectorTracker.markAllConnectorsDirty(); @@ -780,8 +778,10 @@ public abstract class AbstractCommunicationManager implements Serializable { dirtyVisibleConnectors .addAll(getDirtyVisibleConnectors(rootConnectorTracker)); - logger.log(Level.FINE, "Found " + dirtyVisibleConnectors.size() - + " dirty connectors to paint"); + getLogger().log( + Level.FINE, + "Found " + dirtyVisibleConnectors.size() + + " dirty connectors to paint"); for (ClientConnector connector : dirtyVisibleConnectors) { if (connector instanceof Component) { ((Component) connector).updateState(); @@ -841,7 +841,8 @@ public abstract class AbstractCommunicationManager implements Serializable { try { referenceState = stateType.newInstance(); } catch (Exception e) { - logger.log(Level.WARNING, + getLogger().log( + Level.WARNING, "Error creating reference object for state of type " + stateType.getName()); } @@ -1006,16 +1007,16 @@ public abstract class AbstractCommunicationManager implements Serializable { (Class[]) null); ci = (Application.SystemMessages) m.invoke(null, (Object[]) null); } catch (NoSuchMethodException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } catch (IllegalArgumentException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } catch (IllegalAccessException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } catch (InvocationTargetException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } @@ -1054,8 +1055,8 @@ public abstract class AbstractCommunicationManager implements Serializable { is = getThemeResourceAsStream(root, getTheme(root), resource); } catch (final Exception e) { // FIXME: Handle exception - logger.log(Level.FINER, "Failed to get theme resource stream.", - e); + getLogger().log(Level.FINER, + "Failed to get theme resource stream.", e); } if (is != null) { @@ -1074,13 +1075,13 @@ public abstract class AbstractCommunicationManager implements Serializable { r.close(); } catch (final java.io.IOException e) { // FIXME: Handle exception - logger.log(Level.INFO, "Resource transfer failed", e); + getLogger().log(Level.INFO, "Resource transfer failed", e); } outWriter.print("\"" + JsonPaintTarget.escapeJSON(layout.toString()) + "\""); } else { // FIXME: Handle exception - logger.severe("CustomLayout not found: " + resource); + getLogger().severe("CustomLayout not found: " + resource); } } outWriter.print("}"); @@ -1171,8 +1172,9 @@ public abstract class AbstractCommunicationManager implements Serializable { } sortByHierarchy((List) legacyComponents); for (Vaadin6Component c : legacyComponents) { - logger.fine("Painting Vaadin6Component " + c.getClass().getName() - + "@" + Integer.toHexString(c.hashCode())); + getLogger().fine( + "Painting Vaadin6Component " + c.getClass().getName() + "@" + + Integer.toHexString(c.hashCode())); paintTarget.startTag("change"); final String pid = c.getConnectorId(); paintTarget.addAttribute("pid", pid); @@ -1187,6 +1189,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // containers rely on that their updateFromUIDL method has been called // before children start calling e.g. updateCaption Collections.sort(paintables, new Comparator<Component>() { + public int compare(Component c1, Component c2) { int depth1 = 0; while (c1.getParent() != null) { @@ -1472,6 +1475,7 @@ public abstract class AbstractCommunicationManager implements Serializable { enabledConnectors.add(connector); } } + for (int i = 0; i < invocations.size(); i++) { MethodInvocation invocation = invocations.get(i); @@ -1479,7 +1483,7 @@ public abstract class AbstractCommunicationManager implements Serializable { invocation.getConnectorId()); if (connector == null) { - logger.log( + getLogger().log( Level.WARNING, "RPC call to " + invocation.getInterfaceName() + "." + invocation.getMethodName() @@ -1517,7 +1521,7 @@ public abstract class AbstractCommunicationManager implements Serializable { msg += ", caption=" + caption; } } - logger.warning(msg); + getLogger().warning(msg); continue; } @@ -1545,14 +1549,13 @@ public abstract class AbstractCommunicationManager implements Serializable { } handleChangeVariablesError(app, errorComponent, e, changes); - } } } - } catch (JSONException e) { - logger.warning("Unable to parse RPC call from the client: " - + e.getMessage()); + getLogger().warning( + "Unable to parse RPC call from the client: " + + e.getMessage()); // TODO or return success = false? throw new RuntimeException(e); } @@ -1895,8 +1898,9 @@ public abstract class AbstractCommunicationManager implements Serializable { DateFormat dateFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, l); if (!(dateFormat instanceof SimpleDateFormat)) { - logger.warning("Unable to get default date pattern for locale " - + l.toString()); + getLogger().warning( + "Unable to get default date pattern for locale " + + l.toString()); dateFormat = new SimpleDateFormat(); } final String df = ((SimpleDateFormat) dateFormat).toPattern(); @@ -2095,7 +2099,8 @@ public abstract class AbstractCommunicationManager implements Serializable { if (id == null) { id = nextTypeKey++; typeToKey.put(class1, id); - logger.log(Level.FINE, "Mapping " + class1.getName() + " to " + id); + getLogger().log(Level.FINE, + "Mapping " + class1.getName() + " to " + id); } return id.toString(); } @@ -2232,7 +2237,7 @@ public abstract class AbstractCommunicationManager implements Serializable { writeUidlResponse(request, true, pWriter, root, false); pWriter.print("}"); String initialUIDL = sWriter.toString(); - logger.log(Level.FINE, "Initial UIDL:" + initialUIDL); + getLogger().log(Level.FINE, "Initial UIDL:" + initialUIDL); return initialUIDL; } @@ -2386,4 +2391,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } } + private static final Logger getLogger() { + return Logger.getLogger(AbstractCommunicationManager.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java b/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java index c0ae0afc26..bf4ea860a8 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java @@ -32,9 +32,6 @@ import com.vaadin.terminal.ApplicationResource; public abstract class AbstractWebApplicationContext implements ApplicationContext, HttpSessionBindingListener, Serializable { - private static final Logger logger = Logger - .getLogger(AbstractWebApplicationContext.class.getName()); - protected Collection<TransactionListener> listeners = Collections .synchronizedList(new LinkedList<TransactionListener>()); @@ -145,7 +142,7 @@ public abstract class AbstractWebApplicationContext implements // remove same application here. Possible if you got e.g. session // lifetime 1 min but socket write may take longer than 1 min. // FIXME: Handle exception - logger.log(Level.SEVERE, + getLogger().log(Level.SEVERE, "Could not remove application, leaking memory.", e); } } @@ -252,4 +249,8 @@ public abstract class AbstractWebApplicationContext implements return lastRequestTime; } + private Logger getLogger() { + return Logger.getLogger(AbstractWebApplicationContext.class.getName()); + } + }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/ClientConnector.java b/src/com/vaadin/terminal/gwt/server/ClientConnector.java index 359e112738..dfdd58879d 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientConnector.java +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -3,7 +3,7 @@ */ package com.vaadin.terminal.gwt.server; -import java.util.Iterator; +import java.util.Collection; import java.util.List; import com.vaadin.terminal.AbstractClientConnector; @@ -12,7 +12,6 @@ import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.ui.Component; import com.vaadin.ui.ComponentContainer; -import com.vaadin.ui.Root; /** * Interface implemented by all connectors that are capable of communicating @@ -30,8 +29,6 @@ public interface ClientConnector extends Connector, RpcTarget { * * @return an unmodifiable ordered list of pending server to client method * calls (not null) - * - * @since 7.0 */ public List<ClientMethodInvocation> retrievePendingRpcCalls(); @@ -61,7 +58,7 @@ public interface ClientConnector extends Connector, RpcTarget { * Causes a repaint of this connector, and all connectors below it. * * This should only be used in special cases, e.g when the state of a - * descendant depends on the state of a ancestor. + * descendant depends on the state of an ancestor. */ public void requestRepaintAll(); @@ -69,18 +66,18 @@ public interface ClientConnector extends Connector, RpcTarget { * Sets the parent connector of the connector. * * <p> - * This method automatically calls {@link #attach()} if the parent becomes - * attached to the application, regardless of whether it was attached - * previously. Conversely, if the parent is {@code null} and the connector - * is attached to the application, {@link #detach()} is called for the - * connector. + * This method automatically calls {@link #attach()} if the connector + * becomes attached to the application, regardless of whether it was + * attached previously. Conversely, if the parent is {@code null} and the + * connector is attached to the application, {@link #detach()} is called for + * the connector. * </p> * <p> * This method is rarely called directly. One of the * {@link ComponentContainer#addComponent(Component)} or - * {@link AbstractClientConnector#addFeature(Feature)} methods is normally - * used for adding connectors to a container and it will call this method - * implicitly. + * {@link AbstractClientConnector#addExtension(Extension)} methods are + * normally used for adding connectors to a parent and they will call this + * method implicitly. * </p> * * <p> @@ -103,15 +100,13 @@ public interface ClientConnector extends Connector, RpcTarget { * The caller of this method is {@link #setParent(ClientConnector)} if the * parent is itself already attached to the application. If not, the parent * will call the {@link #attach()} for all its children when it is attached - * to the application. This method is always called before the connector is - * painted for the first time. + * to the application. This method is always called before the connector's + * data is sent to the client-side for the first time. * </p> * * <p> * The attachment logic is implemented in {@link AbstractClientConnector}. * </p> - * - * @see #getApplication() */ public void attach(); @@ -119,25 +114,26 @@ public interface ClientConnector extends Connector, RpcTarget { * Notifies the component that it is detached from the application. * * <p> - * The {@link #getApplication()} and {@link #getRoot()} methods might return - * <code>null</code> after this method is called. - * </p> - * - * <p> - * This method must call {@link Root#componentDetached(Component)} to let - * the Root know that a new Component has been attached. - * </p> - * * - * <p> - * The caller of this method is {@link #setParent(Component)} if the parent - * is in the application. When the parent is detached from the application - * it is its response to call {@link #detach()} for all the children and to - * detach itself from the terminal. + * The caller of this method is {@link #setParent(ClientConnector)} if the + * parent is in the application. When the parent is detached from the + * application it is its response to call {@link #detach()} for all the + * children and to detach itself from the terminal. * </p> */ public void detach(); - public Iterator<Extension> getExtensionIterator(); + /** + * Get a read-only collection of all extensions attached to this connector. + * + * @return a collection of extensions + */ + public Collection<Extension> getExtensions(); + /** + * Remove an extension from this connector. + * + * @param extension + * the extension to remove. + */ public void removeExtension(Extension extension); } diff --git a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java index 335067ca7a..171d440796 100644 --- a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java +++ b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java @@ -34,9 +34,6 @@ import com.vaadin.ui.Window; @SuppressWarnings({ "serial", "deprecation" }) public class ComponentSizeValidator implements Serializable { - private final static Logger logger = Logger - .getLogger(ComponentSizeValidator.class.getName()); - private final static int LAYERS_SHOWN = 4; /** @@ -134,7 +131,7 @@ public class ComponentSizeValidator implements Serializable { return parentCanDefineHeight(component); } catch (Exception e) { - logger.log(Level.FINER, + getLogger().log(Level.FINER, "An exception occurred while validating sizes.", e); return true; } @@ -154,7 +151,7 @@ public class ComponentSizeValidator implements Serializable { return parentCanDefineWidth(component); } catch (Exception e) { - logger.log(Level.FINER, + getLogger().log(Level.FINER, "An exception occurred while validating sizes.", e); return true; } @@ -653,11 +650,15 @@ public class ComponentSizeValidator implements Serializable { return; } catch (Exception e) { // TODO Auto-generated catch block - logger.log(Level.FINER, + getLogger().log(Level.FINER, "An exception occurred while validating sizes.", e); } } } + private static Logger getLogger() { + return Logger.getLogger(ComponentSizeValidator.class.getName()); + } + } diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java index 68fb87a986..0e8d1c0152 100644 --- a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -4,7 +4,8 @@ package com.vaadin.terminal.gwt.server; import java.io.PrintWriter; -import java.util.Iterator; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -28,9 +29,6 @@ import com.vaadin.ui.Component; public class DragAndDropService implements VariableOwner, ClientConnector { - private static final Logger logger = Logger - .getLogger(DragAndDropService.class.getName()); - private int lastVisitId; private boolean lastVisitAccepted = false; @@ -50,8 +48,9 @@ public class DragAndDropService implements VariableOwner, ClientConnector { // Validate drop handler owner if (!(owner instanceof DropTarget)) { - logger.severe("DropHandler owner " + owner - + " must implement DropTarget"); + getLogger() + .severe("DropHandler owner " + owner + + " must implement DropTarget"); return; } // owner cannot be null here @@ -81,8 +80,9 @@ public class DragAndDropService implements VariableOwner, ClientConnector { DropHandler dropHandler = (dropTarget).getDropHandler(); if (dropHandler == null) { // No dropHandler returned so no drop can be performed. - logger.fine("DropTarget.getDropHandler() returned null for owner: " - + dropTarget); + getLogger().fine( + "DropTarget.getDropHandler() returned null for owner: " + + dropTarget); return; } @@ -275,13 +275,16 @@ public class DragAndDropService implements VariableOwner, ClientConnector { } - public Iterator<Extension> getExtensionIterator() { + public Collection<Extension> getExtensions() { // TODO Auto-generated method stub - return null; + return Collections.emptySet(); } public void removeExtension(Extension extension) { // TODO Auto-generated method stub + } + private Logger getLogger() { + return Logger.getLogger(DragAndDropService.class.getName()); } } diff --git a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java index 485c98f036..a6032fa98d 100644 --- a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java @@ -94,9 +94,6 @@ import com.vaadin.service.ApplicationContext; */ public class GAEApplicationServlet extends ApplicationServlet { - private static final Logger logger = Logger - .getLogger(GAEApplicationServlet.class.getName()); - // memcache mutex is MUTEX_BASE + sessio id private static final String MUTEX_BASE = "_vmutex"; @@ -209,8 +206,9 @@ public class GAEApplicationServlet extends ApplicationServlet { try { Thread.sleep(RETRY_AFTER_MILLISECONDS); } catch (InterruptedException e) { - logger.finer("Thread.sleep() interrupted while waiting for lock. Trying again. " - + e); + getLogger().finer( + "Thread.sleep() interrupted while waiting for lock. Trying again. " + + e); } } @@ -252,16 +250,16 @@ public class GAEApplicationServlet extends ApplicationServlet { ds.put(entity); } catch (DeadlineExceededException e) { - logger.warning("DeadlineExceeded for " + session.getId()); + getLogger().warning("DeadlineExceeded for " + session.getId()); sendDeadlineExceededNotification(request, response); } catch (NotSerializableException e) { - logger.log(Level.SEVERE, "Not serializable!", e); + getLogger().log(Level.SEVERE, "Not serializable!", e); // TODO this notification is usually not shown - should we redirect // in some other way - can we? sendNotSerializableNotification(request, response); } catch (Exception e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "An exception occurred while servicing request.", e); sendCriticalErrorNotification(request, response); @@ -308,12 +306,14 @@ public class GAEApplicationServlet extends ApplicationServlet { session.setAttribute(WebApplicationContext.class.getName(), applicationContext); } catch (IOException e) { - logger.log(Level.WARNING, + getLogger().log( + Level.WARNING, "Could not de-serialize ApplicationContext for " + session.getId() + " A new one will be created. ", e); } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, + getLogger().log( + Level.WARNING, "Could not de-serialize ApplicationContext for " + session.getId() + " A new one will be created. ", e); @@ -368,8 +368,9 @@ public class GAEApplicationServlet extends ApplicationServlet { List<Entity> entities = pq.asList(Builder .withLimit(CLEANUP_LIMIT)); if (entities != null) { - logger.info("Vaadin cleanup deleting " + entities.size() - + " expired Vaadin sessions."); + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired Vaadin sessions."); List<Key> keys = new ArrayList<Key>(); for (Entity e : entities) { keys.add(e.getKey()); @@ -387,8 +388,9 @@ public class GAEApplicationServlet extends ApplicationServlet { List<Entity> entities = pq.asList(Builder .withLimit(CLEANUP_LIMIT)); if (entities != null) { - logger.info("Vaadin cleanup deleting " + entities.size() - + " expired appengine sessions."); + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired appengine sessions."); List<Key> keys = new ArrayList<Key>(); for (Entity e : entities) { keys.add(e.getKey()); @@ -397,7 +399,11 @@ public class GAEApplicationServlet extends ApplicationServlet { } } } catch (Exception e) { - logger.log(Level.WARNING, "Exception while cleaning.", e); + getLogger().log(Level.WARNING, "Exception while cleaning.", e); } } + + private static final Logger getLogger() { + return Logger.getLogger(GAEApplicationServlet.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java index 1cde164618..70ab452e4e 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java +++ b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java @@ -43,9 +43,6 @@ import com.vaadin.ui.CustomLayout; @SuppressWarnings("serial") public class JsonPaintTarget implements PaintTarget { - private static final Logger logger = Logger.getLogger(JsonPaintTarget.class - .getName()); - /* Document type declarations */ private final static String UIDL_ARG_NAME = "name"; @@ -162,6 +159,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws Paintexception * if the paint operation failed. */ + public void endTag(String tagName) throws PaintException { // In case of null data output nothing: if (tagName == null) { @@ -328,6 +326,7 @@ public class JsonPaintTarget implements PaintTarget { * if the paint operation failed. * */ + public void addText(String str) throws PaintException { tag.addData("\"" + escapeJSON(str) + "\""); } @@ -468,8 +467,8 @@ public class JsonPaintTarget implements PaintTarget { tag.addVariable(new StringVariable(owner, name, escapeJSON(value))); } - public void addVariable(VariableOwner owner, String name, - Component value) throws PaintException { + public void addVariable(VariableOwner owner, String name, Component value) + throws PaintException { tag.addVariable(new StringVariable(owner, name, value.getConnectorId())); } @@ -516,6 +515,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws PaintException * if the paint operation failed. */ + public void addUploadStreamVariable(VariableOwner owner, String name) throws PaintException { startTag("uploadstream"); @@ -535,6 +535,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws PaintException * if the paint operation failed. */ + public void addSection(String sectionTagName, String sectionData) throws PaintException { tag.addData("{\"" + sectionTagName + "\":\"" + escapeJSON(sectionData) @@ -549,6 +550,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws PaintException * if the paint operation failed. */ + public void addUIDL(String xml) throws PaintException { // Ensure that the target is open @@ -582,6 +584,7 @@ public class JsonPaintTarget implements PaintTarget { * @see com.vaadin.terminal.PaintTarget#addXMLSection(String, String, * String) */ + public void addXMLSection(String sectionTagName, String sectionData, String namespace) throws PaintException { @@ -646,12 +649,14 @@ public class JsonPaintTarget implements PaintTarget { * @see com.vaadin.terminal.PaintTarget#startPaintable(com.vaadin.terminal * .Paintable, java.lang.String) */ + public PaintStatus startPaintable(Component connector, String tagName) throws PaintException { boolean topLevelPaintable = openPaintables.isEmpty(); - logger.fine("startPaintable for " + connector.getClass().getName() - + "@" + Integer.toHexString(connector.hashCode())); + getLogger().fine( + "startPaintable for " + connector.getClass().getName() + "@" + + Integer.toHexString(connector.hashCode())); startTag(tagName, true); openPaintables.push(connector); @@ -672,8 +677,9 @@ public class JsonPaintTarget implements PaintTarget { } public void endPaintable(Component paintable) throws PaintException { - logger.fine("endPaintable for " + paintable.getClass().getName() + "@" - + Integer.toHexString(paintable.hashCode())); + getLogger().fine( + "endPaintable for " + paintable.getClass().getName() + "@" + + Integer.toHexString(paintable.hashCode())); ClientConnector openPaintable = openPaintables.peek(); if (paintable != openPaintable) { @@ -692,6 +698,7 @@ public class JsonPaintTarget implements PaintTarget { * * @see com.vaadin.terminal.PaintTarget#addCharacterData(java.lang.String ) */ + public void addCharacterData(String text) throws PaintException { if (text != null) { tag.addData(text); @@ -998,8 +1005,13 @@ public class JsonPaintTarget implements PaintTarget { * * @see com.vaadin.terminal.PaintTarget#isFullRepaint() */ + public boolean isFullRepaint() { return !cacheEnabled; } + private static final Logger getLogger() { + return Logger.getLogger(JsonPaintTarget.class.getName()); + } + } diff --git a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java index 661da57af6..de4f918b75 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java +++ b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java @@ -48,9 +48,6 @@ import com.vaadin.ui.Root; @SuppressWarnings("serial") public class PortletApplicationContext2 extends AbstractWebApplicationContext { - private static final Logger logger = Logger - .getLogger(PortletApplicationContext2.class.getName()); - protected Map<Application, Set<PortletListener>> portletListeners = new HashMap<Application, Set<PortletListener>>(); protected transient PortletSession session; @@ -76,11 +73,11 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { return new File(url.getFile()); } catch (final Exception e) { // FIXME: Handle exception - logger.log( - Level.INFO, - "Cannot access base directory, possible security issue " - + "with Application Server or Servlet Container", - e); + getLogger() + .log(Level.INFO, + "Cannot access base directory, possible security issue " + + "with Application Server or Servlet Container", + e); } } return null; @@ -419,4 +416,8 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { "Portlet mode can only be changed from a portlet request"); } } + + private Logger getLogger() { + return Logger.getLogger(PortletApplicationContext2.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/WebBrowser.java b/src/com/vaadin/terminal/gwt/server/WebBrowser.java index 358f6f38fb..38b5409594 100644 --- a/src/com/vaadin/terminal/gwt/server/WebBrowser.java +++ b/src/com/vaadin/terminal/gwt/server/WebBrowser.java @@ -23,8 +23,6 @@ public class WebBrowser implements Terminal { private int screenHeight = 0; private int screenWidth = 0; - private int clientHeight = 0; - private int clientWidth = 0; private String browserApplication = null; private Locale locale; private String address; @@ -43,6 +41,7 @@ public class WebBrowser implements Terminal { * * @return Always returns null. */ + public String getDefaultTheme() { return null; } @@ -52,6 +51,7 @@ public class WebBrowser implements Terminal { * * @see com.vaadin.terminal.Terminal#getScreenHeight() */ + public int getScreenHeight() { return screenHeight; } @@ -61,35 +61,12 @@ public class WebBrowser implements Terminal { * * @see com.vaadin.terminal.Terminal#getScreenWidth() */ + public int getScreenWidth() { return screenWidth; } /** - * Gets the height of the client (browser window). - * <p> - * Note that the client size is only updated on a full repaint, not when the - * browser window size changes - * - * @return The height of the client or 0 if unknown. - */ - public int getClientHeight() { - return clientHeight; - } - - /** - * Gets the width of the client (browser window) - * <p> - * Note that the client size is only updated on a full repaint, not when the - * browser window size changes - * - * @return The width of the client or 0 if unknown. - */ - public int getClientWidth() { - return clientWidth; - } - - /** * Get the browser user-agent string. * * @return The raw browser userAgent string @@ -367,10 +344,6 @@ public class WebBrowser implements Terminal { * Screen width * @param sh * Screen height - * @param cw - * Client width - * @param ch - * Client height * @param tzo * TimeZone offset in minutes from GMT * @param rtzo @@ -383,9 +356,9 @@ public class WebBrowser implements Terminal { * the current date in milliseconds since the epoch * @param touchDevice */ - void updateClientSideDetails(String sw, String sh, String cw, String ch, - String tzo, String rtzo, String dstSavings, String dstInEffect, - String curDate, boolean touchDevice) { + void updateClientSideDetails(String sw, String sh, String tzo, String rtzo, + String dstSavings, String dstInEffect, String curDate, + boolean touchDevice) { if (sw != null) { try { screenHeight = Integer.parseInt(sh); @@ -394,14 +367,6 @@ public class WebBrowser implements Terminal { screenHeight = screenWidth = 0; } } - if (cw != null) { - try { - clientHeight = Integer.parseInt(ch); - clientWidth = Integer.parseInt(cw); - } catch (final NumberFormatException e) { - clientHeight = clientWidth = 0; - } - } if (tzo != null) { try { // browser->java conversion: min->ms, reverse sign @@ -462,8 +427,7 @@ public class WebBrowser implements Terminal { if (request.getParameter("sw") != null) { updateClientSideDetails(request.getParameter("sw"), - request.getParameter("sh"), request.getParameter("cw"), - request.getParameter("ch"), request.getParameter("tzo"), + request.getParameter("sh"), request.getParameter("tzo"), request.getParameter("rtzo"), request.getParameter("dstd"), request.getParameter("dston"), request.getParameter("curdate"), diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java index d8d3c23e0c..459b6ddd30 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java @@ -4,7 +4,6 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.PrintWriter; -import java.util.Collection; import java.util.Date; import com.google.gwt.core.ext.Generator; @@ -16,14 +15,13 @@ import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; -import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; -import com.vaadin.event.dd.acceptcriteria.ClientCriterion; +import com.vaadin.terminal.gwt.client.ui.dd.AcceptCriterion; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterion; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterionFactory; /** * GWT generator to build {@link VAcceptCriterionFactory} implementation - * dynamically based on {@link ClientCriterion} annotations available in + * dynamically based on {@link AcceptCriterion} annotations available in * classpath. * */ @@ -102,21 +100,23 @@ public class AcceptCriteriaFactoryGenerator extends Generator { sourceWriter.println("name = name.intern();"); - Collection<Class<? extends AcceptCriterion>> clientSideVerifiableCriterion = ClassPathExplorer - .getCriterion(); - - for (Class<? extends AcceptCriterion> class1 : clientSideVerifiableCriterion) { - logger.log(Type.INFO, - "creating mapping for " + class1.getCanonicalName()); - String canonicalName = class1.getCanonicalName(); - Class<? extends VAcceptCriterion> clientClass = class1 - .getAnnotation(ClientCriterion.class).value(); - sourceWriter.print("if (\""); - sourceWriter.print(canonicalName); - sourceWriter.print("\" == name) return GWT.create("); - sourceWriter.print(clientClass.getCanonicalName()); - sourceWriter.println(".class );"); - sourceWriter.print("else "); + JClassType criteriaType = context.getTypeOracle().findType( + VAcceptCriterion.class.getName()); + for (JClassType clientClass : criteriaType.getSubtypes()) { + AcceptCriterion annotation = clientClass + .getAnnotation(AcceptCriterion.class); + if (annotation != null) { + String clientClassName = clientClass.getQualifiedSourceName(); + String serverClassName = clientClass.getAnnotation( + AcceptCriterion.class).value(); + logger.log(Type.INFO, "creating mapping for " + serverClassName); + sourceWriter.print("if (\""); + sourceWriter.print(serverClassName); + sourceWriter.print("\" == name) return GWT.create("); + sourceWriter.print(clientClassName); + sourceWriter.println(".class );"); + sourceWriter.print("else "); + } } sourceWriter.println("return null;"); diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java index 6a0aa0f4c2..1c5b736492 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java @@ -6,33 +6,23 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.File; import java.io.FileFilter; import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; -import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; -import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; -import com.vaadin.event.dd.acceptcriteria.ClientCriterion; -import com.vaadin.terminal.gwt.server.ClientConnector; - /** * Utility class to collect widgetset related information from classpath. * Utility will seek all directories from classpaths, and jar files having @@ -53,9 +43,6 @@ import com.vaadin.terminal.gwt.server.ClientConnector; */ public class ClassPathExplorer { - private static Logger logger = Logger.getLogger(ClassPathExplorer.class - .getName()); - private static final String VAADIN_ADDON_VERSION_ATTRIBUTE = "Vaadin-Package-Version"; /** @@ -92,46 +79,6 @@ public class ClassPathExplorer { } /** - * Finds server side widgets with ClientWidget annotation on the class path - * (entries that can contain widgets/widgetsets - see - * getRawClasspathEntries()). - * - * As a side effect, also accept criteria are searched under the same class - * path entries and added into the acceptCriterion collection. - * - * @return a collection of {@link ClientConnector} classes - */ - public static void findAcceptCriteria() { - logger.info("Searching for accept criteria.."); - long start = System.currentTimeMillis(); - Set<String> keySet = classpathLocations.keySet(); - for (String url : keySet) { - logger.fine("Searching for accept criteria in " - + classpathLocations.get(url)); - searchForPaintables(classpathLocations.get(url), url); - } - long end = System.currentTimeMillis(); - - logger.info("Search took " + (end - start) + "ms"); - - } - - /** - * Finds all accept criteria having client side counterparts (classes with - * the {@link ClientCriterion} annotation). - * - * @return Collection of AcceptCriterion classes - */ - public static Collection<Class<? extends AcceptCriterion>> getCriterion() { - if (acceptCriterion.isEmpty()) { - // accept criterion are searched as a side effect, normally after - // paintable detection - findAcceptCriteria(); - } - return acceptCriterion; - } - - /** * Finds the names and locations of widgetsets available on the class path. * * @return map from widgetset classname to widgetset location URL @@ -154,6 +101,7 @@ public class ClassPathExplorer { sb.append(widgetsets.get(ws)); sb.append("\n"); } + final Logger logger = getLogger(); logger.info(sb.toString()); logger.info("Search took " + (end - start) + "ms"); return widgetsets; @@ -214,7 +162,7 @@ public class ClassPathExplorer { } catch (MalformedURLException e) { // should never happen as based on an existing URL, // only changing end of file name/path part - logger.log(Level.SEVERE, + getLogger().log(Level.SEVERE, "Error locating the widgetset " + classname, e); } } @@ -250,7 +198,7 @@ public class ClassPathExplorer { } } } catch (IOException e) { - logger.log(Level.WARNING, "Error parsing jar file", e); + getLogger().log(Level.WARNING, "Error parsing jar file", e); } } @@ -278,7 +226,7 @@ public class ClassPathExplorer { classpath = classpath.substring(0, classpath.length() - 1); } - logger.fine("Classpath: " + classpath); + getLogger().fine("Classpath: " + classpath); String[] split = classpath.split(pathSep); for (int i = 0; i < split.length; i++) { @@ -312,6 +260,7 @@ public class ClassPathExplorer { include(null, file, locations); } long end = System.currentTimeMillis(); + Logger logger = getLogger(); if (logger.isLoggable(Level.FINE)) { logger.fine("getClassPathLocations took " + (end - start) + "ms"); } @@ -352,7 +301,7 @@ public class ClassPathExplorer { url = new URL("jar:" + url.toExternalForm() + "!/"); JarURLConnection conn = (JarURLConnection) url .openConnection(); - logger.fine(url.toString()); + getLogger().fine(url.toString()); JarFile jarFile = conn.getJarFile(); Manifest manifest = jarFile.getManifest(); if (manifest != null) { @@ -363,9 +312,11 @@ public class ClassPathExplorer { } } } catch (MalformedURLException e) { - logger.log(Level.FINEST, "Failed to inspect JAR file", e); + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); } catch (IOException e) { - logger.log(Level.FINEST, "Failed to inspect JAR file", e); + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); } return false; @@ -445,151 +396,6 @@ public class ClassPathExplorer { } /** - * Searches for all paintable classes and accept criteria under a location - * based on {@link ClientCriterion} annotations. - * - * Note that client criteria are updated directly to the - * {@link #acceptCriterion} field, whereas paintables are added to the - * paintables map given as a parameter. - * - * @param location - * @param locationString - */ - private final static void searchForPaintables(URL location, - String locationString) { - - // Get a File object for the package - File directory = new File(location.getFile()); - - if (directory.exists() && !directory.isHidden()) { - // Get the list of the files contained in the directory - String[] files = directory.list(); - for (int i = 0; i < files.length; i++) { - // we are only interested in .class files - if (files[i].endsWith(".class")) { - // remove the .class extension - String classname = files[i].substring(0, - files[i].length() - 6); - String packageName = locationString - .substring(locationString.lastIndexOf("/") + 1); - classname = packageName + "." + classname; - tryToAdd(classname); - } - } - } else { - try { - // check files in jar file, entries will list all directories - // and files in jar - - URLConnection openConnection = location.openConnection(); - - if (openConnection instanceof JarURLConnection) { - JarURLConnection conn = (JarURLConnection) openConnection; - - JarFile jarFile = conn.getJarFile(); - - // Only scan for paintables in Vaadin add-ons - if (!isVaadinAddon(jarFile)) { - return; - } - - Enumeration<JarEntry> e = jarFile.entries(); - while (e.hasMoreElements()) { - JarEntry entry = e.nextElement(); - String entryname = entry.getName(); - if (!entry.isDirectory() - && entryname.endsWith(".class")) { - String classname = entryname.substring(0, - entryname.length() - 6); - if (classname.startsWith("/")) { - classname = classname.substring(1); - } - classname = classname.replace('/', '.'); - tryToAdd(classname); - } - } - } - } catch (IOException e) { - logger.warning(e.toString()); - } - } - - } - - /** - * A print stream that ignores all output. - * - * This is used to hide error messages from static initializers of classes - * being inspected. - */ - private static PrintStream devnull = new PrintStream(new OutputStream() { - @Override - public void write(int b) throws IOException { - // NOP - } - }); - - /** - * Collection of all {@link AcceptCriterion} classes, updated as a side - * effect of {@link #searchForPaintables(URL, String, Collection)} based on - * {@link ClientCriterion} annotations. - */ - private static Set<Class<? extends AcceptCriterion>> acceptCriterion = new HashSet<Class<? extends AcceptCriterion>>(); - - /** - * Checks a class for the {@link ClientCriterion} annotations, and adds it - * to the appropriate collection. - * - * @param fullclassName - */ - @SuppressWarnings("unchecked") - private static void tryToAdd(final String fullclassName) { - PrintStream out = System.out; - PrintStream err = System.err; - Throwable errorToShow = null; - Level logLevel = null; - try { - System.setErr(devnull); - System.setOut(devnull); - - Class<?> c = Class.forName(fullclassName); - - if (c.getAnnotation(ClientCriterion.class) != null) { - acceptCriterion.add((Class<? extends AcceptCriterion>) c); - } - } catch (UnsupportedClassVersionError e) { - // Inform the user about this as the class might contain a Paintable - // Typically happens when using an add-on that is compiled using a - // newer Java version. - logLevel = Level.INFO; - errorToShow = e; - } catch (ClassNotFoundException e) { - // Don't show to avoid flooding the user with irrelevant messages - logLevel = Level.FINE; - errorToShow = e; - } catch (LinkageError e) { - // Don't show to avoid flooding the user with irrelevant messages - logLevel = Level.FINE; - errorToShow = e; - } catch (Exception e) { - // Don't show to avoid flooding the user with irrelevant messages - logLevel = Level.FINE; - errorToShow = e; - } finally { - System.setErr(err); - System.setOut(out); - } - - // Must be done here after stderr and stdout have been reset. - if (errorToShow != null && logLevel != null) { - logger.log(logLevel, - "Failed to load class " + fullclassName + ". " - + errorToShow.getClass().getName() + ": " - + errorToShow.getMessage()); - } - } - - /** * Find and return the default source directory where to create new * widgetsets. * @@ -601,6 +407,9 @@ public class ClassPathExplorer { * @return URL */ public static URL getDefaultSourceDirectory() { + + final Logger logger = getLogger(); + if (logger.isLoggable(Level.FINE)) { logger.fine("classpathLocations values:"); ArrayList<String> locations = new ArrayList<String>( @@ -632,44 +441,21 @@ public class ClassPathExplorer { } /** - * Checks if the given jarFile is a Vaadin add-on. - * - * @param jarFile - * @return true if the file is an add-on, false otherwise - * @throws IOException - */ - private static boolean isVaadinAddon(JarFile jarFile) throws IOException { - Manifest manifest = jarFile.getManifest(); - if (manifest == null) { - return false; - } - Attributes mainAttributes = manifest.getMainAttributes(); - if (mainAttributes == null) { - return false; - } - - return (mainAttributes.getValue(VAADIN_ADDON_VERSION_ATTRIBUTE) != null); - } - - /** * Test method for helper tool */ public static void main(String[] args) { - ClassPathExplorer.findAcceptCriteria(); - logger.info("Found client criteria:"); - for (Class<? extends AcceptCriterion> cls : acceptCriterion) { - logger.info(cls.getCanonicalName()); - } - - logger.info(""); - logger.info("Searching available widgetsets..."); + getLogger().info("Searching available widgetsets..."); Map<String, URL> availableWidgetSets = ClassPathExplorer .getAvailableWidgetSets(); for (String string : availableWidgetSets.keySet()) { - logger.info(string + " in " + availableWidgetSets.get(string)); + getLogger().info(string + " in " + availableWidgetSets.get(string)); } } + private static final Logger getLogger() { + return Logger.getLogger(ClassPathExplorer.class.getName()); + } + } |