package com.vaadin.addon.spreadsheet.client;
/*
* #%L
* Vaadin Spreadsheet
* %%
* Copyright (C) 2013 - 2015 Vaadin Ltd
* %%
* This program is available under Commercial Vaadin Add-On License 3.0
* (CVALv3).
*
* See the file license.html distributed with this software for more
* information about licensing.
*
* You should have received a copy of the CVALv3 along with this program.
* If not, see <http://vaadin.com/license/cval-3>.
* #L%
*/
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.MeasuredSize;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.VOverlay;
public class SelectionWidget extends Composite {
private class SelectionOutlineWidget extends Widget {
private static final int eventBits = Event.ONMOUSEDOWN
| Event.ONMOUSEMOVE | Event.ONMOUSEUP | Event.TOUCHEVENTS
| Event.ONLOSECAPTURE;
private final DivElement root = Document.get().createDivElement();
private final DivElement top = Document.get().createDivElement();
private final DivElement left = Document.get().createDivElement();
private final DivElement right = Document.get().createDivElement();
private final DivElement bottom = Document.get().createDivElement();
private final DivElement corner = Document.get().createDivElement();
private final DivElement cornerTouchArea = Document.get()
.createDivElement();
private final DivElement topSquare = Document.get().createDivElement();
private final DivElement leftSquare = Document.get().createDivElement();
private final DivElement rightSquare = Document.get()
.createDivElement();
private final DivElement bottomSquare = Document.get()
.createDivElement();
private final DivElement topSquareTouchArea = Document.get()
.createDivElement();
private final DivElement leftSquareTouchArea = Document.get()
.createDivElement();
private final DivElement rightSquareTouchArea = Document.get()
.createDivElement();
private final DivElement bottomSquareTouchArea = Document.get()
.createDivElement();
private int col1;
private int row1;
private int col2;
private int row2;
private int maxColumn;
private int minRow;
private int maxRow;
private int minColumn;
private boolean leftEdgeHidden;
private boolean topEdgeHidden;
private boolean rightEdgeHidden;
private boolean bottomEdgeHidden;
private int width;
private int height;
public SelectionOutlineWidget() {
initDOM();
initListeners();
}
void setSquaresVisible(boolean top, boolean right, boolean bottom,
boolean left) {
topSquareTouchArea.getStyle().setVisibility(
top && !topEdgeHidden ? Visibility.VISIBLE
: Visibility.HIDDEN);
leftSquareTouchArea.getStyle().setVisibility(
left && !leftEdgeHidden ? Visibility.VISIBLE
: Visibility.HIDDEN);
rightSquareTouchArea.getStyle().setVisibility(
right && !rightEdgeHidden ? Visibility.VISIBLE
: Visibility.HIDDEN);
bottomSquareTouchArea.getStyle().setVisibility(
bottom && !bottomEdgeHidden ? Visibility.VISIBLE
: Visibility.HIDDEN);
}
private void initDOM() {
root.setClassName("sheet-selection");
if (touchMode) {
// makes borders bigger with drag-symbol
root.addClassName("touch");
}
top.setClassName("s-top");
left.setClassName("s-left");
right.setClassName("s-right");
bottom.setClassName("s-bottom");
corner.setClassName("s-corner");
cornerTouchArea.setClassName("s-corner-touch");
topSquare.setClassName("square");
leftSquare.setClassName("square");
rightSquare.setClassName("square");
bottomSquare.setClassName("square");
topSquareTouchArea.setClassName("fill-touch-square");
leftSquareTouchArea.setClassName("fill-touch-square");
rightSquareTouchArea.setClassName("fill-touch-square");
bottomSquareTouchArea.setClassName("fill-touch-square");
if (touchMode) {
// append a large touch area for the corner, since it's too
// small otherwise
right.appendChild(cornerTouchArea);
cornerTouchArea.appendChild(corner);
} else {
right.appendChild(corner);
}
top.appendChild(left);
top.appendChild(right);
left.appendChild(bottom);
root.appendChild(top);
if (touchMode) {
top.appendChild(topSquareTouchArea);
left.appendChild(leftSquareTouchArea);
right.appendChild(rightSquareTouchArea);
bottom.appendChild(bottomSquareTouchArea);
topSquareTouchArea.appendChild(topSquare);
leftSquareTouchArea.appendChild(leftSquare);
rightSquareTouchArea.appendChild(rightSquare);
bottomSquareTouchArea.appendChild(bottomSquare);
}
setElement(root);
}
private void initListeners() {
// Widget is not attached in GWT terms, so can't call sinkEvents
// directly
Event.sinkEvents(root, eventBits);
Event.setEventListener(root, new EventListener() {
@Override
public void onBrowserEvent(Event event) {
final Element target = DOM.eventGetTarget(event);
final int type = event.getTypeInt();
boolean touchEvent = type == Event.ONTOUCHSTART
|| type == Event.ONTOUCHEND
|| type == Event.ONTOUCHMOVE
|| type == Event.ONTOUCHCANCEL;
if (paintMode) {
onPaintEvent(event);
event.stopPropagation();
} else if (type == Event.ONMOUSEDOWN) {
if (target.equals(corner)) {
onPaintEvent(event);
event.stopPropagation();
} else if (target.equals(top)) {
} else if (target.equals(left)) {
} else if (target.equals(right)) {
} else if (target.equals(bottom)) {
}
// TODO Implement dragging the selection
} else if (touchEvent) {
if (type == Event.ONTOUCHEND
|| type == Event.ONTOUCHCANCEL) {
Event.releaseCapture(root);
stopSelectingCells(event);
} else if (target.equals(corner)
|| target.equals(cornerTouchArea)) {
if (type == Event.ONTOUCHSTART) {
Event.setCapture(root);
beginSelectingCells(event);
} else {
// corners, resize selection
selectCells(event);
}
} else {
// handles, fill
if (fillMode) {
// same as dragging the corner in normal mode
onPaintEvent(event);
}
}
event.preventDefault();
event.stopPropagation();
}
}
});
}
public void setPosition(int col1, int col2, int row1, int row2) {
root.removeClassName(SheetWidget.toKey(this.col1, this.row1));
if (minColumn > 0 && col1 < minColumn) {
col1 = minColumn;
setLeftEdgeHidden(true);
} else {
setLeftEdgeHidden(false);
}
if (minRow > 0 && row1 < minRow) {
row1 = minRow;
setTopEdgeHidden(true);
} else {
setTopEdgeHidden(false);
}
if (maxRow > 0 && row2 > maxRow) {
row2 = maxRow;
setBottomEdgeHidden(true);
setCornerHidden(true); // paint is hidden if right edge is
// hidden
} else {
setBottomEdgeHidden(false);
setCornerHidden(false);
}
if (maxColumn > 0 && maxColumn < col2) {
col2 = maxColumn;
setRightEdgeHidden(true);
} else {
setRightEdgeHidden(false);
}
this.col1 = col1;
this.row1 = row1;
this.col2 = col2;
this.row2 = row2;
width = col2 - col1;
height = row2 - row1;
if (col1 <= col2 && row1 <= row2) {
root.addClassName(SheetWidget.toKey(this.col1, this.row1));
setVisible(true);
updateWidth();
updateHeight();
} else {
setVisible(false);
}
}
private void updateWidth() {
int w = handler.getColWidthActual(col1);
for (int i = col1 + 1; i <= col2; i++) {
w += handler.getColWidthActual(i);
}
setWidth(w);
}
private void updateHeight() {
int[] rowHeightsPX = handler.getRowHeightsPX();
if (rowHeightsPX != null && rowHeightsPX.length != 0) {
setHeight(countSum(handler.getRowHeightsPX(), row1, row2 + 1));
}
}
private void setWidth(int width) {
top.getStyle().setWidth(width + 1, Unit.PX);
bottom.getStyle().setWidth(width + 1, Unit.PX);
}
private void setHeight(float height) {
left.getStyle().setHeight(height, Unit.PX);
right.getStyle().setHeight(height, Unit.PX);
}
public void setSheetElement(Element element) {
element.appendChild(root);
}
public void setZIndex(int zIndex) {
getElement().getStyle().setZIndex(zIndex);
}
protected void setLeftEdgeHidden(boolean hidden) {
leftEdgeHidden = hidden;
left.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
leftSquareTouchArea.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
protected void setTopEdgeHidden(boolean hidden) {
topEdgeHidden = hidden;
top.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
topSquareTouchArea.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
protected void setRightEdgeHidden(boolean hidden) {
rightEdgeHidden = hidden;
right.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
rightSquareTouchArea.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
protected void setBottomEdgeHidden(boolean hidden) {
bottomEdgeHidden = hidden;
bottom.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
bottomSquareTouchArea.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
protected void setCornerHidden(boolean hidden) {
cornerTouchArea.getStyle().setDisplay(
hidden ? Display.NONE : Display.BLOCK);
corner.getStyle().setDisplay(hidden ? Display.NONE : Display.BLOCK);
}
private void onPaintEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONTOUCHSTART:
if (event.getTouches().length() > 1) {
return;
}
case Event.ONMOUSEDOWN:
beginPaintingCells(event);
break;
case Event.ONMOUSEUP:
case Event.ONTOUCHEND:
case Event.ONTOUCHCANCEL:
DOM.releaseCapture(getElement());
case Event.ONLOSECAPTURE:
stopPaintingCells(event);
break;
case Event.ONMOUSEMOVE:
paintCells(event);
break;
case Event.ONTOUCHMOVE:
paintCells(event);
event.preventDefault();
break;
default:
break;
}
}
public void remove() {
root.removeFromParent();
Event.sinkEvents(root, (~eventBits));
}
public void setLimits(int minRow, int maxRow, int minColumn,
int maxColumn) {
this.minRow = minRow;
this.maxRow = maxRow;
this.minColumn = minColumn;
this.maxColumn = maxColumn;
}
}
private class PaintOutlineWidget extends Widget {
private final DivElement root = Document.get().createDivElement();
private final DivElement top = Document.get().createDivElement();
private final DivElement left = Document.get().createDivElement();
private final DivElement right = Document.get().createDivElement();
private final DivElement bottom = Document.get().createDivElement();
private int col1;
private int row1;
private int col2;
private int row2;
private int maxColumn;
private int minRow;
private int maxRow;
private int minColumn;
public PaintOutlineWidget() {
root.setClassName("sheet-selection");
root.addClassName("paintmode");
top.setClassName("s-top");
left.setClassName("s-left");
right.setClassName("s-right");
bottom.setClassName("s-bottom");
top.appendChild(left);
top.appendChild(right);
left.appendChild(bottom);
root.appendChild(top);
setElement(root);
}
public void setPosition(int col1, int col2, int row1, int row2) {
root.removeClassName(SheetWidget.toKey(this.col1, this.row1));
if (minColumn > 0 && col1 < minColumn) {
col1 = minColumn;
setLeftEdgeHidden(true);
} else {
setLeftEdgeHidden(false);
}
if (minRow > 0 && row1 < minRow) {
row1 = minRow;
setTopEdgeHidden(true);
} else {
setTopEdgeHidden(false);
}
if (maxRow > 0 && row2 > maxRow) {
row2 = maxRow;
setBottomEdgeHidden(true);
} else {
setBottomEdgeHidden(false);
}
if (maxColumn > 0 && maxColumn < col2) {
col2 = maxColumn;
setRightEdgeHidden(true);
} else {
setRightEdgeHidden(false);
}
this.col1 = col1;
this.row1 = row1;
this.col2 = col2;
this.row2 = row2;
if (col1 <= col2 && row1 <= row2) {
root.addClassName(SheetWidget.toKey(this.col1, this.row1));
setVisible(true);
updateWidth();
updateHeight();
} else {
setVisible(false);
}
}
private void updateWidth() {
int w = handler.getColWidthActual(col1);
for (int i = col1 + 1; i <= col2; i++) {
w += handler.getColWidthActual(i);
}
setWidth(w);
}
private void updateHeight() {
int[] rowHeightsPX = handler.getRowHeightsPX();
if (rowHeightsPX != null && rowHeightsPX.length != 0) {
setHeight(countSum(handler.getRowHeightsPX(), row1, row2 + 1));
}
}
private void setWidth(int width) {
root.getStyle().setWidth(width + 1, Unit.PX);
top.getStyle().setWidth(width + 1, Unit.PX);
bottom.getStyle().setWidth(width + 1, Unit.PX);
}
private void setHeight(float height) {
root.getStyle().setHeight(height, Unit.PX);
left.getStyle().setHeight(height, Unit.PX);
right.getStyle().setHeight(height, Unit.PX);
}
public void setSheetElement(Element element) {
element.appendChild(root);
}
public void setZIndex(int zIndex) {
getElement().getStyle().setZIndex(zIndex);
}
protected void setLeftEdgeHidden(boolean hidden) {
left.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
protected void setTopEdgeHidden(boolean hidden) {
top.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
protected void setRightEdgeHidden(boolean hidden) {
right.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
protected void setBottomEdgeHidden(boolean hidden) {
bottom.getStyle().setVisibility(
hidden ? Visibility.HIDDEN : Visibility.VISIBLE);
}
public void remove() {
root.removeFromParent();
}
public void setLimits(int minRow, int maxRow, int minColumn,
int maxColumn) {
this.minRow = minRow;
this.maxRow = maxRow;
this.minColumn = minColumn;
this.maxColumn = maxColumn;
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
getElement().getStyle().clearOverflow();
} else {
getElement().getStyle().setOverflow(Overflow.HIDDEN);
}
}
}
private final SelectionOutlineWidget bottomRight;
private SelectionOutlineWidget bottomLeft;
private SelectionOutlineWidget topRight;
private SelectionOutlineWidget topLeft;
private int col1;
private int row1;
private int col2;
private int row2;
private final PaintOutlineWidget paintBottomRight;
private PaintOutlineWidget paintBottomLeft;
private PaintOutlineWidget paintTopRight;
private PaintOutlineWidget paintTopLeft;
private boolean paintMode;
private boolean touchMode;
private boolean fillMode;
private final SheetHandler handler;
private int origX;
private int origY;
private SheetWidget sheetWidget;
private int horizontalSplitPosition;
private int verticalSplitPosition;
private int totalHeight;
private int totalWidth;
private int tempCol;
private int tempRow;
private int selectionStartCol;
private int selectionStartRow;
private VOverlay touchActions;
private boolean dragging;
private boolean decreaseSelection;
private boolean increaseSelection;
private boolean startCellTopLeft;
private boolean startCellTopRight;
private boolean startCellBottomLeft;
private int clientX;
private int clientY;
private int deltaY;
private int deltaX;
private boolean scrollTimerRunning;
private int paintcol1;
private int paintrow1;
private int paintcol2;
private int paintrow2;
private boolean crossedLeft;
private boolean crossedDown;
public SelectionWidget(SheetHandler actionHandler, SheetWidget sheetWidget) {
handler = actionHandler;
this.sheetWidget = sheetWidget;
touchMode = sheetWidget.isTouchMode();
bottomRight = new SelectionOutlineWidget();
initWidget(bottomRight);
bottomRight.setZIndex(8);
bottomRight.addStyleName("bottom-right");
setVisible(false);
paintBottomRight = new PaintOutlineWidget();
paintBottomRight.addStyleName("bottom-right");
paintBottomRight.setZIndex(9);
Element bottomRightPane = sheetWidget.getBottomRightPane();
bottomRight.setSheetElement(bottomRightPane);
paintBottomRight.setSheetElement(bottomRightPane);
}
public void setHorizontalSplitPosition(int horizontalSplitPosition) {
this.horizontalSplitPosition = horizontalSplitPosition;
if (horizontalSplitPosition > 0 && bottomLeft == null) {
bottomLeft = new SelectionOutlineWidget();
bottomLeft.setSheetElement(sheetWidget.getBottomLeftPane());
bottomLeft.setVisible(false);
bottomLeft.setZIndex(18);
bottomLeft.addStyleName("bottom-left");
paintBottomLeft = new PaintOutlineWidget();
paintBottomLeft.setSheetElement(sheetWidget.getBottomLeftPane());
paintBottomLeft.setVisible(false);
paintBottomLeft.setZIndex(19);
paintBottomLeft.addStyleName("bottom-left");
} else if (horizontalSplitPosition == 0 && bottomLeft != null) {
bottomLeft.remove();
bottomLeft = null;
paintBottomLeft.remove();
paintBottomLeft = null;
}
updateTopLeft();
updateLimits();
}
public void setVerticalSplitPosition(int verticalSplitPosition) {
this.verticalSplitPosition = verticalSplitPosition;
if (verticalSplitPosition > 0 && topRight == null) {
topRight = new SelectionOutlineWidget();
topRight.setSheetElement(sheetWidget.getTopRightPane());
topRight.setVisible(false);
topRight.setZIndex(18);
topRight.addStyleName("top-right");
paintTopRight = new PaintOutlineWidget();
paintTopRight.setSheetElement(sheetWidget.getTopRightPane());
paintTopRight.setVisible(false);
paintTopRight.setZIndex(19);
paintTopRight.addStyleName("top-left");
} else if (verticalSplitPosition == 0 && topRight != null) {
topRight.remove();
topRight = null;
paintTopRight.remove();
paintTopRight = null;
}
updateTopLeft();
updateLimits();
}
private void updateTopLeft() {
if (verticalSplitPosition > 0 && horizontalSplitPosition > 0
&& topLeft == null) {
topLeft = new SelectionOutlineWidget();
topLeft.setSheetElement(sheetWidget.getTopLeftPane());
topLeft.setVisible(false);
topLeft.setZIndex(28);
topLeft.addStyleName("top-left");
paintTopLeft = new PaintOutlineWidget();
paintTopLeft.setSheetElement(sheetWidget.getTopLeftPane());
paintTopLeft.setVisible(false);
paintTopLeft.setZIndex(29);
paintTopLeft.addStyleName("top-left");
} else if (topLeft != null
&& (verticalSplitPosition == 0 || horizontalSplitPosition == 0)) {
topLeft.remove();
topLeft = null;
paintTopLeft.remove();
paintTopLeft = null;
}
}
private void updateLimits() {
bottomRight.setLimits(verticalSplitPosition == 0 ? 0
: verticalSplitPosition + 1, 0,
horizontalSplitPosition == 0 ? 0 : horizontalSplitPosition + 1,
0);
if (bottomLeft != null) {
bottomLeft.setLimits(verticalSplitPosition == 0 ? 0
: verticalSplitPosition + 1, 0, 0, horizontalSplitPosition);
}
if (topRight != null) {
topRight.setLimits(0, verticalSplitPosition,
horizontalSplitPosition == 0 ? 0
: horizontalSplitPosition + 1, 0);
}
if (topLeft != null) {
topLeft.setLimits(0, verticalSplitPosition, 0,
horizontalSplitPosition);
}
paintBottomRight.setLimits(verticalSplitPosition == 0 ? 0
: verticalSplitPosition + 1, 0,
horizontalSplitPosition == 0 ? 0 : horizontalSplitPosition + 1,
0);
if (paintBottomLeft != null) {
paintBottomLeft.setLimits(verticalSplitPosition == 0 ? 0
: verticalSplitPosition + 1, 0, 0, horizontalSplitPosition);
}
if (paintTopRight != null) {
paintTopRight.setLimits(0, verticalSplitPosition,
horizontalSplitPosition == 0 ? 0
: horizontalSplitPosition + 1, 0);
}
if (paintTopLeft != null) {
paintTopLeft.setLimits(0, verticalSplitPosition, 0,
horizontalSplitPosition);
}
}
private void setSelectionWidgetSquaresVisible(boolean visible) {
if (touchMode) {
boolean top, right, bottom, left;
bottom = bottomLeft != null && bottomLeft.width > bottomRight.width ? false
: visible;
right = topRight != null && topRight.height > bottomRight.height ? false
: visible;
bottomRight.setSquaresVisible(bottom, right, bottom, right);
if (bottomLeft != null) {
bottom = bottomRight != null
&& bottomRight.width >= bottomLeft.width ? false
: visible;
left = topLeft != null && topLeft.height > bottomLeft.height ? false
: visible;
bottomLeft.setSquaresVisible(bottom, left, bottom, left);
}
if (topRight != null) {
top = topLeft != null && topLeft.width > topRight.width ? false
: visible;
right = bottomRight != null
&& bottomRight.height >= topRight.height ? false
: visible;
topRight.setSquaresVisible(top, right, top, right);
}
if (topLeft != null) {
top = topRight != null && topRight.width >= topLeft.width ? false
: visible;
left = bottomLeft != null
&& bottomLeft.height >= topLeft.height ? false
: visible;
topLeft.setSquaresVisible(top, left, top, left);
}
}
}
public int getRow1() {
return row1;
}
public int getRow2() {
return row2;
}
public int getCol1() {
return col1;
}
public int getCol2() {
return col2;
}
public void setPosition(int col1, int col2, int row1, int row2) {
this.col1 = col1;
this.row1 = row1;
this.col2 = col2;
this.row2 = row2;
totalHeight = countSum(handler.getRowHeightsPX(), row1, row2 + 1);
totalWidth = countSum(handler.getColWidths(), col1, col2 + 1);
boolean hiddenCellSelected = totalWidth == 0 || totalHeight == 0;
bottomRight.setPosition(col1, col2, row1, row2);
if (hiddenCellSelected) {
bottomRight.setCornerHidden(true);
}
if (verticalSplitPosition > 0 & horizontalSplitPosition > 0) {
topLeft.setPosition(col1, col2, row1, row2);
if (hiddenCellSelected) {
topLeft.setCornerHidden(true);
}
}
if (verticalSplitPosition > 0) {
topRight.setPosition(col1, col2, row1, row2);
if (hiddenCellSelected) {
topRight.setCornerHidden(true);
}
}
if (horizontalSplitPosition > 0) {
bottomLeft.setPosition(col1, col2, row1, row2);
if (hiddenCellSelected) {
bottomLeft.setCornerHidden(true);
}
}
if (fillMode) {
setFillMode(false);
}
if (!dragging) {
showTouchActions();
}
}
public void setPaintPosition(int col1, int col2, int row1, int row2) {
paintcol1 = col1;
paintrow1 = row1;
paintcol2 = col2;
paintrow2 = row2;
paintBottomRight.setPosition(col1, col2, row1, row2);
if (verticalSplitPosition > 0 & horizontalSplitPosition > 0) {
paintTopLeft.setPosition(col1, col2, row1, row2);
}
if (verticalSplitPosition > 0) {
paintTopRight.setPosition(col1, col2, row1, row2);
}
if (horizontalSplitPosition > 0) {
paintBottomLeft.setPosition(col1, col2, row1, row2);
}
}
private void showTouchActions() {
if (touchMode) {
// show touch actions in popup
if (touchActions != null) {
// remove old
touchActions.hide();
}
touchActions = new VOverlay(true);
touchActions.setOwner((Widget) sheetWidget.actionHandler);
touchActions.addStyleName("v-contextmenu");
final MenuBar m = new MenuBar();
m.addItem(new SafeHtmlBuilder().appendEscaped("Fill").toSafeHtml(),
new ScheduledCommand() {
@Override
public void execute() {
setFillMode(true);
touchActions.hide();
}
});
touchActions.add(m);
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
touchActions
.setPopupPositionAndShow(new PositionCallback() {
@Override
public void setPosition(int offsetWidth,
int offsetHeight) {
// above top border
int top = 0;
int left = 0;
int bottom = 0;
int width = 0;
int parentTop = 0;
if (topRight != null
&& topRight.isVisible()) {
top = topRight.top.getAbsoluteTop();
left = topRight.top.getAbsoluteLeft();
width = topRight.top.getClientWidth();
bottom = topRight.bottom
.getAbsoluteBottom() + 5;
if (topLeft.isVisible()) {
width += topLeft.top
.getClientWidth();
}
if (bottomRight.isVisible()) {
bottom = bottomRight.bottom
.getAbsoluteBottom() + 5;
}
} else if (topLeft != null
&& topLeft.isVisible()) {
top = topLeft.top.getAbsoluteTop();
left = topLeft.top.getAbsoluteLeft();
width = topLeft.top.getClientWidth();
bottom = topLeft.bottom
.getAbsoluteBottom() + 5;
if (bottomLeft.isVisible()) {
bottom = bottomLeft.bottom
.getAbsoluteBottom() + 5;
}
} else if (bottomLeft != null
&& bottomLeft.isVisible()) {
top = bottomLeft.top.getAbsoluteTop();
left = bottomLeft.top.getAbsoluteLeft();
width = bottomLeft.top.getClientWidth();
bottom = bottomLeft.bottom
.getAbsoluteBottom() + 5;
if (bottomRight.isVisible()) {
width += bottomRight.top
.getClientWidth();
}
} else {
top = bottomRight.top.getAbsoluteTop();
left = bottomRight.top
.getAbsoluteLeft();
width = bottomRight.top
.getClientWidth();
bottom = bottomRight.bottom
.getAbsoluteBottom() + 5;
}
if (width > sheetWidget.getElement()
.getClientWidth()) {
width = sheetWidget.getElement()
.getClientWidth();
}
if (sheetWidget.hasFrozenRows()) {
parentTop = sheetWidget
.getTopRightPane()
.getAbsoluteTop();
} else {
parentTop = sheetWidget
.getBottomRightPane()
.getAbsoluteTop();
}
top -= offsetHeight + 5;
left += (width / 2) - (offsetWidth / 2);
if (parentTop > top) {
// put under instead
top = bottom + 5;
}
touchActions.setPopupPosition(left, top);
// TODO check for room
}
});
touchActions.show();
}
});
}
}
@Override
public void setWidth(String width) {
}
@Override
public void setHeight(String height) {
}
@Override
public void setVisible(boolean visible) {
if (visible == isVisible()) {
return;
}
super.setVisible(visible);
if (topLeft != null) {
topLeft.setVisible(visible);
}
if (topRight != null) {
topRight.setVisible(visible);
}
if (bottomLeft != null) {
bottomLeft.setVisible(visible);
}
}
public void setPaintVisible(boolean visible) {
if (visible == isPaintVisible()) {
return;
}
paintBottomRight.setVisible(visible);
if (paintTopLeft != null) {
paintTopLeft.setVisible(visible);
}
if (paintTopRight != null) {
paintTopRight.setVisible(visible);
}
if (paintBottomLeft != null) {
paintBottomLeft.setVisible(visible);
}
setSelectionWidgetSquaresVisible(!visible);
}
private boolean isPaintVisible() {
return paintBottomRight.isVisible() || paintBottomLeft != null
&& paintBottomLeft.isVisible() || paintTopRight != null
&& paintTopRight.isVisible() || paintTopLeft != null
&& paintTopLeft.isVisible();
}
@Override
public boolean isVisible() {
return super.isVisible() || bottomLeft != null
&& bottomLeft.isVisible() || topRight != null
&& topRight.isVisible() || topLeft != null
&& topLeft.isVisible();
}
/**
*
* @param sizes
* @param beginIndex
* 1-based inclusive
* @param endIndex
* 1-based exclusive
* @return
*/
public int countSum(int[] sizes, int beginIndex, int endIndex) {
if (sizes == null || sizes.length < endIndex - 1) {
return 0;
}
int pos = 0;
for (int i = beginIndex; i < endIndex; i++) {
pos += sizes[i - 1];
}
return pos;
}
/**
* Returns index of the cell that has the left edge closest to the given
* cursor position. Used for determining how many rows/columns should be
* painted when the mouse cursor is dragged somewhere.
*
* @param cellSizes
* the sizes used to calculate
* @param startIndex
* 1-based index where the cursorPosition refers to
* @param cursorPosition
* the position of the cursor relative to startIndex. Can be
* negative
* @param forSelection
* true if the result is used for touch selection, false if it's
* used for painting cells
* @return
*/
public int closestCellEdgeIndexToCursor(int cellSizes[], int startIndex,
int cursorPosition, boolean forSelection) {
int result = 0;
int pos = 0;
if (cursorPosition < 0) {
if (startIndex > 1) {
while (startIndex > 1 && pos > cursorPosition) {
startIndex--;
pos -= cellSizes[startIndex - 1];
}
if (forSelection && pos < cursorPosition) {
startIndex++;
}
result = startIndex;
} else {
result = 1;
}
} else {
if (startIndex < cellSizes.length) {
while (startIndex <= cellSizes.length && pos < cursorPosition) {
pos += cellSizes[startIndex - 1];
startIndex++;
}
result = startIndex;
} else {
result = cellSizes.length;
}
}
return forSelection ? result : result - 1;
}
private void beginPaintingCells(Event event) {
startCellTopLeft = sheetWidget.isCellRenderedInTopLeftPane(col2, row2);
startCellTopRight = sheetWidget
.isCellRenderedInTopRightPane(col2, row2);
startCellBottomLeft = sheetWidget.isCellRenderedInBottomLeftPane(col2,
row2);
crossedDown = !startCellTopLeft && !startCellTopRight;
crossedLeft = !startCellTopLeft && !startCellBottomLeft;
initialScrollLeft = sheetWidget.sheet.getScrollLeft();
initialScrollTop = sheetWidget.sheet.getScrollTop();
clientX = SpreadsheetWidget.getTouchOrMouseClientX(event);
clientY = SpreadsheetWidget.getTouchOrMouseClientY(event);
tempCol = col2;
tempRow = row2;
paintMode = true;
decreaseSelection = false;
increaseSelection = false;
storeEventPos(event);
DOM.setCapture(getElement());
event.preventDefault();
sheetWidget.getElement().addClassName("selecting");
setSelectionWidgetSquaresVisible(true);
}
private void storeEventPos(Event event) {
Element element = getTopLeftMostElement();
origX = element.getAbsoluteLeft();
origY = element.getAbsoluteTop();
selectionStartCol = col1;
selectionStartRow = row1;
}
private Element getTopLeftMostElement() {
if (sheetWidget.isCellRenderedInTopRightPane(col1, row1)) {
return topRight.getElement();
}
if (sheetWidget.isCellRenderedInBottomLeftPane(col1, row1)) {
return bottomLeft.getElement();
}
if (sheetWidget.isCellRenderedInTopLeftPane(col1, row1)) {
return topLeft.getElement();
}
return bottomRight.getElement();
}
private void stopPaintingCells(Event event) {
paintMode = false;
setPaintVisible(false);
if (scrollTimerRunning) {
stopScrollTimer();
}
if (decreaseSelection) {
handler.onSelectionDecreasePainted(paintcol1, paintrow1);
} else if (increaseSelection) {
int c1 = Math.min(col1, paintcol1);
int c2 = Math.max(col2, paintcol2);
int r1 = Math.min(row1, paintrow1);
int r2 = Math.max(row2, paintrow2);
if (c1 <= c2 && r1 <= r2) {
handler.onSelectionIncreasePainted(c1, c2, r1, r2);
}
}
sheetWidget.getElement().removeClassName("selecting");
setSelectionWidgetSquaresVisible(false);
}
private void beginSelectingCells(Event event) {
startCellTopLeft = sheetWidget.isCellRenderedInTopLeftPane(col2, row2);
startCellTopRight = sheetWidget
.isCellRenderedInTopRightPane(col2, row2);
startCellBottomLeft = sheetWidget.isCellRenderedInBottomLeftPane(col2,
row2);
crossedDown = !startCellTopLeft && !startCellTopRight;
crossedLeft = !startCellTopLeft && !startCellBottomLeft;
initialScrollLeft = sheetWidget.sheet.getScrollLeft();
initialScrollTop = sheetWidget.sheet.getScrollTop();
clientX = SpreadsheetWidget.getTouchOrMouseClientX(event);
clientY = SpreadsheetWidget.getTouchOrMouseClientY(event);
tempCol = col2;
tempRow = row2;
storeEventPos(event);
}
private void selectCells(Event event) {
dragging = true;
final int clientX = SpreadsheetWidget.getTouchOrMouseClientX(event);
final int clientY = SpreadsheetWidget.getTouchOrMouseClientY(event);
// If we're scrolling, do not paint anything
if (checkScrollWhilePainting(clientY, clientX)) {
return;
}
// position in perspective to the top left
int xMousePos = clientX - origX + sheetWidget.sheet.getScrollLeft()
- initialScrollLeft;
int yMousePos = clientY - origY + sheetWidget.sheet.getScrollTop()
- initialScrollTop;
// touch offset; coords are made for mouse movement and need adjustment
// on touch
xMousePos -= 70;
yMousePos -= 20;
final int[] colWidths = handler.getColWidths();
final int[] rowHeightsPX = handler.getRowHeightsPX();
tempCol = closestCellEdgeIndexToCursor(colWidths, selectionStartCol,
xMousePos, true);
tempRow = closestCellEdgeIndexToCursor(rowHeightsPX, selectionStartRow,
yMousePos, true);
sheetWidget.getSheetHandler()
.onSelectingCellsWithDrag(tempCol, tempRow);
}
private void stopSelectingCells(Event event) {
if (scrollTimerRunning) {
stopScrollTimer();
}
sheetWidget.getSheetHandler().onFinishedSelectingCellsWithDrag(
sheetWidget.getSelectedCellColumn(), tempCol,
sheetWidget.getSelectedCellRow(), tempRow);
dragging = false;
showTouchActions();
}
protected void setFillMode(boolean fillMode) {
this.fillMode = fillMode;
if (fillMode) {
bottomRight.addStyleName("fill");
bottomRight.setCornerHidden(fillMode);
if (topLeft != null) {
topLeft.setCornerHidden(fillMode);
topLeft.addStyleName("fill");
}
if (topRight != null) {
topRight.setCornerHidden(fillMode);
topRight.addStyleName("fill");
}
if (bottomLeft != null) {
bottomLeft.setCornerHidden(fillMode);
bottomLeft.addStyleName("fill");
}
setSelectionWidgetSquaresVisible(true);
} else {
bottomRight.removeStyleName("fill");
if (topLeft != null) {
topLeft.removeStyleName("fill");
}
if (topRight != null) {
topRight.removeStyleName("fill");
}
if (bottomLeft != null) {
bottomLeft.removeStyleName("fill");
}
setSelectionWidgetSquaresVisible(false);
}
}
private boolean checkScrollWhilePainting(int y, int x) {
int scrollPaneTop = sheetWidget.sheet.getAbsoluteTop();
int scrollPaneLeft = sheetWidget.sheet.getAbsoluteLeft();
int scrollPaneBottom = sheetWidget.sheet.getAbsoluteBottom();
int scrollPaneRight = sheetWidget.sheet.getAbsoluteRight();
clientX = x;
clientY = y;
if (y < scrollPaneTop) {
if (crossedDown || (!startCellTopRight && !startCellTopLeft)) {
deltaY = y - scrollPaneTop;
}
} else if (y > scrollPaneBottom) {
deltaY = y - scrollPaneBottom;
} else {
deltaY = 0;
}
if (x < scrollPaneLeft) {
if (crossedLeft || (!startCellBottomLeft && !startCellTopLeft)) {
deltaX = x - scrollPaneLeft;
}
} else if (x > scrollPaneRight) {
deltaX = x - scrollPaneRight;
} else {
deltaX = 0;
}
// If we're crossing the top freeze pane border to the scroll area, the
// bottom part must be scrolled all the way up.
boolean scrolled = false;
if (sheetWidget.sheet.getScrollTop() != 0) {
boolean mouseOnTopSide = y < scrollPaneTop;
if (!crossedDown && (startCellTopLeft || startCellTopRight)
&& sheetWidget.isCellRenderedInFrozenPane(tempCol, tempRow)
&& !mouseOnTopSide) {
sheetWidget.sheet.setScrollTop(0);
sheetWidget.onSheetScroll(null);
initialScrollTop = 0;
crossedDown = true;
scrolled = true;
}
}
// If we're crossing the left freeze pane border, the right-hand part
// must be scrolled all the way to the left.
if (sheetWidget.sheet.getScrollLeft() != 0) {
boolean mouseOnLeftSide = x < scrollPaneLeft;
if (!crossedLeft && (startCellTopLeft || startCellBottomLeft)
&& sheetWidget.isCellRenderedInFrozenPane(tempCol, tempRow)
&& !mouseOnLeftSide) {
sheetWidget.sheet.setScrollLeft(0);
sheetWidget.onSheetScroll(null);
initialScrollLeft = 0;
crossedLeft = true;
scrolled = true;
}
}
if ((deltaY < 0 && sheetWidget.sheet.getScrollTop() != 0) || deltaY > 0
|| (deltaX < 0 && sheetWidget.sheet.getScrollLeft() != 0)
|| deltaX > 0) {
startScrollTimer();
scrolled = true;
}
// If the sheet was scrolled due to crossing freeze pane borders during
// drag selection, the actual selection event will be handled on the
// next mouse move event.
if (scrolled) {
return true;
} else {
stopScrollTimer();
return false;
}
}
private void startScrollTimer() {
if (!scrollTimerRunning) {
scrollTimerRunning = true;
scrollTimer.scheduleRepeating(50);
}
}
private void stopScrollTimer() {
deltaX = 0;
deltaY = 0;
scrollTimer.cancel();
scrollTimerRunning = false;
}
private void handleCellShiftOnScroll(int selectionPointX,
int selectionPointY) {
Element target = WidgetUtil.getElementFromPoint(selectionPointX,
selectionPointY);
if (target != null) {
final String className = target.getAttribute("class");
sheetWidget.jsniUtil.parseColRow(className);
int col = sheetWidget.jsniUtil.getParsedCol();
int row = sheetWidget.jsniUtil.getParsedRow();
if (col != 0 && row != 0) {
updatePaintRectangle(clientX, clientY, col, row);
}
}
}
private Timer scrollTimer = new Timer() {
@Override
public void run() {
// Handle scrolling
sheetWidget.sheet.setScrollTop(sheetWidget.sheet.getScrollTop()
+ deltaY / 2);
sheetWidget.sheet.setScrollLeft(sheetWidget.sheet.getScrollLeft()
+ deltaX / 2);
sheetWidget.onSheetScroll(null);
// Determine selection point
int selectionPointX = clientX;
int selectionPointY = clientY;
if (deltaX < 0) {
selectionPointX = sheetWidget.sheet.getAbsoluteLeft()
+ sheetWidget.TOP_LEFT_SELECTION_OFFSET;
} else if (deltaX > 0) {
selectionPointX = sheetWidget.sheet.getAbsoluteRight()
- sheetWidget.BOTTOM_RIGHT_SELECTION_OFFSET;
}
if (deltaY < 0) {
selectionPointY = sheetWidget.sheet.getAbsoluteTop()
+ sheetWidget.TOP_LEFT_SELECTION_OFFSET;
} else if (deltaY > 0) {
selectionPointY = sheetWidget.sheet.getAbsoluteBottom()
- sheetWidget.BOTTOM_RIGHT_SELECTION_OFFSET;
}
// Adjust selection point if we have reached scroll top
if (deltaY != 0 && sheetWidget.sheet.getScrollTop() == 0) {
MeasuredSize ms = new MeasuredSize();
ms.measure(sheetWidget.spreadsheet);
int minimumTop = sheetWidget.spreadsheet.getAbsoluteTop()
+ ms.getPaddingTop()
+ sheetWidget.TOP_LEFT_SELECTION_OFFSET;
if (clientY > minimumTop) {
selectionPointY = clientY;
} else {
selectionPointY = minimumTop;
}
}
// Adjust selection point if we have reached scroll left
if (deltaX != 0 && sheetWidget.sheet.getScrollLeft() == 0) {
MeasuredSize ms = new MeasuredSize();
ms.measure(sheetWidget.spreadsheet);
int minimumLeft = sheetWidget.spreadsheet.getAbsoluteLeft()
+ ms.getPaddingLeft()
+ sheetWidget.TOP_LEFT_SELECTION_OFFSET;
if (clientX > minimumLeft) {
selectionPointX = clientX;
} else {
selectionPointX = minimumLeft;
}
}
// Handle painting or selection
if (paintMode) {
handleCellShiftOnScroll(selectionPointX, selectionPointY);
} else {
Element target = WidgetUtil.getElementFromPoint(
selectionPointX, selectionPointY);
if (target != null) {
final String className = target.getAttribute("class");
sheetWidget.jsniUtil.parseColRow(className);
int col = sheetWidget.jsniUtil.getParsedCol();
int row = sheetWidget.jsniUtil.getParsedRow();
if (col != 0 && row != 0) {
sheetWidget.getSheetHandler().onSelectingCellsWithDrag(
col, row);
}
}
}
}
};
private int initialScrollLeft;
private int initialScrollTop;
private void paintCells(Event event) {
decreaseSelection = false;
increaseSelection = false;
final int clientX = SpreadsheetWidget.getTouchOrMouseClientX(event);
final int clientY = SpreadsheetWidget.getTouchOrMouseClientY(event);
// If we're scrolling, do not paint anything
if (checkScrollWhilePainting(clientY, clientX)) {
return;
}
// position in perspective to the top left
int xMousePos = clientX - origX + sheetWidget.sheet.getScrollLeft()
- initialScrollLeft;
int yMousePos = clientY - origY + sheetWidget.sheet.getScrollTop()
- initialScrollTop;
final int[] colWidths = handler.getColWidths();
final int[] rowHeightsPX = handler.getRowHeightsPX();
int col = closestCellEdgeIndexToCursor(colWidths, col1, xMousePos,
false);
int row = closestCellEdgeIndexToCursor(rowHeightsPX, row1, yMousePos,
false);
if (col >= 0 && row >= 0) {
updatePaintRectangle(clientX, clientY, col, row);
}
}
private void updatePaintRectangle(final int clientX, final int clientY,
int colIndex, int rowIndex) {
// TODO This might need to handle merged cells
// See http://dev.vaadin.com/ticket/17134
if ((colIndex >= col1 && colIndex <= col2)
&& (rowIndex >= row1 && rowIndex <= row2)) {
// case 1: shifting inside the selection
int vDiff = Math.abs(row2 - rowIndex);
int hDiff = Math.abs(col2 - colIndex);
// Shifting inside the selection is prohibited in touch mode!
if (touchMode || (vDiff == 0 && hDiff == 0)) {
setPaintPosition(0, 0, 0, 0);
setPaintVisible(false);
return;
}
setPaintVisible(true);
decreaseSelection = true;
if (vDiff > hDiff) {
int pos = Math.max(row1 + 1, row2 - vDiff + 1);
setPaintPosition(col1, col2, pos, row2);
} else {
int pos = Math.max(col1 + 1, col2 - hDiff + 1);
setPaintPosition(pos, col2, row1, row2);
}
} else if ((rowIndex < row1 || rowIndex > row2)
|| (colIndex < col1 || colIndex > col2)) {
// case 2: shifting outside the selection
setPaintVisible(true);
increaseSelection = true;
int diffDown = rowIndex - row2;
int diffUp = row1 - rowIndex;
int diffLeft = col1 - colIndex;
int diffRight = colIndex - col2;
if (Math.max(diffDown, diffUp) > Math.max(diffLeft, diffRight)) {
// Shift up or down
if (diffDown > diffUp) {
setPaintPosition(col1, col2, row2 + 1, rowIndex);
} else {
setPaintPosition(col1, col2, rowIndex + 1, row1 - 1);
}
} else {
// Shift left or right
if (diffRight > diffLeft) {
setPaintPosition(col2 + 1, colIndex, row1, row2);
} else {
setPaintPosition(colIndex + 1, col1 - 1, row1, row2);
}
}
}
}
}