summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/com/vaadin/client/connectors/GridConnector.java54
-rw-r--r--client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java248
-rw-r--r--client/src/com/vaadin/client/widget/grid/AutoScroller.java698
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java51
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java41
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java93
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java39
-rw-r--r--client/src/com/vaadin/client/widgets/Escalator.java26
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java735
9 files changed, 1938 insertions, 47 deletions
diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java
index 55f07ecf85..495bcd0411 100644
--- a/client/src/com/vaadin/client/connectors/GridConnector.java
+++ b/client/src/com/vaadin/client/connectors/GridConnector.java
@@ -50,6 +50,8 @@ import com.vaadin.client.widget.grid.RowReference;
import com.vaadin.client.widget.grid.RowStyleGenerator;
import com.vaadin.client.widget.grid.events.BodyClickHandler;
import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
+import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
+import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
import com.vaadin.client.widget.grid.events.GridClickEvent;
import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
import com.vaadin.client.widget.grid.events.SelectAllEvent;
@@ -360,6 +362,26 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
}
+ private ColumnReorderHandler<JsonObject> columnReorderHandler = new ColumnReorderHandler<JsonObject>() {
+
+ @Override
+ public void onColumnReorder(ColumnReorderEvent<JsonObject> event) {
+ if (!columnsUpdatedFromState) {
+ List<Column<?, JsonObject>> columns = getWidget().getColumns();
+ final List<String> newColumnOrder = new ArrayList<String>();
+ for (Column<?, JsonObject> column : columns) {
+ if (column instanceof CustomGridColumn) {
+ newColumnOrder.add(((CustomGridColumn) column).id);
+ } // the other case would be the multi selection column
+ }
+ getRpcProxy(GridServerRpc.class).columnsReordered(
+ newColumnOrder, columnOrder);
+ columnOrder = newColumnOrder;
+ getState().columnOrder = newColumnOrder;
+ }
+ }
+ };
+
/**
* Maps a generated column id to a grid column instance
*/
@@ -370,13 +392,22 @@ public class GridConnector extends AbstractHasComponentsConnector implements
private List<String> columnOrder = new ArrayList<String>();
/**
- * updateFromState is set to true when {@link #updateSelectionFromState()}
- * makes changes to selection. This flag tells the
- * {@code internalSelectionChangeHandler} to not send same data straight
- * back to server. Said listener sets it back to false when handling that
- * event.
+ * {@link #selectionUpdatedFromState} is set to true when
+ * {@link #updateSelectionFromState()} makes changes to selection. This flag
+ * tells the {@code internalSelectionChangeHandler} to not send same data
+ * straight back to server. Said listener sets it back to false when
+ * handling that event.
+ */
+ private boolean selectionUpdatedFromState;
+
+ /**
+ * {@link #columnsUpdatedFromState} is set to true when
+ * {@link #updateColumnOrderFromState(List)} is updating the column order
+ * for the widget. This flag tells the {@link #columnReorderHandler} to not
+ * send same data straight back to server. After updates, listener sets the
+ * value back to false.
*/
- private boolean updatedFromState = false;
+ private boolean columnsUpdatedFromState;
private RpcDataSource dataSource;
@@ -386,7 +417,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
if (event.isBatchedSelection()) {
return;
}
- if (!updatedFromState) {
+ if (!selectionUpdatedFromState) {
for (JsonObject row : event.getRemoved()) {
selectedKeys.remove(dataSource.getRowKey(row));
}
@@ -398,7 +429,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
getRpcProxy(GridServerRpc.class).select(
new ArrayList<String>(selectedKeys));
} else {
- updatedFromState = false;
+ selectionUpdatedFromState = false;
}
}
};
@@ -501,6 +532,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements
});
getWidget().setEditorHandler(new CustomEditorHandler());
+
+ getWidget().addColumnReorderHandler(columnReorderHandler);
+
getLayoutManager().registerDependency(this, getWidget().getElement());
layout();
}
@@ -594,7 +628,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements
columns[i] = columnIdToColumn.get(id);
i++;
}
+ columnsUpdatedFromState = true;
getWidget().setColumnOrder(columns);
+ columnsUpdatedFromState = false;
columnOrder = stateColumnOrder;
}
@@ -891,7 +927,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
if (changed) {
// At least for now there's no way to send the selected and/or
// deselected row data. Some data is only stored as keys
- updatedFromState = true;
+ selectionUpdatedFromState = true;
getWidget().fireEvent(
new SelectionEvent<JsonObject>(getWidget(),
(List<JsonObject>) null, null, false));
diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
new file mode 100644
index 0000000000..0710606818
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
@@ -0,0 +1,248 @@
+/*
+ * 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.ui.dd;
+
+import java.util.logging.Logger;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.widgets.Grid;
+
+/**
+ * A simple event handler for elements that can be drag and dropped. Loosely
+ * based on {@link VDragAndDropManager}, but without any Vaadin related stuff.
+ * Properly handles drag start, cancel and end. For example, used in
+ * {@link Grid} column header reordering.
+ * <p>
+ * The showing of the dragged element, drag hints and reacting to drop/cancel is
+ * delegated to {@link DragAndDropCallback} implementation.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class DragAndDropHandler {
+
+ /**
+ * Callback interface for drag and drop.
+ */
+ public interface DragAndDropCallback {
+ /**
+ * Called when the drag has started. The drag can be canceled by
+ * returning {@code false}.
+ *
+ * @param startEvent
+ * the original event that started the drag
+ * @return {@code true} if the drag is OK to start, {@code false} to
+ * cancel
+ */
+ boolean onDragStart(NativeEvent startEvent);
+
+ /**
+ * Called on drag.
+ *
+ * @param event
+ * the event related to the drag
+ */
+ void onDragUpdate(NativePreviewEvent event);
+
+ /**
+ * Called after the has ended on a drop or cancel.
+ */
+ void onDragEnd();
+
+ /**
+ * Called when the drag has ended on a drop.
+ */
+ void onDrop();
+
+ /**
+ * Called when the drag has been canceled.
+ */
+ void onDragCancel();
+ }
+
+ private HandlerRegistration dragStartNativePreviewHandlerRegistration;
+ private HandlerRegistration dragHandlerRegistration;
+
+ private boolean dragging;
+
+ private DragAndDropCallback callback;
+
+ private final NativePreviewHandler dragHandler = new NativePreviewHandler() {
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (dragging) {
+ final int typeInt = event.getTypeInt();
+ switch (typeInt) {
+ case Event.ONKEYDOWN:
+ int keyCode = event.getNativeEvent().getKeyCode();
+ if (keyCode == KeyCodes.KEY_ESCAPE) {
+ // end drag if ESC is hit
+ cancelDrag(event);
+ }
+ break;
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ callback.onDragUpdate(event);
+ // prevent text selection on IE
+ event.getNativeEvent().preventDefault();
+ break;
+ case Event.ONTOUCHCANCEL:
+ cancelDrag(event);
+ break;
+ case Event.ONTOUCHEND:
+ /* Avoid simulated event on drag end */
+ event.getNativeEvent().preventDefault();
+ //$FALL-THROUGH$
+ case Event.ONMOUSEUP:
+ callback.onDragUpdate(event);
+ callback.onDrop();
+ stopDrag();
+ event.cancel();
+ break;
+ default:
+ break;
+ }
+ } else {
+ stopDrag();
+ }
+ }
+
+ };
+
+ private static Logger getLogger() {
+ return Logger.getLogger(DragAndDropHandler.class.getName());
+ }
+
+ /**
+ * This method can be called to trigger drag and drop on any grid element
+ * that can be dragged and dropped.
+ *
+ * @param dragStartingEvent
+ * the drag triggering event, usually a {@link Event#ONMOUSEDOWN}
+ * or {@link Event#ONTOUCHSTART} event on the draggable element
+ *
+ * @param callback
+ * the callback that will handle actual drag and drop related
+ * operations
+ */
+ public void onDragStartOnDraggableElement(
+ final NativeEvent dragStartingEvent,
+ final DragAndDropCallback callback) {
+ dragStartNativePreviewHandlerRegistration = Event
+ .addNativePreviewHandler(new NativePreviewHandler() {
+
+ private int startX = WidgetUtil
+ .getTouchOrMouseClientX(dragStartingEvent);
+ private int startY = WidgetUtil
+ .getTouchOrMouseClientY(dragStartingEvent);
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ final int typeInt = event.getTypeInt();
+ if (typeInt == -1
+ && event.getNativeEvent().getType()
+ .toLowerCase().contains("pointer")) {
+ /*
+ * Ignore PointerEvents since IE10 and IE11 send
+ * also MouseEvents for backwards compatibility.
+ */
+ return;
+ }
+ switch (typeInt) {
+ case Event.ONMOUSEOVER:
+ case Event.ONMOUSEOUT:
+ // we don't care
+ break;
+ case Event.ONKEYDOWN:
+ case Event.ONKEYPRESS:
+ case Event.ONKEYUP:
+ case Event.ONBLUR:
+ case Event.ONFOCUS:
+ // don't cancel possible drag start
+ break;
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ int currentX = WidgetUtil
+ .getTouchOrMouseClientX(event
+ .getNativeEvent());
+ int currentY = WidgetUtil
+ .getTouchOrMouseClientY(event
+ .getNativeEvent());
+ if (Math.abs(startX - currentX) > 3
+ || Math.abs(startY - currentY) > 3) {
+ removeNativePreviewHandlerRegistration();
+ startDrag(dragStartingEvent, event, callback);
+ }
+ break;
+ default:
+ // on any other events, clean up this preview
+ // listener
+ removeNativePreviewHandlerRegistration();
+ break;
+ }
+ }
+ });
+ }
+
+ private void startDrag(NativeEvent startEvent,
+ NativePreviewEvent triggerEvent, DragAndDropCallback callback) {
+ if (callback.onDragStart(startEvent)) {
+ dragging = true;
+ // just capture something to prevent text selection in IE
+ Event.setCapture(RootPanel.getBodyElement());
+ this.callback = callback;
+ dragHandlerRegistration = Event
+ .addNativePreviewHandler(dragHandler);
+ callback.onDragUpdate(triggerEvent);
+ }
+ }
+
+ private void stopDrag() {
+ dragging = false;
+ if (dragHandlerRegistration != null) {
+ dragHandlerRegistration.removeHandler();
+ dragHandlerRegistration = null;
+ }
+ Event.releaseCapture(RootPanel.getBodyElement());
+ if (callback != null) {
+ 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();
+ dragStartNativePreviewHandlerRegistration = null;
+ }
+ }
+}
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..f7c80df623
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java
@@ -0,0 +1,698 @@
+/*
+ * 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.
+ */
+ public interface AutoScrollerCallback {
+
+ /**
+ * Triggered when doing automatic scrolling.
+ * <p>
+ * Because the auto scroller currently only supports scrolling in one
+ * axis, this method is used for both vertical and horizontal scrolling.
+ *
+ * @param scrollDiff
+ * the amount of pixels that have been auto scrolled since
+ * last call
+ */
+ void onAutoScroll(int scrollDiff);
+
+ /**
+ * Triggered when the grid scroll has reached the minimum scroll
+ * position. Depending on the scroll axis, either scrollLeft or
+ * scrollTop is 0.
+ */
+ void onAutoScrollReachedMin();
+
+ /**
+ * Triggered when the grid scroll has reached the max scroll position.
+ * Depending on the scroll axis, either scrollLeft or scrollTop is at
+ * its maximum value.
+ */
+ void onAutoScrollReachedMax();
+ }
+
+ 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) {
+ double scrollPos;
+ double maxScrollPos;
+ double newScrollPos;
+ if (scrollDirection == ScrollAxis.VERTICAL) {
+ scrollPos = grid.getScrollTop();
+ maxScrollPos = getMaxScrollTop();
+ } else {
+ scrollPos = grid.getScrollLeft();
+ maxScrollPos = getMaxScrollLeft();
+ }
+ if (intPixelsToScroll > 0 && scrollPos < maxScrollPos
+ || intPixelsToScroll < 0 && scrollPos > 0) {
+ newScrollPos = scrollPos + intPixelsToScroll;
+ if (scrollDirection == ScrollAxis.VERTICAL) {
+ grid.setScrollTop(newScrollPos);
+ } else {
+ grid.setScrollLeft(newScrollPos);
+ }
+ callback.onAutoScroll(intPixelsToScroll);
+ if (newScrollPos <= 0) {
+ callback.onAutoScrollReachedMin();
+ } else if (newScrollPos >= maxScrollPos) {
+ callback.onAutoScrollReachedMax();
+ }
+ }
+ }
+
+ 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();
+ startBorder += getFrozenColumnsWidth();
+
+ 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());
+ }
+ }
+
+ private double getFrozenColumnsWidth() {
+ double value = getMultiSelectColumnWidth();
+ for (int i = 0; i < grid.getFrozenColumnCount(); i++) {
+ value += grid.getColumn(i).getWidthActual();
+ }
+ return value;
+ }
+
+ private double getMultiSelectColumnWidth() {
+ if (grid.getFrozenColumnCount() >= 0
+ && grid.getSelectionModel().getSelectionColumnRenderer() != null) {
+ // frozen checkbox column is present
+ return getTheadElement().getFirstChildElement()
+ .getFirstChildElement().getOffsetWidth();
+ }
+ return 0.0;
+ }
+
+ private double getMaxScrollLeft() {
+ return grid.getScrollWidth()
+ - (getTableElement().getParentElement().getOffsetWidth() - getFrozenColumnsWidth());
+ }
+
+ private double getMaxScrollTop() {
+ return grid.getScrollHeight() - getTfootElement().getOffsetHeight()
+ - getTheadElement().getOffsetHeight();
+ }
+}
diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java
new file mode 100644
index 0000000000..c72da0c48e
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java
@@ -0,0 +1,51 @@
+/*
+ * 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.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * An event for notifying that the columns in the Grid have been reordered.
+ *
+ * @param <T>
+ * The row type of the grid. The row type is the POJO type from where
+ * the data is retrieved into the column cells.
+ * @since
+ * @author Vaadin Ltd
+ */
+public class ColumnReorderEvent<T> extends GwtEvent<ColumnReorderHandler<T>> {
+
+ /**
+ * Handler type.
+ */
+ private final static Type<ColumnReorderHandler<?>> TYPE = new Type<ColumnReorderHandler<?>>();
+
+ public static final Type<ColumnReorderHandler<?>> getType() {
+ return TYPE;
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public Type<ColumnReorderHandler<T>> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ @Override
+ protected void dispatch(ColumnReorderHandler<T> handler) {
+ handler.onColumnReorder(this);
+ }
+
+}
diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java
new file mode 100644
index 0000000000..e4f258088f
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java
@@ -0,0 +1,41 @@
+/*
+ * 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.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler for a Grid column reorder event, called when the Grid's columns has
+ * been reordered.
+ *
+ * @param <T>
+ * The row type of the grid. The row type is the POJO type from where
+ * the data is retrieved into the column cells.
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface ColumnReorderHandler<T> extends EventHandler {
+
+ /**
+ * A column reorder event, fired by Grid when the columns of the Grid have
+ * been reordered.
+ *
+ * @since
+ * @param event
+ * column reorder event
+ */
+ public void onColumnReorder(ColumnReorderEvent<T> event);
+}
diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java
new file mode 100644
index 0000000000..10bfbfad68
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java
@@ -0,0 +1,93 @@
+/*
+ * 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.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import com.vaadin.client.widgets.Grid.Column;
+
+/**
+ * An event for notifying that the columns in the Grid's have changed
+ * visibility.
+ *
+ * @param <T>
+ * The row type of the grid. The row type is the POJO type from where
+ * the data is retrieved into the column cells.
+ * @since
+ * @author Vaadin Ltd
+ */
+public class ColumnVisibilityChangeEvent<T> extends
+ GwtEvent<ColumnVisibilityChangeHandler<T>> {
+
+ private final static Type<ColumnVisibilityChangeHandler<?>> TYPE = new Type<ColumnVisibilityChangeHandler<?>>();
+
+ public static final Type<ColumnVisibilityChangeHandler<?>> getType() {
+ return TYPE;
+ }
+
+ private final Column<?, T> column;
+
+ private final boolean userOriginated;
+
+ private final boolean hidden;
+
+ public ColumnVisibilityChangeEvent(Column<?, T> column, boolean hidden,
+ boolean userOriginated) {
+ this.column = column;
+ this.hidden = hidden;
+ this.userOriginated = userOriginated;
+ }
+
+ /**
+ * Returns the column where the visibility change occurred.
+ *
+ * @return the column where the visibility change occurred.
+ */
+ public Column<?, T> getColumn() {
+ return column;
+ }
+
+ /**
+ * Is the column hidden or visible.
+ *
+ * @return <code>true</code> if the column was hidden <code>false</code> if
+ * it was set visible
+ */
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ /**
+ * Is the visibility change triggered by user.
+ *
+ * @return <code>true</code> if the change was triggered by user,
+ * <code>false</code> if not
+ */
+ public boolean isUserOriginated() {
+ return userOriginated;
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public com.google.gwt.event.shared.GwtEvent.Type<ColumnVisibilityChangeHandler<T>> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ @Override
+ protected void dispatch(ColumnVisibilityChangeHandler<T> handler) {
+ handler.onVisibilityChange(this);
+ }
+
+}
diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java
new file mode 100644
index 0000000000..10a7660954
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java
@@ -0,0 +1,39 @@
+/*
+ * 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.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler for a Grid column visibility change event, called when the Grid's
+ * columns have changed visibility to hidden or visible.
+ *
+ * @param<T> The row type of the grid. The row type is the POJO type from where
+ * the data is retrieved into the column cells.
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface ColumnVisibilityChangeHandler<T> extends EventHandler {
+
+ /**
+ * A column visibility change event, fired by Grid when a column in the Grid
+ * has changed visibility.
+ *
+ * @param event
+ * column visibility change event
+ */
+ public void onVisibilityChange(ColumnVisibilityChangeEvent<T> event);
+}
diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java
index 679b452fde..828d9ca29e 100644
--- a/client/src/com/vaadin/client/widgets/Escalator.java
+++ b/client/src/com/vaadin/client/widgets/Escalator.java
@@ -1383,10 +1383,10 @@ public class Escalator extends Widget implements RequiresResize,
* first time.
*/
Map<Integer, Double> colWidths = new HashMap<Integer, Double>();
- Double width = Double
- .valueOf(ColumnConfigurationImpl.Column.DEFAULT_COLUMN_WIDTH_PX);
for (int i = 0; i < getColumnConfiguration()
.getColumnCount(); i++) {
+ Double width = Double.valueOf(getColumnConfiguration()
+ .getColumnWidth(i));
Integer col = Integer.valueOf(i);
colWidths.put(col, width);
}
@@ -5786,6 +5786,28 @@ public class Escalator extends Widget implements RequiresResize,
}
/**
+ * Returns the scroll width for the escalator. Note that this is not
+ * necessary the same as {@code Element.scrollWidth} in the DOM.
+ *
+ * @since
+ * @return the scroll width in pixels
+ */
+ public double getScrollWidth() {
+ return horizontalScrollbar.getScrollSize();
+ }
+
+ /**
+ * Returns the scroll height for the escalator. Note that this is not
+ * necessary the same as {@code Element.scrollHeight} in the DOM.
+ *
+ * @since
+ * @return the scroll height in pixels
+ */
+ public double getScrollHeight() {
+ return verticalScrollbar.getScrollSize();
+ }
+
+ /**
* Scrolls the body horizontally so that the column at the given index is
* visible and there is at least {@code padding} pixels in the direction of
* the given scroll destination.
diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index b3906591c0..ea9525f157 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -25,7 +25,9 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
+import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -36,6 +38,7 @@ import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
@@ -54,6 +57,8 @@ import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.touch.client.Point;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
@@ -70,6 +75,8 @@ import com.vaadin.client.renderers.ComplexRenderer;
import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.renderers.WidgetRenderer;
import com.vaadin.client.ui.SubPartAware;
+import com.vaadin.client.ui.dd.DragAndDropHandler;
+import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback;
import com.vaadin.client.widget.escalator.Cell;
import com.vaadin.client.widget.escalator.ColumnConfiguration;
import com.vaadin.client.widget.escalator.EscalatorUpdater;
@@ -81,6 +88,9 @@ import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler;
import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction;
import com.vaadin.client.widget.escalator.Spacer;
import com.vaadin.client.widget.escalator.SpacerUpdater;
+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;
@@ -99,6 +109,10 @@ import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
import com.vaadin.client.widget.grid.events.BodyKeyDownHandler;
import com.vaadin.client.widget.grid.events.BodyKeyPressHandler;
import com.vaadin.client.widget.grid.events.BodyKeyUpHandler;
+import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
+import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
+import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent;
+import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
import com.vaadin.client.widget.grid.events.FooterClickHandler;
import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler;
import com.vaadin.client.widget.grid.events.FooterKeyDownHandler;
@@ -133,6 +147,8 @@ import com.vaadin.client.widget.grid.sort.SortOrder;
import com.vaadin.client.widgets.Escalator.AbstractRowContainer;
import com.vaadin.client.widgets.Escalator.SubPartArguments;
import com.vaadin.client.widgets.Grid.Editor.State;
+import com.vaadin.client.widgets.Grid.StaticSection.StaticCell;
+import com.vaadin.client.widgets.Grid.StaticSection.StaticRow;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridStaticCellType;
@@ -411,6 +427,16 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * Returns <code>true</code> if this row contains spanned cells.
+ *
+ * @since
+ * @return does this row contain spanned cells
+ */
+ public boolean hasSpannedCells() {
+ return !cellGroups.isEmpty();
+ }
+
+ /**
* Merges columns cells in a row
*
* @param columns
@@ -2132,6 +2158,8 @@ public class Grid<T> extends ResizeComposite implements
boolean insertionIsAboveFocusedCell = (added.getStart() <= rowWithFocus);
if (bodyHasFocus && insertionIsAboveFocusedCell) {
rowWithFocus += added.length();
+ rowWithFocus = Math.min(rowWithFocus, escalator.getBody()
+ .getRowCount() - 1);
refreshRow(rowWithFocus);
}
}
@@ -2159,10 +2187,12 @@ public class Grid<T> extends ResizeComposite implements
rowWithFocus = removed.getStart() - 1;
} else {
if (escalator.getHeader().getRowCount() > 0) {
- rowWithFocus = lastFocusedHeaderRow;
+ rowWithFocus = Math.min(lastFocusedHeaderRow, escalator
+ .getHeader().getRowCount() - 1);
containerWithFocus = escalator.getHeader();
} else if (escalator.getFooter().getRowCount() > 0) {
- rowWithFocus = lastFocusedFooterRow;
+ rowWithFocus = Math.min(lastFocusedFooterRow, escalator
+ .getFooter().getRowCount() - 1);
containerWithFocus = escalator.getFooter();
}
}
@@ -2466,7 +2496,7 @@ public class Grid<T> extends ResizeComposite implements
private boolean columnsAreGuaranteedToBeWiderThanGrid() {
double freeSpace = escalator.getInnerWidth();
- for (Column<?, ?> column : getColumns()) {
+ for (Column<?, ?> column : getVisibleColumns()) {
if (column.getWidth() >= 0) {
freeSpace -= column.getWidth();
} else if (column.getMinimumWidth() >= 0) {
@@ -2482,7 +2512,7 @@ public class Grid<T> extends ResizeComposite implements
/* Step 1: Apply all column widths as they are. */
Map<Integer, Double> selfWidths = new LinkedHashMap<Integer, Double>();
- List<Column<?, T>> columns = getColumns();
+ List<Column<?, T>> columns = getVisibleColumns();
for (int index = 0; index < columns.size(); index++) {
selfWidths.put(index, columns.get(index).getWidth());
}
@@ -2523,6 +2553,7 @@ public class Grid<T> extends ResizeComposite implements
final Set<Column<?, T>> columnsToExpand = new HashSet<Column<?, T>>();
List<Column<?, T>> nonFixedColumns = new ArrayList<Column<?, T>>();
Map<Integer, Double> columnSizes = new HashMap<Integer, Double>();
+ final List<Column<?, T>> visibleColumns = getVisibleColumns();
/*
* Set all fixed widths and also calculate the size-to-fit widths
@@ -2531,7 +2562,7 @@ public class Grid<T> extends ResizeComposite implements
* This way we know with how many pixels we have left to expand the
* rest.
*/
- for (Column<?, T> column : getColumns()) {
+ for (Column<?, T> column : visibleColumns) {
final double widthAsIs = column.getWidth();
final boolean isFixedWidth = widthAsIs >= 0;
final double widthFixed = Math.max(widthAsIs,
@@ -2540,11 +2571,11 @@ public class Grid<T> extends ResizeComposite implements
&& column.getExpandRatio() == -1;
if (isFixedWidth) {
- columnSizes.put(indexOfColumn(column), widthFixed);
+ columnSizes.put(visibleColumns.indexOf(column), widthFixed);
reservedPixels += widthFixed;
} else {
nonFixedColumns.add(column);
- columnSizes.put(indexOfColumn(column), -1.0d);
+ columnSizes.put(visibleColumns.indexOf(column), -1.0d);
}
}
@@ -2561,7 +2592,7 @@ public class Grid<T> extends ResizeComposite implements
columnsToExpand.add(column);
}
reservedPixels += newWidth;
- columnSizes.put(indexOfColumn(column), newWidth);
+ columnSizes.put(visibleColumns.indexOf(column), newWidth);
}
/*
@@ -2589,8 +2620,8 @@ public class Grid<T> extends ResizeComposite implements
final Column<?, T> column = i.next();
final int expandRatio = getExpandRatio(column,
defaultExpandRatios);
- final double autoWidth = columnSizes
- .get(indexOfColumn(column));
+ final int columnIndex = visibleColumns.indexOf(column);
+ final double autoWidth = columnSizes.get(columnIndex);
final double maxWidth = getMaxWidth(column);
double expandedWidth = autoWidth + widthPerRatio
* expandRatio;
@@ -2600,7 +2631,7 @@ public class Grid<T> extends ResizeComposite implements
totalRatios -= expandRatio;
aColumnHasMaxedOut = true;
pixelsToDistribute -= maxWidth - autoWidth;
- columnSizes.put(indexOfColumn(column), maxWidth);
+ columnSizes.put(columnIndex, maxWidth);
}
}
} while (aColumnHasMaxedOut);
@@ -2636,13 +2667,14 @@ public class Grid<T> extends ResizeComposite implements
for (Column<?, T> column : columnsToExpand) {
final int expandRatio = getExpandRatio(column,
defaultExpandRatios);
- final double autoWidth = columnSizes.get(indexOfColumn(column));
+ final int columnIndex = visibleColumns.indexOf(column);
+ final double autoWidth = columnSizes.get(columnIndex);
double totalWidth = autoWidth + widthPerRatio * expandRatio;
if (leftOver > 0) {
totalWidth += 1;
leftOver--;
}
- columnSizes.put(indexOfColumn(column), totalWidth);
+ columnSizes.put(columnIndex, totalWidth);
totalRatios -= expandRatio;
}
@@ -2663,7 +2695,7 @@ public class Grid<T> extends ResizeComposite implements
* remove those pixels from other columns
*/
double pixelsToRemoveFromOtherColumns = 0;
- for (Column<?, T> column : getColumns()) {
+ for (Column<?, T> column : visibleColumns) {
/*
* We can't iterate over columnsToExpand, even though that
* would be convenient. This is because some column without
@@ -2672,11 +2704,11 @@ public class Grid<T> extends ResizeComposite implements
*/
double minWidth = getMinWidth(column);
- double currentWidth = columnSizes
- .get(indexOfColumn(column));
+ final int columnIndex = visibleColumns.indexOf(column);
+ double currentWidth = columnSizes.get(columnIndex);
boolean hasAutoWidth = column.getWidth() < 0;
if (hasAutoWidth && currentWidth < minWidth) {
- columnSizes.put(indexOfColumn(column), minWidth);
+ columnSizes.put(columnIndex, minWidth);
pixelsToRemoveFromOtherColumns += (minWidth - currentWidth);
minWidthsCausedReflows = true;
@@ -2702,7 +2734,7 @@ public class Grid<T> extends ResizeComposite implements
for (Column<?, T> column : columnsToExpand) {
final double pixelsToRemove = pixelsToRemovePerRatio
* getExpandRatio(column, defaultExpandRatios);
- int colIndex = indexOfColumn(column);
+ int colIndex = visibleColumns.indexOf(column);
columnSizes.put(colIndex, columnSizes.get(colIndex)
- pixelsToRemove);
}
@@ -2934,11 +2966,424 @@ public class Grid<T> extends ResizeComposite implements
private boolean enabled = true;
+
private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater();
/** A set keeping track of the indices of all currently open details */
private Set<Integer> visibleDetails = new HashSet<Integer>();
+ private boolean columnReorderingAllowed;
+
+ 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 onAutoScroll(int scrollDiff) {
+ autoScrollX = scrollDiff;
+ onDragUpdate(null);
+ }
+
+ @Override
+ public void onAutoScrollReachedMin() {
+ // make sure the drop marker is visible on the left
+ autoScrollX = 0;
+ updateDragDropMarker(clientX);
+ }
+
+ @Override
+ public void onAutoScrollReachedMax() {
+ // make sure the drop marker is visible on the right
+ autoScrollX = 0;
+ updateDragDropMarker(clientX);
+ }
+ };
+ /**
+ * Elements for displaying the dragged column(s) and drop marker
+ * properly
+ */
+ private Element table;
+ private Element tableHeader;
+ /** Marks the column drop location */
+ private Element dropMarker;
+ /** A copy of the dragged column(s), moves with cursor. */
+ private Element dragElement;
+ /** Tracks index of the column whose left side the drop would occur */
+ private int latestColumnDropIndex;
+ /**
+ * Map of possible drop positions for the column and the corresponding
+ * column index.
+ */
+ private final TreeMap<Double, Integer> possibleDropPositions = new TreeMap<Double, Integer>();
+ /**
+ * Makes sure that drag cancel doesn't cause anything unwanted like sort
+ */
+ private HandlerRegistration columnSortPreventRegistration;
+
+ private int clientX;
+
+ /** How much the grid is being auto scrolled while dragging. */
+ private int autoScrollX;
+
+ private void initHeaderDragElementDOM() {
+ if (table == null) {
+ tableHeader = DOM.createTHead();
+ dropMarker = DOM.createDiv();
+ tableHeader.appendChild(dropMarker);
+ table = DOM.createTable();
+ table.appendChild(tableHeader);
+ table.setClassName("header-drag-table");
+ }
+ // update the style names on each run in case primary name has been
+ // modified
+ tableHeader.setClassName(escalator.getHeader().getElement()
+ .getClassName());
+ dropMarker.setClassName(getStylePrimaryName() + "-drop-marker");
+ int topOffset = 0;
+ for (int i = 0; i < eventCell.getRowIndex(); i++) {
+ topOffset += escalator.getHeader().getRowElement(i)
+ .getFirstChildElement().getOffsetHeight();
+ }
+ tableHeader.getStyle().setTop(topOffset, Unit.PX);
+
+ getElement().appendChild(table);
+ }
+
+ @Override
+ public void onDragUpdate(NativePreviewEvent event) {
+ if (event != null) {
+ clientX = WidgetUtil.getTouchOrMouseClientX(event
+ .getNativeEvent());
+ autoScrollX = 0;
+ }
+ resolveDragElementHorizontalPosition(clientX);
+ updateDragDropMarker(clientX);
+ }
+
+ private void updateDragDropMarker(final int clientX) {
+ final double scrollLeft = getScrollLeft();
+ final double cursorXCoordinate = clientX
+ - escalator.getHeader().getElement().getAbsoluteLeft();
+ final Entry<Double, Integer> cellEdgeOnRight = possibleDropPositions
+ .ceilingEntry(cursorXCoordinate);
+ final Entry<Double, Integer> cellEdgeOnLeft = possibleDropPositions
+ .floorEntry(cursorXCoordinate);
+ final double diffToRightEdge = cellEdgeOnRight == null ? Double.MAX_VALUE
+ : cellEdgeOnRight.getKey() - cursorXCoordinate;
+ final double diffToLeftEdge = cellEdgeOnLeft == null ? Double.MAX_VALUE
+ : cursorXCoordinate - cellEdgeOnLeft.getKey();
+
+ double dropMarkerLeft = 0 - scrollLeft;
+ if (diffToRightEdge > diffToLeftEdge) {
+ latestColumnDropIndex = cellEdgeOnLeft.getValue();
+ dropMarkerLeft += cellEdgeOnLeft.getKey();
+ } else {
+ latestColumnDropIndex = cellEdgeOnRight.getValue();
+ dropMarkerLeft += cellEdgeOnRight.getKey();
+ }
+
+ dropMarkerLeft += autoScrollX;
+
+ final double frozenColumnsWidth = getFrozenColumnsWidth();
+ if (dropMarkerLeft < frozenColumnsWidth
+ || dropMarkerLeft > escalator.getHeader().getElement()
+ .getOffsetWidth() || dropMarkerLeft < 0) {
+ dropMarkerLeft = -10000000;
+ }
+ dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX);
+ }
+
+ private void resolveDragElementHorizontalPosition(final int clientX) {
+ double left = clientX - table.getAbsoluteLeft();
+ final double frozenColumnsWidth = getFrozenColumnsWidth();
+ if (left < frozenColumnsWidth) {
+ left = (int) frozenColumnsWidth;
+ }
+
+ // do not show the drag element beyond a spanned header cell
+ // limitation
+ final Double leftBound = possibleDropPositions.firstKey();
+ final Double rightBound = possibleDropPositions.lastKey();
+ double scrollLeft = getScrollLeft();
+ if (left + scrollLeft < leftBound) {
+ left = leftBound - scrollLeft + autoScrollX;
+ } else if (left + scrollLeft > rightBound) {
+ left = rightBound - scrollLeft + autoScrollX;
+ }
+
+ // do not show the drag element beyond the grid
+ left = Math.max(0, Math.min(left, table.getClientWidth()));
+
+ left -= dragElement.getClientWidth() / 2;
+ dragElement.getStyle().setLeft(left, Unit.PX);
+ }
+
+ @Override
+ public boolean onDragStart(NativeEvent startingEvent) {
+ calculatePossibleDropPositions();
+
+ if (possibleDropPositions.isEmpty()) {
+ return false;
+ }
+
+ initHeaderDragElementDOM();
+ // needs to clone focus and sorting indicators too (UX)
+ dragElement = DOM.clone(eventCell.getElement(), true);
+ dragElement.getStyle().clearWidth();
+ dropMarker.getStyle().setProperty("height",
+ dragElement.getStyle().getHeight());
+ tableHeader.appendChild(dragElement);
+ // mark the column being dragged for styling
+ 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);
+ return true;
+ }
+
+ @Override
+ public void onDragEnd() {
+ table.removeFromParent();
+ dragElement.removeFromParent();
+ eventCell.getElement().removeClassName("dragged");
+ }
+
+ @Override
+ public void onDrop() {
+ final int draggedColumnIndex = eventCell.getColumnIndex();
+ final int colspan = header.getRow(eventCell.getRowIndex())
+ .getCell(eventCell.getColumn()).getColspan();
+ if (latestColumnDropIndex != draggedColumnIndex
+ && latestColumnDropIndex != (draggedColumnIndex + colspan)) {
+ List<Column<?, T>> columns = getColumns();
+ List<Column<?, T>> reordered = new ArrayList<Column<?, T>>();
+ if (draggedColumnIndex < latestColumnDropIndex) {
+ reordered.addAll(columns.subList(0, draggedColumnIndex));
+ reordered.addAll(columns.subList(draggedColumnIndex
+ + colspan, latestColumnDropIndex));
+ reordered.addAll(columns.subList(draggedColumnIndex,
+ draggedColumnIndex + colspan));
+ reordered.addAll(columns.subList(latestColumnDropIndex,
+ columns.size()));
+ } else {
+ reordered.addAll(columns.subList(0, latestColumnDropIndex));
+ reordered.addAll(columns.subList(draggedColumnIndex,
+ draggedColumnIndex + colspan));
+ reordered.addAll(columns.subList(latestColumnDropIndex,
+ draggedColumnIndex));
+ reordered.addAll(columns.subList(draggedColumnIndex
+ + colspan, columns.size()));
+ }
+ reordered.remove(selectionColumn); // since setColumnOrder will
+ // add it anyway!
+
+ Column<?, T>[] array = reordered.toArray(new Column[reordered
+ .size()]);
+ setColumnOrder(array);
+ transferCellFocusOnDrop();
+ } // else no reordering
+ }
+
+ private void transferCellFocusOnDrop() {
+ final Cell focusedCell = cellFocusHandler.getFocusedCell();
+ if (focusedCell != null) {
+ final int focusedCellColumnIndex = focusedCell.getColumn();
+ final int focusedRowIndex = focusedCell.getRow();
+ final int draggedColumnIndex = eventCell.getColumnIndex();
+ // transfer focus if it was effected by the new column order
+ final RowContainer rowContainer = escalator
+ .findRowContainer(focusedCell.getElement());
+ if (focusedCellColumnIndex == draggedColumnIndex) {
+ // move with the dragged column
+ final int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1
+ : latestColumnDropIndex;
+ cellFocusHandler.setCellFocus(focusedRowIndex,
+ adjustedDropIndex, rowContainer);
+ } else if (latestColumnDropIndex <= focusedCellColumnIndex
+ && draggedColumnIndex > focusedCellColumnIndex) {
+ cellFocusHandler.setCellFocus(focusedRowIndex,
+ focusedCellColumnIndex + 1, rowContainer);
+ } else if (latestColumnDropIndex > focusedCellColumnIndex
+ && draggedColumnIndex < focusedCellColumnIndex) {
+ cellFocusHandler.setCellFocus(focusedRowIndex,
+ focusedCellColumnIndex - 1, rowContainer);
+ }
+ }
+ }
+
+ @Override
+ public void onDragCancel() {
+ // cancel next click so that we may prevent column sorting if
+ // mouse was released on top of the dragged cell
+ if (columnSortPreventRegistration == null) {
+ columnSortPreventRegistration = Event
+ .addNativePreviewHandler(new NativePreviewHandler() {
+
+ @Override
+ public void onPreviewNativeEvent(
+ NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONCLICK) {
+ event.cancel();
+ event.getNativeEvent().preventDefault();
+ columnSortPreventRegistration
+ .removeHandler();
+ columnSortPreventRegistration = null;
+ }
+ }
+ });
+ }
+ autoScroller.stop();
+ }
+
+ private double getFrozenColumnsWidth() {
+ double value = getMultiSelectColumnWidth();
+ for (int i = 0; i < getFrozenColumnCount(); i++) {
+ value += getColumn(i).getWidthActual();
+ }
+ return value;
+ }
+
+ private double getMultiSelectColumnWidth() {
+ if (getSelectionModel().getSelectionColumnRenderer() != null) {
+ // frozen checkbox column is present, it is always the first
+ // column
+ return escalator.getHeader().getElement()
+ .getFirstChildElement().getFirstChildElement()
+ .getOffsetWidth();
+ }
+ return 0.0;
+ }
+
+ /**
+ * Returns the amount of frozen columns. The selection column is always
+ * considered frozen, since it can't be moved.
+ */
+ private int getSelectionAndFrozenColumnCount() {
+ // no matter if selection column is frozen or not, it is considered
+ // frozen for column dnd reorder
+ if (getSelectionModel().getSelectionColumnRenderer() != null) {
+ return Math.max(0, getFrozenColumnCount()) + 1;
+ } else {
+ return Math.max(0, getFrozenColumnCount());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ private void calculatePossibleDropPositions() {
+ possibleDropPositions.clear();
+
+ final int draggedCellIndex = eventCell.getColumnIndex();
+ final StaticRow<?> draggedCellRow = header.getRow(eventCell
+ .getRowIndex());
+ final int draggedCellRightIndex = draggedCellIndex
+ + draggedCellRow.getCell(eventCell.getColumn())
+ .getColspan();
+ final int frozenColumns = getSelectionAndFrozenColumnCount();
+ final Range draggedCellRange = Range.between(draggedCellIndex,
+ draggedCellRightIndex);
+ /*
+ * If the dragged cell intersects with a spanned cell in any other
+ * header or footer row, then the drag is limited inside that
+ * spanned cell. The same rules apply: the cell can't be dropped
+ * inside another spanned cell. The left and right bounds keep track
+ * of the edges of the most limiting spanned cell.
+ */
+ int leftBound = -1;
+ int rightBound = getColumnCount() + 1;
+
+ final HashSet<Integer> unavailableColumnDropIndices = new HashSet<Integer>();
+ final List<StaticRow<?>> rows = new ArrayList<StaticRow<?>>();
+ rows.addAll(header.getRows());
+ rows.addAll(footer.getRows());
+ for (StaticRow<?> row : rows) {
+ if (!row.hasSpannedCells()) {
+ continue;
+ }
+ final boolean isDraggedCellRow = row.equals(draggedCellRow);
+ for (int cellColumnIndex = frozenColumns; cellColumnIndex < getColumnCount(); cellColumnIndex++) {
+ StaticCell cell = row.getCell(getColumn(cellColumnIndex));
+ int colspan = cell.getColspan();
+ if (colspan <= 1) {
+ continue;
+ }
+ final int cellColumnRightIndex = cellColumnIndex + colspan;
+ final Range cellRange = Range.between(cellColumnIndex,
+ cellColumnRightIndex);
+ final boolean intersects = draggedCellRange
+ .intersects(cellRange);
+ if (intersects && !isDraggedCellRow) {
+ // if the currently iterated cell is inside or same as
+ // the dragged cell, then it doesn't restrict the drag
+ if (cellRange.isSubsetOf(draggedCellRange)) {
+ cellColumnIndex = cellColumnRightIndex - 1;
+ continue;
+ }
+ /*
+ * if the dragged cell is a spanned cell and it crosses
+ * with the currently iterated cell without sharing
+ * either start or end then not possible to drag the
+ * cell.
+ */
+ if (!draggedCellRange.isSubsetOf(cellRange)) {
+ return;
+ }
+ // the spanned cell overlaps the dragged cell (but is
+ // not the dragged cell)
+ if (cellColumnIndex <= draggedCellIndex
+ && cellColumnIndex > leftBound) {
+ leftBound = cellColumnIndex;
+ }
+ if (cellColumnRightIndex < rightBound) {
+ rightBound = cellColumnRightIndex;
+ }
+ cellColumnIndex = cellColumnRightIndex - 1;
+ }
+
+ else { // can't drop inside a spanned cell, or this is the
+ // dragged cell
+ while (colspan > 1) {
+ cellColumnIndex++;
+ colspan--;
+ unavailableColumnDropIndices.add(cellColumnIndex);
+ }
+ }
+ }
+ }
+
+ if (leftBound == (rightBound - 1)) {
+ return;
+ }
+
+ double position = getFrozenColumnsWidth();
+ // iterate column indices and add possible drop positions
+ for (int i = frozenColumns; i < getColumnCount(); i++) {
+ if (!unavailableColumnDropIndices.contains(i)) {
+ if (leftBound != -1) {
+ if (i >= leftBound && i <= rightBound) {
+ possibleDropPositions.put(position, i);
+ }
+ } else {
+ possibleDropPositions.put(position, i);
+ }
+ }
+ position += getColumn(i).getWidthActual();
+ }
+ if (leftBound == -1) {
+ // add the right side of the last column as columns.size()
+ possibleDropPositions.put(position, getColumnCount());
+ }
+ }
+
+ };
+
/**
* Enumeration for easy setting of selection mode.
*/
@@ -3041,6 +3486,10 @@ public class Grid<T> extends ResizeComposite implements
private boolean editable = true;
+ private boolean hidden = false;
+
+ private boolean hideable = false;
+
private String headerCaption = "";
private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH;
@@ -3211,6 +3660,9 @@ public class Grid<T> extends ResizeComposite implements
* This action is done "finally", once the current execution loop
* returns. This is done to reduce overhead of unintentionally always
* recalculate all columns, when modifying several columns at once.
+ * <p>
+ * If the column is currently {@link #isHidden() hidden}, then this set
+ * width has effect only once the column has been made visible again.
*
* @param pixels
* the width in pixels or negative for auto sizing
@@ -3218,14 +3670,17 @@ public class Grid<T> extends ResizeComposite implements
public Column<C, T> setWidth(double pixels) {
if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) {
widthUser = pixels;
- scheduleColumnWidthRecalculator();
+ if (!isHidden()) {
+ scheduleColumnWidthRecalculator();
+ }
}
return this;
}
void doSetWidth(double pixels) {
+ assert !isHidden() : "applying width for a hidden column";
if (grid != null) {
- int index = grid.columns.indexOf(this);
+ int index = grid.getVisibleColumns().indexOf(this);
ColumnConfiguration conf = grid.escalator
.getColumnConfiguration();
conf.setColumnWidth(index, pixels);
@@ -3237,6 +3692,9 @@ public class Grid<T> extends ResizeComposite implements
* <p>
* <em>Note:</em> If a negative value was given to
* {@link #setWidth(double)}, that same negative value is returned here.
+ * <p>
+ * <em>Note:</em> Returns the value, even if the column is currently
+ * {@link #isHidden() hidden}.
*
* @return pixel width of the column, or a negative number if the column
* width has been automatically calculated.
@@ -3251,13 +3709,18 @@ public class Grid<T> extends ResizeComposite implements
* Returns the effective pixel width of the column.
* <p>
* This differs from {@link #getWidth()} only when the column has been
- * automatically resized.
+ * automatically resized, or when the column is currently
+ * {@link #isHidden() hidden}, when the value is 0.
*
* @return pixel width of the column.
*/
public double getWidthActual() {
+ if (isHidden()) {
+ return 0;
+ }
return grid.escalator.getColumnConfiguration()
- .getColumnWidthActual(grid.columns.indexOf(this));
+ .getColumnWidthActual(
+ grid.getVisibleColumns().indexOf(this));
}
void reapplyWidth() {
@@ -3294,6 +3757,75 @@ public class Grid<T> extends ResizeComposite implements
return sortable;
}
+ /**
+ * Hides or shows the column. By default columns are visible before
+ * explicitly hiding them.
+ *
+ * @since
+ * @param hidden
+ * <code>true</code> to hide the column, <code>false</code>
+ * to show
+ */
+ public void setHidden(boolean hidden) {
+ if (this.hidden != hidden) {
+ if (hidden) {
+ grid.escalator.getColumnConfiguration().removeColumns(
+ grid.getVisibleColumns().indexOf(this), 1);
+ this.hidden = hidden;
+ } else {
+ this.hidden = hidden;
+ grid.escalator.getColumnConfiguration().insertColumns(
+ grid.getVisibleColumns().indexOf(this), 1);
+ }
+ scheduleColumnWidthRecalculator();
+ this.grid.fireEvent(new ColumnVisibilityChangeEvent<T>(this,
+ hidden, false));
+ }
+ }
+
+ /**
+ * Is this column hidden. Default is {@code false}.
+ *
+ * @since
+ * @return <code>true</code> if the column is currently hidden,
+ * <code>false</code> otherwise
+ */
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ /**
+ * Set whether it is possible for the user to hide this column or not.
+ * Default is {@code false}.
+ * <p>
+ * <em>Note:</em> it is still possible to hide the column
+ * programmatically using {@link #setHidden(boolean)}.
+ *
+ * @since
+ * @param hideable
+ * <code>true</code> if the user can hide this column,
+ * <code>false</code> if not
+ */
+ public void setHideable(boolean hideable) {
+ this.hideable = hideable;
+ // TODO update whether sidebar/popup can be opened
+ }
+
+ /**
+ * Is it possible for the the user to hide this column. Default is
+ * {@code false}.
+ * <p>
+ * <em>Note:</em> the column can be programmatically hidden using
+ * {@link #setHidden(boolean)} regardless of the returned value.
+ *
+ * @since
+ * @return <code>true</code> if the user can hide the column,
+ * <code>false</code> if not
+ */
+ public boolean isHideable() {
+ return hideable;
+ }
+
@Override
public String toString() {
String details = "";
@@ -4239,7 +4771,8 @@ public class Grid<T> extends ResizeComposite implements
int columnIndex = columns.indexOf(column);
// Remove from column configuration
- escalator.getColumnConfiguration().removeColumns(columnIndex, 1);
+ escalator.getColumnConfiguration().removeColumns(
+ getVisibleColumns().indexOf(column), 1);
updateFrozenColumns();
@@ -4254,6 +4787,8 @@ public class Grid<T> extends ResizeComposite implements
/**
* Returns the amount of columns in the grid.
+ * <p>
+ * <em>NOTE:</em> this includes the hidden columns in the count.
*
* @return The number of columns in the grid
*/
@@ -4262,7 +4797,9 @@ public class Grid<T> extends ResizeComposite implements
}
/**
- * Returns a list of columns in the grid.
+ * Returns a list columns in the grid, including hidden columns.
+ * <p>
+ * For currently visible columns, use {@link #getVisibleColumns()}.
*
* @return A unmodifiable list of the columns in the grid
*/
@@ -4272,6 +4809,24 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * Returns a list of the currently visible columns in the grid.
+ * <p>
+ * No {@link Column#isHidden() hidden} columns included.
+ *
+ * @since
+ * @return A unmodifiable list of the currently visible columns in the grid
+ */
+ public List<Column<?, T>> getVisibleColumns() {
+ ArrayList<Column<?, T>> visible = new ArrayList<Column<?, T>>();
+ for (Column<?, T> c : columns) {
+ if (!c.isHidden()) {
+ visible.add(c);
+ }
+ }
+ return Collections.unmodifiableList(visible);
+ }
+
+ /**
* Returns a column by its index in the grid.
*
* @param index
@@ -4288,17 +4843,6 @@ public class Grid<T> extends ResizeComposite implements
}
/**
- * Returns current index of given column
- *
- * @param column
- * column in grid
- * @return column index, or <code>-1</code> if not in this Grid
- */
- protected int indexOfColumn(Column<?, T> column) {
- return columns.indexOf(column);
- }
-
- /**
* Returns the header section of this grid. The default header contains a
* single row displaying the column captions.
*
@@ -4667,8 +5211,12 @@ public class Grid<T> extends ResizeComposite implements
if (newSize > oldSize) {
body.insertRows(oldSize, newSize - oldSize);
+ cellFocusHandler.rowsAddedToBody(Range.withLength(oldSize,
+ newSize - oldSize));
} else if (newSize < oldSize) {
body.removeRows(newSize, oldSize - newSize);
+ cellFocusHandler.rowsRemovedFromBody(Range.withLength(
+ newSize, oldSize - newSize));
}
if (newSize > 0) {
@@ -4676,6 +5224,10 @@ public class Grid<T> extends ResizeComposite implements
Range visibleRowRange = escalator.getVisibleRowRange();
dataSource.ensureAvailability(visibleRowRange.getStart(),
visibleRowRange.length());
+ } else {
+ // We won't expect any data more data updates, so just make
+ // the bookkeeping happy
+ dataAvailable(0, 0);
}
assert body.getRowCount() == newSize;
@@ -4735,7 +5287,7 @@ public class Grid<T> extends ResizeComposite implements
+ getColumnCount() + ")");
}
- this.frozenColumnCount = numberOfColumns;
+ frozenColumnCount = numberOfColumns;
updateFrozenColumns();
}
@@ -4877,6 +5429,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
@@ -4885,6 +5448,26 @@ public class Grid<T> extends ResizeComposite implements
return escalator.getScrollLeft();
}
+ /**
+ * Returns the height of the scrollable area in pixels.
+ *
+ * @since
+ * @return the height of the scrollable area in pixels
+ */
+ public double getScrollHeight() {
+ return escalator.getScrollHeight();
+ }
+
+ /**
+ * Returns the width of the scrollable area in pixels.
+ *
+ * @since
+ * @return the width of the scrollable area in pixels.
+ */
+ public double getScrollWidth() {
+ return escalator.getScrollWidth();
+ }
+
private static final Logger getLogger() {
return Logger.getLogger(Grid.class.getName());
}
@@ -5035,6 +5618,10 @@ public class Grid<T> extends ResizeComposite implements
if (!isElementInChildWidget(e)) {
+ if (handleHeaderCellDragStartEvent(event, container)) {
+ return;
+ }
+
// Sorting through header Click / KeyUp
if (handleHeaderDefaultRowEvent(event, container)) {
return;
@@ -5196,6 +5783,31 @@ public class Grid<T> extends ResizeComposite implements
return true;
}
+ private boolean handleHeaderCellDragStartEvent(Event event,
+ RowContainer container) {
+ if (!isColumnReorderingAllowed()) {
+ return false;
+ }
+ if (container != escalator.getHeader()) {
+ return false;
+ }
+ if (eventCell.getColumnIndex() < escalator.getColumnConfiguration()
+ .getFrozenColumnCount()) {
+ return false;
+ }
+
+ if (event.getTypeInt() == Event.ONMOUSEDOWN
+ && event.getButton() == NativeEvent.BUTTON_LEFT
+ || event.getTypeInt() == Event.ONTOUCHSTART) {
+ dndHandler.onDragStartOnDraggableElement(event,
+ headerCellDndCallback);
+ event.preventDefault();
+ event.stopPropagation();
+ return true;
+ }
+ return false;
+ }
+
private Point rowEventTouchStartingPoint;
private CellStyleGenerator<T> cellStyleGenerator;
private RowStyleGenerator<T> rowStyleGenerator;
@@ -5878,6 +6490,34 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * Register a column reorder handler to this Grid. The event for this
+ * handler is fired when the Grid's columns are reordered.
+ *
+ * @since
+ * @param handler
+ * the handler for the event
+ * @return the registration for the event
+ */
+ public HandlerRegistration addColumnReorderHandler(
+ ColumnReorderHandler<T> handler) {
+ return addHandler(handler, ColumnReorderEvent.getType());
+ }
+
+ /**
+ * Register a column visibility change handler to this Grid. The event for
+ * this handler is fired when the Grid's columns change visibility.
+ *
+ * @since
+ * @param handler
+ * the handler for the event
+ * @return the registration for the event
+ */
+ public HandlerRegistration addColumnVisibilityChangeHandler(
+ ColumnVisibilityChangeHandler<T> handler) {
+ return addHandler(handler, ColumnVisibilityChangeEvent.getType());
+ }
+
+ /**
* Apply sorting to data source.
*/
private void sort(boolean userOriginated) {
@@ -5929,6 +6569,27 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * Returns whether columns can be reordered with drag and drop.
+ *
+ * @since
+ * @return <code>true</code> if columns can be reordered, false otherwise
+ */
+ public boolean isColumnReorderingAllowed() {
+ return columnReorderingAllowed;
+ }
+
+ /**
+ * Sets whether column reordering with drag and drop is allowed or not.
+ *
+ * @since
+ * @param columnReorderingAllowed
+ * specifies whether column reordering is allowed
+ */
+ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
+ this.columnReorderingAllowed = columnReorderingAllowed;
+ }
+
+ /**
* Sets a new column order for the grid. All columns which are not ordered
* here will remain in the order they were before as the last columns of
* grid.
@@ -5979,6 +6640,8 @@ public class Grid<T> extends ResizeComposite implements
for (FooterRow row : footer.getRows()) {
row.calculateColspans();
}
+
+ fireEvent(new ColumnReorderEvent<T>());
}
/**