diff options
20 files changed, 887 insertions, 202 deletions
diff --git a/build/build.xml b/build/build.xml index 0aa379660d..573c799f7d 100644 --- a/build/build.xml +++ b/build/build.xml @@ -845,7 +845,7 @@ <property file="${gpg.passphrase.file}" /> <echo>Publishing ${output-dir}/WebContent/WEB-INF/lib/${lib-jar-name} to Maven repository</echo> - <artifact:mvn> + <artifact:mvn failonerror="true"> <arg value="gpg:sign-and-deploy-file"/> <!-- .. is a workaround as maven runs in the build directory --> <sysproperty key="file" value="../${output-dir}/WebContent/WEB-INF/lib/${lib-jar-name}" /> diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 2be35f053f..be6d578112 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::testBenchServerStatus); + 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 testBenchServerStatus; + 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 TestBench to get details about server status + */ + if (json.containsKey("tbss")) { + testBenchServerStatus = json.getValueMap("tbss"); + } 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()); 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/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 6b22f3c9f3..533d6a78ae 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,23 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * the new vertical scroll position, in pixels */ public void setScrollPosition(int position) { - getElement().setScrollTop(position); + if (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); + } + } + + private boolean isAndroidWithBrokenScrollTop() { + return BrowserInfo.getBrowserString().contains("Android 3") + || BrowserInfo.getBrowserString().contains("Android 4"); } public void onScroll(ScrollEvent 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..d4b59ed23a 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,34 @@ 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 + .getBrowserString().contains("Android 3") + || BrowserInfo.getBrowserString().contains("Android 4"); + public TouchScrollDelegate(Element... elements) { scrollableElements = elements; } + public void setScrollHandler(ScrollHandler scrollHandler) { + this.scrollHandler = scrollHandler; + } + public static TouchScrollDelegate getActiveScrollDelegate() { return activeScrollDelegate; } @@ -116,37 +126,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 +135,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 +179,6 @@ public class TouchScrollDelegate implements NativePreviewHandler { } else { moveTransformationToScrolloffset(); } - transitionOn = false; } private void animateToScrollPosition(int to, int from) { @@ -184,7 +187,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 +210,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 +240,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 +248,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 +274,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 +295,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 +316,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 +370,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 +398,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 +415,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 +492,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 +539,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/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/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/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/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/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index 0d67086339..58d6a18592 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -530,6 +530,9 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet WrappedPortletResponse wrappedResponse = new WrappedPortletResponse( response, getDeploymentConfiguration()); + RequestTimer requestTimer = RequestTimer.get(wrappedRequest); + requestTimer.start(wrappedRequest); + RequestType requestType = getRequestType(request); if (requestType == RequestType.UNKNOWN) { @@ -719,6 +722,9 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet Root.setCurrentRoot(null); Application.setCurrentApplication(null); } + + requestTimer.stop(); + RequestTimer.set(wrappedRequest, requestTimer); } } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 18cc3f97f4..799271b979 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -403,6 +403,9 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( this); + RequestTimer requestTimer = RequestTimer.get(request); + requestTimer.start(request); + RequestType requestType = getRequestType(request); if (!ensureCookiesEnabled(requestType, request, response)) { return; @@ -539,6 +542,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements Application.setCurrentApplication(null); } + requestTimer.stop(); + RequestTimer.set(request, requestTimer); } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index a1a917130f..b780f66a23 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -691,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); @@ -735,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>(); @@ -1096,6 +1096,20 @@ public abstract class AbstractCommunicationManager implements Serializable { if (dragAndDropService != null) { dragAndDropService.printJSONResponse(outWriter); } + + writePerformanceDataForTestBench(request, outWriter); + } + + /** + * Adds the performance timing data used by TestBench 3 to the UIDL + * response. + */ + private void writePerformanceDataForTestBench(final WrappedRequest request, + final PrintWriter outWriter) { + Long totalTime = (Long) request.getAttribute("TOTAL"); + Long lastRequestTime = (Long) request.getAttribute("LASTREQUEST"); + outWriter.write(String.format(", \"tbss\":[%d, %d]", totalTime, + lastRequestTime)); } private void legacyPaint(PaintTarget paintTarget, @@ -2161,7 +2175,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/RequestTimer.java b/src/com/vaadin/terminal/gwt/server/RequestTimer.java new file mode 100644 index 0000000000..d47f444bef --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/RequestTimer.java @@ -0,0 +1,80 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import com.vaadin.terminal.WrappedRequest; + +/** + * 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 { + public static final String SESSION_ATTR_ID = "REQUESTTIMER"; + + private long requestStartTime = 0; + private long totalSessionTime = 0; + private long lastRequestTime = -1; + + /** + * Starts the timing of a request. This should be called before any + * processing of the request. + * + * @param request + * the request. + */ + public void start(WrappedRequest request) { + requestStartTime = System.nanoTime(); + request.setAttribute("TOTAL", totalSessionTime); + request.setAttribute("LASTREQUEST", lastRequestTime); + } + + /** + * Stops the timing of a request. This should be called when all processing + * of a request has finished. + */ + public void stop() { + // Measure and store the total handling time. This data can be + // used in TestBench 3 tests. + long time = (System.nanoTime() - requestStartTime) / 1000000; + lastRequestTime = time; + totalSessionTime += time; + } + + /** + * Returns a valid request timer for the specified request. Timers are + * session bound. + * + * @param request + * the request for which to get a valid timer. + * @return a valid timer. + */ + public static RequestTimer get(WrappedRequest request) { + RequestTimer timer = (RequestTimer) request + .getSessionAttribute(SESSION_ATTR_ID); + if (timer == null) { + timer = new RequestTimer(); + } + return timer; + } + + /** + * Associates the specified request timer with the specified request. Since + * {@link #get(RequestWrapper)} will, at one point or another, return a new + * instance, this method should be called to keep the request <-> timer + * association updated. + * + * @param request + * the request for which to set the timer. + * @param requestTimer + * the timer. + */ + public static void set(WrappedRequest request, RequestTimer requestTimer) { + request.setSessionAttribute(RequestTimer.SESSION_ATTR_ID, requestTimer); + } +} diff --git a/tests/testbench/com/vaadin/tests/components/TouchScrollables.java b/tests/testbench/com/vaadin/tests/components/TouchScrollables.java new file mode 100644 index 0000000000..636bddead8 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/TouchScrollables.java @@ -0,0 +1,267 @@ +package com.vaadin.tests.components; + +import java.util.Collection; + +import com.vaadin.data.Item; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.event.Action; +import com.vaadin.event.Action.Handler; +import com.vaadin.event.DataBoundTransferable; +import com.vaadin.event.dd.DragAndDropEvent; +import com.vaadin.event.dd.DropHandler; +import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; +import com.vaadin.event.dd.acceptcriteria.SourceIs; +import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; +import com.vaadin.tests.util.Person; +import com.vaadin.tests.util.PersonContainer; +import com.vaadin.tests.util.TestUtils; +import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractSelect.AbstractSelectTargetDetails; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Component; +import com.vaadin.ui.CssLayout; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; +import com.vaadin.ui.Table; + +public class TouchScrollables extends TestBase { + java.util.Random r = new java.util.Random(1); + + private HorizontalLayout testSelector; + + @Override + public void setup() { + testSelector = new HorizontalLayout(); + getLayout().addComponent(testSelector); + + CssLayout cssLayout = new CssLayout(); + final Table table = new Table(); + + Button button = new Button("Toggle lazyloading"); + button.addListener(new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + if (table.getCacheRate() == 100) { + table.setCacheRate(2); + table.setPageLength(15); + } else { + table.setCacheRate(100); + table.setHeight("400px"); + } + } + }); + cssLayout.addComponent(button); + + button = new Button("Toggle selectable"); + button.addListener(new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + table.setSelectable(!table.isSelectable()); + } + }); + cssLayout.addComponent(button); + + table.addContainerProperty("foo", String.class, "bar"); + table.setRowHeaderMode(Table.ROW_HEADER_MODE_INDEX); + for (int i = 0; i < 1000; i++) { + table.addItem(); + } + cssLayout.addComponent(table); + cssLayout.setCaption("Table"); + + addTest(cssLayout); + + cssLayout = new CssLayout(); + cssLayout.setCaption("Panel"); + + final Panel p = new Panel(); + p.setHeight("400px"); + p.setCaption("Panel"); + Label l50 = null; + for (int i = 0; i < 100; i++) { + Label c = new Label("Label" + i); + p.addComponent(c); + if (i == 50) { + l50 = c; + } + } + + final Label l = l50; + button = new Button("Scroll to label 50", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + getLayout().getRoot().scrollIntoView(l); + } + }); + cssLayout.addComponent(button); + button = new Button("Scroll to 100px", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + p.setScrollTop(100); + } + }); + cssLayout.addComponent(button); + cssLayout.addComponent(p); + + addTest(cssLayout); + + TestUtils + .injectCSS( + getLayout().getRoot(), + "body * {-webkit-user-select: none;} .v-table-row-drag-middle .v-table-cell-content {" + + " background-color: inherit ; border-bottom: 1px solid cyan;" + + "}" + + ".v-table-row-drag-middle .v-table-cell-wrapper {" + + " margin-bottom: -1px;" + "}" + "" + + ); + + addDDSortableTable(); + + } + + private void addDDSortableTable() { + final Table table; + table = new Table(); + table.setCaption("DD sortable table with context menus"); + // table.setWidth("100%"); + table.setPageLength(10); + table.setRowHeaderMode(Table.ROW_HEADER_MODE_ID); + table.setSelectable(true); + table.setMultiSelect(true); + + table.addActionHandler(new Handler() { + + Action[] actions = new Action[] { new Action("FOO"), + new Action("BAR"), new Action("CAR") }; + + public Action[] getActions(Object target, Object sender) { + return actions; + } + + public void handleAction(Action action, Object sender, Object target) { + getLayout().getRoot().showNotification(action.getCaption()); + + } + }); + + populateTable(table); + + /* + * Make table rows draggable + */ + table.setDragMode(Table.TableDragMode.ROW); + + table.setDropHandler(new DropHandler() { + // accept only drags from this table + AcceptCriterion crit = new SourceIs(table); + + public AcceptCriterion getAcceptCriterion() { + return crit; + } + + public void drop(DragAndDropEvent dropEvent) { + AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dropEvent + .getTargetDetails(); + DataBoundTransferable transferable = (DataBoundTransferable) dropEvent + .getTransferable(); + Object itemIdOver = dropTargetData.getItemIdOver(); + Object itemId = transferable.getItemId(); + if (itemId == null || itemIdOver == null + || itemId.equals(itemIdOver)) { + return; // no move happened + } + + // IndexedContainer goodies... (hint: don't use it in real apps) + IndexedContainer containerDataSource = (IndexedContainer) table + .getContainerDataSource(); + int newIndex = containerDataSource.indexOfId(itemIdOver) - 1; + if (dropTargetData.getDropLocation() != VerticalDropLocation.TOP) { + newIndex++; + } + if (newIndex < 0) { + newIndex = 0; + } + Object idAfter = containerDataSource.getIdByIndex(newIndex); + Collection<?> selections = (Collection<?>) table.getValue(); + if (selections != null && selections.contains(itemId)) { + // dragged a selected item, if multiple rows selected, drag + // them too (functionality similar to apple mail) + for (Object object : selections) { + moveAfter(containerDataSource, object, idAfter); + } + + } else { + // move just the dragged row, not considering selection at + // all + moveAfter(containerDataSource, itemId, idAfter); + } + + } + + private void moveAfter(IndexedContainer containerDataSource, + Object itemId, Object idAfter) { + try { + IndexedContainer clone = null; + clone = (IndexedContainer) containerDataSource.clone(); + containerDataSource.removeItem(itemId); + Item newItem = containerDataSource.addItemAfter(idAfter, + itemId); + Item item = clone.getItem(itemId); + for (Object propId : item.getItemPropertyIds()) { + newItem.getItemProperty(propId).setValue( + item.getItemProperty(propId).getValue()); + } + + // TODO Auto-generated method stub + } catch (CloneNotSupportedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + }); + addTest(table); + } + + private void populateTable(Table table) { + table.addContainerProperty("Name", String.class, ""); + table.addContainerProperty("Weight", Integer.class, 0); + + PersonContainer testData = PersonContainer.createWithTestData(); + + for (int i = 0; i < 40; i++) { + Item addItem = table.addItem("Item" + i); + Person p = testData.getIdByIndex(i); + addItem.getItemProperty("Name").setValue( + p.getFirstName() + " " + p.getLastName()); + addItem.getItemProperty("Weight").setValue(50 + r.nextInt(60)); + } + + } + + private Component testComponent; + + private void addTest(final AbstractComponent t) { + Button button = new Button(t.getCaption()); + testSelector.addComponent(button); + button.addListener(new Button.ClickListener() { + + public void buttonClick(ClickEvent event) { + if (testComponent != null) { + getLayout().removeComponent(testComponent); + } + testComponent = t; + getLayout().addComponent(t); + } + }); + } + + @Override + protected String getDescription() { + return "Various components and setups suitable for testing scrolling on touch devices."; + } + + @Override + protected Integer getTicketNumber() { + return null; + } +} diff --git a/tests/testbench/com/vaadin/tests/components/table/TestCurrentPageFirstItem.html b/tests/testbench/com/vaadin/tests/components/table/TestCurrentPageFirstItem.html new file mode 100644 index 0000000000..ac06706aa5 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/table/TestCurrentPageFirstItem.html @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="" /> +<title>TestCurrentPageFirstItem</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">TestCurrentPageFirstItem</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/run/TestCurrentPageFirstItem?restartApplication</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runTestCurrentPageFirstItem::/VHorizontalLayout[0]/ChildComponentContainer[4]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runTestCurrentPageFirstItem::/VHorizontalLayout[0]/ChildComponentContainer[5]/VButton[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runTestCurrentPageFirstItem::/VHorizontalLayout[0]/ChildComponentContainer[4]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>screenCapture</td> + <td></td> + <td></td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/tests/testbench/com/vaadin/tests/components/table/TestCurrentPageFirstItem.java b/tests/testbench/com/vaadin/tests/components/table/TestCurrentPageFirstItem.java index 1748c27426..7fb096739a 100644 --- a/tests/testbench/com/vaadin/tests/components/table/TestCurrentPageFirstItem.java +++ b/tests/testbench/com/vaadin/tests/components/table/TestCurrentPageFirstItem.java @@ -1,60 +1,77 @@ package com.vaadin.tests.components.table; -import com.vaadin.Application; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.util.IndexedContainer; +import com.vaadin.tests.components.TestBase; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Root.LegacyWindow; +import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Table; import com.vaadin.ui.VerticalLayout; -public class TestCurrentPageFirstItem extends Application.LegacyApplication - implements ClickListener { +public class TestCurrentPageFirstItem extends TestBase implements ClickListener { private Button buttonIndex; private Button buttonItem; - private Table table; + private Table[] tables = new Table[4]; private int counter = 0; IndexedContainer container = new IndexedContainer(); @Override - public void init() { - try { - LegacyWindow main = new LegacyWindow("Table header Test"); - setMainWindow(main); - main.setSizeFull(); - // setTheme("testtheme"); - VerticalLayout baseLayout = new VerticalLayout(); - main.setContent(baseLayout); - - table = new Table(); - container.addContainerProperty("row", String.class, ""); - table.setContainerDataSource(container); - table.setWidth("100%"); - table.setPageLength(3); - buttonIndex = new Button("Add row and select last index", this); - buttonItem = new Button("Add row and select last item", this); - - baseLayout.addComponent(table); - baseLayout.addComponent(buttonIndex); - baseLayout.addComponent(buttonItem); - } catch (Exception e) { - e.printStackTrace(); + public void setup() { + container.addContainerProperty("row", String.class, ""); + + HorizontalLayout baseLayout = new HorizontalLayout(); + baseLayout.setHeight("115px"); + getMainWindow().setContent(baseLayout); + + for (int i = 0; i < tables.length; ++i) { + Table t = new Table(); + t.setContainerDataSource(container); + t.setWidth("100px"); + baseLayout.addComponent(t); + tables[i] = t; } + tables[0].setSizeFull(); + tables[0].setCaption("Full"); + tables[1].setHeight("100px"); + tables[1].setCaption("100px"); + tables[2].setHeight("95%"); + tables[2].setCaption("95%"); + tables[3].setPageLength(3); + tables[3].setCaption("3 rows"); + + buttonIndex = new Button("Add row and select last index", this); + buttonItem = new Button("Add row and select last item", this); + baseLayout.addComponent(buttonIndex); + baseLayout.addComponent(buttonItem); } public void buttonClick(ClickEvent event) { Item item = container.addItem(++counter); item.getItemProperty("row").setValue(counter + ""); - table.select(counter); - if (event.getButton() == buttonIndex) { - table.setCurrentPageFirstItemIndex(((Container.Indexed) table - .getContainerDataSource()).indexOfId(counter)); - } else { - table.setCurrentPageFirstItemId(counter); + for (int i = 0; i < tables.length; ++i) { + Table t = tables[i]; + t.select(counter); + if (event.getButton() == buttonIndex) { + t.setCurrentPageFirstItemIndex(((Container.Indexed) t + .getContainerDataSource()).indexOfId(counter)); + } else { + t.setCurrentPageFirstItemId(counter); + } } } + + @Override + protected String getDescription() { + return "Table height changes when using setCurrentPageFirstItemId"; + } + + @Override + protected Integer getTicketNumber() { + return 2864; + } } diff --git a/tests/testbench/com/vaadin/tests/components/tabsheet/ScrollbarsInNestedTabsheets.html b/tests/testbench/com/vaadin/tests/components/tabsheet/ScrollbarsInNestedTabsheets.html new file mode 100644 index 0000000000..f5579c9875 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/tabsheet/ScrollbarsInNestedTabsheets.html @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="" /> +<title>ScrollbarsInNestedTabsheets</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">ScrollbarsInNestedTabsheets</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/run/ScrollbarsInNestedTabsheets?restartApplication</td> + <td></td> +</tr> +<tr> + <td>mouseClick</td> + <td>vaadin=runScrollbarsInNestedTabsheets::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VTabsheet[0]/VTabsheetPanel[0]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]</td> + <td>27,8</td> +</tr> +<tr> + <td>screenCapture</td> + <td></td> + <td>should-not-have-scrollbars</td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/tests/testbench/com/vaadin/tests/components/tabsheet/ScrollbarsInNestedTabsheets.java b/tests/testbench/com/vaadin/tests/components/tabsheet/ScrollbarsInNestedTabsheets.java new file mode 100644 index 0000000000..de250539ff --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/tabsheet/ScrollbarsInNestedTabsheets.java @@ -0,0 +1,58 @@ +package com.vaadin.tests.components.tabsheet; + +import com.vaadin.tests.components.TestBase; +import com.vaadin.ui.DateField; +import com.vaadin.ui.Label; +import com.vaadin.ui.Layout; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +@SuppressWarnings("serial") +public class ScrollbarsInNestedTabsheets extends TestBase { + + @Override + public void setup() { + setTheme("chameleon"); + final Label l = new Label("Select Sub Tab 2"); + final TabSheet t = new TabSheet(); + final TabSheet t2 = getTabSheet(); + t.addTab(t2, "Main Tab"); + addComponent(l); + addComponent(t); + } + + private TabSheet getTabSheet() { + final TabSheet t = new TabSheet(); + t.addTab(getDummyLayout1(), "Sub Tab 1"); + t.addTab(getDummyLayout2(), "Sub Tab 2"); + + return t; + } + + private Layout getDummyLayout1() { + final VerticalLayout l = new VerticalLayout(); + l.addComponent(new DateField("Date")); + + return l; + } + + private Layout getDummyLayout2() { + final VerticalLayout l = new VerticalLayout(); + l.addComponent(new DateField("Date")); + l.addComponent(new TextField("TextField")); + + return l; + } + + @Override + protected String getDescription() { + return "Nested tabsheets show unwanted scrollbars with Chameleon theme when the inner tabsheet is resized"; + } + + @Override + protected Integer getTicketNumber() { + return 8625; + } + +}
\ No newline at end of file |