<commit_before>/*
 * Copyright 2000-2014 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.client.ui.grid;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.KeyCodeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.touch.client.Point;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasVisibility;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.Util;
import com.vaadin.client.data.DataChangeHandler;
import com.vaadin.client.data.DataSource;
import com.vaadin.client.ui.SubPartAware;
import com.vaadin.client.ui.grid.GridFooter.FooterRow;
import com.vaadin.client.ui.grid.GridHeader.HeaderRow;
import com.vaadin.client.ui.grid.GridStaticSection.StaticCell;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler.GridKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler.GridKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler.GridKeyUpHandler;
import com.vaadin.client.ui.grid.keyevents.BodyKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.BodyKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.BodyKeyUpHandler;
import com.vaadin.client.ui.grid.keyevents.FooterKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.FooterKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.FooterKeyUpHandler;
import com.vaadin.client.ui.grid.keyevents.GridKeyDownEvent;
import com.vaadin.client.ui.grid.keyevents.GridKeyPressEvent;
import com.vaadin.client.ui.grid.keyevents.GridKeyUpEvent;
import com.vaadin.client.ui.grid.keyevents.HeaderKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.HeaderKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.HeaderKeyUpHandler;
import com.vaadin.client.ui.grid.renderers.ComplexRenderer;
import com.vaadin.client.ui.grid.renderers.WidgetRenderer;
import com.vaadin.client.ui.grid.selection.HasSelectionChangeHandlers;
import com.vaadin.client.ui.grid.selection.SelectionChangeEvent;
import com.vaadin.client.ui.grid.selection.SelectionChangeHandler;
import com.vaadin.client.ui.grid.selection.SelectionModel;
import com.vaadin.client.ui.grid.selection.SelectionModelMulti;
import com.vaadin.client.ui.grid.selection.SelectionModelNone;
import com.vaadin.client.ui.grid.selection.SelectionModelSingle;
import com.vaadin.client.ui.grid.sort.Sort;
import com.vaadin.client.ui.grid.sort.SortEvent;
import com.vaadin.client.ui.grid.sort.SortEventHandler;
import com.vaadin.client.ui.grid.sort.SortOrder;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.Range;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.ui.grid.SortDirection;
import com.vaadin.shared.ui.grid.SortEventOriginator;

/**
 * A data grid view that supports columns and lazy loading of data rows from a
 * data source.
 * 
 * <h3>Columns</h3>
 * <p>
 * The {@link GridColumn} class defines the renderer used to render a cell in
 * the grid. Implement {@link GridColumn#getValue(Object)} to retrieve the cell
 * value from the row object and return the cell renderer to render that cell.
 * </p>
 * <p>
 * {@link GridColumn}s contain other properties like the width of the column and
 * the visiblity of the column. If you want to change a column's properties
 * after it has been added to the grid you can get a column object for a
 * specific column index using {@link Grid#getColumn(int)}.
 * </p>
 * <p>
 * 
 * TODO Explain about headers/footers once the multiple header/footer api has
 * been implemented
 * 
 * <h3>Data sources</h3>
 * <p>
 * TODO Explain about what a data source is and how it should be implemented.
 * </p>
 * 
 * @param <T>
 *            The row type of the grid. The row type is the POJO type from where
 *            the data is retrieved into the column cells.
 * @since
 * @author <NAME>
 */
