com.vaadin.addon.spreadsheet.client.SelectionWidget.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.addon.spreadsheet.client.SelectionWidget.java

Source

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);
                }
            }
        }
    }
}