diff options
Diffstat (limited to 'src/com/vaadin')
89 files changed, 2141 insertions, 761 deletions
diff --git a/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java index 91950f5d4f..9159fa358b 100644 --- a/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java +++ b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java @@ -294,7 +294,8 @@ public class ContainerHierarchicalWrapper implements Container.Hierarchical, return ((Container.Hierarchical) container).hasChildren(itemId); } - return children.get(itemId) != null; + LinkedList<Object> list = children.get(itemId); + return (list != null && !list.isEmpty()); } /* diff --git a/src/com/vaadin/data/util/IndexedContainer.java b/src/com/vaadin/data/util/IndexedContainer.java index 1e0a2fae1a..bcaa5eda42 100644 --- a/src/com/vaadin/data/util/IndexedContainer.java +++ b/src/com/vaadin/data/util/IndexedContainer.java @@ -865,7 +865,6 @@ public class IndexedContainer extends * @see com.vaadin.data.Property#setValue(java.lang.Object) */ public void setValue(Object newValue) throws Property.ReadOnlyException { - // Gets the Property set final Map<Object, Object> propertySet = items.get(itemId); @@ -875,8 +874,10 @@ public class IndexedContainer extends } else if (getType().isAssignableFrom(newValue.getClass())) { propertySet.put(propertyId, newValue); } else { - throw new IllegalArgumentException("Value is of invalid type, " - + getType().getName() + " expected"); + throw new IllegalArgumentException( + "Value is of invalid type, got " + + newValue.getClass().getName() + " but " + + getType().getName() + " was expected"); } // update the container filtering if this property is being filtered diff --git a/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java b/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java index 8562bc70ec..0d29e9a327 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java +++ b/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java @@ -6,6 +6,9 @@ */ package com.vaadin.event.dd.acceptcriteria; +import java.util.logging.Level; +import java.util.logging.Logger; + import com.vaadin.event.TransferableImpl; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.terminal.PaintException; @@ -22,27 +25,39 @@ import com.vaadin.ui.Component; @SuppressWarnings("serial") @ClientCriterion(VDragSourceIs.class) public class SourceIs extends ClientSideCriterion { + private static final Logger logger = Logger.getLogger(SourceIs.class + .getName()); - private Component[] component; + private Component[] components; public SourceIs(Component... component) { - this.component = component; + components = component; } @Override public void paintContent(PaintTarget target) throws PaintException { super.paintContent(target); - target.addAttribute("c", component.length); - for (int i = 0; i < component.length; i++) { - target.addAttribute("component" + i, component[i]); + int paintedComponents = 0; + for (int i = 0; i < components.length; i++) { + Component c = components[i]; + if (c.getApplication() != null) { + target.addAttribute("component" + paintedComponents++, c); + } else { + logger.log( + Level.WARNING, + "SourceIs component {0} at index {1} is not attached to the component hierachy and will thus be ignored", + new Object[] { c.getClass().getName(), + Integer.valueOf(i) }); + } } + target.addAttribute("c", paintedComponents); } public boolean accept(DragAndDropEvent dragEvent) { if (dragEvent.getTransferable() instanceof TransferableImpl) { Component sourceComponent = ((TransferableImpl) dragEvent .getTransferable()).getSourceComponent(); - for (Component c : component) { + for (Component c : components) { if (c == sourceComponent) { return true; } diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java index 170e949116..ff77c5904a 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java @@ -69,6 +69,7 @@ public class ApplicationConfiguration implements EntryPoint { if (value === null || value === undefined) { return null; } else { + // $entry not needed as function is not exported return @java.lang.Boolean::valueOf(Z)(value); } }-*/; @@ -89,6 +90,7 @@ public class ApplicationConfiguration implements EntryPoint { if (value === null || value === undefined) { return null; } else { + // $entry not needed as function is not exported return @java.lang.Integer::valueOf(I)(value); } }-*/; @@ -589,7 +591,7 @@ public class ApplicationConfiguration implements EntryPoint { */ public native static void registerCallback(String widgetsetName) /*-{ - var callbackHandler = @com.vaadin.terminal.gwt.client.ApplicationConfiguration::startApplication(Ljava/lang/String;); + var callbackHandler = $entry(@com.vaadin.terminal.gwt.client.ApplicationConfiguration::startApplication(Ljava/lang/String;)); $wnd.vaadin.registerWidgetset(widgetsetName, callbackHandler); }-*/; diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 2d9398320e..739c232a72 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -256,10 +256,10 @@ public class ApplicationConnection { /*-{ var ap = this; var client = {}; - client.isActive = function() { + client.isActive = $entry(function() { return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()() || ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::isExecutingDeferredCommands()(); - } + }); var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()(); if (vi) { client.getVersionInfo = function() { @@ -267,12 +267,21 @@ public class ApplicationConnection { } } - client.getElementByPath = function(id) { + client.getProfilingData = $entry(function() { + var pd = [ + ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::lastProcessingTime, + ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::totalProcessingTime + ]; + pd = pd.concat(ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::serverTimingInfo); + return pd; + }); + + client.getElementByPath = $entry(function(id) { return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); - } - client.getPathForElement = function(element) { + }); + client.getPathForElement = $entry(function(element) { return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); - } + }); $wnd.vaadin.clients[TTAppId] = client; }-*/; @@ -314,22 +323,22 @@ public class ApplicationConnection { if ($wnd.vaadin.forceSync) { oldSync = $wnd.vaadin.forceSync; } - $wnd.vaadin.forceSync = function() { + $wnd.vaadin.forceSync = $entry(function() { if (oldSync) { oldSync(); } app.@com.vaadin.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()(); - } + }); var oldForceLayout; if ($wnd.vaadin.forceLayout) { oldForceLayout = $wnd.vaadin.forceLayout; } - $wnd.vaadin.forceLayout = function() { + $wnd.vaadin.forceLayout = $entry(function() { if (oldForceLayout) { oldForceLayout(); } app.@com.vaadin.terminal.gwt.client.ApplicationConnection::forceLayout()(); - } + }); }-*/; /** @@ -536,21 +545,22 @@ public class ApplicationConnection { return; case 503: - // We'll assume msec instead of the usual seconds - int delay = Integer.parseInt(response - .getHeader("Retry-After")); - VConsole.log("503, retrying in " + delay + "msec"); - (new Timer() { - @Override - public void run() { - // TODO why? Here used to be "activeRequests--;" - // but can't see why exactly - hasActiveRequest = false; - doUidlRequest(uri, payload, synchronous); - } - }).schedule(delay); - return; - + /* + * We'll assume msec instead of the usual seconds. If + * there's no Retry-After header, handle the error like + * a 500, as per RFC 2616 section 10.5.4. + */ + String delay = response.getHeader("Retry-After"); + if (delay != null) { + VConsole.log("503, retrying in " + delay + "msec"); + (new Timer() { + @Override + public void run() { + doUidlRequest(uri, payload, synchronous); + } + }).schedule(Integer.parseInt(delay)); + return; + } } if ((statusCode / 100) == 4) { @@ -561,6 +571,13 @@ public class ApplicationConnection { + statusCode, statusCode); endRequest(); return; + } else if ((statusCode / 100) == 5) { + // Something's wrong on the server, there's nothing the + // client can do except maybe try again. + showCommunicationError("Server error. Error code: " + + statusCode, statusCode); + endRequest(); + return; } String contentType = response.getHeader("Content-Type"); @@ -663,6 +680,26 @@ public class ApplicationConnection { } int cssWaits = 0; + + /** + * Holds the time spent rendering the last request + */ + protected int lastProcessingTime; + + /** + * Holds the total time spent rendering requests during the lifetime of the + * session. + */ + protected int totalProcessingTime; + + /** + * Holds the timing information from the server-side. How much time was + * spent servicing the last request and how much time has been spent + * servicing the session so far. These values are always one request behind, + * since they cannot be measured before the request is finished. + */ + private ValueMap serverTimingInfo; + static final int MAX_CSS_WAITS = 100; protected void handleWhenCSSLoaded(final String jsonText, @@ -999,6 +1036,12 @@ public class ApplicationConnection { handleUIDLDuration.logDuration( " * Handling type mappings from server completed", 10); + /* + * Hook for e.g. TestBench to get details about server peformance + */ + if (json.containsKey("timings")) { + serverTimingInfo = json.getValueMap("timings"); + } Command c = new Command() { public void execute() { @@ -1183,10 +1226,12 @@ public class ApplicationConnection { // TODO build profiling for widget impl loading time - final long prosessingTime = (new Date().getTime()) - - start.getTime(); + lastProcessingTime = (int) ((new Date().getTime()) - start + .getTime()); + totalProcessingTime += lastProcessingTime; + VConsole.log(" Processing time was " - + String.valueOf(prosessingTime) + "ms for " + + String.valueOf(lastProcessingTime) + "ms for " + jsonText.length() + " characters of JSON"); VConsole.log("Referenced paintables: " + connectorMap.size()); @@ -2367,6 +2412,10 @@ public class ApplicationConnection { return true; } + if (!manageCaption) { + VConsole.error(Util.getConnectorString(connector) + + " called updateComponent with manageCaption=false. The parameter was ignored - override delegateCaption() to return false instead. It is however not recommended to use caption this way at all."); + } return false; } diff --git a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java index ef1dc481b1..064f175301 100644 --- a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java +++ b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java @@ -28,6 +28,8 @@ public class BrowserInfo { private static final String OS_WINDOWS = "win"; private static final String OS_LINUX = "lin"; private static final String OS_MACOSX = "mac"; + private static final String OS_ANDROID = "android"; + private static final String OS_IOS = "ios"; private static BrowserInfo instance; @@ -167,13 +169,18 @@ public class BrowserInfo { if (osClass != null) { cssClass = cssClass + " " + prefix + osClass; } + } return cssClass; } private String getOperatingSystemClass() { - if (browserDetails.isWindows()) { + if (browserDetails.isAndroid()) { + return OS_ANDROID; + } else if (browserDetails.isIOS()) { + return OS_IOS; + } else if (browserDetails.isWindows()) { return OS_WINDOWS; } else if (browserDetails.isLinux()) { return OS_LINUX; @@ -312,4 +319,59 @@ public class BrowserInfo { && Util.getNativeScrollbarSize() > 0; } + /** + * Checks if the browser is run on iOS + * + * @return true if the browser is run on iOS, false otherwise + */ + public boolean isIOS() { + return browserDetails.isIOS(); + } + + /** + * Checks if the browser is run on Android + * + * @return true if the browser is run on Android, false otherwise + */ + public boolean isAndroid() { + return browserDetails.isAndroid(); + } + + /** + * Checks if the browser is capable of handling scrolling natively or if a + * touch scroll helper is needed for scrolling. + * + * @return true if browser needs a touch scroll helper, false if the browser + * can handle scrolling natively + */ + public boolean requiresTouchScrollDelegate() { + if (!isTouchDevice()) { + return false; + } + + if (isAndroid() && isWebkit() && getWebkitVersion() < 534) { + return true; + } + // if (isIOS() && isWebkit() && getWebkitVersion() < ???) { + // return true; + // } + + return false; + } + + /** + * Tests if this is an Android devices with a broken scrollTop + * implementation + * + * @return true if scrollTop cannot be trusted on this device, false + * otherwise + */ + public boolean isAndroidWithBrokenScrollTop() { + return isAndroid() + && (getOperatingSystemMajorVersion() == 3 || getOperatingSystemMajorVersion() == 4); + } + + private int getOperatingSystemMajorVersion() { + return browserDetails.getOperatingSystemMajorVersion(); + } } diff --git a/src/com/vaadin/terminal/gwt/client/CSSRule.java b/src/com/vaadin/terminal/gwt/client/CSSRule.java index 4d9196c8d6..c36b0611e8 100644 --- a/src/com/vaadin/terminal/gwt/client/CSSRule.java +++ b/src/com/vaadin/terminal/gwt/client/CSSRule.java @@ -33,6 +33,7 @@ public class CSSRule { for(var i = 0; i < sheets.length; i++) { var sheet = sheets[i]; if(sheet.href && sheet.href.indexOf("VAADIN/themes")>-1) { + // $entry not needed as function is not exported this.@com.vaadin.terminal.gwt.client.CSSRule::rules = @com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet, selector, deep); return; } @@ -58,6 +59,7 @@ public class CSSRule { // IE handles imported sheet differently if(deep && sheet.imports && sheet.imports.length > 0) { for(var i=0; i < sheet.imports.length; i++) { + // $entry not needed as function is not exported var imports = @com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet.imports[i], selector, deep); allMatches.concat(imports); } @@ -83,6 +85,7 @@ public class CSSRule { } } else if(deep && r.type == 3) { // Search @import stylesheet + // $entry not needed as function is not exported var imports = @com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(r.styleSheet, selector, deep); allMatches = allMatches.concat(imports); } @@ -102,6 +105,7 @@ public class CSSRule { /*-{ var j = this.@com.vaadin.terminal.gwt.client.CSSRule::rules.length; for(var i=0; i<j; i++) { + // $entry not needed as function is not exported var value = this.@com.vaadin.terminal.gwt.client.CSSRule::rules[i].style[propertyName]; if(value) return value; diff --git a/src/com/vaadin/terminal/gwt/client/ComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java index 34f3f13f03..5f9171084e 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java @@ -119,4 +119,12 @@ public interface ComponentConnector extends ServerConnector { */ public boolean delegateCaptionHandling(); + /** + * Sets the enabled state of the widget associated to this connector. + * + * @param widgetEnabled + * true if the widget should be enabled, false otherwise + */ + public void setWidgetEnabled(boolean widgetEnabled); + } diff --git a/src/com/vaadin/terminal/gwt/client/ComponentDetailMap.java b/src/com/vaadin/terminal/gwt/client/ComponentDetailMap.java index 3405d838a7..dfbcf9d38b 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentDetailMap.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentDetailMap.java @@ -62,6 +62,7 @@ final class ComponentDetailMap extends JavaScriptObject { private final native void fillWithValues(Collection<ComponentDetail> list) /*-{ for(var key in this) { + // $entry not needed as function is not exported list.@java.util.Collection::add(Ljava/lang/Object;)(this[key]); } }-*/; diff --git a/src/com/vaadin/terminal/gwt/client/ComputedStyle.java b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java index 7dc937cee7..76f2328711 100644 --- a/src/com/vaadin/terminal/gwt/client/ComputedStyle.java +++ b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java @@ -179,6 +179,7 @@ public class ComputedStyle { if (isNaN(number)) return null; else + // $entry not needed as function is not exported return @java.lang.Integer::valueOf(I)(number); }-*/; diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManager.java b/src/com/vaadin/terminal/gwt/client/LayoutManager.java index 55d3fbe272..79a0030140 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManager.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java @@ -63,10 +63,30 @@ public class LayoutManager { this.connection = connection; } + /** + * Gets the layout manager associated with the given + * {@link ApplicationConnection}. + * + * @param connection + * the application connection to get a layout manager for + * @return the layout manager associated with the provided application + * connection + */ public static LayoutManager get(ApplicationConnection connection) { return connection.getLayoutManager(); } + /** + * Registers that a ManagedLayout is depending on the size of an Element. + * This causes this layout manager to measure the element in the beginning + * of every layout phase and call the appropriate layout method of the + * managed layout if the size of the element has changed. + * + * @param owner + * the ManagedLayout that depends on an element + * @param element + * the Element that should be measured + */ public void registerDependency(ManagedLayout owner, Element element) { MeasuredSize measuredSize = ensureMeasured(element); setNeedsLayout(owner); @@ -98,6 +118,16 @@ public class LayoutManager { } } + /** + * Assigns a measured size to an element. Method defined as protected to + * allow separate implementation for IE8 in which delete not always works. + * + * @param element + * the dom element to attach the measured size to + * @param measuredSize + * the measured size to attach to the element. If + * <code>null</code>, any previous measured size is removed. + */ protected native void setMeasuredSize(Element element, MeasuredSize measuredSize) /*-{ @@ -124,6 +154,17 @@ public class LayoutManager { return measuredSize; } + /** + * Registers that a ManagedLayout is no longer depending on the size of an + * Element. + * + * @see #registerDependency(ManagedLayout, Element) + * + * @param owner + * the ManagedLayout no longer depends on an element + * @param element + * the Element that that no longer needs to be measured + */ public void unregisterDependency(ManagedLayout owner, Element element) { MeasuredSize measuredSize = getMeasuredSize(element, null); if (measuredSize == null) { @@ -176,8 +217,6 @@ public class LayoutManager { private void doLayout() { VConsole.log("Starting layout phase"); - Duration totalDuration = new Duration(); - Map<ManagedLayout, Integer> layoutCounts = new HashMap<ManagedLayout, Integer>(); int passes = 0; @@ -192,9 +231,6 @@ public class LayoutManager { needsVerticalLayout.clear(); for (ComponentConnector connector : needsMeasure) { - // Ensure the MeasuredSize is attached to the DOM element before - // doing measurements to avoid reflows in "some" browsers - getMeasuredSize(connector); currentDependencyTree.setNeedsMeasure(connector, true); } needsMeasure.clear(); @@ -358,9 +394,8 @@ public class LayoutManager { VConsole.log("Invoke post layout listeners in " + (totalDuration.elapsedMillis() - postLayoutStart) + " ms"); - VConsole.log("<b>Total layout phase time: " - + totalDuration.elapsedMillis() + "ms</b>"); - + VConsole.log("Total layout phase time: " + + totalDuration.elapsedMillis() + "ms"); } private void logConnectorStatus(int connectorId) { @@ -573,84 +608,408 @@ public class LayoutManager { layoutNow(); } - // TODO Rename to setNeedsLayout + /** + * Marks that a ManagedLayout should be layouted in the next layout phase + * even if none of the elements managed by the layout have been resized. + * + * @param layout + * the managed layout that should be layouted + */ public final void setNeedsLayout(ManagedLayout layout) { setNeedsHorizontalLayout(layout); setNeedsVerticalLayout(layout); } + /** + * Marks that a ManagedLayout should be layouted horizontally in the next + * layout phase even if none of the elements managed by the layout have been + * resized horizontally. + * + * For SimpleManagedLayout which is always layouted in both directions, this + * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + * + * @param layout + * the managed layout that should be layouted + */ public final void setNeedsHorizontalLayout(ManagedLayout layout) { needsHorizontalLayout.add(layout); } + /** + * Marks that a ManagedLayout should be layouted vertically in the next + * layout phase even if none of the elements managed by the layout have been + * resized vertically. + * + * For SimpleManagedLayout which is always layouted in both directions, this + * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + * + * @param layout + * the managed layout that should be layouted + */ public final void setNeedsVerticalLayout(ManagedLayout layout) { needsVerticalLayout.add(layout); } - public boolean isMeasured(Element element) { - return getMeasuredSize(element, nullSize) != nullSize; - } - + /** + * Gets the outer height (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured outer height (including margins, paddings and + * borders) of the element in pixels. + */ public final int getOuterHeight(Element element) { return getMeasuredSize(element, nullSize).getOuterHeight(); } + /** + * Gets the outer width (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured outer width (including margins, paddings and + * borders) of the element in pixels. + */ public final int getOuterWidth(Element element) { return getMeasuredSize(element, nullSize).getOuterWidth(); } + /** + * Gets the inner height (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured inner height (excluding margins, paddings and + * borders) of the element in pixels. + */ public final int getInnerHeight(Element element) { return getMeasuredSize(element, nullSize).getInnerHeight(); } + /** + * Gets the inner width (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured inner width (excluding margins, paddings and + * borders) of the element in pixels. + */ public final int getInnerWidth(Element element) { return getMeasuredSize(element, nullSize).getInnerWidth(); } + /** + * Gets the border height (top border + bottom border) of the given element, + * provided that it has been measured. These elements are guaranteed to be + * measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured border height (top border + bottom border) of the + * element in pixels. + */ public final int getBorderHeight(Element element) { return getMeasuredSize(element, nullSize).getBorderHeight(); } + /** + * Gets the padding height (top padding + bottom padding) of the given + * element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured padding height (top padding + bottom padding) of the + * element in pixels. + */ public int getPaddingHeight(Element element) { return getMeasuredSize(element, nullSize).getPaddingHeight(); } + /** + * Gets the border width (left border + right border) of the given element, + * provided that it has been measured. These elements are guaranteed to be + * measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured border width (left border + right border) of the + * element in pixels. + */ public int getBorderWidth(Element element) { return getMeasuredSize(element, nullSize).getBorderWidth(); } + /** + * Gets the padding width (left padding + right padding) of the given + * element, provided that it has been measured. These elements are + * guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured padding width (left padding + right padding) of the + * element in pixels. + */ public int getPaddingWidth(Element element) { return getMeasuredSize(element, nullSize).getPaddingWidth(); } + /** + * Gets the top padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top padding of the element in pixels. + */ public int getPaddingTop(Element element) { return getMeasuredSize(element, nullSize).getPaddingTop(); } + /** + * Gets the left padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left padding of the element in pixels. + */ public int getPaddingLeft(Element element) { return getMeasuredSize(element, nullSize).getPaddingLeft(); } + /** + * Gets the bottom padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom padding of the element in pixels. + */ public int getPaddingBottom(Element element) { return getMeasuredSize(element, nullSize).getPaddingBottom(); } + /** + * Gets the right padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right padding of the element in pixels. + */ public int getPaddingRight(Element element) { return getMeasuredSize(element, nullSize).getPaddingRight(); } + /** + * Gets the top margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top margin of the element in pixels. + */ public int getMarginTop(Element element) { return getMeasuredSize(element, nullSize).getMarginTop(); } + /** + * Gets the right margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right margin of the element in pixels. + */ public int getMarginRight(Element element) { return getMeasuredSize(element, nullSize).getMarginRight(); } + /** + * Gets the bottom margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom margin of the element in pixels. + */ public int getMarginBottom(Element element) { return getMeasuredSize(element, nullSize).getMarginBottom(); } + /** + * Gets the left margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left margin of the element in pixels. + */ public int getMarginLeft(Element element) { return getMeasuredSize(element, nullSize).getMarginLeft(); } @@ -663,6 +1022,19 @@ public class LayoutManager { return getMeasuredSize(element, nullSize).getMarginHeight(); } + /** + * Registers the outer height (including margins, borders and paddings) of a + * component. This can be used as an optimization by ManagedLayouts; by + * informing the LayoutManager about what size a component will have, the + * layout propagation can continue directly without first measuring the + * potentially resized elements. + * + * @param component + * the component for which the size is reported + * @param outerHeight + * the new outer height (including margins, borders and paddings) + * of the component in pixels + */ public void reportOuterHeight(ComponentConnector component, int outerHeight) { MeasuredSize measuredSize = getMeasuredSize(component); if (isLayoutRunning()) { @@ -679,6 +1051,19 @@ public class LayoutManager { } } + /** + * Registers the height reserved for a relatively sized component. This can + * be used as an optimization by ManagedLayouts; by informing the + * LayoutManager about what size a component will have, the layout + * propagation can continue directly without first measuring the potentially + * resized elements. + * + * @param component + * the relatively sized component for which the size is reported + * @param assignedHeight + * the inner height of the relatively sized component's parent + * element in pixels + */ public void reportHeightAssignedToRelative(ComponentConnector component, int assignedHeight) { assert component.isRelativeHeight(); @@ -689,6 +1074,19 @@ public class LayoutManager { reportOuterHeight(component, effectiveHeight); } + /** + * Registers the width reserved for a relatively sized component. This can + * be used as an optimization by ManagedLayouts; by informing the + * LayoutManager about what size a component will have, the layout + * propagation can continue directly without first measuring the potentially + * resized elements. + * + * @param component + * the relatively sized component for which the size is reported + * @param assignedWidth + * the inner width of the relatively sized component's parent + * element in pixels + */ public void reportWidthAssignedToRelative(ComponentConnector component, int assignedWidth) { assert component.isRelativeWidth(); @@ -703,6 +1101,19 @@ public class LayoutManager { return Float.parseFloat(size.substring(0, size.length() - 1)); } + /** + * Registers the outer width (including margins, borders and paddings) of a + * component. This can be used as an optimization by ManagedLayouts; by + * informing the LayoutManager about what size a component will have, the + * layout propagation can continue directly without first measuring the + * potentially resized elements. + * + * @param component + * the component for which the size is reported + * @param outerWidth + * the new outer width (including margins, borders and paddings) + * of the component in pixels + */ public void reportOuterWidth(ComponentConnector component, int outerWidth) { MeasuredSize measuredSize = getMeasuredSize(component); if (isLayoutRunning()) { @@ -719,6 +1130,18 @@ public class LayoutManager { } } + /** + * Adds a listener that will be notified whenever the size of a specific + * element changes. Adding a listener to an element also ensures that all + * sizes for that element will be available starting from the next layout + * phase. + * + * @param element + * the element that should be checked for size changes + * @param listener + * an ElementResizeListener that will be informed whenever the + * size of the target element has changed + */ public void addElementResizeListener(Element element, ElementResizeListener listener) { Collection<ElementResizeListener> listeners = elementResizeListeners @@ -731,6 +1154,18 @@ public class LayoutManager { listeners.add(listener); } + /** + * Removes an element resize listener from the provided element. This might + * cause this LayoutManager to stop tracking the size of the element if no + * other sources are interested in the size. + * + * @param element + * the element to which the element resize listener was + * previously added + * @param listener + * the ElementResizeListener that should no longer get informed + * about size changes to the target element. + */ public void removeElementResizeListener(Element element, ElementResizeListener listener) { Collection<ElementResizeListener> listeners = elementResizeListeners @@ -751,6 +1186,16 @@ public class LayoutManager { } } + /** + * Informs this LayoutManager that the size of a component might have + * changed. If there is no upcoming layout phase, a new layout phase is + * scheduled. This method should be used whenever a size might have changed + * from outside of Vaadin's normal update phase, e.g. when an icon has been + * loaded or when the user resizes some part of the UI using the mouse. + * + * @param component + * the component whose size might have changed. + */ public void setNeedsMeasure(ComponentConnector component) { if (isLayoutRunning()) { currentDependencyTree.setNeedsMeasure(component, true); diff --git a/src/com/vaadin/terminal/gwt/client/ServerConnector.java b/src/com/vaadin/terminal/gwt/client/ServerConnector.java index f179b29054..c10f4bb411 100644 --- a/src/com/vaadin/terminal/gwt/client/ServerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ServerConnector.java @@ -46,11 +46,9 @@ public interface ServerConnector extends Connector { public ApplicationConnection getConnection(); /** - * Tests whether the connector is enabled or not. Disabled connectors will - * ignore all attempts at communications. Received messages will be - * discarded. This method must check that the connector is enabled in - * context, that is if it's parent is disabled, this method must return - * false. + * Tests whether the connector is enabled or not. This method checks that + * the connector is enabled in context, i.e. if the parent connector is + * disabled, this method must return false. * * @return true if the connector is enabled, false otherwise */ diff --git a/src/com/vaadin/terminal/gwt/client/Util.java b/src/com/vaadin/terminal/gwt/client/Util.java index bfe63caefd..c392a0ba9c 100644 --- a/src/com/vaadin/terminal/gwt/client/Util.java +++ b/src/com/vaadin/terminal/gwt/client/Util.java @@ -978,6 +978,19 @@ public class Util { */ final Element target = getElementFromPoint(touch.getClientX(), touch.getClientY()); + + /* + * Fixes infocusable form fields in Safari of iOS 5.x and some Android + * browsers. + */ + Widget targetWidget = findWidget(target, null); + if (targetWidget instanceof com.google.gwt.user.client.ui.Focusable) { + final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) targetWidget; + toBeFocusedWidget.setFocus(true); + } else if (targetWidget instanceof Focusable) { + ((Focusable) targetWidget).focus(); + } + Scheduler.get().scheduleDeferred(new ScheduledCommand() { public void execute() { try { diff --git a/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java b/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java index 89e106f063..6e0417149c 100644 --- a/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java +++ b/src/com/vaadin/terminal/gwt/client/VBrowserDetails.java @@ -31,14 +31,19 @@ public class VBrowserDetails implements Serializable { private boolean isOpera = false; private boolean isIE = false; - private boolean isWindows = false; - private boolean isMacOSX = false; - private boolean isLinux = false; + private OperatingSystem os = OperatingSystem.UNKNOWN; + + public enum OperatingSystem { + UNKNOWN, WINDOWS, MACOSX, LINUX, IOS, ANDROID; + } private float browserEngineVersion = -1; private int browserMajorVersion = -1; private int browserMinorVersion = -1; + private int osMajorVersion = -1; + private int osMinorVersion = -1; + /** * Create an instance based on the given user agent. * @@ -122,14 +127,80 @@ public class VBrowserDetails implements Serializable { // Operating system if (userAgent.contains("windows ")) { - isWindows = true; + os = OperatingSystem.WINDOWS; } else if (userAgent.contains("linux")) { - isLinux = true; + if (userAgent.contains("android")) { + os = OperatingSystem.ANDROID; + parseAndroidVersion(userAgent); + } else { + os = OperatingSystem.LINUX; + + } } else if (userAgent.contains("macintosh") || userAgent.contains("mac osx") || userAgent.contains("mac os x")) { - isMacOSX = true; + if (userAgent.contains("ipad") || userAgent.contains("ipod") + || userAgent.contains("iphone")) { + os = OperatingSystem.IOS; + parseIOSVersion(userAgent); + } else { + os = OperatingSystem.MACOSX; + } + } + } + + private void parseAndroidVersion(String userAgent) { + // Android 5.1; + if (!userAgent.contains("android")) { + return; + } + + String osVersionString = safeSubstring(userAgent, + userAgent.indexOf("android ") + "android ".length(), + userAgent.length()); + osVersionString = safeSubstring(osVersionString, 0, + osVersionString.indexOf(";")); + String[] parts = osVersionString.split("\\."); + parseOsVersion(parts); + } + + private void parseIOSVersion(String userAgent) { + // OS 5_1 like Mac OS X + if (!userAgent.contains("os ") || !userAgent.contains(" like mac")) { + return; + } + + String osVersionString = safeSubstring(userAgent, + userAgent.indexOf("os ") + 3, userAgent.indexOf(" like mac")); + String[] parts = osVersionString.split("_"); + parseOsVersion(parts); + } + + private void parseOsVersion(String[] parts) { + osMajorVersion = -1; + osMinorVersion = -1; + + if (parts.length >= 1) { + try { + osMajorVersion = Integer.parseInt(parts[0]); + } catch (Exception e) { + } + } + if (parts.length >= 2) { + try { + osMinorVersion = Integer.parseInt(parts[1]); + } catch (Exception e) { + } + // Some Androids report version numbers as "2.1-update1" + if (osMinorVersion == -1 && parts[1].contains("-")) { + try { + osMinorVersion = Integer.parseInt(parts[1].substring(0, + parts[1].indexOf('-'))); + } catch (Exception ee) { + } + } } + } private void parseVersionString(String versionString) { @@ -306,7 +377,7 @@ public class VBrowserDetails implements Serializable { * @return true if run on Windows, false otherwise */ public boolean isWindows() { - return isWindows; + return os == OperatingSystem.WINDOWS; } /** @@ -315,7 +386,7 @@ public class VBrowserDetails implements Serializable { * @return true if run on Mac OSX, false otherwise */ public boolean isMacOSX() { - return isMacOSX; + return os == OperatingSystem.MACOSX; } /** @@ -324,7 +395,45 @@ public class VBrowserDetails implements Serializable { * @return true if run on Linux, false otherwise */ public boolean isLinux() { - return isLinux; + return os == OperatingSystem.LINUX; + } + + /** + * Tests if the browser is run on Android. + * + * @return true if run on Android, false otherwise + */ + public boolean isAndroid() { + return os == OperatingSystem.ANDROID; + } + + /** + * Tests if the browser is run in iOS. + * + * @return true if run in iOS, false otherwise + */ + public boolean isIOS() { + return os == OperatingSystem.IOS; + } + + /** + * Returns the major version of the operating system. Currently only + * supported for mobile devices (iOS/Android) + * + * @return The major version or -1 if unknown + */ + public int getOperatingSystemMajorVersion() { + return osMajorVersion; + } + + /** + * Returns the minor version of the operating system. Currently only + * supported for mobile devices (iOS/Android) + * + * @return The minor version or -1 if unknown + */ + public int getOperatingSystemMinorVersion() { + return osMinorVersion; } /** diff --git a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java index c2fa4f46bf..5eaf78f255 100644 --- a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java +++ b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java @@ -180,6 +180,9 @@ public class VDebugConsole extends VOverlay implements Console { private static final String help = "Drag title=move, shift-drag=resize, doubleclick title=min/max." + "Use debug=quiet to log only to browser console."; + private static final int DEFAULT_WIDTH = 650; + private static final int DEFAULT_HEIGHT = 400; + public VDebugConsole() { super(false, false); getElement().getStyle().setOverflow(Overflow.HIDDEN); @@ -301,10 +304,20 @@ public class VDebugConsole extends VOverlay implements Console { height = Integer.parseInt(split[3]); autoScrollValue = Boolean.valueOf(split[4]); } else { - width = 400; - height = 150; - top = Window.getClientHeight() - 160; - left = Window.getClientWidth() - 410; + int windowHeight = Window.getClientHeight(); + int windowWidth = Window.getClientWidth(); + width = DEFAULT_WIDTH; + height = DEFAULT_HEIGHT; + + if (height > windowHeight / 2) { + height = windowHeight / 2; + } + if (width > windowWidth / 2) { + width = windowWidth / 2; + } + + top = windowHeight - (height + 10); + left = windowWidth - (width + 10); } setPixelSize(width, height); setPopupPosition(left, top); diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index 294626e71a..fdc06b0e21 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -163,6 +163,15 @@ public class JsonEncoder { return outerArray; } + /** + * Returns the transport type for the given value. Only returns a transport + * type for internally handled values. + * + * @param value + * The value that should be transported + * @return One of the JsonEncode.VTYPE_ constants or null if the value + * cannot be transported using an internally handled type. + */ private static String getTransportType(Object value) { if (value == null) { return VTYPE_NULL; diff --git a/src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java b/src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java index f4305ffcaa..e61775a640 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java +++ b/src/com/vaadin/terminal/gwt/client/communication/MethodInvocation.java @@ -4,6 +4,7 @@ package com.vaadin.terminal.gwt.client.communication; +import java.io.Serializable; import java.util.Arrays; /** @@ -12,19 +13,24 @@ import java.util.Arrays; * * @since 7.0 */ -public class MethodInvocation { +public class MethodInvocation implements Serializable { private final String connectorId; private final String interfaceName; private final String methodName; - private final Object[] parameters; + private Object[] parameters; public MethodInvocation(String connectorId, String interfaceName, - String methodName, Object[] parameters) { + String methodName) { this.connectorId = connectorId; this.interfaceName = interfaceName; this.methodName = methodName; - this.parameters = parameters; + } + + public MethodInvocation(String connectorId, String interfaceName, + String methodName, Object[] parameters) { + this(connectorId, interfaceName, methodName); + setParameters(parameters); } public String getConnectorId() { @@ -43,9 +49,14 @@ public class MethodInvocation { return parameters; } + public void setParameters(Object[] parameters) { + this.parameters = parameters; + } + @Override public String toString() { return connectorId + ":" + interfaceName + "." + methodName + "(" + Arrays.toString(parameters) + ")"; } + }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java index 2458a27eac..fdb04f0ddf 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java @@ -5,8 +5,8 @@ package com.vaadin.terminal.gwt.client.ui; import java.util.Set; -import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.ComponentConnector; @@ -102,10 +102,7 @@ public abstract class AbstractComponentConnector extends AbstractConnector .getTabIndex()); } - if (getWidget() instanceof FocusWidget) { - FocusWidget fw = (FocusWidget) getWidget(); - fw.setEnabled(isEnabled()); - } + setWidgetEnabled(isEnabled()); // Style names String styleName = getStyleNames(getWidget().getStylePrimaryName()); @@ -141,6 +138,12 @@ public abstract class AbstractComponentConnector extends AbstractConnector updateComponentSize(); } + public void setWidgetEnabled(boolean widgetEnabled) { + if (getWidget() instanceof HasEnabled) { + ((HasEnabled) getWidget()).setEnabled(widgetEnabled); + } + } + private void updateComponentSize() { String newWidth = getState().getWidth(); String newHeight = getState().getHeight(); @@ -338,12 +341,13 @@ public abstract class AbstractComponentConnector extends AbstractConnector public void onUnregister() { super.onUnregister(); - // Warn if widget is still attached to DOM. It should never be at this - // point. + // Show an error if widget is still attached to DOM. It should never be + // at this point. if (getWidget() != null && getWidget().isAttached()) { - VConsole.log("Widget for unregistered connector " + getWidget().removeFromParent(); + VConsole.error("Widget is still attached to the DOM after the connector (" + Util.getConnectorString(this) - + " is still attached to the DOM."); + + ") has been unregistered. Widget was removed."); } } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java index d6ffc0f091..526631e4b2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java @@ -23,6 +23,14 @@ public abstract class AbstractComponentContainerConnector extends private final boolean debugLogging = false; /** + * Temporary storage for last enabled state to be able to see if it has + * changed. Can be removed once we are able to listen specifically for + * enabled changes in the state. Widget.isEnabled() cannot be used as all + * Widgets do not implement HasEnabled + */ + private boolean lastWidgetEnabledState = true; + + /** * Default constructor */ public AbstractComponentContainerConnector() { @@ -85,4 +93,18 @@ public abstract class AbstractComponentContainerConnector extends ConnectorHierarchyChangeEvent.TYPE, handler); } + @Override + public void setWidgetEnabled(boolean widgetEnabled) { + if (lastWidgetEnabledState == widgetEnabled) { + return; + } + lastWidgetEnabledState = widgetEnabled; + + super.setWidgetEnabled(widgetEnabled); + for (ComponentConnector c : getChildren()) { + // Update children as they might be affected by the enabled state of + // their parent + c.setWidgetEnabled(c.isEnabled()); + } + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/ClickRPC.java b/src/com/vaadin/terminal/gwt/client/ui/ClickRpc.java index 6c81b282e8..37d6443f55 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ClickRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ClickRpc.java @@ -6,7 +6,7 @@ package com.vaadin.terminal.gwt.client.ui; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -public interface ClickRPC extends ServerRpc { +public interface ClickRpc extends ServerRpc { /** * Called when a click event has occurred and there are server side * listeners for the event. diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 6b22f3c9f3..62697c4d98 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -3,6 +3,8 @@ */ package com.vaadin.terminal.gwt.client.ui; +import java.util.ArrayList; + import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; @@ -21,6 +23,7 @@ import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.FocusImpl; +import com.vaadin.terminal.gwt.client.BrowserInfo; /** * A scrollhandlers similar to {@link ScrollPanel}. @@ -126,7 +129,11 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * @return the vertical scroll position, in pixels */ public int getScrollPosition() { - return getElement().getScrollTop(); + if (getElement().getPropertyJSO("_vScrollTop") != null) { + return getElement().getPropertyInt("_vScrollTop"); + } else { + return getElement().getScrollTop(); + } } /** @@ -146,7 +153,18 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * the new vertical scroll position, in pixels */ public void setScrollPosition(int position) { - getElement().setScrollTop(position); + if (BrowserInfo.get().isAndroidWithBrokenScrollTop()) { + ArrayList<com.google.gwt.dom.client.Element> elements = TouchScrollDelegate + .getElements(getElement()); + for (com.google.gwt.dom.client.Element el : elements) { + final Style style = el.getStyle(); + style.setProperty("webkitTransform", "translate3d(0px," + + -position + "px,0px)"); + } + getElement().setPropertyInt("_vScrollTop", position); + } else { + getElement().setScrollTop(position); + } } public void onScroll(ScrollEvent event) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java index 09caa9ad82..7a5d85e34b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickEventHandler.java @@ -35,5 +35,5 @@ public abstract class LayoutClickEventHandler extends AbstractClickEventHandler getLayoutClickRPC().layoutClick(mouseDetails, getChildComponent(event)); } - protected abstract LayoutClickRPC getLayoutClickRPC(); + protected abstract LayoutClickRpc getLayoutClickRPC(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickRPC.java b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickRpc.java index 20b9c8750b..5b76f398a9 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/LayoutClickRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/LayoutClickRpc.java @@ -7,7 +7,7 @@ import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -public interface LayoutClickRPC extends ServerRpc { +public interface LayoutClickRpc extends ServerRpc { /** * Called when a layout click event has occurred and there are server side * listeners for the event. diff --git a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java index 3c08741de5..8b2248aff6 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java @@ -4,19 +4,22 @@ package com.vaadin.terminal.gwt.client.ui; import java.util.ArrayList; -import java.util.Date; +import com.google.gwt.animation.client.Animation; +import com.google.gwt.core.client.Duration; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.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.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.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.VConsole; /** @@ -64,27 +67,33 @@ public class TouchScrollDelegate implements NativePreviewHandler { private static final double FRICTION = 0.002; private static final double DECELERATION = 0.002; private static final int MAX_DURATION = 1500; - private int origX; private int origY; private Element[] scrollableElements; private Element scrolledElement; private int origScrollTop; private HandlerRegistration handlerRegistration; + private double lastAnimatedTranslateY; private int lastClientY; - private double pixxelsPerMs; - private boolean transitionPending = false; private int deltaScrollPos; private boolean transitionOn = false; private int finalScrollTop; private ArrayList<Element> layers; private boolean moved; + private ScrollHandler scrollHandler; private static TouchScrollDelegate activeScrollDelegate; + private static final boolean androidWithBrokenScrollTop = BrowserInfo.get() + .isAndroidWithBrokenScrollTop(); + public TouchScrollDelegate(Element... elements) { scrollableElements = elements; } + public void setScrollHandler(ScrollHandler scrollHandler) { + this.scrollHandler = scrollHandler; + } + public static TouchScrollDelegate getActiveScrollDelegate() { return activeScrollDelegate; } @@ -116,37 +125,8 @@ public class TouchScrollDelegate implements NativePreviewHandler { public void onTouchStart(TouchStartEvent event) { if (activeScrollDelegate == null && event.getTouches().length() == 1) { - - Touch touch = event.getTouches().get(0); - if (detectScrolledElement(touch)) { - VConsole.log("TouchDelegate takes over"); - event.stopPropagation(); - handlerRegistration = Event.addNativePreviewHandler(this); - activeScrollDelegate = this; - hookTransitionEndListener(scrolledElement - .getFirstChildElement()); - origX = touch.getClientX(); - origY = touch.getClientY(); - yPositions[0] = origY; - eventTimeStamps[0] = new Date(); - nextEvent = 1; - - if (transitionOn) { - // TODO calculate current position of ongoing transition, - // fix to that and start scroll from there. Another option - // is to investigate if we can get even close the same - // framerate with scheduler based impl instead of using - // transitions (GWT examples has impl of this, with jsni - // though). This is very smooth on native ipad, now we - // ignore touch starts during animation. - origScrollTop = scrolledElement.getScrollTop(); - } else { - origScrollTop = scrolledElement.getScrollTop(); - } - moved = false; - // event.preventDefault(); - // event.stopPropagation(); - } + NativeEvent nativeEvent = event.getNativeEvent(); + doTouchStart(nativeEvent); } else { /* * Touch scroll is currenly on (possibly bouncing). Ignore. @@ -154,16 +134,39 @@ public class TouchScrollDelegate implements NativePreviewHandler { } } - private native void hookTransitionEndListener(Element element) - /*-{ - if(!element.hasTransitionEndListener) { - var that = this; - element.addEventListener("webkitTransitionEnd",function(event){ - that.@com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate::onTransitionEnd()(); - },false); - element.hasTransitionEndListener = true; + private void doTouchStart(NativeEvent nativeEvent) { + if (transitionOn) { + momentum.cancel(); + } + Touch touch = nativeEvent.getTouches().get(0); + if (detectScrolledElement(touch)) { + VConsole.log("TouchDelegate takes over"); + nativeEvent.stopPropagation(); + handlerRegistration = Event.addNativePreviewHandler(this); + activeScrollDelegate = this; + origY = touch.getClientY(); + yPositions[0] = origY; + eventTimeStamps[0] = getTimeStamp(); + nextEvent = 1; + + origScrollTop = getScrollTop(); + VConsole.log("ST" + origScrollTop); + + moved = false; + // event.preventDefault(); + // event.stopPropagation(); + } + } + + private int getScrollTop() { + if (androidWithBrokenScrollTop) { + if (scrolledElement.getPropertyJSO("_vScrollTop") != null) { + return scrolledElement.getPropertyInt("_vScrollTop"); + } + return 0; } - }-*/; + return scrolledElement.getScrollTop(); + } private void onTransitionEnd() { if (finalScrollTop < 0) { @@ -175,7 +178,6 @@ public class TouchScrollDelegate implements NativePreviewHandler { } else { moveTransformationToScrolloffset(); } - transitionOn = false; } private void animateToScrollPosition(int to, int from) { @@ -184,7 +186,14 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (time <= 0) { time = 1; // get animation and transition end event } - translateTo(time, -to + origScrollTop); + VConsole.log("Animate " + time + " " + from + " " + to); + int translateTo = -to + origScrollTop; + int fromY = -from + origScrollTop; + if (androidWithBrokenScrollTop) { + fromY -= origScrollTop; + translateTo -= origScrollTop; + } + translateTo(time, fromY, translateTo); } private int getAnimationTimeForDistance(int dist) { @@ -200,16 +209,21 @@ public class TouchScrollDelegate implements NativePreviewHandler { * scrolltop, causing onscroll event. */ private void moveTransformationToScrolloffset() { - for (Element el : layers) { - Style style = el.getStyle(); - style.setProperty("webkitTransitionProperty", "none"); - style.setProperty("webkitTransform", "translate3d(0,0,0)"); + if (androidWithBrokenScrollTop) { + scrolledElement.setPropertyInt("_vScrollTop", finalScrollTop); + if (scrollHandler != null) { + scrollHandler.onScroll(null); + } + } else { + for (Element el : layers) { + Style style = el.getStyle(); + style.setProperty("webkitTransform", "translate3d(0,0,0)"); + } + scrolledElement.setScrollTop(finalScrollTop); } - scrolledElement.setScrollTop(finalScrollTop); activeScrollDelegate = null; handlerRegistration.removeHandler(); handlerRegistration = null; - } /** @@ -225,14 +239,7 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (el.isOrHasChild(target) && el.getScrollHeight() > el.getClientHeight()) { scrolledElement = el; - NodeList<Node> childNodes = scrolledElement.getChildNodes(); - layers = new ArrayList<Element>(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.getItem(i); - if (item.getNodeType() == Node.ELEMENT_NODE) { - layers.add((Element) item); - } - } + layers = getElements(scrolledElement); return true; } @@ -240,10 +247,21 @@ public class TouchScrollDelegate implements NativePreviewHandler { return false; } + public static ArrayList<Element> getElements(Element scrolledElement2) { + NodeList<Node> childNodes = scrolledElement2.getChildNodes(); + ArrayList<Element> l = new ArrayList<Element>(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.getItem(i); + if (item.getNodeType() == Node.ELEMENT_NODE) { + l.add((Element) item); + } + } + return l; + } + private void onTouchMove(NativeEvent event) { if (!moved) { - Date date = new Date(); - long l = (date.getTime() - eventTimeStamps[0].getTime()); + double l = (getTimeStamp() - eventTimeStamps[0]); VConsole.log(l + " ms from start to move"); } boolean handleMove = readPositionAndSpeed(event); @@ -255,15 +273,15 @@ public class TouchScrollDelegate implements NativePreviewHandler { int overscroll = (deltaScrollTop + origScrollTop) - getMaxFinalY(); overscroll = overscroll / 2; - if (overscroll > scrolledElement.getClientHeight() / 2) { - overscroll = scrolledElement.getClientHeight() / 2; + if (overscroll > getMaxOverScroll()) { + overscroll = getMaxOverScroll(); } deltaScrollTop = getMaxFinalY() + overscroll - origScrollTop; } else if (finalPos < 0) { // spring effect at the beginning int overscroll = finalPos / 2; - if (-overscroll > scrolledElement.getClientHeight() / 2) { - overscroll = -scrolledElement.getClientHeight() / 2; + if (-overscroll > getMaxOverScroll()) { + overscroll = -getMaxOverScroll(); } deltaScrollTop = overscroll - origScrollTop; } @@ -276,16 +294,20 @@ public class TouchScrollDelegate implements NativePreviewHandler { private void quickSetScrollPosition(int deltaX, int deltaY) { deltaScrollPos = deltaY; - translateTo(0, -deltaScrollPos); + if (androidWithBrokenScrollTop) { + deltaY += origScrollTop; + translateTo(-deltaY); + } else { + translateTo(-deltaScrollPos); + } } private static final int EVENTS_FOR_SPEED_CALC = 3; public static final int SIGNIFICANT_MOVE_THRESHOLD = 3; private int[] yPositions = new int[EVENTS_FOR_SPEED_CALC]; - private Date[] eventTimeStamps = new Date[EVENTS_FOR_SPEED_CALC]; + private double[] eventTimeStamps = new double[EVENTS_FOR_SPEED_CALC]; private int nextEvent = 0; - private Date transitionStart; - private Date transitionDuration; + private Animation momentum; /** * @@ -293,12 +315,11 @@ public class TouchScrollDelegate implements NativePreviewHandler { * @return */ private boolean readPositionAndSpeed(NativeEvent event) { - Date now = new Date(); Touch touch = event.getChangedTouches().get(0); lastClientY = touch.getClientY(); int eventIndx = nextEvent++; eventIndx = eventIndx % EVENTS_FOR_SPEED_CALC; - eventTimeStamps[eventIndx] = now; + eventTimeStamps[eventIndx] = getTimeStamp(); yPositions[eventIndx] = lastClientY; return isMovedSignificantly(); } @@ -348,15 +369,11 @@ public class TouchScrollDelegate implements NativePreviewHandler { // VConsole.log("To max overscroll"); finalY = getMaxFinalY() + getMaxOverScroll(); int fixedPixelsToMove = finalY - currentY; - pixelsPerMs = pixelsPerMs * pixelsToMove / fixedPixelsToMove - / FRICTION; pixelsToMove = fixedPixelsToMove; } else if (finalY < 0 - getMaxOverScroll()) { // VConsole.log("to min overscroll"); finalY = -getMaxOverScroll(); int fixedPixelsToMove = finalY - currentY; - pixelsPerMs = pixelsPerMs * pixelsToMove / fixedPixelsToMove - / FRICTION; pixelsToMove = fixedPixelsToMove; } else { duration = (int) (Math.abs(pixelsPerMs / DECELERATION)); @@ -380,8 +397,13 @@ public class TouchScrollDelegate implements NativePreviewHandler { return; } - int translateY = -finalY + origScrollTop; - translateTo(duration, translateY); + int translateTo = -finalY + origScrollTop; + int fromY = -currentY + origScrollTop; + if (androidWithBrokenScrollTop) { + fromY -= origScrollTop; + translateTo -= origScrollTop; + } + translateTo(duration, fromY, translateTo); } private double calculateSpeed() { @@ -392,47 +414,75 @@ public class TouchScrollDelegate implements NativePreviewHandler { } int idx = nextEvent % EVENTS_FOR_SPEED_CALC; final int firstPos = yPositions[idx]; - final Date firstTs = eventTimeStamps[idx]; + final double firstTs = eventTimeStamps[idx]; idx += EVENTS_FOR_SPEED_CALC; idx--; idx = idx % EVENTS_FOR_SPEED_CALC; final int lastPos = yPositions[idx]; - final Date lastTs = eventTimeStamps[idx]; + final double lastTs = eventTimeStamps[idx]; // speed as in change of scrolltop == -speedOfTouchPos - return (firstPos - lastPos) - / (double) (lastTs.getTime() - firstTs.getTime()); + return (firstPos - lastPos) / (lastTs - firstTs); } /** * Note positive scrolltop moves layer up, positive translate moves layer * down. - * - * @param duration - * @param translateY */ - private void translateTo(int duration, int translateY) { + private void translateTo(double translateY) { for (Element el : layers) { - final Style style = el.getStyle(); - if (duration > 0) { - style.setProperty("webkitTransitionDuration", duration + "ms"); - style.setProperty("webkitTransitionTimingFunction", - "cubic-bezier(0,0,0.25,1)"); - style.setProperty("webkitTransitionProperty", - "-webkit-transform"); - transitionOn = true; - transitionStart = new Date(); - transitionDuration = new Date(); - } else { - style.setProperty("webkitTransitionProperty", "none"); - } + Style style = el.getStyle(); style.setProperty("webkitTransform", "translate3d(0px," + translateY + "px,0px)"); } } + /** + * Note positive scrolltop moves layer up, positive translate moves layer + * down. + * + * @param duration + */ + private void translateTo(int duration, final int fromY, final int finalY) { + if (duration > 0) { + transitionOn = true; + + momentum = new Animation() { + + @Override + protected void onUpdate(double progress) { + lastAnimatedTranslateY = (fromY + (finalY - fromY) + * progress); + translateTo(lastAnimatedTranslateY); + } + + @Override + protected double interpolate(double progress) { + return 1 + Math.pow(progress - 1, 3); + } + + @Override + protected void onComplete() { + super.onComplete(); + transitionOn = false; + onTransitionEnd(); + } + + @Override + protected void onCancel() { + int delta = (int) (finalY - lastAnimatedTranslateY); + finalScrollTop -= delta; + moveTransformationToScrolloffset(); + transitionOn = false; + } + }; + momentum.run(duration); + } + } + private int getMaxOverScroll() { - return scrolledElement.getClientHeight() / 4; + return androidWithBrokenScrollTop ? 0 : scrolledElement + .getClientHeight() / 3; } private int getMaxFinalY() { @@ -441,14 +491,18 @@ public class TouchScrollDelegate implements NativePreviewHandler { } public void onPreviewNativeEvent(NativePreviewEvent event) { + int typeInt = event.getTypeInt(); if (transitionOn) { /* * TODO allow starting new events. See issue in onTouchStart */ event.cancel(); + + if (typeInt == Event.ONTOUCHSTART) { + doTouchStart(event.getNativeEvent()); + } return; } - int typeInt = event.getTypeInt(); switch (typeInt) { case Event.ONTOUCHMOVE: if (!event.isCanceled()) { @@ -484,4 +538,15 @@ public class TouchScrollDelegate implements NativePreviewHandler { public void setElements(com.google.gwt.user.client.Element[] elements) { scrollableElements = elements; } + + /** + * long calcucation are not very efficient in GWT, so this helper method + * returns timestamp in double. + * + * @return + */ + public static double getTimeStamp() { + return Duration.currentTimeMillis(); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java index 2307f67ac5..80b6254e02 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java @@ -21,7 +21,7 @@ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; import com.vaadin.terminal.gwt.client.ui.Connect; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; import com.vaadin.terminal.gwt.client.ui.absolutelayout.VAbsoluteLayout.AbsoluteWrapper; import com.vaadin.ui.AbsoluteLayout; @@ -38,20 +38,20 @@ public class AbsoluteLayoutConnector extends } @Override - protected LayoutClickRPC getLayoutClickRPC() { + protected LayoutClickRpc getLayoutClickRPC() { return rpc; }; }; - private AbsoluteLayoutServerRPC rpc; + private AbsoluteLayoutServerRpc rpc; private Map<String, AbsoluteWrapper> connectorIdToComponentWrapper = new HashMap<String, AbsoluteWrapper>(); @Override protected void init() { super.init(); - rpc = RpcProxy.create(AbsoluteLayoutServerRPC.class, this); + rpc = RpcProxy.create(AbsoluteLayoutServerRpc.class, this); } /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutServerRpc.java index df7ee1b82e..d626eb5b6c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutServerRpc.java @@ -4,8 +4,8 @@ package com.vaadin.terminal.gwt.client.ui.absolutelayout; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; -public interface AbsoluteLayoutServerRPC extends LayoutClickRPC, ServerRpc { +public interface AbsoluteLayoutServerRpc extends LayoutClickRpc, ServerRpc { }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java index 62a5e8ac8b..a555ecd392 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java @@ -57,7 +57,11 @@ public class ButtonConnector extends AbstractComponentConnector implements blurHandlerRegistration = EventHelper.updateBlurHandler(this, blurHandlerRegistration); // Set text - getWidget().setText(getState().getCaption()); + if (getState().isHtmlContentAllowed()) { + getWidget().setHtml(getState().getCaption()); + } else { + getWidget().setText(getState().getCaption()); + } // handle error if (null != getState().getErrorMessage()) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java index f26cdae0c6..fdc053b3ae 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java +++ b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java @@ -17,6 +17,10 @@ import com.vaadin.ui.Button; public class ButtonState extends ComponentState { private boolean disableOnClick = false; private int clickShortcutKeyCode = 0; + /** + * If caption should be rendered in HTML + */ + private boolean htmlContentAllowed = false; /** * Checks whether the button should be disabled on the client side on next @@ -62,4 +66,30 @@ public class ButtonState extends ComponentState { this.clickShortcutKeyCode = clickShortcutKeyCode; } + /** + * Set whether the caption text is rendered as HTML or not. You might need + * to retheme button to allow higher content than the original text style. + * + * If set to true, the captions are passed to the browser as html and the + * developer is responsible for ensuring no harmful html is used. If set to + * false, the content is passed to the browser as plain text. + * + * @param htmlContentAllowed + * <code>true</code> if caption is rendered as HTML, + * <code>false</code> otherwise + */ + public void setHtmlContentAllowed(boolean htmlContentAllowed) { + this.htmlContentAllowed = htmlContentAllowed; + } + + /** + * Return HTML rendering setting. + * + * @return <code>true</code> if the caption text is to be rendered as HTML, + * <code>false</code> otherwise + */ + public boolean isHtmlContentAllowed() { + return htmlContentAllowed; + } + } 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 f7d73d3b5e..e5e7dbba8b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java @@ -98,6 +98,10 @@ public class VButton extends FocusWidget implements ClickHandler { captionElement.setInnerText(text); } + public void setHtml(String html) { + captionElement.setInnerHTML(html); + } + @SuppressWarnings("deprecation") @Override /* diff --git a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java index 76fb9ab926..7df31a8593 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java @@ -20,7 +20,7 @@ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; import com.vaadin.terminal.gwt.client.ui.Connect; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; import com.vaadin.terminal.gwt.client.ui.VMarginInfo; import com.vaadin.terminal.gwt.client.ui.csslayout.VCssLayout.FlowPane; import com.vaadin.ui.CssLayout; @@ -38,19 +38,19 @@ public class CssLayoutConnector extends AbstractLayoutConnector { } @Override - protected LayoutClickRPC getLayoutClickRPC() { + protected LayoutClickRpc getLayoutClickRPC() { return rpc; }; }; - private CssLayoutServerRPC rpc; + private CssLayoutServerRpc rpc; private Map<ComponentConnector, VCaption> childToCaption = new HashMap<ComponentConnector, VCaption>(); @Override protected void init() { super.init(); - rpc = RpcProxy.create(CssLayoutServerRPC.class, this); + rpc = RpcProxy.create(CssLayoutServerRpc.class, this); } @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutServerRpc.java index 067ea54fdc..7ba89d4c4c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutServerRpc.java @@ -4,8 +4,8 @@ package com.vaadin.terminal.gwt.client.ui.csslayout; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; -public interface CssLayoutServerRPC extends LayoutClickRPC, ServerRpc { +public interface CssLayoutServerRpc extends LayoutClickRpc, ServerRpc { }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java b/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java index 5208d7cacf..b4194c40a6 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java @@ -362,9 +362,9 @@ public class VCustomLayout extends ComplexPanel { private native void publishResizedFunction(Element element) /*-{ var self = this; - element.notifyChildrenOfSizeChange = function() { + element.notifyChildrenOfSizeChange = $entry(function() { self.@com.vaadin.terminal.gwt.client.ui.customlayout.VCustomLayout::notifyChildrenOfSizeChange()(); - }; + }); }-*/; /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateFieldCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateFieldCalendar.java index 21e0e0820d..84b3c678eb 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateFieldCalendar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/datefield/VDateFieldCalendar.java @@ -45,6 +45,13 @@ public class VDateFieldCalendar extends VDateField { */ @SuppressWarnings("deprecation") protected void updateValueFromPanel() { + + // If field is invisible at the beginning, client can still be null when + // this function is called. + if (getClient() == null) { + return; + } + Date date2 = calendarPanel.getDate(); Date currentDate = getCurrentDate(); if (currentDate == null || date2.getTime() != currentDate.getTime()) { 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 5cbfabbb11..d09b81e1e1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java @@ -508,9 +508,9 @@ public class VDragAndDropWrapper extends VCustomComponent implements protected native void hookHtml5DragStart(Element el) /*-{ var me = this; - el.addEventListener("dragstart", function(ev) { + el.addEventListener("dragstart", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); }-*/; /** @@ -522,21 +522,21 @@ public class VDragAndDropWrapper extends VCustomComponent implements /*-{ var me = this; - el.addEventListener("dragenter", function(ev) { + el.addEventListener("dragenter", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); - el.addEventListener("dragleave", function(ev) { + el.addEventListener("dragleave", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); - el.addEventListener("dragover", function(ev) { + el.addEventListener("dragover", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); - el.addEventListener("drop", function(ev) { + el.addEventListener("drop", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); }-*/; public boolean updateDropDetails(VDragEvent drag) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java index f819b0559a..bb511524e5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java @@ -39,9 +39,9 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { /*-{ var me = this; - el.attachEvent("ondragstart", function(ev) { + el.attachEvent("ondragstart", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); }-*/; @Override @@ -49,21 +49,21 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { /*-{ var me = this; - el.attachEvent("ondragenter", function(ev) { + el.attachEvent("ondragenter", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); - el.attachEvent("ondragleave", function(ev) { + el.attachEvent("ondragleave", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); - el.attachEvent("ondragover", function(ev) { + el.attachEvent("ondragover", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); - el.attachEvent("ondrop", function(ev) { + el.attachEvent("ondrop", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); }-*/; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedConnector.java b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedConnector.java index 423aac1974..81ac195c8e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedConnector.java @@ -35,12 +35,12 @@ public class EmbeddedConnector extends AbstractComponentConnector implements public static final String ALTERNATE_TEXT = "alt"; - EmbeddedServerRPC rpc; + EmbeddedServerRpc rpc; @Override protected void init() { super.init(); - rpc = RpcProxy.create(EmbeddedServerRPC.class, this); + rpc = RpcProxy.create(EmbeddedServerRpc.class, this); } public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedServerRpc.java index e3410e408e..7f36c812bc 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/embedded/EmbeddedServerRpc.java @@ -4,7 +4,7 @@ package com.vaadin.terminal.gwt.client.ui.embedded; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.ClickRPC; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; -public interface EmbeddedServerRPC extends ClickRPC, ServerRpc { +public interface EmbeddedServerRpc extends ClickRpc, ServerRpc { }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java index 82f26383a1..e4a31b96ef 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java @@ -22,7 +22,7 @@ import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; import com.vaadin.terminal.gwt.client.ui.Connect; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; import com.vaadin.terminal.gwt.client.ui.VMarginInfo; import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout.Cell; import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; @@ -41,18 +41,18 @@ public class GridLayoutConnector extends AbstractComponentContainerConnector } @Override - protected LayoutClickRPC getLayoutClickRPC() { + protected LayoutClickRpc getLayoutClickRPC() { return rpc; }; }; - private GridLayoutServerRPC rpc; + private GridLayoutServerRpc rpc; private boolean needCaptionUpdate = false; @Override public void init() { - rpc = RpcProxy.create(GridLayoutServerRPC.class, this); + rpc = RpcProxy.create(GridLayoutServerRpc.class, this); getLayoutManager().registerDependency(this, getWidget().spacingMeasureElement); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutServerRpc.java index 1b7f1a15d9..cd8df297ec 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutServerRpc.java @@ -4,8 +4,8 @@ package com.vaadin.terminal.gwt.client.ui.gridlayout; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; -public interface GridLayoutServerRPC extends LayoutClickRPC, ServerRpc { +public interface GridLayoutServerRpc extends LayoutClickRpc, ServerRpc { }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java index 801c405826..84d3b73285 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java @@ -58,7 +58,11 @@ public class NativeButtonConnector extends AbstractComponentConnector implements blurHandlerRegistration); // Set text - getWidget().setText(getState().getCaption()); + if (getState().isHtmlContentAllowed()) { + getWidget().setHTML(getState().getCaption()); + } else { + getWidget().setText(getState().getCaption()); + } // handle error if (null != getState().getErrorMessage()) { 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 d0b8f73eb1..01ac4fab6a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java @@ -65,6 +65,11 @@ public class VNativeButton extends Button implements ClickHandler { } @Override + public void setHTML(String html) { + captionElement.setInnerHTML(html); + } + + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); 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 eb97160f52..0d222044ba 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java +++ b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java @@ -160,6 +160,13 @@ public class VNotification extends VOverlay { } super.show(); setPosition(position); + /** + * Android 4 fails to render notifications correctly without a little + * nudge (#8551) + */ + if (BrowserInfo.get().isAndroid()) { + Util.setStyleTemporarily(getElement(), "display", "none"); + } } @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java index d36046d6cb..174da61bd3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java @@ -23,7 +23,7 @@ import com.vaadin.terminal.gwt.client.communication.RpcProxy; import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; import com.vaadin.terminal.gwt.client.ui.VMarginInfo; import com.vaadin.terminal.gwt.client.ui.layout.ComponentConnectorLayoutSlot; import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; @@ -31,7 +31,7 @@ import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; public abstract class AbstractOrderedLayoutConnector extends AbstractLayoutConnector implements Paintable, DirectionalManagedLayout { - AbstractOrderedLayoutServerRPC rpc; + AbstractOrderedLayoutServerRpc rpc; private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( this) { @@ -43,7 +43,7 @@ public abstract class AbstractOrderedLayoutConnector extends } @Override - protected LayoutClickRPC getLayoutClickRPC() { + protected LayoutClickRpc getLayoutClickRPC() { return rpc; }; @@ -51,7 +51,7 @@ public abstract class AbstractOrderedLayoutConnector extends @Override public void init() { - rpc = RpcProxy.create(AbstractOrderedLayoutServerRPC.class, this); + rpc = RpcProxy.create(AbstractOrderedLayoutServerRpc.class, this); getLayoutManager().registerDependency(this, getWidget().spacingMeasureElement); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutServerRpc.java index f23cda3512..5a29eacada 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutServerRpc.java @@ -4,9 +4,9 @@ package com.vaadin.terminal.gwt.client.ui.orderedlayout; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.LayoutClickRPC; +import com.vaadin.terminal.gwt.client.ui.LayoutClickRpc; -public interface AbstractOrderedLayoutServerRPC extends LayoutClickRPC, +public interface AbstractOrderedLayoutServerRpc extends LayoutClickRpc, ServerRpc { }
\ No newline at end of file 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 a8512762f1..9555c38a36 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java @@ -43,11 +43,11 @@ public class PanelConnector extends AbstractComponentContainerConnector private Integer uidlScrollLeft; - private PanelServerRPC rpc; + private PanelServerRpc rpc; @Override public void init() { - rpc = RpcProxy.create(PanelServerRPC.class, this); + rpc = RpcProxy.create(PanelServerRpc.class, this); VPanel panel = getWidget(); LayoutManager layoutManager = getLayoutManager(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelServerRpc.java index 9a556cfe0e..9b59344aec 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelServerRpc.java @@ -4,8 +4,8 @@ package com.vaadin.terminal.gwt.client.ui.panel; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.ClickRPC; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; -public interface PanelServerRPC extends ClickRPC, ServerRpc { +public interface PanelServerRpc extends ClickRpc, ServerRpc { }
\ No newline at end of file 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 408c15383c..46f82d60b7 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java @@ -47,7 +47,7 @@ import com.vaadin.ui.Root; public class RootConnector extends AbstractComponentContainerConnector implements Paintable, MayScrollChildren { - private RootServerRPC rpc = RpcProxy.create(RootServerRPC.class, this); + private RootServerRpc rpc = RpcProxy.create(RootServerRpc.class, this); private HandlerRegistration childStateChangeHandlerRegistration; diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRpc.java index f32b972dfe..389500949d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRpc.java @@ -4,8 +4,8 @@ package com.vaadin.terminal.gwt.client.ui.root; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.ClickRPC; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; -public interface RootServerRPC extends ClickRPC, ServerRpc { +public interface RootServerRpc extends ClickRpc, ServerRpc { }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java index 13fc55d7ea..12a69d5556 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java @@ -306,6 +306,7 @@ public class VRoot extends SimplePanel implements ResizeHandler, /*-{ var j; for(j in $wnd.vaadin.vaadinConfigurations) { + // $entry not needed as function is not exported list.@java.util.Collection::add(Ljava/lang/Object;)(j); } }-*/; 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 7340c2cbd9..b3921204dc 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java @@ -30,12 +30,12 @@ import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.Splitter public abstract class AbstractSplitPanelConnector extends AbstractComponentContainerConnector implements SimpleManagedLayout { - private AbstractSplitPanelRPC rpc; + private AbstractSplitPanelRpc rpc; @Override protected void init() { super.init(); - rpc = RpcProxy.create(AbstractSplitPanelRPC.class, this); + rpc = RpcProxy.create(AbstractSplitPanelRpc.class, this); // TODO Remove getWidget().client = getConnection(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelRPC.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelRpc.java index 15cd47a656..cc043838ff 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelRpc.java @@ -6,7 +6,7 @@ package com.vaadin.terminal.gwt.client.ui.splitpanel; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -public interface AbstractSplitPanelRPC extends ServerRpc { +public interface AbstractSplitPanelRpc extends ServerRpc { /** * Called when the position has been updated by the user. 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 13f3adbc72..8b80eed840 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java @@ -3,6 +3,8 @@ */ package com.vaadin.terminal.gwt.client.ui.splitpanel; +import java.io.Serializable; + import com.vaadin.terminal.gwt.client.ComponentState; import com.vaadin.terminal.gwt.client.Connector; @@ -44,7 +46,7 @@ public class AbstractSplitPanelState extends ComponentState { this.splitterState = splitterState; } - public static class SplitterState { + public static class SplitterState implements Serializable { private float position; private String positionUnit; private boolean positionReversed = false; 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 563ca04abe..c45c26c4ac 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java @@ -18,6 +18,7 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; @@ -545,6 +546,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (touchScrollDelegate == null) { touchScrollDelegate = new TouchScrollDelegate( scrollBodyPanel.getElement()); + touchScrollDelegate.setScrollHandler(this); } return touchScrollDelegate; @@ -3833,6 +3835,14 @@ public class VScrollTable extends FlowPanel implements HasWidgets, DOM.appendChild(container, preSpacer); DOM.appendChild(container, table); DOM.appendChild(container, postSpacer); + if (BrowserInfo.get().isTouchDevice()) { + NodeList<Node> childNodes = container.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Element item = (Element) childNodes.getItem(i); + item.getStyle().setProperty("webkitTransform", + "translate3d(0,0,0)"); + } + } } @@ -4370,7 +4380,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public class VScrollTableRow extends Panel implements ActionOwner { - private static final int TOUCHSCROLL_TIMEOUT = 70; + private static final int TOUCHSCROLL_TIMEOUT = 100; private static final int DRAGMODE_MULTIROW = 2; protected ArrayList<Widget> childWidgets = new ArrayList<Widget>(); private boolean selected = false; @@ -5020,9 +5030,11 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } }; } - contextTouchTimeout.cancel(); - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + } } break; case Event.ONMOUSEDOWN: @@ -6175,6 +6187,11 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * The row to ensure is visible */ private void ensureRowIsVisible(VScrollTableRow row) { + if (BrowserInfo.get().isTouchDevice()) { + // Skip due to android devices that have broken scrolltop will may + // get odd scrolling here. + return; + } Util.scrollIntoViewVertically(row.getElement()); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java index b2141e06e5..7bd392b503 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java @@ -234,9 +234,9 @@ public class VTextField extends TextBoxBase implements Field, ChangeHandler, protected native void attachCutEventListener(Element el) /*-{ var me = this; - el.oncut = function() { + el.oncut = $entry(function() { me.@com.vaadin.terminal.gwt.client.ui.textfield.VTextField::onCut()(); - }; + }); }-*/; protected native void detachCutEventListener(Element el) diff --git a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java index 18cfc643d3..174a4b88ca 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java @@ -8,9 +8,9 @@ public class UploadIFrameOnloadStrategy { native void hookEvents(com.google.gwt.dom.client.Element iframe, VUpload upload) /*-{ - iframe.onload = function() { + iframe.onload = $entry(function() { upload.@com.vaadin.terminal.gwt.client.ui.upload.VUpload::onSubmitComplete()(); - }; + }); }-*/; /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java index 19d38a8a95..17a7e46bd5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java @@ -13,11 +13,11 @@ public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy { @Override native void hookEvents(Element iframe, VUpload upload) /*-{ - iframe.onreadystatechange = function() { + iframe.onreadystatechange = $entry(function() { if (iframe.readyState == 'complete') { upload.@com.vaadin.terminal.gwt.client.ui.upload.VUpload::onSubmitComplete()(); } - }; + }); }-*/; @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java b/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java index d40f954cdc..484000b8d1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java +++ b/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java @@ -34,9 +34,9 @@ public class VVideo extends VMediaBase { private native void updateDimensionsWhenMetadataLoaded(Element el) /*-{ var self = this; - el.addEventListener('loadedmetadata', function(e) { - $entry(self.@com.vaadin.terminal.gwt.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight)); - }, false); + el.addEventListener('loadedmetadata', $entry(function(e) { + self.@com.vaadin.terminal.gwt.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight); + }), false); }-*/; diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java b/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java index 85f4213d3e..6979982a9c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java @@ -44,7 +44,7 @@ public class WindowConnector extends AbstractComponentContainerConnector } }; - private WindowServerRPC rpc; + private WindowServerRpc rpc; boolean minWidthChecked = false; @@ -56,7 +56,7 @@ public class WindowConnector extends AbstractComponentContainerConnector @Override protected void init() { super.init(); - rpc = RpcProxy.create(WindowServerRPC.class, this); + rpc = RpcProxy.create(WindowServerRpc.class, this); getLayoutManager().registerDependency(this, getWidget().contentPanel.getElement()); diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/WindowServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/window/WindowServerRpc.java index 782284b562..4723c55786 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/window/WindowServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/window/WindowServerRpc.java @@ -4,7 +4,7 @@ package com.vaadin.terminal.gwt.client.ui.window; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.ClickRPC; +import com.vaadin.terminal.gwt.client.ui.ClickRpc; -public interface WindowServerRPC extends ClickRPC, ServerRpc { +public interface WindowServerRpc extends ClickRpc, ServerRpc { }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index 8235859758..77698805de 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -509,6 +509,9 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet protected void handleRequest(PortletRequest request, PortletResponse response) throws PortletException, IOException { + RequestTimer requestTimer = new RequestTimer(); + requestTimer.start(); + AbstractApplicationPortletWrapper portletWrapper = new AbstractApplicationPortletWrapper( this); @@ -610,6 +613,10 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet if (application.isRunning()) { switch (requestType) { case RENDER: + case ACTION: + // Both action requests and render requests are ok + // without a Root as they render the initial HTML + // and then do a second request try { root = application .getRootForRequest(wrappedRequest); @@ -714,6 +721,10 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } finally { Root.setCurrentRoot(null); Application.setCurrentApplication(null); + + requestTimer + .stop((AbstractWebApplicationContext) application + .getContext()); } } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 18cc3f97f4..6ab2748332 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -400,6 +400,9 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements private void service(WrappedHttpServletRequest request, WrappedHttpServletResponse response) throws ServletException, IOException { + RequestTimer requestTimer = new RequestTimer(); + requestTimer.start(); + AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( this); @@ -537,8 +540,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } finally { Root.setCurrentRoot(null); Application.setCurrentApplication(null); - } + requestTimer + .stop((AbstractWebApplicationContext) application + .getContext()); + } } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index bb6e726166..c57e7d8bc1 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -17,6 +17,7 @@ import java.io.Serializable; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.security.GeneralSecurityException; import java.text.CharacterIterator; import java.text.DateFormat; @@ -690,7 +691,7 @@ public abstract class AbstractCommunicationManager implements Serializable { outWriter.print(getSecurityKeyUIDL(request)); } - writeUidlResponse(repaintAll, outWriter, root, analyzeLayouts); + writeUidlResponse(request, repaintAll, outWriter, root, analyzeLayouts); closeJsonMessage(outWriter); @@ -734,7 +735,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } @SuppressWarnings("unchecked") - public void writeUidlResponse(boolean repaintAll, + public void writeUidlResponse(WrappedRequest request, boolean repaintAll, final PrintWriter outWriter, Root root, boolean analyzeLayouts) throws PaintException { ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<ClientConnector>(); @@ -807,8 +808,9 @@ public abstract class AbstractCommunicationManager implements Serializable { if (null != state) { // encode and send shared state try { + // FIXME Use declared type JSONArray stateJsonArray = JsonCodec.encode(state, - application); + state.getClass(), application); sharedStates .put(connector.getConnectorId(), stateJsonArray); } catch (JSONException e) { @@ -897,9 +899,10 @@ public abstract class AbstractCommunicationManager implements Serializable { invocationJson.put(invocation.getInterfaceName()); invocationJson.put(invocation.getMethodName()); JSONArray paramJson = new JSONArray(); - for (int i = 0; i < invocation.getParameters().length; ++i) { + for (int i = 0; i < invocation.getParameterTypes().length; ++i) { paramJson.put(JsonCodec.encode( - invocation.getParameters()[i], application)); + invocation.getParameters()[i], + invocation.getParameterTypes()[i], application)); } invocationJson.put(paramJson); rpcCalls.put(invocationJson); @@ -1093,6 +1096,19 @@ public abstract class AbstractCommunicationManager implements Serializable { if (dragAndDropService != null) { dragAndDropService.printJSONResponse(outWriter); } + + writePerformanceData(outWriter); + } + + /** + * Adds the performance timing data (used by TestBench 3) to the UIDL + * response. + */ + private void writePerformanceData(final PrintWriter outWriter) { + AbstractWebApplicationContext ctx = (AbstractWebApplicationContext) application + .getContext(); + outWriter.write(String.format(", \"timings\":[%d, %d]", + ctx.getTotalSessionTime(), ctx.getLastRequestTime())); } private void legacyPaint(PaintTarget paintTarget, @@ -1167,7 +1183,7 @@ public abstract class AbstractCommunicationManager implements Serializable { * The child to check * @return true if the child is visible to the client, false otherwise */ - private boolean isVisible(Component child) { + static boolean isVisible(Component child) { HasComponents parent = child.getParent(); if (parent == null || !child.isVisible()) { return child.isVisible(); @@ -1368,43 +1384,6 @@ public abstract class AbstractCommunicationManager implements Serializable { } /** - * Helper class for parsing variable change RPC calls. - * - * Note that variable changes still only support the old data types and - * partly use Vaadin 6 way of encoding of values. Other RPC method calls - * support more data types. - * - * @since 7.0 - */ - private class VariableChange { - private final String name; - private final Object value; - - public VariableChange(MethodInvocation invocation) throws JSONException { - name = (String) invocation.getParameters()[0]; - value = invocation.getParameters()[1]; - } - - /** - * Returns the variable name for the modification. - * - * @return variable name - */ - public String getName() { - return name; - } - - /** - * Returns the (parsed and converted) value of the updated variable. - * - * @return variable value - */ - public Object getValue() { - return value; - } - } - - /** * Processes a message burst received from the client. * * A burst can contain any number of RPC calls, including legacy variable @@ -1427,108 +1406,93 @@ public abstract class AbstractCommunicationManager implements Serializable { final String burst) { boolean success = true; try { - List<MethodInvocation> invocations = parseInvocations(burst); - - // Perform the method invocations, grouping consecutive variable - // changes for the same Paintable. + Set<Connector> enabledConnectors = new HashSet<Connector>(); - // Combining of variable changes is currently needed to preserve the - // old semantics for any component that relies on them. If the - // support for legacy variable change events is removed, each call - // can be performed separately and thelogic here simplified. + List<MethodInvocation> invocations = parseInvocations(burst); + for (MethodInvocation invocation : invocations) { + final ClientConnector connector = getConnector(app, + invocation.getConnectorId()); + if (connector != null && connector.isConnectorEnabled()) { + enabledConnectors.add(connector); + } + } for (int i = 0; i < invocations.size(); i++) { MethodInvocation invocation = invocations.get(i); - MethodInvocation nextInvocation = null; - if (i + 1 < invocations.size()) { - nextInvocation = invocations.get(i + 1); - } - - final String interfaceName = invocation.getInterfaceName(); + final ClientConnector connector = getConnector(app, + invocation.getConnectorId()); - if (!ApplicationConnection.UPDATE_VARIABLE_INTERFACE - .equals(interfaceName)) { - // handle other RPC calls than variable changes - applyInvocation(app, invocation); + if (connector == null) { + logger.log( + Level.WARNING, + "RPC call to " + invocation.getInterfaceName() + + "." + invocation.getMethodName() + + " received for connector " + + invocation.getConnectorId() + + " but no such connector could be found"); continue; } - final ClientConnector connector = getConnector(app, - invocation.getConnectorId()); - final VariableOwner owner = (VariableOwner) connector; - - boolean connectorEnabled = (connector != null && connector - .isConnectorEnabled()); - - if (owner != null && connectorEnabled) { - VariableChange change = new VariableChange(invocation); - - // TODO could optimize with a single value map if only one - // change for a paintable - - Map<String, Object> m = new HashMap<String, Object>(); - m.put(change.getName(), change.getValue()); - while (nextInvocation != null - && invocation.getConnectorId().equals( - nextInvocation.getConnectorId()) - && ApplicationConnection.UPDATE_VARIABLE_METHOD - .equals(nextInvocation.getMethodName())) { - i++; - invocation = nextInvocation; - change = new VariableChange(invocation); - m.put(change.getName(), change.getValue()); - if (i + 1 < invocations.size()) { - nextInvocation = invocations.get(i + 1); - } else { - nextInvocation = null; - } - } - try { - changeVariables(source, owner, m); - } catch (Exception e) { - Component errorComponent = null; - if (owner instanceof Component) { - errorComponent = (Component) owner; - } else if (owner instanceof DragAndDropService) { - if (m.get("dhowner") instanceof Component) { - errorComponent = (Component) m.get("dhowner"); - } + if (!enabledConnectors.contains(connector)) { + + if (invocation instanceof LegacyChangeVariablesInvocation) { + LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation; + // TODO convert window close to a separate RPC call and + // handle above - not a variable change + + // Handle special case where window-close is called + // after the window has been removed from the + // application or the application has closed + Map<String, Object> changes = legacyInvocation + .getVariableChanges(); + if (changes.size() == 1 && changes.containsKey("close") + && Boolean.TRUE.equals(changes.get("close"))) { + // Silently ignore this + continue; } - handleChangeVariablesError(app, errorComponent, e, m); - } - } else { - // TODO convert window close to a separate RPC call and - // handle above - not a variable change - - VariableChange change = new VariableChange(invocation); - - // Handle special case where window-close is called - // after the window has been removed from the - // application or the application has closed - if ("close".equals(change.getName()) - && Boolean.TRUE.equals(change.getValue())) { - // Silently ignore this - continue; } - // Ignore variable change - String msg = "Warning: Ignoring RPC call for "; - if (owner != null) { - msg += "disabled component " + owner.getClass(); - String caption = ((Component) owner).getCaption(); + // Connector is disabled, log a warning and move to the next + String msg = "Ignoring RPC call for disabled connector " + + connector.getClass().getName(); + if (connector instanceof Component) { + String caption = ((Component) connector).getCaption(); if (caption != null) { msg += ", caption=" + caption; } - } else { - msg += "non-existent component, VAR_PID=" - + invocation.getConnectorId(); - // TODO should this cause the message to be ignored? - success = false; } logger.warning(msg); continue; } + + if (invocation instanceof ServerRpcMethodInvocation) { + ServerRpcManager.applyInvocation(connector, + (ServerRpcMethodInvocation) invocation); + } else { + + // All code below is for legacy variable changes + LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation; + Map<String, Object> changes = legacyInvocation + .getVariableChanges(); + try { + changeVariables(source, (VariableOwner) connector, + changes); + } catch (Exception e) { + Component errorComponent = null; + if (connector instanceof Component) { + errorComponent = (Component) connector; + } else if (connector instanceof DragAndDropService) { + Object dropHandlerOwner = changes.get("dhowner"); + if (dropHandlerOwner instanceof Component) { + errorComponent = (Component) dropHandlerOwner; + } + } + handleChangeVariablesError(app, errorComponent, e, + changes); + + } + } } } catch (JSONException e) { @@ -1542,34 +1506,6 @@ public abstract class AbstractCommunicationManager implements Serializable { } /** - * Execute an RPC call from the client by finding its target and letting the - * RPC mechanism call the correct method for it. - * - * @param app - * - * @param invocation - */ - protected void applyInvocation(Application app, MethodInvocation invocation) { - Connector c = app.getConnector(invocation.getConnectorId()); - if (c instanceof RpcTarget) { - ServerRpcManager.applyInvocation((RpcTarget) c, invocation); - } else if (c == null) { - logger.log( - Level.WARNING, - "RPC call " + invocation.getInterfaceName() + "." - + invocation.getMethodName() - + " received for connector id " - + invocation.getConnectorId() - + " but no such connector could be found"); - - } else { - logger.log(Level.WARNING, "RPC call received for connector " - + c.getClass().getName() + " (" + c.getConnectorId() - + ") but the connector is not a ServerRpcTarget"); - } - } - - /** * Parse a message burst from the client into a list of MethodInvocation * instances. * @@ -1584,25 +1520,94 @@ public abstract class AbstractCommunicationManager implements Serializable { ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>(); + MethodInvocation previousInvocation = null; // parse JSON to MethodInvocations for (int i = 0; i < invocationsJson.length(); ++i) { + JSONArray invocationJson = invocationsJson.getJSONArray(i); - String connectorId = invocationJson.getString(0); - String interfaceName = invocationJson.getString(1); - String methodName = invocationJson.getString(2); - JSONArray parametersJson = invocationJson.getJSONArray(3); - Object[] parameters = new Object[parametersJson.length()]; - for (int j = 0; j < parametersJson.length(); ++j) { - parameters[j] = JsonCodec.decode( - parametersJson.getJSONArray(j), application); + + MethodInvocation invocation = parseInvocation(invocationJson, + previousInvocation); + if (invocation != null) { + // Can be null iff the invocation was a legacy invocation and it + // was merged with the previous one + invocations.add(invocation); + previousInvocation = invocation; } - MethodInvocation invocation = new MethodInvocation(connectorId, - interfaceName, methodName, parameters); - invocations.add(invocation); } return invocations; } + private MethodInvocation parseInvocation(JSONArray invocationJson, + MethodInvocation previousInvocation) throws JSONException { + String connectorId = invocationJson.getString(0); + String interfaceName = invocationJson.getString(1); + String methodName = invocationJson.getString(2); + + JSONArray parametersJson = invocationJson.getJSONArray(3); + + if (LegacyChangeVariablesInvocation.isLegacyVariableChange( + interfaceName, methodName)) { + if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) { + previousInvocation = null; + } + + return parseLegacyChangeVariablesInvocation(connectorId, + interfaceName, methodName, + (LegacyChangeVariablesInvocation) previousInvocation, + parametersJson); + } else { + return parseServerRpcInvocation(connectorId, interfaceName, + methodName, parametersJson); + } + + } + + private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation( + String connectorId, String interfaceName, String methodName, + LegacyChangeVariablesInvocation previousInvocation, + JSONArray parametersJson) throws JSONException { + if (parametersJson.length() != 2) { + throw new JSONException( + "Invalid parameters in legacy change variables call. Expected 2, was " + + parametersJson.length()); + } + String variableName = (String) JsonCodec + .decodeInternalType(String.class, true, + parametersJson.getJSONArray(0), application); + Object value = JsonCodec.decodeInternalType( + parametersJson.getJSONArray(1), application); + + if (previousInvocation != null + && previousInvocation.getConnectorId().equals(connectorId)) { + previousInvocation.setVariableChange(variableName, value); + return null; + } else { + return new LegacyChangeVariablesInvocation(connectorId, + variableName, value); + } + } + + private ServerRpcMethodInvocation parseServerRpcInvocation( + String connectorId, String interfaceName, String methodName, + JSONArray parametersJson) throws JSONException { + ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation( + connectorId, interfaceName, methodName, parametersJson.length()); + + Object[] parameters = new Object[parametersJson.length()]; + Type[] declaredRpcMethodParameterTypes = invocation.getMethod() + .getGenericParameterTypes(); + + for (int j = 0; j < parametersJson.length(); ++j) { + JSONArray parameterJson = parametersJson.getJSONArray(j); + Type parameterType = declaredRpcMethodParameterTypes[j]; + parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType, + parameterJson, application); + } + invocation.setParameters(parameters); + return invocation; + } + protected void changeVariables(Object source, final VariableOwner owner, Map<String, Object> m) { owner.changeVariables(source, m); @@ -2126,7 +2131,18 @@ public abstract class AbstractCommunicationManager implements Serializable { String initialUIDL = getInitialUIDL(combinedRequest, root); params.put("uidl", initialUIDL); } - response.getWriter().write(params.toString()); + + // NOTE! GateIn requires, for some weird reason, getOutputStream + // to be used instead of getWriter() (it seems to interpret + // application/json as a binary content type) + final OutputStream out = response.getOutputStream(); + final PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + + outWriter.write(params.toString()); + // NOTE GateIn requires the buffers to be flushed to work + outWriter.flush(); + out.flush(); } catch (RootRequiresMoreInformationException e) { // Requiring more information at this point is not allowed // TODO handle in a better way @@ -2158,7 +2174,7 @@ public abstract class AbstractCommunicationManager implements Serializable { if (isXSRFEnabled(root.getApplication())) { pWriter.print(getSecurityKeyUIDL(request)); } - writeUidlResponse(true, pWriter, root, false); + writeUidlResponse(request, true, pWriter, root, false); pWriter.print("}"); String initialUIDL = sWriter.toString(); logger.log(Level.FINE, "Initial UIDL:" + initialUIDL); diff --git a/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java b/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java index c8335a8607..c0ae0afc26 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java @@ -44,6 +44,10 @@ public abstract class AbstractWebApplicationContext implements protected HashMap<Application, AbstractCommunicationManager> applicationToAjaxAppMgrMap = new HashMap<Application, AbstractCommunicationManager>(); + private long totalSessionTime = 0; + + private long lastRequestTime = -1; + public void addTransactionListener(TransactionListener listener) { if (listener != null) { listeners.add(listener); @@ -222,4 +226,30 @@ public abstract class AbstractWebApplicationContext implements return relativeUri.substring(index + 1, next); } + /** + * @return The total time spent servicing requests in this session. + */ + public long getTotalSessionTime() { + return totalSessionTime; + } + + /** + * Sets the time spent servicing the last request in the session and updates + * the total time spent servicing requests in this session. + * + * @param time + * the time spent in the last request. + */ + public void setLastRequestTime(long time) { + lastRequestTime = time; + totalSessionTime += time; + } + + /** + * @return the time spent servicing the last request in this session. + */ + public long getLastRequestTime() { + return lastRequestTime; + } + }
\ 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 cc4c1161a0..7a1f0fad68 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientConnector.java +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -16,7 +16,7 @@ import com.vaadin.terminal.gwt.client.Connector; * @since 7.0.0 * */ -public interface ClientConnector extends Connector { +public interface ClientConnector extends Connector, RpcTarget { /** * Returns the list of pending server to client RPC calls and clears the * list. diff --git a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java index 2edcb8a9f2..99633a13d6 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java +++ b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.server; import java.io.Serializable; +import java.lang.reflect.Method; /** * Internal class for keeping track of pending server to client method @@ -18,21 +19,27 @@ public class ClientMethodInvocation implements Serializable, private final String interfaceName; private final String methodName; private final Object[] parameters; + private Class<?>[] parameterTypes; - // used for sorting calls between different Paintables in the same Root + // used for sorting calls between different connectors in the same Root private final long sequenceNumber; // TODO may cause problems when clustering etc. private static long counter = 0; public ClientMethodInvocation(ClientConnector connector, - String interfaceName, String methodName, Object[] parameters) { + String interfaceName, Method method, Object[] parameters) { this.connector = connector; this.interfaceName = interfaceName; - this.methodName = methodName; + methodName = method.getName(); + parameterTypes = method.getParameterTypes(); this.parameters = (null != parameters) ? parameters : new Object[0]; sequenceNumber = ++counter; } + public Class<?>[] getParameterTypes() { + return parameterTypes; + } + public ClientConnector getConnector() { return connector; } diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java index ca499d024d..d3fe5a890b 100644 --- a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -233,4 +233,9 @@ public class DragAndDropService implements VariableOwner, ClientConnector { public List<ClientMethodInvocation> retrievePendingRpcCalls() { return null; } + + public RpcManager getRpcManager(Class<?> rpcInterface) { + // TODO Use rpc for drag'n'drop + return null; + } } diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index 1824a16fb2..375cce4161 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -10,6 +10,8 @@ import java.beans.PropertyDescriptor; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -25,6 +27,7 @@ import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; +import com.vaadin.ui.Component; /** * Decoder for converting RPC parameters and other values from JSON in transfer @@ -36,6 +39,12 @@ public class JsonCodec implements Serializable { private static Map<Class<?>, String> typeToTransportType = new HashMap<Class<?>, String>(); + /** + * Note! This does not contain primitives. + * <p> + */ + private static Map<String, Class<?>> transportTypeToType = new HashMap<String, Class<?>>(); + static { registerType(String.class, JsonEncoder.VTYPE_STRING); registerType(Connector.class, JsonEncoder.VTYPE_CONNECTOR); @@ -49,8 +58,6 @@ public class JsonCodec implements Serializable { registerType(double.class, JsonEncoder.VTYPE_DOUBLE); registerType(Long.class, JsonEncoder.VTYPE_LONG); registerType(long.class, JsonEncoder.VTYPE_LONG); - // transported as string representation - registerType(Enum.class, JsonEncoder.VTYPE_STRING); registerType(String[].class, JsonEncoder.VTYPE_STRINGARRAY); registerType(Object[].class, JsonEncoder.VTYPE_ARRAY); registerType(Map.class, JsonEncoder.VTYPE_MAP); @@ -60,96 +67,260 @@ public class JsonCodec implements Serializable { private static void registerType(Class<?> type, String transportType) { typeToTransportType.put(type, transportType); + if (!type.isPrimitive()) { + transportTypeToType.put(transportType, type); + } + } + + public static boolean isInternalTransportType(String transportType) { + return transportTypeToType.containsKey(transportType); + } + + public static boolean isInternalType(Type type) { + if (type instanceof Class && ((Class<?>) type).isPrimitive()) { + // All primitive types are handled internally + return true; + } + return typeToTransportType.containsKey(getClassForType(type)); + } + + private static Class<?> getClassForType(Type type) { + if (type instanceof ParameterizedType) { + return (Class<?>) (((ParameterizedType) type).getRawType()); + } else { + return (Class<?>) type; + } + } + + public static String getTransportType(JSONArray encodedValue) + throws JSONException { + return encodedValue.getString(0); + } + + private static Class<?> getType(String transportType) { + return transportTypeToType.get(transportType); } /** - * Convert a JSON array with two elements (type and value) into a - * server-side type, recursively if necessary. + * Decodes the given value and type, restricted to using only internal + * types. * - * @param value - * JSON array with two elements + * @param valueAndType * @param application - * mapper between connector ID and {@link Connector} objects - * @return converted value (does not contain JSON types) * @throws JSONException - * if the conversion fails */ - public static Object decode(JSONArray value, Application application) - throws JSONException { - return decodeVariableValue(value.getString(0), value.get(1), + @Deprecated + public static Object decodeInternalType(JSONArray valueAndType, + Application application) throws JSONException { + String transportType = getTransportType(valueAndType); + return decodeInternalType(getType(transportType), true, valueAndType, application); } - private static Object decodeVariableValue(String variableType, - Object value, Application application) throws JSONException { - Object val = null; - // TODO type checks etc. - if (JsonEncoder.VTYPE_ARRAY.equals(variableType)) { - val = decodeArray((JSONArray) value, application); - } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { - val = decodeList((JSONArray) value, application); - } else if (JsonEncoder.VTYPE_SET.equals(variableType)) { - val = decodeSet((JSONArray) value, application); - } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(variableType)) { - val = decodeConnectorMap((JSONObject) value, application); - } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { - val = decodeMap((JSONObject) value, application); - } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(variableType)) { - val = decodeStringArray((JSONArray) value); - } else if (JsonEncoder.VTYPE_STRING.equals(variableType)) { - val = value; - } else if (JsonEncoder.VTYPE_INTEGER.equals(variableType)) { - // TODO handle properly - val = Integer.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_LONG.equals(variableType)) { - // TODO handle properly - val = Long.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_FLOAT.equals(variableType)) { - // TODO handle properly - val = Float.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_DOUBLE.equals(variableType)) { - // TODO handle properly - val = Double.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { - // TODO handle properly - val = Boolean.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { - val = application.getConnector(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_NULL.equals(variableType)) { - val = null; + public static Object decodeInternalOrCustomType(Type targetType, + JSONArray valueAndType, Application application) + throws JSONException { + if (isInternalType(targetType)) { + return decodeInternalType(targetType, false, valueAndType, + application); } else { - // Try to decode object using fields - return decodeObject(variableType, (JSONObject) value, application); + return decodeCustomType(targetType, valueAndType, application); + } + } + public static Object decodeCustomType(Type targetType, + JSONArray valueAndType, Application application) + throws JSONException { + if (isInternalType(targetType)) { + throw new JSONException("decodeCustomType cannot be used for " + + targetType + ", which is an internal type"); + } + String transportType = getCustomTransportType(getClassForType(targetType)); + String encodedTransportType = valueAndType.getString(0); + if (!transportTypesCompatible(encodedTransportType, transportType)) { + throw new JSONException("Expected a value of type " + transportType + + ", received " + encodedTransportType); } - return val; + // Try to decode object using fields + return decodeObject(targetType, (JSONObject) valueAndType.get(1), + application); } - private static Object decodeMap(JSONObject jsonMap, Application application) - throws JSONException { + /** + * Decodes a value that is of an internal type. + * <p> + * Ensures the encoded value is of the same type as target type. + * </p> + * <p> + * Allows restricting collections so that they must be declared using + * generics. If this is used then all objects in the collection are encoded + * using the declared type. Otherwise only internal types are allowed in + * collections. + * </p> + * + * @param targetType + * The type that should be returned by this method + * @param valueAndType + * The encoded value and type array + * @param application + * A reference to the application + * @param enforceGenericsInCollections + * true if generics should be enforce, false to only allow + * internal types in collections + * @return + * @throws JSONException + */ + public static Object decodeInternalType(Type targetType, + boolean restrictToInternalTypes, JSONArray valueAndType, + Application application) throws JSONException { + String encodedTransportType = valueAndType.getString(0); + if (!isInternalType(targetType)) { + throw new JSONException("Type " + targetType + + " is not a supported internal type."); + } + String transportType = getInternalTransportType(targetType); + if (!transportTypesCompatible(encodedTransportType, transportType)) { + throw new JSONException("Expected a value of type " + targetType + + ", received " + getType(encodedTransportType)); + } + + Object encodedJsonValue = valueAndType.get(1); + + if (JsonEncoder.VTYPE_NULL.equals(encodedTransportType)) { + return null; + } + // Collections + if (JsonEncoder.VTYPE_LIST.equals(transportType)) { + return decodeList(targetType, restrictToInternalTypes, + (JSONArray) encodedJsonValue, application); + } else if (JsonEncoder.VTYPE_SET.equals(transportType)) { + return decodeSet(targetType, restrictToInternalTypes, + (JSONArray) encodedJsonValue, application); + } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(transportType)) { + return decodeConnectorToObjectMap(targetType, + restrictToInternalTypes, (JSONObject) encodedJsonValue, + application); + } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) { + return decodeStringToObjectMap(targetType, restrictToInternalTypes, + (JSONObject) encodedJsonValue, application); + } + + // Arrays + if (JsonEncoder.VTYPE_ARRAY.equals(transportType)) { + + return decodeObjectArray(targetType, (JSONArray) encodedJsonValue, + application); + + } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(transportType)) { + return decodeStringArray((JSONArray) encodedJsonValue); + } + + // Special Vaadin types + + String stringValue = String.valueOf(encodedJsonValue); + + if (JsonEncoder.VTYPE_CONNECTOR.equals(transportType)) { + return application.getConnector(stringValue); + } + + // Standard Java types + + if (JsonEncoder.VTYPE_STRING.equals(transportType)) { + return stringValue; + } else if (JsonEncoder.VTYPE_INTEGER.equals(transportType)) { + return Integer.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_LONG.equals(transportType)) { + return Long.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_FLOAT.equals(transportType)) { + return Float.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_DOUBLE.equals(transportType)) { + return Double.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_BOOLEAN.equals(transportType)) { + return Boolean.valueOf(stringValue); + } + + throw new JSONException("Unknown type " + transportType); + } + + private static boolean transportTypesCompatible( + String encodedTransportType, String transportType) { + if (encodedTransportType == null) { + return false; + } + if (encodedTransportType.equals(transportType)) { + return true; + } + if (encodedTransportType.equals(JsonEncoder.VTYPE_NULL)) { + return true; + } + + return false; + } + + @Deprecated + private static Map<String, Object> decodeStringToObjectMap(Type targetType, + boolean restrictToInternalTypes, JSONObject jsonMap, + Application application) throws JSONException { HashMap<String, Object> map = new HashMap<String, Object>(); Iterator<String> it = jsonMap.keys(); while (it.hasNext()) { String key = it.next(); - map.put(key, decode(jsonMap.getJSONArray(key), application)); + JSONArray encodedValueAndType = jsonMap.getJSONArray(key); + Object decodedChild = decodeChild(targetType, + restrictToInternalTypes, 1, encodedValueAndType, + application); + map.put(key, decodedChild); } return map; } - private static Object decodeConnectorMap(JSONObject jsonMap, - Application application) throws JSONException { + @Deprecated + private static Map<Connector, Object> decodeConnectorToObjectMap( + Type targetType, boolean restrictToInternalTypes, + JSONObject jsonMap, Application application) throws JSONException { HashMap<Connector, Object> map = new HashMap<Connector, Object>(); Iterator<String> it = jsonMap.keys(); while (it.hasNext()) { String connectorId = it.next(); Connector connector = application.getConnector(connectorId); - map.put(connector, - decode(jsonMap.getJSONArray(connectorId), application)); + JSONArray encodedValueAndType = jsonMap.getJSONArray(connectorId); + Object decodedChild = decodeChild(targetType, + restrictToInternalTypes, 1, encodedValueAndType, + application); + map.put(connector, decodedChild); } return map; } + /** + * @param targetType + * @param restrictToInternalTypes + * @param typeIndex + * The index of a generic type to use to define the child type + * that should be decoded + * @param encodedValueAndType + * @param application + * @return + * @throws JSONException + */ + private static Object decodeChild(Type targetType, + boolean restrictToInternalTypes, int typeIndex, + JSONArray encodedValueAndType, Application application) + throws JSONException { + if (!restrictToInternalTypes && targetType instanceof ParameterizedType) { + Type childType = ((ParameterizedType) targetType) + .getActualTypeArguments()[typeIndex]; + // Only decode the given type + return decodeInternalOrCustomType(childType, encodedValueAndType, + application); + } else { + // Only internal types when not enforcing a given type to avoid + // security issues + return decodeInternalType(encodedValueAndType, application); + } + } + private static String[] decodeStringArray(JSONArray jsonArray) throws JSONException { int length = jsonArray.length(); @@ -160,43 +331,93 @@ public class JsonCodec implements Serializable { return tokens.toArray(new String[tokens.size()]); } - private static Object decodeArray(JSONArray jsonArray, - Application application) throws JSONException { - List list = decodeList(jsonArray, application); + private static Object[] decodeObjectArray(Type targetType, + JSONArray jsonArray, Application application) throws JSONException { + List list = decodeList(List.class, true, jsonArray, application); return list.toArray(new Object[list.size()]); } - private static List<Object> decodeList(JSONArray jsonArray, + private static List<Object> decodeList(Type targetType, + boolean restrictToInternalTypes, JSONArray jsonArray, Application application) throws JSONException { List<Object> list = new ArrayList<Object>(); for (int i = 0; i < jsonArray.length(); ++i) { // each entry always has two elements: type and value - JSONArray entryArray = jsonArray.getJSONArray(i); - list.add(decode(entryArray, application)); + JSONArray encodedValueAndType = jsonArray.getJSONArray(i); + Object decodedChild = decodeChild(targetType, + restrictToInternalTypes, 0, encodedValueAndType, + application); + list.add(decodedChild); } return list; } - private static Set<Object> decodeSet(JSONArray jsonArray, + private static Set<Object> decodeSet(Type targetType, + boolean restrictToInternalTypes, JSONArray jsonArray, Application application) throws JSONException { HashSet<Object> set = new HashSet<Object>(); - set.addAll(decodeList(jsonArray, application)); + set.addAll(decodeList(List.class, restrictToInternalTypes, jsonArray, + application)); return set; } /** - * Encode a value to a JSON representation for transport from the server to - * the client. + * Returns the name that should be used as field name in the JSON. We strip + * "set" from the setter, keeping the result - this is easy to do on both + * server and client, avoiding some issues with cASE. E.g setZIndex() + * becomes "ZIndex". Also ensures that both getter and setter are present, + * returning null otherwise. * - * @param value - * value to convert - * @param application - * mapper between connector ID and {@link Connector} objects - * @return JSON representation of the value - * @throws JSONException - * if encoding a value fails (e.g. NaN or infinite number) + * @param pd + * @return the name to be used or null if both getter and setter are not + * found. */ - public static JSONArray encode(Object value, Application application) + private static String getTransportFieldName(PropertyDescriptor pd) { + if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { + return null; + } + return pd.getWriteMethod().getName().substring(3); + } + + private static Object decodeObject(Type targetType, + JSONObject serializedObject, Application application) + throws JSONException { + + Class<?> targetClass = getClassForType(targetType); + try { + Object decodedObject = targetClass.newInstance(); + for (PropertyDescriptor pd : Introspector.getBeanInfo(targetClass) + .getPropertyDescriptors()) { + + String fieldName = getTransportFieldName(pd); + if (fieldName == null) { + continue; + } + JSONArray encodedFieldValue = serializedObject + .getJSONArray(fieldName); + Type fieldType = pd.getReadMethod().getGenericReturnType(); + Object decodedFieldValue = decodeInternalOrCustomType( + fieldType, encodedFieldValue, application); + + pd.getWriteMethod().invoke(decodedObject, decodedFieldValue); + } + + return decodedObject; + } catch (IllegalArgumentException e) { + throw new JSONException(e); + } catch (IllegalAccessException e) { + throw new JSONException(e); + } catch (InvocationTargetException e) { + throw new JSONException(e); + } catch (InstantiationException e) { + throw new JSONException(e); + } catch (IntrospectionException e) { + throw new JSONException(e); + } + } + + @Deprecated + private static JSONArray encode(Object value, Application application) throws JSONException { return encode(value, null, application); } @@ -205,14 +426,14 @@ public class JsonCodec implements Serializable { Application application) throws JSONException { if (null == value) { - return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL); + return encodeNull(); } if (valueType == null) { valueType = value.getClass(); } - String transportType = getTransportType(valueType); + String internalTransportType = getInternalTransportType(valueType); if (value instanceof String[]) { String[] array = (String[]) value; JSONArray jsonArray = new JSONArray(); @@ -225,16 +446,16 @@ public class JsonCodec implements Serializable { } else if (value instanceof Boolean) { return combineTypeAndValue(JsonEncoder.VTYPE_BOOLEAN, value); } else if (value instanceof Number) { - return combineTypeAndValue(transportType, value); + return combineTypeAndValue(internalTransportType, value); } else if (value instanceof Collection) { - if (transportType == null) { + if (internalTransportType == null) { throw new RuntimeException( "Unable to serialize unsupported type: " + valueType); } Collection<?> collection = (Collection<?>) value; JSONArray jsonArray = encodeCollection(collection, application); - return combineTypeAndValue(transportType, jsonArray); + return combineTypeAndValue(internalTransportType, jsonArray); } else if (value instanceof Object[]) { Object[] array = (Object[]) value; JSONArray jsonArray = encodeArrayContents(array, application); @@ -252,18 +473,28 @@ public class JsonCodec implements Serializable { } } else if (value instanceof Connector) { Connector connector = (Connector) value; + if (value instanceof Component + && !(AbstractCommunicationManager + .isVisible((Component) value))) { + return encodeNull(); + } return combineTypeAndValue(JsonEncoder.VTYPE_CONNECTOR, connector.getConnectorId()); - } else if (transportType != null) { - return combineTypeAndValue(transportType, String.valueOf(value)); + } else if (internalTransportType != null) { + return combineTypeAndValue(internalTransportType, + String.valueOf(value)); } else { // Any object that we do not know how to encode we encode by looping // through fields - return combineTypeAndValue(valueType.getName(), + return combineTypeAndValue(getCustomTransportType(valueType), encodeObject(value, application)); } } + private static JSONArray encodeNull() { + return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL); + } + private static Object encodeObject(Object value, Application application) throws JSONException { JSONObject jsonMap = new JSONObject(); @@ -288,68 +519,11 @@ public class JsonCodec implements Serializable { return jsonMap; } - /** - * Returns the name that should be used as field name in the JSON. We strip - * "set" from the setter, keeping the result - this is easy to do on both - * server and client, avoiding some issues with cASE. E.g setZIndex() - * becomes "ZIndex". Also ensures that both getter and setter are present, - * returning null otherwise. - * - * @param pd - * @return the name to be used or null if both getter and setter are not - * found. - */ - private static String getTransportFieldName(PropertyDescriptor pd) { - if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { - return null; - } - return pd.getWriteMethod().getName().substring(3); - } - - private static Object decodeObject(String type, - JSONObject serializedObject, Application application) - throws JSONException { - - Class<?> cls; - try { - cls = Class.forName(type); - - Object decodedObject = cls.newInstance(); - for (PropertyDescriptor pd : Introspector.getBeanInfo(cls) - .getPropertyDescriptors()) { - - String fieldName = getTransportFieldName(pd); - if (fieldName == null) { - continue; - } - JSONArray encodedObject = serializedObject - .getJSONArray(fieldName); - pd.getWriteMethod().invoke(decodedObject, - decode(encodedObject, application)); - } - - return decodedObject; - } catch (ClassNotFoundException e) { - throw new JSONException(e); - } catch (IllegalArgumentException e) { - throw new JSONException(e); - } catch (IllegalAccessException e) { - throw new JSONException(e); - } catch (InvocationTargetException e) { - throw new JSONException(e); - } catch (InstantiationException e) { - throw new JSONException(e); - } catch (IntrospectionException e) { - throw new JSONException(e); - } - } - private static JSONArray encodeArrayContents(Object[] array, Application application) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : array) { - // TODO handle object graph loops? - jsonArray.put(encode(o, application)); + jsonArray.put(encode(o, null, application)); } return jsonArray; } @@ -358,7 +532,6 @@ public class JsonCodec implements Serializable { Application application) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : collection) { - // TODO handle object graph loops? jsonArray.put(encode(o, application)); } return jsonArray; @@ -378,7 +551,7 @@ public class JsonCodec implements Serializable { "Only maps with String/Connector keys are currently supported (#8602)"); } - jsonMap.put((String) mapKey, encode(mapValue, application)); + jsonMap.put((String) mapKey, encode(mapValue, null, application)); } return jsonMap; } @@ -395,21 +568,6 @@ public class JsonCodec implements Serializable { } /** - * Gets the transport type for the value. Returns null if no transport type - * can be found. - * - * @param value - * @return - * @throws JSONException - */ - private static String getTransportType(Object value) { - if (null == value) { - return JsonEncoder.VTYPE_NULL; - } - return getTransportType(value.getClass()); - } - - /** * Gets the transport type for the given class. Returns null if no transport * type can be found. * @@ -418,9 +576,12 @@ public class JsonCodec implements Serializable { * @return * @throws JSONException */ - private static String getTransportType(Class<?> valueType) { - return typeToTransportType.get(valueType); + private static String getInternalTransportType(Type valueType) { + return typeToTransportType.get(getClassForType(valueType)); + } + private static String getCustomTransportType(Class<?> targetType) { + return targetType.getName(); } } diff --git a/src/com/vaadin/terminal/gwt/server/LegacyChangeVariablesInvocation.java b/src/com/vaadin/terminal/gwt/server/LegacyChangeVariablesInvocation.java new file mode 100644 index 0000000000..42fa3ab5a5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/LegacyChangeVariablesInvocation.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.server; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; + +public class LegacyChangeVariablesInvocation extends MethodInvocation { + private Map<String, Object> variableChanges = new HashMap<String, Object>(); + + public LegacyChangeVariablesInvocation(String connectorId, + String variableName, Object value) { + super(connectorId, ApplicationConnection.UPDATE_VARIABLE_INTERFACE, + ApplicationConnection.UPDATE_VARIABLE_METHOD); + setVariableChange(variableName, value); + } + + public static boolean isLegacyVariableChange(String interfaceName, + String methodName) { + return ApplicationConnection.UPDATE_VARIABLE_METHOD + .equals(interfaceName) + && ApplicationConnection.UPDATE_VARIABLE_METHOD + .equals(methodName); + } + + public void setVariableChange(String name, Object value) { + variableChanges.put(name, value); + } + + public Map<String, Object> getVariableChanges() { + return variableChanges; + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/RequestTimer.java b/src/com/vaadin/terminal/gwt/server/RequestTimer.java new file mode 100644 index 0000000000..6c0edec466 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/RequestTimer.java @@ -0,0 +1,43 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.Serializable; + +/** + * Times the handling of requests and stores the information as an attribute in + * the request. The timing info is later passed on to the client in the UIDL and + * the client provides JavaScript API for accessing this data from e.g. + * TestBench. + * + * @author Jonatan Kronqvist / Vaadin Ltd + */ +public class RequestTimer implements Serializable { + private long requestStartTime = 0; + + /** + * Starts the timing of a request. This should be called before any + * processing of the request. + */ + public void start() { + requestStartTime = System.nanoTime(); + } + + /** + * Stops the timing of a request. This should be called when all processing + * of a request has finished. + * + * @param context + */ + public void stop(AbstractWebApplicationContext context) { + // Measure and store the total handling time. This data can be + // used in TestBench 3 tests. + long time = (System.nanoTime() - requestStartTime) / 1000000; + + // The timings must be stored in the context, since a new + // RequestTimer is created for every request. + context.setLastRequestTime(time); + } +} diff --git a/src/com/vaadin/terminal/gwt/server/RpcManager.java b/src/com/vaadin/terminal/gwt/server/RpcManager.java index 5fcfda50a5..d240ab8467 100644 --- a/src/com/vaadin/terminal/gwt/server/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/server/RpcManager.java @@ -6,8 +6,6 @@ package com.vaadin.terminal.gwt.server; import java.io.Serializable; -import com.vaadin.terminal.gwt.client.communication.MethodInvocation; - /** * Server side RPC manager that can invoke methods based on RPC calls received * from the client. @@ -15,5 +13,5 @@ import com.vaadin.terminal.gwt.client.communication.MethodInvocation; * @since 7.0 */ public interface RpcManager extends Serializable { - public void applyInvocation(MethodInvocation invocation); + public void applyInvocation(ServerRpcMethodInvocation invocation); } diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java index cdab4b327f..07f83864c2 100644 --- a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java @@ -8,12 +8,10 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.terminal.gwt.client.Connector; -import com.vaadin.terminal.gwt.client.communication.MethodInvocation; /** * Server side RPC manager that handles RPC calls coming from the client. @@ -26,13 +24,9 @@ import com.vaadin.terminal.gwt.client.communication.MethodInvocation; */ public class ServerRpcManager<T> implements RpcManager { - private final RpcTarget target; private final T implementation; private final Class<T> rpcInterface; - private static final Map<String, Method> invocationMethodCache = new ConcurrentHashMap<String, Method>( - 128, 0.75f, 1); - private static final Map<Class<?>, Class<?>> boxedTypes = new HashMap<Class<?>, Class<?>>(); static { try { @@ -59,9 +53,7 @@ public class ServerRpcManager<T> implements RpcManager { * @param rpcInterface * RPC interface type */ - public ServerRpcManager(RpcTarget target, T implementation, - Class<T> rpcInterface) { - this.target = target; + public ServerRpcManager(T implementation, Class<T> rpcInterface) { this.implementation = implementation; this.rpcInterface = rpcInterface; } @@ -76,39 +68,23 @@ public class ServerRpcManager<T> implements RpcManager { * method invocation to perform */ public static void applyInvocation(RpcTarget target, - MethodInvocation invocation) { - try { - Class<?> rpcInterfaceClass = Class.forName(invocation - .getInterfaceName()); - RpcManager manager = target.getRpcManager(rpcInterfaceClass); - if (manager != null) { - manager.applyInvocation(invocation); - } else { - getLogger() - .log(Level.WARNING, - "RPC call received for RpcTarget " - + target.getClass().getName() - + " (" - + invocation.getConnectorId() - + ") but the target has not registered any RPC interfaces"); - } - } catch (ClassNotFoundException e) { - throw new RuntimeException("Class for RPC interface " - + invocation.getInterfaceName() + " of the target " - + target + " could not be found."); + ServerRpcMethodInvocation invocation) { + RpcManager manager = target.getRpcManager(invocation + .getInterfaceClass()); + if (manager != null) { + manager.applyInvocation(invocation); + } else { + getLogger() + .log(Level.WARNING, + "RPC call received for RpcTarget " + + target.getClass().getName() + + " (" + + invocation.getConnectorId() + + ") but the target has not registered any RPC interfaces"); } } /** - * Returns the RPC target of this RPC manager instance. - * - * @return RpcTarget, typically a {@link Connector} - */ - public RpcTarget getTarget() { - return target; - } - - /** * Returns the RPC interface implementation for the RPC target. * * @return RPC interface implementation @@ -133,21 +109,11 @@ public class ServerRpcManager<T> implements RpcManager { * @param invocation * method invocation to perform */ - public void applyInvocation(MethodInvocation invocation) { - String methodName = invocation.getMethodName(); - // here, we already know that the interface is an rpcInterface - Object[] arguments = invocation.getParameters(); - - Method method = findInvocationMethod(rpcInterface, methodName, - arguments.length); - if (method == null) { - throw new RuntimeException(implementation + " does not contain " - + rpcInterface.getName() + "." + methodName + " with " - + arguments.length + " parameters"); - } - + public void applyInvocation(ServerRpcMethodInvocation invocation) { + Method method = invocation.getMethod(); Class<?>[] parameterTypes = method.getParameterTypes(); Object[] args = new Object[parameterTypes.length]; + Object[] arguments = invocation.getParameters(); for (int i = 0; i < args.length; i++) { // no conversion needed for basic cases // Class<?> type = parameterTypes[i]; @@ -159,41 +125,10 @@ public class ServerRpcManager<T> implements RpcManager { try { method.invoke(implementation, args); } catch (Exception e) { - throw new RuntimeException(methodName, e); - } - } - - private Method findInvocationMethod(Class<?> targetType, String methodName, - int parameterCount) { - // TODO currently only using method name and number of parameters as the - // signature - String signature = targetType.getName() + "." + methodName + "(" - + parameterCount; - Method invocationMethod = invocationMethodCache.get(signature); - - if (invocationMethod == null) { - invocationMethod = doFindInvocationMethod(targetType, methodName, - parameterCount); - - if (invocationMethod != null) { - invocationMethodCache.put(signature, invocationMethod); - } - } - - return invocationMethod; - } - - private Method doFindInvocationMethod(Class<?> targetType, - String methodName, int parameterCount) { - Method[] methods = targetType.getMethods(); - for (Method method : methods) { - Class<?>[] parameterTypes = method.getParameterTypes(); - if (method.getName().equals(methodName) - && parameterTypes.length == parameterCount) { - return method; - } + throw new RuntimeException("Unable to invoke method " + + invocation.getMethodName() + " in " + + invocation.getInterfaceName(), e); } - return null; } private static Logger getLogger() { diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java new file mode 100644 index 0000000000..6f278f7797 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java @@ -0,0 +1,107 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.server; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public class ServerRpcMethodInvocation extends MethodInvocation { + + private static final Map<String, Method> invocationMethodCache = new ConcurrentHashMap<String, Method>( + 128, 0.75f, 1); + + private final Method method; + + private Class<? extends ServerRpc> interfaceClass; + + public ServerRpcMethodInvocation(String connectorId, String interfaceName, + String methodName, int parameterCount) { + super(connectorId, interfaceName, methodName); + + interfaceClass = findClass(); + method = findInvocationMethod(interfaceClass, methodName, + parameterCount); + } + + private Class<? extends ServerRpc> findClass() { + try { + Class<?> rpcInterface = Class.forName(getInterfaceName()); + if (!ServerRpc.class.isAssignableFrom(rpcInterface)) { + throw new IllegalArgumentException("The interface " + + getInterfaceName() + "is not a server RPC interface."); + } + return (Class<? extends ServerRpc>) rpcInterface; + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("The server RPC interface " + + getInterfaceName() + " could not be found", e); + } finally { + + } + } + + public Class<? extends ServerRpc> getInterfaceClass() { + return interfaceClass; + } + + public Method getMethod() { + return method; + } + + /** + * Tries to find the method from the cache or alternatively by invoking + * {@link #doFindInvocationMethod(Class, String, int)} and updating the + * cache. + * + * @param targetType + * @param methodName + * @param parameterCount + * @return + */ + private Method findInvocationMethod(Class<?> targetType, String methodName, + int parameterCount) { + // TODO currently only using method name and number of parameters as the + // signature + String signature = targetType.getName() + "." + methodName + "(" + + parameterCount; + Method invocationMethod = invocationMethodCache.get(signature); + + if (invocationMethod == null) { + invocationMethod = doFindInvocationMethod(targetType, methodName, + parameterCount); + + if (invocationMethod != null) { + invocationMethodCache.put(signature, invocationMethod); + } + } + + return invocationMethod; + } + + /** + * Tries to find the method from the class by looping through available + * methods. + * + * @param targetType + * @param methodName + * @param parameterCount + * @return + */ + private Method doFindInvocationMethod(Class<?> targetType, + String methodName, int parameterCount) { + Method[] methods = targetType.getMethods(); + for (Method method : methods) { + Class<?>[] parameterTypes = method.getParameterTypes(); + if (method.getName().equals(methodName) + && parameterTypes.length == parameterCount) { + return method; + } + } + return null; + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java b/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java index 2c9828b66b..3838695aa3 100644 --- a/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java +++ b/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java @@ -13,6 +13,7 @@ import javax.portlet.ClientDataRequest; import javax.portlet.PortletRequest; import javax.portlet.ResourceRequest; +import com.vaadin.Application; import com.vaadin.terminal.CombinedRequest; import com.vaadin.terminal.DeploymentConfiguration; import com.vaadin.terminal.WrappedRequest; @@ -118,8 +119,21 @@ public class WrappedPortletRequest implements WrappedRequest { } public BrowserDetails getBrowserDetails() { - // No browserDetails available for normal requests - return null; + return new BrowserDetails() { + public String getUriFragment() { + return null; + } + + public String getWindowName() { + return null; + } + + public WebBrowser getWebBrowser() { + PortletApplicationContext2 context = (PortletApplicationContext2) Application + .getCurrentApplication().getContext(); + return context.getBrowser(); + } + }; } public Locale getLocale() { diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java index e09545962b..013df4710c 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.PrintWriter; +import java.io.Serializable; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -52,6 +53,8 @@ public class SerializerMapGenerator extends Generator { TypeOracle typeOracle = context.getTypeOracle(); Set<JClassType> typesNeedingSerializers = findTypesNeedingSerializers( typeOracle, logger); + warnIfNotJavaSerializable(typesNeedingSerializers, typeOracle, + logger); Set<JClassType> typesWithExistingSerializers = findTypesWithExistingSerializers( typeOracle, logger); Set<JClassType> serializerMappings = new HashSet<JClassType>(); @@ -77,6 +80,34 @@ public class SerializerMapGenerator extends Generator { return packageName + "." + className; } + /** + * Emits a warning for all classes that are used in communication but do not + * implement java.io.Serializable. Implementing java.io.Serializable is not + * needed for communication but for the server side Application to be + * serializable i.e. work in GAE for instance. + * + * @param typesNeedingSerializers + * @param typeOracle + * @param logger + */ + private void warnIfNotJavaSerializable( + Set<JClassType> typesNeedingSerializers, TypeOracle typeOracle, + TreeLogger logger) { + JClassType javaSerializable = typeOracle.findType(Serializable.class + .getName()); + for (JClassType type : typesNeedingSerializers) { + boolean serializable = type.isAssignableTo(javaSerializable); + if (!serializable) { + logger.log( + Type.ERROR, + type + + " is used in RPC or shared state but does not implement " + + Serializable.class.getName() + + ". Communication will work but the Application on server side cannot be serialized if it refers to objects of this type."); + } + } + } + private Set<JClassType> findTypesWithExistingSerializers( TypeOracle typeOracle, TreeLogger logger) { JClassType serializerInterface = typeOracle @@ -233,8 +264,9 @@ public class SerializerMapGenerator extends Generator { return; } - if (serializableTypes.contains(type)) + if (serializableTypes.contains(type)) { return; + } JClassType typeClass = type.isClass(); if (typeClass != null) { diff --git a/src/com/vaadin/ui/AbsoluteLayout.java b/src/com/vaadin/ui/AbsoluteLayout.java index e548798abe..9ba005f75a 100644 --- a/src/com/vaadin/ui/AbsoluteLayout.java +++ b/src/com/vaadin/ui/AbsoluteLayout.java @@ -16,7 +16,7 @@ import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.absolutelayout.AbsoluteLayoutServerRPC; +import com.vaadin.terminal.gwt.client.ui.absolutelayout.AbsoluteLayoutServerRpc; import com.vaadin.terminal.gwt.client.ui.absolutelayout.AbsoluteLayoutState; /** @@ -28,7 +28,7 @@ import com.vaadin.terminal.gwt.client.ui.absolutelayout.AbsoluteLayoutState; public class AbsoluteLayout extends AbstractLayout implements LayoutClickNotifier { - private AbsoluteLayoutServerRPC rpc = new AbsoluteLayoutServerRPC() { + private AbsoluteLayoutServerRpc rpc = new AbsoluteLayoutServerRpc() { public void layoutClick(MouseEventDetails mouseDetails, Connector clickedConnector) { diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java index 83e6f54ad3..79a07ae00e 100644 --- a/src/com/vaadin/ui/AbstractComponent.java +++ b/src/com/vaadin/ui/AbstractComponent.java @@ -534,6 +534,31 @@ public abstract class AbstractComponent implements Component, MethodEventSource return parent; } + /** + * Returns the closest ancestor with the given type. + * <p> + * To find the Window that contains the component, use {@code Window w = + * getParent(Window.class);} + * </p> + * + * @param <T> + * The type of the ancestor + * @param parentType + * The ancestor class we are looking for + * @return The first ancestor that can be assigned to the given class. Null + * if no ancestor with the correct type could be found. + */ + public <T extends HasComponents> T findAncestor(Class<T> parentType) { + HasComponents p = getParent(); + while (p != null) { + if (parentType.isAssignableFrom(p.getClass())) { + return parentType.cast(p); + } + p = p.getParent(); + } + return null; + } + /* * Sets the parent component. Don't add a JavaDoc comment here, we use the * default documentation from implemented interface. @@ -1461,7 +1486,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource private void setActionManagerViewer() { if (actionManager != null && getRoot() != null) { // Attached and has action manager - Window w = findParentOfType(Window.class, this); + Window w = findAncestor(Window.class); if (w != null) { actionManager.setViewer(w); } else { @@ -1471,32 +1496,6 @@ public abstract class AbstractComponent implements Component, MethodEventSource } - /** - * Helper method for finding the first parent component of a given type. - * Useful e.g. for finding the Window the component is inside. - * - * @param <T> - * @param parentType - * The type to look for - * @param c - * The target component - * @return A parent component of type {@literal parentType} or null if no - * parent component in the hierarchy can be assigned to the given - * type. - */ - private static <T extends Component> T findParentOfType( - Class<T> parentType, Component c) { - Component p = c.getParent(); - if (p == null) { - return null; - } - - if (parentType.isAssignableFrom(p.getClass())) { - return (T) p; - } - return findParentOfType(parentType, p); - } - public void addShortcutListener(ShortcutListener shortcut) { getActionManager().addAction(shortcut); } @@ -1522,7 +1521,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * registered */ protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) { - rpcManagerMap.put(rpcInterfaceType, new ServerRpcManager<T>(this, + rpcManagerMap.put(rpcInterfaceType, new ServerRpcManager<T>( implementation, rpcInterfaceType)); } @@ -1597,7 +1596,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - addMethodInvocationToQueue(rpcInterfaceName, method.getName(), args); + addMethodInvocationToQueue(rpcInterfaceName, method, args); // TODO no need to do full repaint if only RPC calls requestRepaint(); return null; @@ -1618,10 +1617,10 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @since 7.0 */ protected void addMethodInvocationToQueue(String interfaceName, - String methodName, Object[] parameters) { + Method method, Object[] parameters) { // add to queue pendingInvocations.add(new ClientMethodInvocation(this, interfaceName, - methodName, parameters)); + method, parameters)); } /** diff --git a/src/com/vaadin/ui/AbstractComponentContainer.java b/src/com/vaadin/ui/AbstractComponentContainer.java index b597451a57..1c857a03cd 100644 --- a/src/com/vaadin/ui/AbstractComponentContainer.java +++ b/src/com/vaadin/ui/AbstractComponentContainer.java @@ -215,17 +215,6 @@ public abstract class AbstractComponentContainer extends AbstractComponent } @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - if (getParent() != null && !getParent().isEnabled()) { - // some ancestor still disabled, don't update children - return; - } else { - requestRepaintAll(); - } - } - - @Override public void setVisible(boolean visible) { if (getState().isVisible() == visible) { return; @@ -379,6 +368,15 @@ public abstract class AbstractComponentContainer extends AbstractComponent */ public static void requestRepaintAll(HasComponents container) { container.requestRepaint(); + if (container instanceof Panel) { + Panel p = (Panel) container; + // #2924 Panel is invalid, really invalid. + // Panel.getComponentIterator returns the children of content, not + // of Panel... + if (p.getContent() != null) { + p.getContent().requestRepaint(); + } + } for (Iterator<Component> childIterator = container .getComponentIterator(); childIterator.hasNext();) { Component c = childIterator.next(); diff --git a/src/com/vaadin/ui/AbstractOrderedLayout.java b/src/com/vaadin/ui/AbstractOrderedLayout.java index 0f2f670331..3606fa6572 100644 --- a/src/com/vaadin/ui/AbstractOrderedLayout.java +++ b/src/com/vaadin/ui/AbstractOrderedLayout.java @@ -19,7 +19,7 @@ import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutServerRPC; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutServerRpc; import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutState; @SuppressWarnings("serial") @@ -27,7 +27,7 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier, Vaadin6Component { - private AbstractOrderedLayoutServerRPC rpc = new AbstractOrderedLayoutServerRPC() { + private AbstractOrderedLayoutServerRpc rpc = new AbstractOrderedLayoutServerRpc() { public void layoutClick(MouseEventDetails mouseDetails, Connector clickedConnector) { diff --git a/src/com/vaadin/ui/AbstractSplitPanel.java b/src/com/vaadin/ui/AbstractSplitPanel.java index 5eb46b85a9..5205952621 100644 --- a/src/com/vaadin/ui/AbstractSplitPanel.java +++ b/src/com/vaadin/ui/AbstractSplitPanel.java @@ -13,7 +13,7 @@ import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelRPC; +import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelRpc; import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelState; import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelState.SplitterState; import com.vaadin.tools.ReflectTools; @@ -33,7 +33,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { private Unit posUnit; - private AbstractSplitPanelRPC rpc = new AbstractSplitPanelRPC() { + private AbstractSplitPanelRpc rpc = new AbstractSplitPanelRpc() { public void splitterClick(MouseEventDetails mouseDetails) { fireEvent(new SplitterClickEvent(AbstractSplitPanel.this, @@ -71,7 +71,6 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { return null; } i++; - AbstractSplitPanelState state = getState(); if (i == 1) { return (getFirstComponent() == null ? getSecondComponent() : getFirstComponent()); diff --git a/src/com/vaadin/ui/Button.java b/src/com/vaadin/ui/Button.java index f5e45ef3ef..876fe593e2 100644 --- a/src/com/vaadin/ui/Button.java +++ b/src/com/vaadin/ui/Button.java @@ -489,4 +489,33 @@ public class Button extends AbstractComponent implements return (ButtonState) super.getState(); } + /** + * Set whether the caption text is rendered as HTML or not. You might need + * to retheme button to allow higher content than the original text style. + * + * If set to true, the captions are passed to the browser as html and the + * developer is responsible for ensuring no harmful html is used. If set to + * false, the content is passed to the browser as plain text. + * + * @param htmlContentAllowed + * <code>true</code> if caption is rendered as HTML, + * <code>false</code> otherwise + */ + public void setHtmlContentAllowed(boolean htmlContentAllowed) { + if (getState().isHtmlContentAllowed() != htmlContentAllowed) { + getState().setHtmlContentAllowed(htmlContentAllowed); + requestRepaint(); + } + } + + /** + * Return HTML rendering setting + * + * @return <code>true</code> if the caption text is to be rendered as HTML, + * <code>false</code> otherwise + */ + public boolean isHtmlContentAllowed() { + return getState().isHtmlContentAllowed(); + } + } diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java index eacf17b6a7..3632c4ca5e 100644 --- a/src/com/vaadin/ui/Component.java +++ b/src/com/vaadin/ui/Component.java @@ -17,7 +17,6 @@ import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.gwt.client.ComponentState; import com.vaadin.terminal.gwt.server.ClientConnector; -import com.vaadin.terminal.gwt.server.RpcTarget; /** * {@code Component} is the top-level interface that is and must be implemented @@ -52,8 +51,7 @@ import com.vaadin.terminal.gwt.server.RpcTarget; * @VERSION@ * @since 3.0 */ -public interface Component extends ClientConnector, Sizeable, Serializable, - RpcTarget { +public interface Component extends ClientConnector, Sizeable, Serializable { /** * Gets all user-defined CSS style names of a component. If the component diff --git a/src/com/vaadin/ui/CssLayout.java b/src/com/vaadin/ui/CssLayout.java index ac4f4b31a9..0a2656af31 100644 --- a/src/com/vaadin/ui/CssLayout.java +++ b/src/com/vaadin/ui/CssLayout.java @@ -11,7 +11,7 @@ import com.vaadin.event.LayoutEvents.LayoutClickListener; import com.vaadin.event.LayoutEvents.LayoutClickNotifier; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.csslayout.CssLayoutServerRPC; +import com.vaadin.terminal.gwt.client.ui.csslayout.CssLayoutServerRpc; import com.vaadin.terminal.gwt.client.ui.csslayout.CssLayoutState; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; @@ -58,7 +58,7 @@ import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; */ public class CssLayout extends AbstractLayout implements LayoutClickNotifier { - private CssLayoutServerRPC rpc = new CssLayoutServerRPC() { + private CssLayoutServerRpc rpc = new CssLayoutServerRpc() { public void layoutClick(MouseEventDetails mouseDetails, Connector clickedConnector) { diff --git a/src/com/vaadin/ui/CustomLayout.java b/src/com/vaadin/ui/CustomLayout.java index 0d74fe9878..97cea1c49d 100644 --- a/src/com/vaadin/ui/CustomLayout.java +++ b/src/com/vaadin/ui/CustomLayout.java @@ -9,8 +9,14 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import com.vaadin.terminal.PaintException; +import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.ui.customlayout.CustomLayoutState; +import com.vaadin.terminal.gwt.server.JsonPaintTarget; /** * <p> @@ -42,7 +48,7 @@ import com.vaadin.terminal.gwt.client.ui.customlayout.CustomLayoutState; * @since 3.0 */ @SuppressWarnings("serial") -public class CustomLayout extends AbstractLayout { +public class CustomLayout extends AbstractLayout implements Vaadin6Component { private static final int BUFFER_SIZE = 10000; @@ -299,4 +305,20 @@ public class CustomLayout extends AbstractLayout { "CustomLayout does not support margins."); } + public void changeVariables(Object source, Map<String, Object> variables) { + // Nothing to see here + } + + public void paintContent(PaintTarget target) throws PaintException { + // Workaround to make the CommunicationManager read the template file + // and send it to the client + String templateName = getState().getTemplateName(); + if (templateName != null && templateName.length() != 0) { + Set<Object> usedResources = ((JsonPaintTarget) target) + .getUsedResources(); + String resourceName = "layouts/" + templateName + ".html"; + usedResources.add(resourceName); + } + } + } diff --git a/src/com/vaadin/ui/Embedded.java b/src/com/vaadin/ui/Embedded.java index 052436cef7..1bcd984666 100644 --- a/src/com/vaadin/ui/Embedded.java +++ b/src/com/vaadin/ui/Embedded.java @@ -17,7 +17,7 @@ import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; import com.vaadin.terminal.gwt.client.ui.embedded.EmbeddedConnector; -import com.vaadin.terminal.gwt.client.ui.embedded.EmbeddedServerRPC; +import com.vaadin.terminal.gwt.client.ui.embedded.EmbeddedServerRpc; /** * Component for embedding external objects. @@ -80,7 +80,7 @@ public class Embedded extends AbstractComponent implements Vaadin6Component { private String altText; - private EmbeddedServerRPC rpc = new EmbeddedServerRPC() { + private EmbeddedServerRpc rpc = new EmbeddedServerRpc() { public void click(MouseEventDetails mouseDetails) { fireEvent(new ClickEvent(Embedded.this, mouseDetails)); } diff --git a/src/com/vaadin/ui/GridLayout.java b/src/com/vaadin/ui/GridLayout.java index 689cdcf28e..0ab729ce5c 100644 --- a/src/com/vaadin/ui/GridLayout.java +++ b/src/com/vaadin/ui/GridLayout.java @@ -22,7 +22,7 @@ import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.gridlayout.GridLayoutServerRPC; +import com.vaadin.terminal.gwt.client.ui.gridlayout.GridLayoutServerRpc; import com.vaadin.terminal.gwt.client.ui.gridlayout.GridLayoutState; /** @@ -56,7 +56,7 @@ public class GridLayout extends AbstractLayout implements Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier, Vaadin6Component { - private GridLayoutServerRPC rpc = new GridLayoutServerRPC() { + private GridLayoutServerRpc rpc = new GridLayoutServerRpc() { public void layoutClick(MouseEventDetails mouseDetails, Connector clickedConnector) { diff --git a/src/com/vaadin/ui/Panel.java b/src/com/vaadin/ui/Panel.java index e358462bbb..b2916f78c7 100644 --- a/src/com/vaadin/ui/Panel.java +++ b/src/com/vaadin/ui/Panel.java @@ -18,7 +18,7 @@ import com.vaadin.terminal.Scrollable; import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; -import com.vaadin.terminal.gwt.client.ui.panel.PanelServerRPC; +import com.vaadin.terminal.gwt.client.ui.panel.PanelServerRpc; import com.vaadin.terminal.gwt.client.ui.panel.PanelState; import com.vaadin.ui.Component.Focusable; @@ -47,7 +47,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, */ protected ActionManager actionManager; - private PanelServerRPC rpc = new PanelServerRPC() { + private PanelServerRpc rpc = new PanelServerRpc() { public void click(MouseEventDetails mouseDetails) { fireEvent(new ClickEvent(Panel.this, mouseDetails)); } diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java index 541127e092..405ae8da93 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -33,7 +33,7 @@ import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.gwt.client.ComponentState; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.notification.VNotification; -import com.vaadin.terminal.gwt.client.ui.root.RootServerRPC; +import com.vaadin.terminal.gwt.client.ui.root.RootServerRpc; import com.vaadin.terminal.gwt.client.ui.root.RootState; import com.vaadin.terminal.gwt.client.ui.root.VRoot; import com.vaadin.tools.ReflectTools; @@ -409,7 +409,7 @@ public abstract class Root extends AbstractComponentContainer implements private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( this); - private RootServerRPC rpc = new RootServerRPC() { + private RootServerRpc rpc = new RootServerRpc() { public void click(MouseEventDetails mouseDetails) { fireEvent(new ClickEvent(Root.this, mouseDetails)); } diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index 2fffedd9d6..ba4c6529c5 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -1930,7 +1930,7 @@ public class Table extends AbstractSelect implements Action.Container, if (index < firstIndexNotInCache && index >= pageBufferFirstIndex && pageBuffer[CELL_GENERATED_ROW][indexInOldBuffer] == null - && pageBuffer[CELL_ITEMID][indexInOldBuffer] == id) { + && id.equals(pageBuffer[CELL_ITEMID][indexInOldBuffer])) { // we already have data in our cache, // recycle it instead of fetching it via // getValue/getPropertyValue diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java index 6c3d75a920..3c17baf414 100644 --- a/src/com/vaadin/ui/Window.java +++ b/src/com/vaadin/ui/Window.java @@ -24,7 +24,7 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.window.WindowServerRPC; +import com.vaadin.terminal.gwt.client.ui.window.WindowServerRpc; import com.vaadin.terminal.gwt.client.ui.window.WindowState; /** @@ -76,7 +76,7 @@ import com.vaadin.terminal.gwt.client.ui.window.WindowState; public class Window extends Panel implements FocusNotifier, BlurNotifier, Vaadin6Component { - private WindowServerRPC rpc = new WindowServerRPC() { + private WindowServerRpc rpc = new WindowServerRpc() { public void click(MouseEventDetails mouseDetails) { fireEvent(new ClickEvent(Window.this, mouseDetails)); |