public class Grid<T> extends Composite implements
        HasSelectionChangeHandlers<T>, SubPartAware {

    public static abstract class AbstractGridKeyEvent<T, HANDLER extends AbstractGridKeyEventHandler>
            extends KeyCodeEvent<HANDLER> {

        /**
         * Enum describing different section of Grid.
         */
        public enum GridSection {
            HEADER, BODY, FOOTER
        }

        private Grid<T> grid;
        protected Cell activeCell;
        protected GridSection activeSection;
        private final Type<HANDLER> associatedType = new Type<HANDLER>(
                getBrowserEventType(), this);

        public AbstractGridKeyEvent(Grid<T> grid) {
            this.grid = grid;
        }

        protected abstract String getBrowserEventType();

        /**
         * Gets the Grid instance for this event.
         * 
         * @return grid
         */
        public Grid<T> getGrid() {
            return grid;
        }

        /**
         * Gets the active cell for this event.
         * 
         * @return active cell
         */
        public Cell getActiveCell() {
            return activeCell;
        }

        @Override
        protected void dispatch(HANDLER handler) {
            activeCell = grid.activeCellHandler.getActiveCell();
            activeSection = GridSection.FOOTER;
            final RowContainer container = grid.activeCellHandler.container;
            if (container == grid.escalator.getHeader()) {
                activeSection = GridSection.HEADER;
            } else if (container == grid.escalator.getBody()) {
                activeSection = GridSection.BODY;
            }
        }

        @Override
        public Type<HANDLER> getAssociatedType() {
            return associatedType;
        }
    }

    private GridKeyDownEvent<T> keyDown = new GridKeyDownEvent<T>(this);
    private GridKeyUpEvent<T> keyUp = new GridKeyUpEvent<T>(this);
    private GridKeyPressEvent<T> keyPress = new GridKeyPressEvent<T>(this);

    private class ActiveCellHandler {

        private RowContainer container = escalator.getBody();
        private int activeRow = 0;
        private int activeColumn = 0;
        private int lastActiveBodyRow = 0;
        private int lastActiveHeaderRow = 0;
        private int lastActiveFooterRow = 0;
        private TableCellElement cellWithActiveStyle = null;
        private TableRowElement rowWithActiveStyle = null;

        public ActiveCellHandler() {
            sinkEvents(getNavigationEvents());
        }

        private Cell getActiveCell() {
            return new Cell(activeRow, activeColumn, cellWithActiveStyle);
        }

        /**
         * Sets style names for given cell when needed.
         */
        public void updateActiveCellStyle(FlyweightCell cell,
                RowContainer cellContainer) {
            int cellRow = cell.getRow();
            int cellColumn = cell.getColumn();
            int colSpan = cell.getColSpan();
            boolean columnActive = Range.withLength(cellColumn, colSpan)
                    .contains(activeColumn);

            if (cellContainer == container) {
                // Cell is in the current container
                if (cellRow == activeRow && columnActive) {
                    if (cellWithActiveStyle != cell.getElement()) {
                        // Cell is correct but it does not have active style
                        if (cellWithActiveStyle != null) {
                            // Remove old active style
                            setStyleName(cellWithActiveStyle,
                                    cellActiveStyleName, false);
                        }
                        cellWithActiveStyle = cell.getElement();
                        // Add active style to correct cell.
                        setStyleName(cellWithActiveStyle, cellActiveStyleName,
                                true);
                    }
                } else if (cellWithActiveStyle == cell.getElement()) {
                    // Due to escalator reusing cells, a new cell has the same
                    // element but is not the active cell.
                    setStyleName(cellWithActiveStyle, cellActiveStyleName,
                            false);
                    cellWithActiveStyle = null;
                }
            }

            if (cellContainer == escalator.getHeader()
                    || cellContainer == escalator.getFooter()) {
                // Correct header and footer column also needs highlighting
                setStyleName(cell.getElement(), headerFooterActiveStyleName,
                        columnActive);
            }
        }

        /**
         * Sets active row style name for given row if needed.
         * 
         * @param row
         *            a row object
         */
        public void updateActiveRowStyle(Row row) {
            if (activeRow == row.getRow() && container == escalator.getBody()) {
                if (row.getElement() != rowWithActiveStyle) {
                    // Row should have active style but does not have it.
                    if (rowWithActiveStyle != null) {
                        setStyleName(rowWithActiveStyle, rowActiveStyleName,
                                false);
                    }
                    rowWithActiveStyle = row.getElement();
                    setStyleName(rowWithActiveStyle, rowActiveStyleName, true);
                }
            } else if (rowWithActiveStyle == row.getElement()
                    || (container != escalator.getBody() && rowWithActiveStyle != null)) {
                // Remove active style.
                setStyleName(rowWithActiveStyle, rowActiveStyleName, false);
                rowWithActiveStyle = null;
            }
        }

        /**
         * Sets currently active cell to a cell in given container with given
         * indices.
         * 
         * @param row
         *            new active row
         * @param column
         *            new active column
         * @param container
         *            new container
         */
        private void setActiveCell(int row, int column, RowContainer container) {
            if (row == activeRow && column == activeColumn
                    && container == this.container) {
                return;
            }

            int oldRow = activeRow;
            int oldColumn = activeColumn;
            activeRow = row;
            activeColumn = column;

            if (container == escalator.getBody()) {
                scrollToRow(activeRow);
            }
            escalator.scrollToColumn(activeColumn, ScrollDestination.ANY, 10);

            if (this.container == container) {
                if (container != escalator.getBody()) {
                    if (oldColumn == activeColumn && oldRow != activeRow) {
                        refreshRow(oldRow);
                    } else if (oldColumn != activeColumn) {
                        refreshHeader();
                        refreshFooter();
                    }
                } else {
                    if (oldRow != activeRow) {
                        refreshRow(oldRow);
                    }

                    if (oldColumn != activeColumn) {
                        refreshHeader();
                        refreshFooter();
                    }
                }
            } else {
                RowContainer oldContainer = this.container;
                this.container = container;

                if (oldContainer == escalator.getBody()) {
                    lastActiveBodyRow = oldRow;
                } else if (oldContainer == escalator.getHeader()) {
                    lastActiveHeaderRow = oldRow;
                } else {
                    lastActiveFooterRow = oldRow;
                }

                if (oldColumn != activeColumn) {
                    refreshHeader();
                    refreshFooter();
                    if (oldContainer == escalator.getBody()) {
                        oldContainer.refreshRows(oldRow, 1);
                    }
                } else {
                    oldContainer.refreshRows(oldRow, 1);
                }
            }
            refreshRow(activeRow);
        }

        /**
         * Sets currently active cell used for keyboard navigation. Note that
         * active cell is not JavaScript {@code document.activeElement}.
         * 
         * @param cell
         *            a cell object
         */
        public void setActiveCell(Cell cell) {
            setActiveCell(cell.getRow(), cell.getColumn(),
                    escalator.findRowContainer(cell.getElement()));
        }

        /**
         * Gets list of events that can be used for active cell navigation.
         * 
         * @return list of navigation related event types
         */
        public Collection<String> getNavigationEvents() {
            return Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.CLICK);
        }

        /**
         * Handle events that can change the currently active cell.
         */
        public void handleNavigationEvent(Event event, Cell cell) {
            if (event.getType().equals(BrowserEvents.CLICK) && cell != null) {
                setActiveCell(cell);
                getElement().focus();
            } else if (event.getType().equals(BrowserEvents.KEYDOWN)) {
                int newRow = activeRow;
                int newColumn = activeColumn;
                RowContainer newContainer = container;

                switch (event.getKeyCode()) {
                case KeyCodes.KEY_DOWN:
                    newRow += 1;
                    break;
                case KeyCodes.KEY_UP:
                    newRow -= 1;
                    break;
                case KeyCodes.KEY_RIGHT:
                    newColumn += 1;
                    break;
                case KeyCodes.KEY_LEFT:
                    newColumn -= 1;
                    break;
                case KeyCodes.KEY_TAB:
                    if (event.getShiftKey()) {
                        newContainer = getPreviousContainer(container);
                    } else {
                        newContainer = getNextContainer(container);
                    }

                    if (newContainer == container) {
                        return;
                    }
                    break;
                default:
                    return;
                }

                if (newContainer != container) {
                    if (newContainer == escalator.getBody()) {
                        newRow = lastActiveBodyRow;
                    } else if (newContainer == escalator.getHeader()) {
                        newRow = lastActiveHeaderRow;
                    } else {
                        newRow = lastActiveFooterRow;
                    }
                } else if (newRow < 0) {
                    newContainer = getPreviousContainer(newContainer);

                    if (newContainer == container) {
                        newRow = 0;
                    } else if (newContainer == escalator.getBody()) {
                        newRow = getLastVisibleRowIndex();
                    } else {
                        newRow = newContainer.getRowCount() - 1;
                    }
                } else if (newRow >= container.getRowCount()) {
                    newContainer = getNextContainer(newContainer);

                    if (newContainer == container) {
                        newRow = container.getRowCount() - 1;
                    } else if (newContainer == escalator.getBody()) {
                        newRow = getFirstVisibleRowIndex();
                    } else {
                        newRow = 0;
                    }
                }

                if (newContainer.getRowCount() == 0) {
                    // There are no rows in the container. Can't change the
                    // active cell.
                    return;
                }

                if (newColumn < 0) {
                    newColumn = 0;
                } else if (newColumn >= getColumnCount()) {
                    newColumn = getColumnCount() - 1;
                }

                event.preventDefault();
                event.stopPropagation();

                setActiveCell(newRow, newColumn, newContainer);
            }

        }

        private RowContainer getPreviousContainer(RowContainer current) {
            if (current == escalator.getFooter()) {
                current = escalator.getBody();
            } else if (current == escalator.getBody()) {
                current = escalator.getHeader();
            } else {
                return current;
            }

            if (current.getRowCount() == 0) {
                return getPreviousContainer(current);
            }
            return current;
        }

        private RowContainer getNextContainer(RowContainer current) {
            if (current == escalator.getHeader()) {
                current = escalator.getBody();
            } else if (current == escalator.getBody()) {
                current = escalator.getFooter();
            } else {
                return current;
            }

            if (current.getRowCount() == 0) {
                return getNextContainer(current);
            }
            return current;
        }

        private void refreshRow(int row) {
            container.refreshRows(row, 1);
        }
    }

    private class SelectionColumn extends GridColumn<Boolean, T> {
        private boolean initDone = false;

        public SelectionColumn(final Renderer<Boolean> selectColumnRenderer) {
            super(selectColumnRenderer);
        }

        public void initDone() {
            initDone = true;
        }

        @Override
        public void setVisible(boolean visible) {
            if (!visible && initDone) {
                throw new UnsupportedOperationException("The selection "
                        + "column cannot be modified after init");
            } else {
                super.setVisible(visible);
            }
        }

        @Override
        public void setWidth(int pixels) {
            if (pixels != getWidth() && initDone) {
                throw new UnsupportedOperationException("The selection "
                        + "column cannot be modified after init");
            } else {
                super.setWidth(pixels);
            }
        }

        @Override
        public Boolean getValue(T row) {
            return Boolean.valueOf(isSelected(row));
        }
    }

    /**
     * Class for sorting at a later time
     */
    private class LazySorter extends Timer {

        private Cell cell;

        private boolean multisort;

        @Override
        public void run() {
            SortOrder sortingOrder = getSortOrder(getColumnFromVisibleIndex(cell
                    .getColumn()));
            if (sortingOrder == null) {
                /*
                 * No previous sorting, sort Ascending
                 */
                sort(cell, SortDirection.ASCENDING, multisort);

            } else {
                // Toggle sorting
                SortDirection direction = sortingOrder.getDirection();
                if (direction == SortDirection.ASCENDING) {
                    sort(cell, SortDirection.DESCENDING, multisort);
                } else {
                    sort(cell, SortDirection.ASCENDING, multisort);
                }
            }
        }

        /**
         * Set the cell reference to the primary cell that sorting should be
         * done for.
         * 
         * @param cell
         * 
         */
        public void setCellReference(Cell cell) {
            this.cell = cell;
        }

        /**
         * Is multiple column sorting is enabled/disabled
         * 
         * @param multisort
         *            true if multiple column sorting is enabled
         */
        public void setMultisort(boolean multisort) {
            this.multisort = multisort;
        }

        /**
         * Sorts the column in a direction
         */
        private void sort(Cell cell, SortDirection direction, boolean multisort) {
            TableCellElement th = TableCellElement.as(cell.getElement());

            // Apply primary sorting on clicked column
            GridColumn<?, ?> columnInstance = getColumnFromVisibleIndex(cell
                    .getColumn());
            Sort sorting = Sort.by(columnInstance, direction);

            // Re-apply old sorting to the sort order
            if (multisort) {
                for (SortOrder order : getSortOrder()) {
                    if (order.getColumn() != columnInstance) {
                        sorting = sorting.then(order.getColumn(),
                                order.getDirection());
                    }
                }
            }

            // Perform sorting; indicate originator as user
            Grid.this.setSortOrder(sorting.build(), SortEventOriginator.USER);
        }
    }

    /**
     * Escalator used internally by grid to render the rows
     */
    private Escalator escalator = GWT.create(Escalator.class);

    private final GridHeader header = GWT.create(GridHeader.class);

    private final GridFooter footer = GWT.create(GridFooter.class);

    /**
     * List of columns in the grid. Order defines the visible order.
     */
    private final List<GridColumn<?, T>> columns = new ArrayList<GridColumn<?, T>>();

    /**
     * The datasource currently in use. <em>Note:</em> it is <code>null</code>
     * on initialization, but not after that.
     */
    private DataSource<T> dataSource;

    /**
     * Currently available row range in DataSource.
     */
    private Range currentDataAvailable = Range.withLength(0, 0);

    /**
     * The last column frozen counter from the left
     */
    private GridColumn<?, T> lastFrozenColumn;

    /**
     * Current sort order. The (private) sort() method reads this list to
     * determine the order in which to present rows.
     */
    private List<SortOrder> sortOrder = new ArrayList<SortOrder>();

    private Renderer<Boolean> selectColumnRenderer = null;

    private SelectionColumn selectionColumn;

    private String rowHasDataStyleName;
    private String rowSelectedStyleName;
    private String cellActiveStyleName;
    private String rowActiveStyleName;
    private String headerFooterActiveStyleName;

    /**
     * Current selection model.
     */
    private SelectionModel<T> selectionModel;

    protected final ActiveCellHandler activeCellHandler;

    private final LazySorter lazySorter = new LazySorter();

    /**
     * Enumeration for easy setting of selection mode.
     */
    public enum SelectionMode {

        /**
         * Shortcut for {@link SelectionModelSingle}.
         */
        SINGLE {

            @Override
            protected <T> SelectionModel<T> createModel() {
                return new SelectionModelSingle<T>();
            }
        },

        /**
         * Shortcut for {@link SelectionModelMulti}.
         */
        MULTI {

            @Override
            protected <T> SelectionModel<T> createModel() {
                return new SelectionModelMulti<T>();
            }
        },

        /**
         * Shortcut for {@link SelectionModelNone}.
         */
        NONE {

            @Override
            protected <T> SelectionModel<T> createModel() {
                return new SelectionModelNone<T>();
            }
        };

        protected abstract <T> SelectionModel<T> createModel();
    }

    /**
     * Base class for grid columns internally used by the Grid. The user should
     * use {@link GridColumn} when creating new columns.
     * 
     * @param <C>
     *            the column type
     * 
     * @param <T>
     *            the row type
     */
    static abstract class AbstractGridColumn<C, T> implements HasVisibility {

        /**
         * the column is associated with
         */
        private Grid<T> grid;

        /**
         * Should the column be visible in the grid
         */
        private boolean visible = true;

        /**
         * The text displayed in the header of the column
         */
        @Deprecated
        private String header;

        /**
         * Text displayed in the column footer
         */
        @Deprecated
        private String footer;

        /**
         * Width of column in pixels
         */
        private int width = 100;

        /**
         * Renderer for rendering a value into the cell
         */
        private Renderer<? super C> bodyRenderer;

        private boolean sortable = false;

        /**
         * Constructs a new column with a custom renderer.
         * 
         * @param renderer
         *            The renderer to use for rendering the cells
         */
        public AbstractGridColumn(Renderer<? super C> renderer) {
            if (renderer == null) {
                throw new IllegalArgumentException("Renderer cannot be null.");
            }
            bodyRenderer = renderer;
        }

        /**
         * Internally used by the grid to set itself
         * 
         * @param grid
         */<commit_msg>Removed unused deprecated fields #13334<commit_after>/*
 * Copyright 2000-2014 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.client.ui.grid;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.KeyCodeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.touch.client.Point;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasVisibility;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.Util;
import com.vaadin.client.data.DataChangeHandler;
import com.vaadin.client.data.DataSource;
import com.vaadin.client.ui.SubPartAware;
import com.vaadin.client.ui.grid.GridFooter.FooterRow;
import com.vaadin.client.ui.grid.GridHeader.HeaderRow;
import com.vaadin.client.ui.grid.GridStaticSection.StaticCell;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler.GridKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler.GridKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.AbstractGridKeyEventHandler.GridKeyUpHandler;
import com.vaadin.client.ui.grid.keyevents.BodyKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.BodyKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.BodyKeyUpHandler;
import com.vaadin.client.ui.grid.keyevents.FooterKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.FooterKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.FooterKeyUpHandler;
import com.vaadin.client.ui.grid.keyevents.GridKeyDownEvent;
import com.vaadin.client.ui.grid.keyevents.GridKeyPressEvent;
import com.vaadin.client.ui.grid.keyevents.GridKeyUpEvent;
import com.vaadin.client.ui.grid.keyevents.HeaderKeyDownHandler;
import com.vaadin.client.ui.grid.keyevents.HeaderKeyPressHandler;
import com.vaadin.client.ui.grid.keyevents.HeaderKeyUpHandler;
import com.vaadin.client.ui.grid.renderers.ComplexRenderer;
import com.vaadin.client.ui.grid.renderers.WidgetRenderer;
import com.vaadin.client.ui.grid.selection.HasSelectionChangeHandlers;
import com.vaadin.client.ui.grid.selection.SelectionChangeEvent;
import com.vaadin.client.ui.grid.selection.SelectionChangeHandler;
import com.vaadin.client.ui.grid.selection.SelectionModel;
import com.vaadin.client.ui.grid.selection.SelectionModelMulti;
import com.vaadin.client.ui.grid.selection.SelectionModelNone;
import com.vaadin.client.ui.grid.selection.SelectionModelSingle;
import com.vaadin.client.ui.grid.sort.Sort;
import com.vaadin.client.ui.grid.sort.SortEvent;
import com.vaadin.client.ui.grid.sort.SortEventHandler;
import com.vaadin.client.ui.grid.sort.SortOrder;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.Range;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.ui.grid.SortDirection;
import com.vaadin.shared.ui.grid.SortEventOriginator;

/**
 * A data grid view that supports columns and lazy loading of data rows from a
 * data source.
 * 
 * <h3>Columns</h3>
 * <p>
 * The {@link GridColumn} class defines the renderer used to render a cell in
 * the grid. Implement {@link GridColumn#getValue(Object)} to retrieve the cell
 * value from the row object and return the cell renderer to render that cell.
 * </p>
 * <p>
 * {@link GridColumn}s contain other properties like the width of the column and
 * the visiblity of the column. If you want to change a column's properties
 * after it has been added to the grid you can get a column object for a
 * specific column index using {@link Grid#getColumn(int)}.
 * </p>
 * <p>
 * 
 * TODO Explain about headers/footers once the multiple header/footer api has
 * been implemented
 * 
 * <h3>Data sources</h3>
 * <p>
 * TODO Explain about what a data source is and how it should be implemented.
 * </p>
 * 
 * @param <T>
 *            The row type of the grid. The row type is the POJO type from where
 *            the data is retrieved into the column cells.
 * @since
 * @author <NAME>
 */
public class Grid<T> extends Composite implements
        HasSelectionChangeHandlers<T>, SubPartAware {

    public static abstract class AbstractGridKeyEvent<T, HANDLER extends AbstractGridKeyEventHandler>
            extends KeyCodeEvent<HANDLER> {

        /**
         * Enum describing different section of Grid.
         */
        public enum GridSection {
            HEADER, BODY, FOOTER
        }

        private Grid<T> grid;
        protected Cell activeCell;
        protected GridSection activeSection;
        private final Type<HANDLER> associatedType = new Type<HANDLER>(
                getBrowserEventType(), this);

        public AbstractGridKeyEvent(Grid<T> grid) {
            this.grid = grid;
        }

        protected abstract String getBrowserEventType();

        /**
         * Gets the Grid instance for this event.
         * 
         * @return grid
         */
        public Grid<T> getGrid() {
            return grid;
        }

        /**
         * Gets the active cell for this event.
         * 
         * @return active cell
         */
        public Cell getActiveCell() {
            return activeCell;
        }

        @Override
        protected void dispatch(HANDLER handler) {
            activeCell = grid.activeCellHandler.getActiveCell();
            activeSection = GridSection.FOOTER;
            final RowContainer container = grid.activeCellHandler.container;
            if (container == grid.escalator.getHeader()) {
                activeSection = GridSection.HEADER;
            } else if (container == grid.escalator.getBody()) {
                activeSection = GridSection.BODY;
            }
        }

        @Override
        public Type<HANDLER> getAssociatedType() {
            return associatedType;
        }
    }

    private GridKeyDownEvent<T> keyDown = new GridKeyDownEvent<T>(this);
    private GridKeyUpEvent<T> keyUp = new GridKeyUpEvent<T>(this);
    private GridKeyPressEvent<T> keyPress = new GridKeyPressEvent<T>(this);

    private class ActiveCellHandler {

        private RowContainer container = escalator.getBody();
        private int activeRow = 0;
        private int activeColumn = 0;
        private int lastActiveBodyRow = 0;
        private int lastActiveHeaderRow = 0;
        private int lastActiveFooterRow = 0;
        private TableCellElement cellWithActiveStyle = null;
        private TableRowElement rowWithActiveStyle = null;

        public ActiveCellHandler() {
            sinkEvents(getNavigationEvents());
        }

        private Cell getActiveCell() {
            return new Cell(activeRow, activeColumn, cellWithActiveStyle);
        }

        /**
         * Sets style names for given cell when needed.
         */
        public void updateActiveCellStyle(FlyweightCell cell,
                RowContainer cellContainer) {
            int cellRow = cell.getRow();
            int cellColumn = cell.getColumn();
            int colSpan = cell.getColSpan();
            boolean columnActive = Range.withLength(cellColumn, colSpan)
                    .contains(activeColumn);

            if (cellContainer == container) {
                // Cell is in the current container
                if (cellRow == activeRow && columnActive) {
                    if (cellWithActiveStyle != cell.getElement()) {
                        // Cell is correct but it does not have active style
                        if (cellWithActiveStyle != null) {
                            // Remove old active style
                            setStyleName(cellWithActiveStyle,
                                    cellActiveStyleName, false);
                        }
                        cellWithActiveStyle = cell.getElement();
                        // Add active style to correct cell.
                        setStyleName(cellWithActiveStyle, cellActiveStyleName,
                                true);
                    }
                } else if (cellWithActiveStyle == cell.getElement()) {
                    // Due to escalator reusing cells, a new cell has the same
                    // element but is not the active cell.
                    setStyleName(cellWithActiveStyle, cellActiveStyleName,
                            false);
                    cellWithActiveStyle = null;
                }
            }

            if (cellContainer == escalator.getHeader()
                    || cellContainer == escalator.getFooter()) {
                // Correct header and footer column also needs highlighting
                setStyleName(cell.getElement(), headerFooterActiveStyleName,
                        columnActive);
            }
        }

        /**
         * Sets active row style name for given row if needed.
         * 
         * @param row
         *            a row object
         */
        public void updateActiveRowStyle(Row row) {
            if (activeRow == row.getRow() && container == escalator.getBody()) {
                if (row.getElement() != rowWithActiveStyle) {
                    // Row should have active style but does not have it.
                    if (rowWithActiveStyle != null) {
                        setStyleName(rowWithActiveStyle, rowActiveStyleName,
                                false);
                    }
                    rowWithActiveStyle = row.getElement();
                    setStyleName(rowWithActiveStyle, rowActiveStyleName, true);
                }
            } else if (rowWithActiveStyle == row.getElement()
                    || (container != escalator.getBody() && rowWithActiveStyle != null)) {
                // Remove active style.
                setStyleName(rowWithActiveStyle, rowActiveStyleName, false);
                rowWithActiveStyle = null;
            }
        }

        /**
         * Sets currently active cell to a cell in given container with given
         * indices.
         * 
         * @param row
         *            new active row
         * @param column
         *            new active column
         * @param container
         *            new container
         */
        private void setActiveCell(int row, int column, RowContainer container) {
            if (row == activeRow && column == activeColumn
                    && container == this.container) {
                return;
            }

            int oldRow = activeRow;
            int oldColumn = activeColumn;
            activeRow = row;
            activeColumn = column;

            if (container == escalator.getBody()) {
                scrollToRow(activeRow);
            }
            escalator.scrollToColumn(activeColumn, ScrollDestination.ANY, 10);

            if (this.container == container) {
                if (container != escalator.getBody()) {
                    if (oldColumn == activeColumn && oldRow != activeRow) {
                        refreshRow(oldRow);
                    } else if (oldColumn != activeColumn) {
                        refreshHeader();
                        refreshFooter();
                    }
                } else {
                    if (oldRow != activeRow) {
                        refreshRow(oldRow);
                    }

                    if (oldColumn != activeColumn) {
                        refreshHeader();
                        refreshFooter();
                    }
                }
            } else {
                RowContainer oldContainer = this.container;
                this.container = container;

                if (oldContainer == escalator.getBody()) {
                    lastActiveBodyRow = oldRow;
                } else if (oldContainer == escalator.getHeader()) {
                    lastActiveHeaderRow = oldRow;
                } else {
                    lastActiveFooterRow = oldRow;
                }

                if (oldColumn != activeColumn) {
                    refreshHeader();
                    refreshFooter();
                    if (oldContainer == escalator.getBody()) {
                        oldContainer.refreshRows(oldRow, 1);
                    }
                } else {
                    oldContainer.refreshRows(oldRow, 1);
                }
            }
            refreshRow(activeRow);
        }

        /**
         * Sets currently active cell used for keyboard navigation. Note that
         * active cell is not JavaScript {@code document.activeElement}.
         * 
         * @param cell
         *            a cell object
         */
        public void setActiveCell(Cell cell) {
            setActiveCell(cell.getRow(), cell.getColumn(),
                    escalator.findRowContainer(cell.getElement()));
        }

        /**
         * Gets list of events that can be used for active cell navigation.
         * 
         * @return list of navigation related event types
         */
        public Collection<String> getNavigationEvents() {
            return Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.CLICK);
        }

        /**
         * Handle events that can change the currently active cell.
         */
        public void handleNavigationEvent(Event event, Cell cell) {
            if (event.getType().equals(BrowserEvents.CLICK) && cell != null) {
                setActiveCell(cell);
                getElement().focus();
            } else if (event.getType().equals(BrowserEvents.KEYDOWN)) {
                int newRow = activeRow;
                int newColumn = activeColumn;
                RowContainer newContainer = container;

                switch (event.getKeyCode()) {
                case KeyCodes.KEY_DOWN:
                    newRow += 1;
                    break;
                case KeyCodes.KEY_UP:
                    newRow -= 1;
                    break;
                case KeyCodes.KEY_RIGHT:
                    newColumn += 1;
                    break;
                case KeyCodes.KEY_LEFT:
                    newColumn -= 1;
                    break;
                case KeyCodes.KEY_TAB:
                    if (event.getShiftKey()) {
                        newContainer = getPreviousContainer(container);
                    } else {
                        newContainer = getNextContainer(container);
                    }

                    if (newContainer == container) {
                        return;
                    }
                    break;
                default:
                    return;
                }

                if (newContainer != container) {
                    if (newContainer == escalator.getBody()) {
                        newRow = lastActiveBodyRow;
                    } else if (newContainer == escalator.getHeader()) {
                        newRow = lastActiveHeaderRow;
                    } else {
                        newRow = lastActiveFooterRow;
                    }
                } else if (newRow < 0) {
                    newContainer = getPreviousContainer(newContainer);

                    if (newContainer == container) {
                        newRow = 0;
                    } else if (newContainer == escalator.getBody()) {
                        newRow = getLastVisibleRowIndex();
                    } else {
                        newRow = newContainer.getRowCount() - 1;
                    }
                } else if (newRow >= container.getRowCount()) {
                    newContainer = getNextContainer(newContainer);

                    if (newContainer == container) {
                        newRow = container.getRowCount() - 1;
                    } else if (newContainer == escalator.getBody()) {
                        newRow = getFirstVisibleRowIndex();
                    } else {
                        newRow = 0;
                    }
                }

                if (newContainer.getRowCount() == 0) {
                    // There are no rows in the container. Can't change the
                    // active cell.
                    return;
                }

                if (newColumn < 0) {
                    newColumn = 0;
                } else if (newColumn >= getColumnCount()) {
                    newColumn = getColumnCount() - 1;
                }

                event.preventDefault();
                event.stopPropagation();

                setActiveCell(newRow, newColumn, newContainer);
            }

        }

        private RowContainer getPreviousContainer(RowContainer current) {
            if (current == escalator.getFooter()) {
                current = escalator.getBody();
            } else if (current == escalator.getBody()) {
                current = escalator.getHeader();
            } else {
                return current;
            }

            if (current.getRowCount() == 0) {
                return getPreviousContainer(current);
            }
            return current;
        }

        private RowContainer getNextContainer(RowContainer current) {
            if (current == escalator.getHeader()) {
                current = escalator.getBody();
            } else if (current == escalator.getBody()) {
                current = escalator.getFooter();
            } else {
                return current;
            }

            if (current.getRowCount() == 0) {
                return getNextContainer(current);
            }
            return current;
        }

        private void refreshRow(int row) {
            container.refreshRows(row, 1);
        }
    }

    private class SelectionColumn extends GridColumn<Boolean, T> {
        private boolean initDone = false;

        public SelectionColumn(final Renderer<Boolean> selectColumnRenderer) {
            super(selectColumnRenderer);
        }

        public void initDone() {
            initDone = true;
        }

        @Override
        public void setVisible(boolean visible) {
            if (!visible && initDone) {
                throw new UnsupportedOperationException("The selection "
                        + "column cannot be modified after init");
            } else {
                super.setVisible(visible);
            }
        }

        @Override
        public void setWidth(int pixels) {
            if (pixels != getWidth() && initDone) {
                throw new UnsupportedOperationException("The selection "
                        + "column cannot be modified after init");
            } else {
                super.setWidth(pixels);
            }
        }

        @Override
        public Boolean getValue(T row) {
            return Boolean.valueOf(isSelected(row));
        }
    }

    /**
     * Class for sorting at a later time
     */
    private class LazySorter extends Timer {

        private Cell cell;

        private boolean multisort;

        @Override
        public void run() {
            SortOrder sortingOrder = getSortOrder(getColumnFromVisibleIndex(cell
                    .getColumn()));
            if (sortingOrder == null) {
                /*
                 * No previous sorting, sort Ascending
                 */
                sort(cell, SortDirection.ASCENDING, multisort);

            } else {
                // Toggle sorting
                SortDirection direction = sortingOrder.getDirection();
                if (direction == SortDirection.ASCENDING) {
                    sort(cell, SortDirection.DESCENDING, multisort);
                } else {
                    sort(cell, SortDirection.ASCENDING, multisort);
                }
            }
        }

        /**
         * Set the cell reference to the primary cell that sorting should be
         * done for.
         * 
         * @param cell
         * 
         */
        public void setCellReference(Cell cell) {
            this.cell = cell;
        }

        /**
         * Is multiple column sorting is enabled/disabled
         * 
         * @param multisort
         *            true if multiple column sorting is enabled
         */
        public void setMultisort(boolean multisort) {
            this.multisort = multisort;
        }

        /**
         * Sorts the column in a direction
         */
        private void sort(Cell cell, SortDirection direction, boolean multisort) {
            TableCellElement th = TableCellElement.as(cell.getElement());

            // Apply primary sorting on clicked column
            GridColumn<?, ?> columnInstance = getColumnFromVisibleIndex(cell
                    .getColumn());
            Sort sorting = Sort.by(columnInstance, direction);

            // Re-apply old sorting to the sort order
            if (multisort) {
                for (SortOrder order : getSortOrder()) {
                    if (order.getColumn() != columnInstance) {
                        sorting = sorting.then(order.getColumn(),
                                order.getDirection());
                    }
                }
            }

            // Perform sorting; indicate originator as user
            Grid.this.setSortOrder(sorting.build(), SortEventOriginator.USER);
        }
    }

    /**
     * Escalator used internally by grid to render the rows
     */
    private Escalator escalator = GWT.create(Escalator.class);

    private final GridHeader header = GWT.create(GridHeader.class);

    private final GridFooter footer = GWT.create(GridFooter.class);

    /**
     * List of columns in the grid. Order defines the visible order.
     */
    private final List<GridColumn<?, T>> columns = new ArrayList<GridColumn<?, T>>();

    /**
     * The datasource currently in use. <em>Note:</em> it is <code>null</code>
     * on initialization, but not after that.
     */
    private DataSource<T> dataSource;

    /**
     * Currently available row range in DataSource.
     */
    private Range currentDataAvailable = Range.withLength(0, 0);

    /**
     * The last column frozen counter from the left
     */
    private GridColumn<?, T> lastFrozenColumn;

    /**
     * Current sort order. The (private) sort() method reads this list to
     * determine the order in which to present rows.
     */
    private List<SortOrder> sortOrder = new ArrayList<SortOrder>();

    private Renderer<Boolean> selectColumnRenderer = null;

    private SelectionColumn selectionColumn;

    private String rowHasDataStyleName;
    private String rowSelectedStyleName;
    private String cellActiveStyleName;
    private String rowActiveStyleName;
    private String headerFooterActiveStyleName;

    /**
     * Current selection model.
     */
    private SelectionModel<T> selectionModel;

    protected final ActiveCellHandler activeCellHandler;

    private final LazySorter lazySorter = new LazySorter();

    /**
     * Enumeration for easy setting of selection mode.
     */
    public enum SelectionMode {

        /**
         * Shortcut for {@link SelectionModelSingle}.
         */
        SINGLE {

            @Override
            protected <T> SelectionModel<T> createModel() {
                return new SelectionModelSingle<T>();
            }
        },

        /**
         * Shortcut for {@link SelectionModelMulti}.
         */
        MULTI {

            @Override
            protected <T> SelectionModel<T> createModel() {
                return new SelectionModelMulti<T>();
            }
        },

        /**
         * Shortcut for {@link SelectionModelNone}.
         */
        NONE {

            @Override
            protected <T> SelectionModel<T> createModel() {
                return new SelectionModelNone<T>();
            }
        };

        protected abstract <T> SelectionModel<T> createModel();
    }

    /**
     * Base class for grid columns internally used by the Grid. The user should
     * use {@link GridColumn} when creating new columns.
     * 
     * @param <C>
     *            the column type
     * 
     * @param <T>
     *            the row type
     */
    static abstract class AbstractGridColumn<C, T> implements HasVisibility {

        /**
         * the column is associated with
         */
        private Grid<T> grid;

        /**
         * Should the column be visible in the grid
         */
        private boolean visible = true;

        /**
         * Width of column in pixels
         */
        private int width = 100;

        /**
         * Renderer for rendering a value into the cell
         */
        private Renderer<? super C> bodyRenderer;

        private boolean sortable = false;

        /**
         * Constructs a new column with a custom renderer.
         * 
         * @param renderer
         *            The renderer to use for rendering the cells
         */
        public AbstractGridColumn(Renderer<? super C> renderer) {
            if (renderer == null) {
                throw new IllegalArgumentException("Renderer cannot be null.");
            }
            bodyRenderer = renderer;
        }

        /**
         * Internally used by the grid to set itself
         * 
         * @param grid
         */
{'fraction_non_alphanumeric': 0.08656537009559942, 'fraction_numerical': 0.0018218357342749281, 'mean_word_length': 1.6850226928895613, 'pattern_counts': {'":': 0, '<': 135, '<?xml version=': 0, '>': 135, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6807083', 'n_tokens_mistral': 13589, 'n_tokens_neox': 12812, 'n_words': 3421}