aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPekka Hyvönen <pekka@vaadin.com>2015-02-24 10:56:00 +0200
committerPekka Hyvönen <pekka@vaadin.com>2015-02-24 17:23:05 +0200
commit6db686cd52e2c72387f555523eedf4f81708d0ab (patch)
tree6c7e56c527c3c507930c39f3c1f2ae0e8c9a617d
parentbff2a3f558ea0416ae48b5595b59057a3475d6b6 (diff)
downloadvaadin-framework-6db686cd52e2c72387f555523eedf4f81708d0ab.tar.gz
vaadin-framework-6db686cd52e2c72387f555523eedf4f81708d0ab.zip
Auto scroll when dnd reorder columns and cursor close to Grid edge. (#16643)
Change-Id: I38c18d44afb6666677c1593541021d36c8cb2afc
-rw-r--r--client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java45
-rw-r--r--client/src/com/vaadin/client/widget/grid/AutoScroller.java649
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java53
3 files changed, 720 insertions, 27 deletions
diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
index c8b651bd4e..eec52d5def 100644
--- a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
+++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
@@ -47,8 +47,11 @@ public class DragAndDropHandler {
public interface DragAndDropCallback {
/**
* Called when the drag has started.
+ *
+ * @param startEvent
+ * the original event that started the drag
*/
- void showDragElement();
+ void onDragStart(NativeEvent startEvent);
/**
* Called on drag.
@@ -56,12 +59,12 @@ public class DragAndDropHandler {
* @param event
* the event related to the drag
*/
- void updateDragElement(NativePreviewEvent event);
+ void onDragUpdate(NativePreviewEvent event);
/**
- * Called when the has ended on a drop or cancel.
+ * Called after the has ended on a drop or cancel.
*/
- void removeDragElement();
+ void onDragEnd();
/**
* Called when the drag has ended on a drop.
@@ -69,7 +72,7 @@ public class DragAndDropHandler {
void onDrop();
/**
- * Called when the drag has been canceled before drop.
+ * Called when the drag has been canceled.
*/
void onDragCancel();
}
@@ -97,7 +100,7 @@ public class DragAndDropHandler {
break;
case Event.ONMOUSEMOVE:
case Event.ONTOUCHMOVE:
- callback.updateDragElement(event);
+ callback.onDragUpdate(event);
// prevent text selection on IE
event.getNativeEvent().preventDefault();
break;
@@ -109,7 +112,7 @@ public class DragAndDropHandler {
event.getNativeEvent().preventDefault();
//$FALL-THROUGH$
case Event.ONMOUSEUP:
- callback.updateDragElement(event);
+ callback.onDragUpdate(event);
callback.onDrop();
stopDrag();
event.cancel();
@@ -122,14 +125,6 @@ public class DragAndDropHandler {
}
}
- private void cancelDrag(NativePreviewEvent event) {
- callback.onDragCancel();
- callback.removeDragElement();
- stopDrag();
- event.cancel();
- event.getNativeEvent().preventDefault();
- }
-
};
private static Logger getLogger() {
@@ -194,7 +189,7 @@ public class DragAndDropHandler {
if (Math.abs(startX - currentX) > 3
|| Math.abs(startY - currentY) > 3) {
removeNativePreviewHandlerRegistration();
- startDrag(event, callback);
+ startDrag(dragStartingEvent, event, callback);
}
break;
default:
@@ -207,15 +202,15 @@ public class DragAndDropHandler {
});
}
- private void startDrag(NativePreviewEvent event,
- DragAndDropCallback callback) {
+ private void startDrag(NativeEvent startEvent,
+ NativePreviewEvent triggerEvent, DragAndDropCallback callback) {
dragging = true;
// just capture something to prevent text selection in IE
Event.setCapture(RootPanel.getBodyElement());
this.callback = callback;
dragHandlerRegistration = Event.addNativePreviewHandler(dragHandler);
- callback.showDragElement();
- callback.updateDragElement(event);
+ callback.onDragStart(startEvent);
+ callback.onDragUpdate(triggerEvent);
}
private void stopDrag() {
@@ -226,11 +221,19 @@ public class DragAndDropHandler {
}
Event.releaseCapture(RootPanel.getBodyElement());
if (callback != null) {
- callback.removeDragElement();
+ callback.onDragEnd();
callback = null;
}
}
+ private void cancelDrag(NativePreviewEvent event) {
+ callback.onDragCancel();
+ callback.onDragEnd();
+ stopDrag();
+ event.cancel();
+ event.getNativeEvent().preventDefault();
+ }
+
private void removeNativePreviewHandlerRegistration() {
if (dragStartNativePreviewHandlerRegistration != null) {
dragStartNativePreviewHandlerRegistration.removeHandler();
diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java
new file mode 100644
index 0000000000..8f4e2036fc
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.widget.grid;
+
+import com.google.gwt.animation.client.AnimationScheduler;
+import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback;
+import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle;
+import com.google.gwt.dom.client.BrowserEvents;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableSectionElement;
+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.client.WidgetUtil;
+import com.vaadin.client.widgets.Grid;
+
+/**
+ * A class for handling automatic scrolling vertically / horizontally in the
+ * Grid when the cursor is close enough the edge of the body of the grid,
+ * depending on the scroll direction chosen.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class AutoScroller {
+
+ /**
+ * Callback that notifies when the cursor is on top of a new row or column
+ * because of the automatic scrolling.
+ *
+ * @since
+ */
+ public interface AutoScrollerCallback {
+
+ /**
+ * Triggered when doing automatic horizontal scrolling.
+ *
+ * @param scrollDiff
+ * the amount of pixels that have been auto scrolled since
+ * last call
+ */
+ void onHorizontalAutoScroll(int scrollDiff);
+
+ /**
+ * Triggered when doing automatic vertical scrolling.
+ *
+ * @param scrollDiff
+ * the amount of pixels that have been auto scrolled since
+ * last call
+ */
+ void onVerticalAutoScroll(int scrollDiff);
+ }
+
+ public enum ScrollAxis {
+ VERTICAL, HORIZONTAL
+ };
+
+ /** The maximum number of pixels per second to autoscroll. */
+ private static final int SCROLL_TOP_SPEED_PX_SEC = 500;
+
+ /**
+ * The minimum area where the grid doesn't scroll while the pointer is
+ * pressed.
+ */
+ private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50;
+
+ /** The size of the autoscroll area, both top/left and bottom/right. */
+ private int scrollAreaPX = 100;
+
+ /**
+ * This class's main objective is to listen when to stop autoscrolling, and
+ * make sure everything stops accordingly.
+ */
+ private class TouchEventHandler implements NativePreviewHandler {
+ @Override
+ public void onPreviewNativeEvent(final NativePreviewEvent event) {
+ /*
+ * Remember: targetElement is always where touchstart started, not
+ * where the finger is pointing currently.
+ */
+ switch (event.getTypeInt()) {
+ case Event.ONTOUCHSTART: {
+ if (event.getNativeEvent().getTouches().length() == 1) {
+ /*
+ * Something has dropped a touchend/touchcancel and the
+ * scroller is most probably running amok. Let's cancel it
+ * and pretend that everything's going as expected
+ *
+ * Because this is a preview, this code is run before start
+ * event can be passed to the start(...) method.
+ */
+ stop();
+
+ /*
+ * Related TODO: investigate why iOS seems to ignore a
+ * touchend/touchcancel when frames are dropped, and/or if
+ * something can be done about that.
+ */
+ }
+ break;
+ }
+
+ case Event.ONTOUCHMOVE:
+ event.cancel();
+ break;
+
+ case Event.ONTOUCHEND:
+ case Event.ONTOUCHCANCEL:
+ // TODO investigate if this works as desired
+ stop();
+ break;
+ }
+ }
+
+ }
+
+ /**
+ * This class's responsibility is to scroll the table while a pointer is
+ * kept in a scrolling zone.
+ * <p>
+ * <em>Techical note:</em> This class is an AnimationCallback because we
+ * need a timer: when the finger is kept in place while the grid scrolls, we
+ * still need to be able to make new selections. So, instead of relying on
+ * events (which won't be fired, since the pointer isn't necessarily
+ * moving), we do this check on each frame while the pointer is "active"
+ * (mouse is pressed, finger is on screen).
+ */
+ private class AutoScrollingFrame implements AnimationCallback {
+
+ /**
+ * If the acceleration gradient area is smaller than this, autoscrolling
+ * will be disabled (it becomes too quick to accelerate to be usable).
+ */
+ private static final int GRADIENT_MIN_THRESHOLD_PX = 10;
+
+ /**
+ * The speed at which the gradient area recovers, once scrolling in that
+ * direction has started.
+ */
+ private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1;
+ private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d;
+
+ /**
+ * The lowest y/x-coordinate on the {@link Event#getClientY() client-y}
+ * or {@link Event#getClientX() client-x} from where we need to start
+ * scrolling towards the top/left.
+ */
+ private int startBound = -1;
+
+ /**
+ * The highest y/x-coordinate on the {@link Event#getClientY() client-y}
+ * or {@link Event#getClientX() client-x} from where we need to
+ * scrolling towards the bottom.
+ */
+ private int endBound = -1;
+
+ /**
+ * The area where the selection acceleration takes place. If &lt;
+ * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled
+ */
+ private final int gradientArea;
+
+ /**
+ * The number of pixels per seconds we currently are scrolling (negative
+ * is towards the top/left, positive is towards the bottom/right).
+ */
+ private double scrollSpeed = 0;
+
+ private double prevTimestamp = 0;
+
+ /**
+ * This field stores fractions of pixels to scroll, to make sure that
+ * we're able to scroll less than one px per frame.
+ */
+ private double pixelsToScroll = 0.0d;
+
+ /** Should this animator be running. */
+ private boolean running = false;
+
+ /** The handle in which this instance is running. */
+ private AnimationHandle handle;
+
+ /**
+ * The pointer's pageY (VERTICAL) / pageX (HORIZONTAL) coordinate
+ * depending on scrolling axis.
+ */
+ private int scrollingAxisPageCoordinate;
+
+ /** @see #doScrollAreaChecks(int) */
+ private int finalStartBound;
+
+ /** @see #doScrollAreaChecks(int) */
+ private int finalEndBound;
+
+ private boolean scrollAreaShouldRebound = false;
+
+ public AutoScrollingFrame(final int startBound, final int endBound,
+ final int gradientArea) {
+ finalStartBound = startBound;
+ finalEndBound = endBound;
+ this.gradientArea = gradientArea;
+ }
+
+ @Override
+ public void execute(final double timestamp) {
+ final double timeDiff = timestamp - prevTimestamp;
+ prevTimestamp = timestamp;
+
+ reboundScrollArea(timeDiff);
+
+ pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d);
+ final int intPixelsToScroll = (int) pixelsToScroll;
+ pixelsToScroll -= intPixelsToScroll;
+
+ if (intPixelsToScroll != 0) {
+ if (scrollDirection == ScrollAxis.VERTICAL) {
+ grid.setScrollTop(grid.getScrollTop() + intPixelsToScroll);
+ callback.onVerticalAutoScroll(intPixelsToScroll);
+ } else {
+ grid.setScrollLeft(grid.getScrollLeft() + intPixelsToScroll);
+ callback.onHorizontalAutoScroll(intPixelsToScroll);
+ }
+ }
+
+ reschedule();
+ }
+
+ /**
+ * If the scroll are has been offset by the pointer starting out there,
+ * move it back a bit
+ */
+ private void reboundScrollArea(double timeDiff) {
+ if (!scrollAreaShouldRebound) {
+ return;
+ }
+
+ int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS
+ * timeDiff);
+ if (startBound < finalStartBound) {
+ startBound += reboundPx;
+ startBound = Math.min(startBound, finalStartBound);
+ updateScrollSpeed(scrollingAxisPageCoordinate);
+ } else if (endBound > finalEndBound) {
+ endBound -= reboundPx;
+ endBound = Math.max(endBound, finalEndBound);
+ updateScrollSpeed(scrollingAxisPageCoordinate);
+ }
+ }
+
+ private void updateScrollSpeed(final int pointerPageCordinate) {
+
+ final double ratio;
+ if (pointerPageCordinate < startBound) {
+ final double distance = pointerPageCordinate - startBound;
+ ratio = Math.max(-1, distance / gradientArea);
+ }
+
+ else if (pointerPageCordinate > endBound) {
+ final double distance = pointerPageCordinate - endBound;
+ ratio = Math.min(1, distance / gradientArea);
+ }
+
+ else {
+ ratio = 0;
+ }
+
+ scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC;
+ }
+
+ public void start() {
+ running = true;
+ reschedule();
+ }
+
+ public void stop() {
+ running = false;
+
+ if (handle != null) {
+ handle.cancel();
+ handle = null;
+ }
+ }
+
+ private void reschedule() {
+ if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) {
+ handle = AnimationScheduler.get().requestAnimationFrame(this,
+ grid.getElement());
+ }
+ }
+
+ public void updatePointerCoords(int pageX, int pageY) {
+ final int pageCordinate;
+ if (scrollDirection == ScrollAxis.VERTICAL) {
+ pageCordinate = pageY;
+ } else {
+ pageCordinate = pageX;
+ }
+ doScrollAreaChecks(pageCordinate);
+ updateScrollSpeed(pageCordinate);
+ scrollingAxisPageCoordinate = pageCordinate;
+ }
+
+ /**
+ * This method checks whether the first pointer event started in an area
+ * that would start scrolling immediately, and does some actions
+ * accordingly.
+ * <p>
+ * If it is, that scroll area will be offset "beyond" the pointer (above
+ * if pointer is towards the top/left, otherwise below/right).
+ */
+ private void doScrollAreaChecks(int pageCordinate) {
+ /*
+ * The first run makes sure that neither scroll position is
+ * underneath the finger, but offset to either direction from
+ * underneath the pointer.
+ */
+ if (startBound == -1) {
+ startBound = Math.min(finalStartBound, pageCordinate);
+ endBound = Math.max(finalEndBound, pageCordinate);
+ }
+
+ /*
+ * Subsequent runs make sure that the scroll area grows (but doesn't
+ * shrink) with the finger, but no further than the final bound.
+ */
+ else {
+ int oldTopBound = startBound;
+ if (startBound < finalStartBound) {
+ startBound = Math.max(startBound,
+ Math.min(finalStartBound, pageCordinate));
+ }
+
+ int oldBottomBound = endBound;
+ if (endBound > finalEndBound) {
+ endBound = Math.min(endBound,
+ Math.max(finalEndBound, pageCordinate));
+ }
+
+ final boolean startDidNotMove = oldTopBound == startBound;
+ final boolean endDidNotMove = oldBottomBound == endBound;
+ final boolean wasMovement = pageCordinate != scrollingAxisPageCoordinate;
+ scrollAreaShouldRebound = (startDidNotMove && endDidNotMove && wasMovement);
+ }
+ }
+ }
+
+ /**
+ * This handler makes sure that pointer movements are handled.
+ * <p>
+ * Essentially, a native preview handler is registered (so that selection
+ * gestures can happen outside of the selection column). The handler itself
+ * makes sure that it's detached when the pointer is "lifted".
+ */
+ private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() {
+ @Override
+ public void onPreviewNativeEvent(final NativePreviewEvent event) {
+ if (autoScroller == null) {
+ stop();
+ return;
+ }
+
+ final NativeEvent nativeEvent = event.getNativeEvent();
+ int pageY = 0;
+ int pageX = 0;
+ switch (event.getTypeInt()) {
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent);
+ pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent);
+ autoScroller.updatePointerCoords(pageX, pageY);
+ break;
+ case Event.ONMOUSEUP:
+ case Event.ONTOUCHEND:
+ case Event.ONTOUCHCANCEL:
+ stop();
+ break;
+ }
+ }
+ };
+ /** The registration info for {@link #scrollPreviewHandler} */
+ private HandlerRegistration handlerRegistration;
+
+ /**
+ * The top/left bound, as calculated from the {@link Event#getClientY()
+ * client-y} or {@link Event#getClientX() client-x} coordinates.
+ */
+ private double startingBound = -1;
+
+ /**
+ * The bottom/right bound, as calculated from the {@link Event#getClientY()
+ * client-y} or or {@link Event#getClientX() client-x} coordinates.
+ */
+ private int endingBound = -1;
+
+ /** The size of the autoscroll acceleration area. */
+ private int gradientArea;
+
+ private Grid<?> grid;
+
+ private HandlerRegistration nativePreviewHandlerRegistration;
+
+ private ScrollAxis scrollDirection;
+
+ private AutoScrollingFrame autoScroller;
+
+ private AutoScrollerCallback callback;
+
+ /**
+ * Creates a new instance for scrolling the given grid.
+ *
+ * @param grid
+ * the grid to auto scroll
+ */
+ public AutoScroller(Grid<?> grid) {
+ this.grid = grid;
+ }
+
+ /**
+ * Starts the automatic scrolling detection. The given event must be a touch
+ * start or a left mouse triggered mouse down event.
+ *
+ * @param startEvent
+ * the event that starts the automatic scroll
+ * @param scrollAxis
+ * the axis along which the scrolling should happen
+ * @param callback
+ * the callback for getting info about the automatic scrolling
+ */
+ public void start(final NativeEvent startEvent, ScrollAxis scrollAxis,
+ AutoScrollerCallback callback) {
+ if (BrowserEvents.TOUCHSTART.equals(startEvent.getType())
+ || (BrowserEvents.MOUSEDOWN.equals(startEvent.getType()) && startEvent
+ .getButton() == NativeEvent.BUTTON_LEFT)) {
+ scrollDirection = scrollAxis;
+ this.callback = callback;
+ injectNativeHandler();
+ start(startEvent);
+ startEvent.preventDefault();
+ startEvent.stopPropagation();
+ } else {
+ throw new IllegalStateException("received unexpected event: "
+ + startEvent.getType());
+ }
+ }
+
+ /**
+ * Stops the automatic scrolling.
+ */
+ public void stop() {
+ if (handlerRegistration != null) {
+ handlerRegistration.removeHandler();
+ handlerRegistration = null;
+ }
+
+ if (autoScroller != null) {
+ autoScroller.stop();
+ autoScroller = null;
+ }
+
+ removeNativeHandler();
+ }
+
+ /**
+ * Set the auto scroll area height or width depending on the scrolling axis.
+ * This is the amount of pixels from the edge of the grid that the scroll is
+ * triggered.
+ * <p>
+ * Defaults to 100px.
+ *
+ * @param px
+ * the height/width for the auto scroll area depending on
+ * direction
+ */
+ public void setScrollAreaPX(int px) {
+ scrollAreaPX = px;
+ }
+
+ /**
+ * Returns the size of the auto scroll area in pixels.
+ * <p>
+ * Defaults to 100px.
+ *
+ * @return size in pixels
+ */
+ public int getScrollAreaPX() {
+ return scrollAreaPX;
+ }
+
+ private void start(final NativeEvent event) {
+ /*
+ * bounds are updated whenever the autoscroll cycle starts, to make sure
+ * that the widget hasn't changed in size, moved around, or whatnot.
+ */
+ updateScrollBounds();
+
+ assert handlerRegistration == null : "handlerRegistration was not null";
+ assert autoScroller == null : "autoScroller was not null";
+ handlerRegistration = Event
+ .addNativePreviewHandler(scrollPreviewHandler);
+ autoScroller = new AutoScrollingFrame((int) Math.ceil(startingBound),
+ endingBound, gradientArea);
+ autoScroller.start();
+ }
+
+ private void updateScrollBounds() {
+ double startBorder = getBodyClientStart();
+ final int endBorder = getBodyClientEnd();
+
+ for (int i = 0; i < grid.getFrozenColumnCount(); i++) {
+ startBorder += grid.getColumn(i).getWidthActual();
+ }
+
+ final int scrollCompensation = getScrollCompensation();
+ startingBound = scrollCompensation + startBorder + scrollAreaPX;
+ endingBound = scrollCompensation + endBorder - scrollAreaPX;
+ gradientArea = scrollAreaPX;
+
+ // modify bounds if they're too tightly packed
+ if (endingBound - startingBound < MIN_NO_AUTOSCROLL_AREA_PX) {
+ double adjustment = MIN_NO_AUTOSCROLL_AREA_PX
+ - (endingBound - startingBound);
+ startingBound -= adjustment / 2;
+ endingBound += adjustment / 2;
+ gradientArea -= adjustment / 2;
+ }
+ }
+
+ private int getScrollCompensation() {
+ Element cursor = grid.getElement();
+ int scroll = 0;
+ while (cursor != null) {
+ scroll -= scrollDirection == ScrollAxis.VERTICAL ? cursor
+ .getScrollTop() : cursor.getScrollLeft();
+ cursor = cursor.getParentElement();
+ }
+
+ return scroll;
+ }
+
+ private void injectNativeHandler() {
+ removeNativeHandler();
+ nativePreviewHandlerRegistration = Event
+ .addNativePreviewHandler(new TouchEventHandler());
+ }
+
+ private void removeNativeHandler() {
+ if (nativePreviewHandlerRegistration != null) {
+ nativePreviewHandlerRegistration.removeHandler();
+ nativePreviewHandlerRegistration = null;
+ }
+ }
+
+ private TableElement getTableElement() {
+ final Element root = grid.getElement();
+ final Element tablewrapper = Element.as(root.getChild(2));
+ if (tablewrapper != null) {
+ return TableElement.as(tablewrapper.getFirstChildElement());
+ } else {
+ return null;
+ }
+ }
+
+ private TableSectionElement getTbodyElement() {
+ TableElement table = getTableElement();
+ if (table != null) {
+ return table.getTBodies().getItem(0);
+ } else {
+ return null;
+ }
+ }
+
+ private TableSectionElement getTheadElement() {
+ TableElement table = getTableElement();
+ if (table != null) {
+ return table.getTHead();
+ } else {
+ return null;
+ }
+ }
+
+ private TableSectionElement getTfootElement() {
+ TableElement table = getTableElement();
+ if (table != null) {
+ return table.getTFoot();
+ } else {
+ return null;
+ }
+ }
+
+ /** Get the "top" of an element in relation to "client" coordinates. */
+ @SuppressWarnings("static-method")
+ private int getClientTop(final Element e) {
+ Element cursor = e;
+ int top = 0;
+ while (cursor != null) {
+ top += cursor.getOffsetTop();
+ cursor = cursor.getOffsetParent();
+ }
+ return top;
+ }
+
+ /** Get the "left" of an element in relation to "client" coordinates. */
+ @SuppressWarnings("static-method")
+ private int getClientLeft(final Element e) {
+ Element cursor = e;
+ int left = 0;
+ while (cursor != null) {
+ left += cursor.getOffsetLeft();
+ cursor = cursor.getOffsetParent();
+ }
+ return left;
+ }
+
+ private int getBodyClientEnd() {
+ if (scrollDirection == ScrollAxis.VERTICAL) {
+ return getClientTop(getTfootElement()) - 1;
+ } else {
+ TableSectionElement tbodyElement = getTbodyElement();
+ return getClientLeft(tbodyElement) + tbodyElement.getOffsetWidth()
+ - 1;
+ }
+
+ }
+
+ private int getBodyClientStart() {
+ if (scrollDirection == ScrollAxis.VERTICAL) {
+ return getClientTop(grid.getElement())
+ + getTheadElement().getOffsetHeight();
+ } else {
+ return getClientLeft(getTbodyElement());
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index d54c023e3d..514cb6185c 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -83,6 +83,9 @@ import com.vaadin.client.widget.escalator.RowContainer;
import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent;
import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler;
import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction;
+import com.vaadin.client.widget.grid.AutoScroller;
+import com.vaadin.client.widget.grid.AutoScroller.AutoScrollerCallback;
+import com.vaadin.client.widget.grid.AutoScroller.ScrollAxis;
import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.CellStyleGenerator;
import com.vaadin.client.widget.grid.DataAvailableEvent;
@@ -2865,8 +2868,22 @@ public class Grid<T> extends ResizeComposite implements
private DragAndDropHandler dndHandler = new DragAndDropHandler();
+ private AutoScroller autoScroller = new AutoScroller(this);
+
private DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() {
+ private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() {
+
+ @Override
+ public void onVerticalAutoScroll(int scrollDiff) {
+ // NOP
+ }
+
+ @Override
+ public void onHorizontalAutoScroll(int scrollDiff) {
+ onDragUpdate(null);
+ }
+ };
/**
* Elements for displaying the dragged column(s) and drop marker
* properly
@@ -2884,6 +2901,8 @@ public class Grid<T> extends ResizeComposite implements
*/
private HandlerRegistration columnSortPreventRegistration;
+ private int clientX;
+
private void initHeaderDragElementDOM() {
if (table == null) {
tableHeader = DOM.createTHead();
@@ -2902,9 +2921,11 @@ public class Grid<T> extends ResizeComposite implements
}
@Override
- public void updateDragElement(NativePreviewEvent event) {
- int clientX = WidgetUtil.getTouchOrMouseClientX(event
- .getNativeEvent());
+ public void onDragUpdate(NativePreviewEvent event) {
+ if (event != null) {
+ clientX = WidgetUtil.getTouchOrMouseClientX(event
+ .getNativeEvent());
+ }
resolveDragElementHorizontalPosition(clientX);
updateDragDropMarker(clientX);
}
@@ -2940,7 +2961,7 @@ public class Grid<T> extends ResizeComposite implements
}
@Override
- public void showDragElement() {
+ public void onDragStart(NativeEvent startingEvent) {
initHeaderDragElementDOM();
// needs to clone focus and sorting indicators too (UX)
dragElement = DOM.clone(eventCell.getElement(), true);
@@ -2952,10 +2973,14 @@ public class Grid<T> extends ResizeComposite implements
eventCell.getElement().addClassName("dragged");
// mark the floating cell, for styling & testing
dragElement.addClassName("dragged-column-header");
+ // start the auto scroll handler
+ autoScroller.setScrollAreaPX(60);
+ autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL,
+ autoScrollerCallback);
}
@Override
- public void removeDragElement() {
+ public void onDragEnd() {
table.removeFromParent();
dragElement.removeFromParent();
eventCell.getElement().removeClassName("dragged");
@@ -2977,8 +3002,8 @@ public class Grid<T> extends ResizeComposite implements
@SuppressWarnings("unchecked")
Column<?, T>[] array = reordered.toArray(new Column[reordered
.size()]);
- transferCellFocusOnDrop();
setColumnOrder(array);
+ transferCellFocusOnDrop();
} // else no reordering
}
@@ -3007,6 +3032,9 @@ public class Grid<T> extends ResizeComposite implements
&& draggedColumnIndex < focusedCellColumnIndex) {
cellFocusHandler.setCellFocus(focusedRowIndex,
focusedCellColumnIndex - 1, rowContainer);
+ } else {
+ cellFocusHandler.setCellFocus(focusedRowIndex,
+ focusedCellColumnIndex, rowContainer);
}
}
}
@@ -3032,7 +3060,9 @@ public class Grid<T> extends ResizeComposite implements
}
});
}
+ autoScroller.stop();
}
+
};
/**
@@ -4973,6 +5003,17 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * Sets the horizontal scroll offset
+ *
+ * @since
+ * @param px
+ * the number of pixels this grid should be scrolled right
+ */
+ public void setScrollLeft(double px) {
+ escalator.setScrollLeft(px);
+ }
+
+ /**
* Gets the horizontal scroll offset
*
* @return the number of pixels this grid is scrolled to the right