com.vaadin.client.widgets.Grid.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.client.widgets.Grid.java

Source

/*
 * Copyright 2000-2018 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.widgets;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

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.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyEvent;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
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.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasEnabled;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ResizeComposite;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComputedStyle;
import com.vaadin.client.DeferredWorker;
import com.vaadin.client.Focusable;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.WidgetUtil.Reference;
import com.vaadin.client.data.DataChangeHandler;
import com.vaadin.client.data.DataSource;
import com.vaadin.client.data.DataSource.RowHandle;
import com.vaadin.client.renderers.ComplexRenderer;
import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.renderers.WidgetRenderer;
import com.vaadin.client.ui.FocusUtil;
import com.vaadin.client.ui.SubPartAware;
import com.vaadin.client.ui.dd.DragAndDropHandler;
import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback;
import com.vaadin.client.ui.dd.DragHandle;
import com.vaadin.client.ui.dd.DragHandle.DragHandleCallback;
import com.vaadin.client.widget.escalator.Cell;
import com.vaadin.client.widget.escalator.ColumnConfiguration;
import com.vaadin.client.widget.escalator.EscalatorUpdater;
import com.vaadin.client.widget.escalator.FlyweightCell;
import com.vaadin.client.widget.escalator.Row;
import com.vaadin.client.widget.escalator.RowContainer;
import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler;
import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction;
import com.vaadin.client.widget.escalator.Spacer;
import com.vaadin.client.widget.escalator.SpacerUpdater;
import com.vaadin.client.widget.escalator.events.RowHeightChangedEvent;
import com.vaadin.client.widget.escalator.events.RowHeightChangedHandler;
import com.vaadin.client.widget.escalator.events.SpacerIndexChangedEvent;
import com.vaadin.client.widget.escalator.events.SpacerIndexChangedHandler;
import com.vaadin.client.widget.escalator.events.SpacerVisibilityChangedEvent;
import com.vaadin.client.widget.escalator.events.SpacerVisibilityChangedHandler;
import com.vaadin.client.widget.grid.AutoScroller;
import com.vaadin.client.widget.grid.AutoScroller.AutoScrollerCallback;
import com.vaadin.client.widget.grid.AutoScroller.ScrollAxis;
import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.CellStyleGenerator;
import com.vaadin.client.widget.grid.DataAvailableEvent;
import com.vaadin.client.widget.grid.DataAvailableHandler;
import com.vaadin.client.widget.grid.DefaultEditorEventHandler;
import com.vaadin.client.widget.grid.DetailsGenerator;
import com.vaadin.client.widget.grid.EditorHandler;
import com.vaadin.client.widget.grid.EditorHandler.EditorRequest;
import com.vaadin.client.widget.grid.EventCellReference;
import com.vaadin.client.widget.grid.GridEventHandler;
import com.vaadin.client.widget.grid.HeightAwareDetailsGenerator;
import com.vaadin.client.widget.grid.RendererCellReference;
import com.vaadin.client.widget.grid.RowReference;
import com.vaadin.client.widget.grid.RowStyleGenerator;
import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler;
import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler;
import com.vaadin.client.widget.grid.events.BodyClickHandler;
import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
import com.vaadin.client.widget.grid.events.BodyKeyDownHandler;
import com.vaadin.client.widget.grid.events.BodyKeyPressHandler;
import com.vaadin.client.widget.grid.events.BodyKeyUpHandler;
import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
import com.vaadin.client.widget.grid.events.ColumnResizeEvent;
import com.vaadin.client.widget.grid.events.ColumnResizeHandler;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
import com.vaadin.client.widget.grid.events.FooterClickHandler;
import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler;
import com.vaadin.client.widget.grid.events.FooterKeyDownHandler;
import com.vaadin.client.widget.grid.events.FooterKeyPressHandler;
import com.vaadin.client.widget.grid.events.FooterKeyUpHandler;
import com.vaadin.client.widget.grid.events.GridClickEvent;
import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
import com.vaadin.client.widget.grid.events.GridEnabledEvent;
import com.vaadin.client.widget.grid.events.GridEnabledHandler;
import com.vaadin.client.widget.grid.events.GridKeyDownEvent;
import com.vaadin.client.widget.grid.events.GridKeyPressEvent;
import com.vaadin.client.widget.grid.events.GridKeyUpEvent;
import com.vaadin.client.widget.grid.events.GridSelectionAllowedEvent;
import com.vaadin.client.widget.grid.events.GridSelectionAllowedHandler;
import com.vaadin.client.widget.grid.events.HeaderClickHandler;
import com.vaadin.client.widget.grid.events.HeaderDoubleClickHandler;
import com.vaadin.client.widget.grid.events.HeaderKeyDownHandler;
import com.vaadin.client.widget.grid.events.HeaderKeyPressHandler;
import com.vaadin.client.widget.grid.events.HeaderKeyUpHandler;
import com.vaadin.client.widget.grid.events.ScrollEvent;
import com.vaadin.client.widget.grid.events.ScrollHandler;
import com.vaadin.client.widget.grid.events.SelectAllEvent;
import com.vaadin.client.widget.grid.events.SelectAllHandler;
import com.vaadin.client.widget.grid.selection.HasSelectionHandlers;
import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer;
import com.vaadin.client.widget.grid.selection.SelectionEvent;
import com.vaadin.client.widget.grid.selection.SelectionHandler;
import com.vaadin.client.widget.grid.selection.SelectionModel;
import com.vaadin.client.widget.grid.selection.SelectionModelWithSelectionColumn;
import com.vaadin.client.widget.grid.sort.Sort;
import com.vaadin.client.widget.grid.sort.SortEvent;
import com.vaadin.client.widget.grid.sort.SortHandler;
import com.vaadin.client.widget.grid.sort.SortOrder;
import com.vaadin.client.widgets.Escalator.AbstractRowContainer;
import com.vaadin.client.widgets.Escalator.SubPartArguments;
import com.vaadin.client.widgets.Grid.Editor.State;
import com.vaadin.client.widgets.Grid.StaticSection.StaticCell;
import com.vaadin.client.widgets.Grid.StaticSection.StaticRow;
import com.vaadin.shared.Range;
import com.vaadin.shared.Registration;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.grid.ColumnResizeMode;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridConstants.Section;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.util.SharedUtil;

/**
 * A data grid view that supports columns and lazy loading of data rows from a
 * data source.
 *
 * <h1>Columns</h1>
 * <p>
 * Each column in Grid is represented by a {@link Column}. Each
 * {@code GridColumn} has a custom implementation for
 * {@link Column#getValue(Object)} that gets the row object as an argument, and
 * returns the value for that particular column, extracted from the row object.
 * <p>
 * Each column also has a Renderer. Its function is to take the value that is
 * given by the {@code GridColumn} and display it to the user. A simple column
 * might have a {@link com.vaadin.client.renderers.TextRenderer TextRenderer}
 * that simply takes in a {@code String} and displays it as the cell's content.
 * A more complex renderer might be
 * {@link com.vaadin.client.renderers.ProgressBarRenderer ProgressBarRenderer}
 * that takes in a floating point number, and displays a progress bar instead,
 * based on the given number.
 * <p>
 * <em>See:</em> {@link #addColumn(Column)}, {@link #addColumn(Column, int)} and
 * {@link #addColumns(Column...)}. <em>Also</em>
 * {@link Column#setRenderer(Renderer)}.
 *
 * <h1>Data Sources</h1>
 * <p>
 * Grid gets its data from a {@link DataSource}, providing row objects to Grid
 * from a user-defined endpoint. It can be either a local in-memory data source
 * (e.g. {@link com.vaadin.client.widget.grid.datasources.ListDataSource
 * ListDataSource}) or even a remote one, retrieving data from e.g. a REST API
 * (see {@link com.vaadin.client.data.AbstractRemoteDataSource
 * AbstractRemoteDataSource}).
 *
 *
 * @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 7.4
 * @author Vaadin Ltd
 */
public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, SubPartAware, DeferredWorker,
        Focusable, com.google.gwt.user.client.ui.Focusable, HasWidgets, HasEnabled {

    private static final String STYLE_NAME = "v-grid";

    private static final String SELECT_ALL_CHECKBOX_CLASSNAME = "-select-all-checkbox";

    /**
     * Abstract base class for Grid header and footer sections.
     *
     * @since 7.5.0
     *
     * @param <ROWTYPE>
     *            the type of the rows in the section
     */
    public abstract static class StaticSection<ROWTYPE extends StaticSection.StaticRow<?>> {

        /**
         * A header or footer cell. Has a simple textual caption.
         *
         */
        public static class StaticCell {

            private Object content = null;

            private int colspan = 1;

            private StaticSection<?> section;

            private GridStaticCellType type = GridStaticCellType.TEXT;

            private String styleName = null;

            private String description = null;

            private ContentMode descriptionContentMode = ContentMode.TEXT;

            /**
             * Sets the text displayed in this cell.
             *
             * @param text
             *            a plain text caption
             */
            public void setText(String text) {
                detach();
                this.content = text;
                this.type = GridStaticCellType.TEXT;
                section.requestSectionRefresh();
            }

            /**
             * Returns the text displayed in this cell.
             *
             * @return the plain text caption
             */
            public String getText() {
                if (type != GridStaticCellType.TEXT) {
                    throw new IllegalStateException("Cannot fetch Text from a cell with type " + type);
                }
                return (String) content;
            }

            protected StaticSection<?> getSection() {
                assert section != null;
                return section;
            }

            protected void setSection(StaticSection<?> section) {
                this.section = section;
            }

            /**
             * Returns the amount of columns the cell spans. By default is 1.
             *
             * @return The amount of columns the cell spans.
             */
            public int getColspan() {
                return colspan;
            }

            /**
             * Sets the amount of columns the cell spans. Must be more or equal
             * to 1. By default is 1.
             *
             * @param colspan
             *            the colspan to set
             */
            public void setColspan(int colspan) {
                if (colspan < 1) {
                    throw new IllegalArgumentException("Colspan cannot be less than 1");
                }

                this.colspan = colspan;
                section.requestSectionRefresh();
            }

            /**
             * Returns the html inside the cell.
             *
             * @throws IllegalStateException
             *             if trying to retrive HTML from a cell with a type
             *             other than {@link GridStaticCellType#HTML}.
             * @return the html content of the cell.
             */
            public String getHtml() {
                if (type != GridStaticCellType.HTML) {
                    throw new IllegalStateException("Cannot fetch HTML from a cell with type " + type);
                }
                return (String) content;
            }

            /**
             * Sets the content of the cell to the provided html. All previous
             * content is discarded and the cell type is set to
             * {@link GridStaticCellType#HTML}.
             *
             * @param html
             *            The html content of the cell
             */
            public void setHtml(String html) {
                detach();

                this.content = html;
                this.type = GridStaticCellType.HTML;
                section.requestSectionRefresh();
            }

            /**
             * Returns the widget in the cell.
             *
             * @throws IllegalStateException
             *             if the cell is not {@link GridStaticCellType#WIDGET}
             *
             * @return the widget in the cell
             */
            public Widget getWidget() {
                if (type != GridStaticCellType.WIDGET) {
                    throw new IllegalStateException("Cannot fetch Widget from a cell with type " + type);
                }
                return (Widget) content;
            }

            /**
             * Set widget as the content of the cell. The type of the cell
             * becomes {@link GridStaticCellType#WIDGET}. All previous content
             * is discarded.
             *
             * @param widget
             *            The widget to add to the cell. Should not be
             *            previously attached anywhere (widget.getParent ==
             *            null).
             */
            public void setWidget(Widget widget) {
                if (this.content == widget) {
                    return;
                }

                detach();
                this.content = widget;
                this.type = GridStaticCellType.WIDGET;
                section.requestSectionRefresh();
            }

            /**
             * Returns the type of the cell.
             *
             * @return the type of content the cell contains.
             */
            public GridStaticCellType getType() {
                return type;
            }

            /**
             * Returns the custom style name for this cell.
             *
             * @return the style name or null if no style name has been set
             */
            public String getStyleName() {
                return styleName;
            }

            /**
             * Sets a custom style name for this cell.
             *
             * @param styleName
             *            the style name to set or null to not use any style
             *            name
             */
            public void setStyleName(String styleName) {
                this.styleName = styleName;
                section.requestSectionRefresh();

            }

            /**
             * Called when the cell is detached from the row
             *
             * @since 7.6.3
             */
            void detach() {
                if (content instanceof Widget) {
                    // Widget in the cell, detach it
                    section.getGrid().detachWidget((Widget) content);
                }
            }

            /**
             * Gets the tooltip for the cell.
             * <p>
             * The tooltip is shown in the mode returned by
             * {@link #getDescriptionContentMode()}.
             *
             * @return the tooltip text for this cell
             * @since 8.4
             */
            public String getDescription() {
                return description;
            }

            /**
             * Sets the tooltip for the cell.
             * <p>
             * By default, tooltips are shown as plain text. For HTML tooltips,
             * see {@link #setDescription(String, ContentMode)} or
             * {@link #setDescriptionContentMode(ContentMode)}.
             *
             * @param description
             *            the tooltip to show when hovering the cell
             * @since 8.4
             */
            public void setDescription(String description) {
                this.description = description;
            }

            /**
             * Sets the tooltip for the cell to be shown with the given content
             * mode.
             *
             * @see ContentMode
             * @param description
             *            the tooltip to show when hovering the cell
             * @param descriptionContentMode
             *            the content mode to use for the tooltip (HTML or plain
             *            text)
             * @since 8.4
             */
            public void setDescription(String description, ContentMode descriptionContentMode) {
                setDescription(description);
                setDescriptionContentMode(descriptionContentMode);
            }

            /**
             * Gets the content mode for the tooltip.
             * <p>
             * The content mode determines how the tooltip is shown.
             *
             * @see ContentMode
             * @return the content mode for the tooltip
             * @since 8.4
             */
            public ContentMode getDescriptionContentMode() {
                return descriptionContentMode;
            }

            /**
             * Sets the content mode for the tooltip.
             *
             * @see ContentMode
             * @param descriptionContentMode
             *            the content mode for the tooltip
             * @since 8.4
             */
            public void setDescriptionContentMode(ContentMode descriptionContentMode) {
                this.descriptionContentMode = descriptionContentMode;
            }
        }

        /**
         * Abstract base class for Grid header and footer rows.
         *
         * @param <CELLTYPE>
         *            the type of the cells in the row
         */
        public abstract static class StaticRow<CELLTYPE extends StaticCell> {

            private Map<Column<?, ?>, CELLTYPE> cells = new HashMap<>();

            private StaticSection<?> section;

            /**
             * Map from cell meta data to sets of spanned columns .
             */
            private Map<CELLTYPE, Set<Column<?, ?>>> cellGroups = new HashMap<>();

            /**
             * A custom style name for the row or null if none is set.
             */
            private String styleName = null;

            /**
             * Returns the cell on given GridColumn. If the column is merged
             * returned cell is the cell for the whole group.
             *
             * @param column
             *            the column in grid
             * @return the cell on given column, merged cell for merged columns,
             *         null if not found
             */
            public CELLTYPE getCell(Column<?, ?> column) {
                CELLTYPE cell = getMergedCellForColumn(column);
                if (cell != null) {
                    return cell;
                }
                return cells.get(column);
            }

            /**
             * Returns <code>true</code> if this row contains spanned cells.
             *
             * @since 7.5.0
             * @return does this row contain spanned cells
             */
            public boolean hasSpannedCells() {
                return !cellGroups.isEmpty();
            }

            /**
             * Merges columns cells in a row.
             *
             * @param columns
             *            the columns which header should be merged
             * @return the remaining visible cell after the merge, or the cell
             *         on first column if all are hidden
             */
            public CELLTYPE join(Column<?, ?>... columns) {
                if (columns.length <= 1) {
                    throw new IllegalArgumentException("You can't merge less than 2 columns together.");
                }

                HashSet<Column<?, ?>> columnGroup = new HashSet<>();
                // NOTE: this doesn't care about hidden columns, those are
                // filtered in calculateColspans()
                for (Column<?, ?> column : columns) {
                    if (!cells.containsKey(column)) {
                        throw new IllegalArgumentException("Given column does not exists on row " + column);
                    } else if (getMergedCellForColumn(column) != null) {
                        throw new IllegalStateException("Column is already in a group.");
                    }
                    columnGroup.add(column);
                }

                CELLTYPE joinedCell = createCell();
                cellGroups.put(joinedCell, columnGroup);
                joinedCell.setSection(getSection());

                calculateColspans();

                return joinedCell;
            }

            /**
             * Merges columns cells in a row.
             *
             * @param cells
             *            The cells to merge. Must be from the same row.
             * @return The remaining visible cell after the merge, or the first
             *         cell if all columns are hidden
             */
            public CELLTYPE join(CELLTYPE... cells) {
                if (cells.length <= 1) {
                    throw new IllegalArgumentException("You can't merge less than 2 cells together.");
                }

                Column<?, ?>[] columns = new Column<?, ?>[cells.length];

                int j = 0;
                for (Column<?, ?> column : this.cells.keySet()) {
                    CELLTYPE cell = this.cells.get(column);
                    if (!this.cells.containsValue(cells[j])) {
                        throw new IllegalArgumentException("Given cell does not exists on row");
                    } else if (cell.equals(cells[j])) {
                        columns[j++] = column;
                        if (j == cells.length) {
                            break;
                        }
                    }
                }

                return join(columns);
            }

            private CELLTYPE getMergedCellForColumn(Column<?, ?> column) {
                for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups.entrySet()) {
                    if (entry.getValue().contains(column)) {
                        return entry.getKey();
                    }
                }
                return null;
            }

            protected int getSizeOfCellGroup(Column<?, ?> column) {
                for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups.entrySet()) {
                    if (entry.getValue().contains(column)) {
                        return entry.getValue().size();
                    }
                }
                return 0;
            }

            void calculateColspans() {
                // Reset all cells
                for (CELLTYPE cell : this.cells.values()) {
                    cell.setColspan(1);
                }
                // Set colspan for grouped cells
                for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups.entrySet()) {
                    CELLTYPE mergedCell = entry.getKey();
                    if (!checkMergedCellIsContinuous(entry.getValue())) {
                        // on error simply break the merged cell
                        mergedCell.setColspan(1);
                    } else {
                        int colSpan = 0;
                        for (Column<?, ?> column : entry.getValue()) {
                            if (!column.isHidden()) {
                                colSpan++;
                            }
                        }
                        // colspan can't be 0
                        mergedCell.setColspan(Math.max(1, colSpan));
                    }
                }
            }

            private boolean checkMergedCellIsContinuous(Set<Column<?, ?>> mergedCell) {
                // no matter if hidden or not, just check for continuous order
                final List<Column<?, ?>> columnOrder = new ArrayList<>(section.grid.getColumns());

                if (!columnOrder.containsAll(mergedCell)) {
                    return false;
                }

                for (int i = 0; i < columnOrder.size(); ++i) {
                    if (!mergedCell.contains(columnOrder.get(i))) {
                        continue;
                    }

                    for (int j = 1; j < mergedCell.size(); ++j) {
                        if (!mergedCell.contains(columnOrder.get(i + j))) {
                            return false;
                        }
                    }
                    return true;
                }
                return false;
            }

            protected void addCell(Column<?, ?> column) {
                CELLTYPE cell = createCell();
                cell.setSection(getSection());
                cells.put(column, cell);
            }

            protected void removeCell(Column<?, ?> column) {
                cells.remove(column);
            }

            protected abstract CELLTYPE createCell();

            protected StaticSection<?> getSection() {
                return section;
            }

            protected void setSection(StaticSection<?> section) {
                this.section = section;
            }

            /**
             * Returns the custom style name for this row.
             *
             * @return the style name or null if no style name has been set
             */
            public String getStyleName() {
                return styleName;
            }

            /**
             * Sets a custom style name for this row.
             *
             * @param styleName
             *            the style name to set or null to not use any style
             *            name
             */
            public void setStyleName(String styleName) {
                this.styleName = styleName;
                section.requestSectionRefresh();
            }

            /**
             * Called when the row is detached from the grid
             *
             * @since 7.6.3
             */
            void detach() {
                // Avoid calling detach twice for a merged cell
                HashSet<CELLTYPE> cells = new HashSet<>();
                for (Column<?, ?> column : getSection().grid.getColumns()) {
                    cells.add(getCell(column));
                }
                for (CELLTYPE cell : cells) {
                    cell.detach();
                }
            }
        }

        private Grid<?> grid;

        private List<ROWTYPE> rows = new ArrayList<>();

        private boolean visible = true;

        /**
         * Creates and returns a new instance of the row type.
         *
         * @return the created row
         */
        protected abstract ROWTYPE createRow();

        /**
         * Informs the grid that this section should be re-rendered.
         * <p>
         * <b>Note</b> that re-render means calling update() on each cell,
         * preAttach()/postAttach()/preDetach()/postDetach() is not called as
         * the cells are not removed from the DOM.
         */
        protected abstract void requestSectionRefresh();

        /**
         * Sets the visibility of the whole section.
         *
         * @param visible
         *            true to show this section, false to hide
         */
        public void setVisible(boolean visible) {
            if (this.visible != visible) {
                this.visible = visible;
                requestSectionRefresh();
            }
        }

        /**
         * Returns the visibility of this section.
         *
         * @return true if visible, false otherwise.
         */
        public boolean isVisible() {
            return visible;
        }

        /**
         * Inserts a new row at the given position. Shifts the row currently at
         * that position and any subsequent rows down (adds one to their
         * indices).
         *
         * @param index
         *            the position at which to insert the row
         * @return the new row
         *
         * @throws IndexOutOfBoundsException
         *             if the index is out of bounds
         * @see #appendRow()
         * @see #prependRow()
         * @see #removeRow(int)
         * @see #removeRow(StaticRow)
         */
        public ROWTYPE addRowAt(int index) {
            ROWTYPE row = createRow();
            row.setSection(this);
            for (int i = 0; i < getGrid().getColumnCount(); ++i) {
                row.addCell(grid.getColumn(i));
            }
            rows.add(index, row);

            requestSectionRefresh();
            return row;
        }

        /**
         * Adds a new row at the top of this section.
         *
         * @return the new row
         * @see #appendRow()
         * @see #addRowAt(int)
         * @see #removeRow(int)
         * @see #removeRow(StaticRow)
         */
        public ROWTYPE prependRow() {
            return addRowAt(0);
        }

        /**
         * Adds a new row at the bottom of this section.
         *
         * @return the new row
         * @see #prependRow()
         * @see #addRowAt(int)
         * @see #removeRow(int)
         * @see #removeRow(StaticRow)
         */
        public ROWTYPE appendRow() {
            return addRowAt(rows.size());
        }

        /**
         * Removes the row at the given position.
         *
         * @param index
         *            the position of the row
         *
         * @throws IndexOutOfBoundsException
         *             if the index is out of bounds
         * @see #addRowAt(int)
         * @see #appendRow()
         * @see #prependRow()
         * @see #removeRow(StaticRow)
         */
        public void removeRow(int index) {
            ROWTYPE row = rows.remove(index);
            row.detach();
            requestSectionRefresh();
        }

        /**
         * Removes the given row from the section.
         *
         * @param row
         *            the row to be removed
         *
         * @throws IllegalArgumentException
         *             if the row does not exist in this section
         * @see #addRowAt(int)
         * @see #appendRow()
         * @see #prependRow()
         * @see #removeRow(int)
         */
        public void removeRow(ROWTYPE row) {
            try {
                removeRow(rows.indexOf(row));
            } catch (IndexOutOfBoundsException e) {
                throw new IllegalArgumentException("Section does not contain the given row");
            }
        }

        /**
         * Returns the row at the given position.
         *
         * @param index
         *            the position of the row
         * @return the row with the given index
         *
         * @throws IndexOutOfBoundsException
         *             if the index is out of bounds
         */
        public ROWTYPE getRow(int index) {
            try {
                return rows.get(index);
            } catch (IndexOutOfBoundsException e) {
                throw new IllegalArgumentException("Row with index " + index + " does not exist");
            }
        }

        /**
         * Returns the number of rows in this section.
         *
         * @return the number of rows
         */
        public int getRowCount() {
            return rows.size();
        }

        protected List<ROWTYPE> getRows() {
            return rows;
        }

        protected int getVisibleRowCount() {
            return isVisible() ? getRowCount() : 0;
        }

        protected void addColumn(Column<?, ?> column) {
            for (ROWTYPE row : rows) {
                row.addCell(column);
            }
        }

        protected void removeColumn(Column<?, ?> column) {
            for (ROWTYPE row : rows) {
                row.removeCell(column);
            }
        }

        protected void setGrid(Grid<?> grid) {
            this.grid = grid;
        }

        protected Grid<?> getGrid() {
            assert grid != null;
            return grid;
        }

        protected void updateColSpans() {
            for (ROWTYPE row : rows) {
                if (row.hasSpannedCells()) {
                    row.calculateColspans();
                }
            }
        }
    }

    /**
     * Represents the header section of a Grid. A header consists of a single
     * header row containing a header cell for each column. Each cell has a
     * simple textual caption.
     */
    protected static class Header extends StaticSection<HeaderRow> {
        private HeaderRow defaultRow;

        private boolean markAsDirty = false;

        @Override
        public void removeRow(int index) {
            HeaderRow removedRow = getRow(index);
            super.removeRow(index);
            if (removedRow == defaultRow) {
                setDefaultRow(null);
            }
        }

        /**
         * Sets the default row of this header. The default row is a special
         * header row providing a user interface for sorting columns.
         *
         * @param row
         *            the new default row, or null for no default row
         *
         * @throws IllegalArgumentException
         *             this header does not contain the row
         */
        public void setDefaultRow(HeaderRow row) {
            if (row == defaultRow) {
                return;
            }
            if (row != null && !getRows().contains(row)) {
                throw new IllegalArgumentException("Cannot set a default row that does not exist in the container");
            }
            if (defaultRow != null) {
                defaultRow.setDefault(false);
            }
            if (row != null) {
                row.setDefault(true);
            }

            defaultRow = row;
            requestSectionRefresh();
        }

        /**
         * Returns the current default row of this header. The default row is a
         * special header row providing a user interface for sorting columns.
         *
         * @return the default row or null if no default row set
         */
        public HeaderRow getDefaultRow() {
            return defaultRow;
        }

        @Override
        protected HeaderRow createRow() {
            return new HeaderRow();
        }

        @Override
        protected void requestSectionRefresh() {
            markAsDirty = true;

            /*
             * Defer the refresh so if we multiple times call refreshSection()
             * (for example when updating cell values) we only get one actual
             * refresh in the end.
             */
            Scheduler.get().scheduleFinally(() -> {
                if (markAsDirty) {
                    markAsDirty = false;
                    getGrid().refreshHeader();
                }
            });
        }

        /**
         * Returns the events consumed by the header.
         *
         * @return a collection of BrowserEvents
         */
        public Collection<String> getConsumedEvents() {
            return Arrays.asList(BrowserEvents.TOUCHSTART, BrowserEvents.TOUCHMOVE, BrowserEvents.TOUCHEND,
                    BrowserEvents.TOUCHCANCEL, BrowserEvents.CLICK);
        }

        @Override
        protected void addColumn(Column<?, ?> column) {
            super.addColumn(column);

            // Add default content for new columns.
            if (defaultRow != null) {
                column.setDefaultHeaderContent(defaultRow.getCell(column));
            }
        }
    }

    /**
     * A single row in a grid header section.
     *
     */
    public static class HeaderRow extends StaticSection.StaticRow<HeaderCell> {

        private boolean isDefault = false;

        protected void setDefault(boolean isDefault) {
            this.isDefault = isDefault;
            if (isDefault) {
                for (Column<?, ?> column : getSection().grid.getColumns()) {
                    column.setDefaultHeaderContent(getCell(column));
                }
            }
        }

        public boolean isDefault() {
            return isDefault;
        }

        @Override
        protected HeaderCell createCell() {
            return new HeaderCell();
        }
    }

    /**
     * A single cell in a grid header row. Has a caption and, if it's in a
     * default row, a drag handle.
     */
    public static class HeaderCell extends StaticSection.StaticCell {
    }

    /**
     * Represents the footer section of a Grid. The footer is always empty.
     */
    protected static class Footer extends StaticSection<FooterRow> {
        private boolean markAsDirty = false;

        @Override
        protected FooterRow createRow() {
            return new FooterRow();
        }

        @Override
        protected void requestSectionRefresh() {
            markAsDirty = true;

            /*
             * Defer the refresh so if we multiple times call refreshSection()
             * (for example when updating cell values) we only get one actual
             * refresh in the end.
             */
            Scheduler.get().scheduleFinally(() -> {
                if (markAsDirty) {
                    markAsDirty = false;
                    getGrid().refreshFooter();
                }
            });
        }
    }

    /**
     * A single cell in a grid Footer row. Has a textual caption.
     *
     */
    public static class FooterCell extends StaticSection.StaticCell {
    }

    /**
     * A single row in a grid Footer section.
     *
     */
    public static class FooterRow extends StaticSection.StaticRow<FooterCell> {

        @Override
        protected FooterCell createCell() {
            return new FooterCell();
        }
    }

    private static class EditorRequestImpl<T> implements EditorRequest<T> {

        /**
         * A callback interface used to notify the invoker of the editor handler
         * of completed editor requests.
         *
         * @param <T>
         *            the row data type
         */
        public static interface RequestCallback<T> {
            /**
             * The method that must be called when the request has been
             * processed correctly.
             *
             * @param request
             *            the original request object
             */
            public void onSuccess(EditorRequest<T> request);

            /**
             * The method that must be called when processing the request has
             * produced an aborting error.
             *
             * @param request
             *            the original request object
             */
            public void onError(EditorRequest<T> request);
        }

        private Grid<T> grid;
        private final int rowIndex;
        private final int columnIndexDOM;
        private RequestCallback<T> callback;
        private boolean completed = false;

        public EditorRequestImpl(Grid<T> grid, int rowIndex, int columnIndexDOM, RequestCallback<T> callback) {
            this.grid = grid;
            this.rowIndex = rowIndex;
            this.columnIndexDOM = columnIndexDOM;
            this.callback = callback;
        }

        @Override
        public int getRowIndex() {
            return rowIndex;
        }

        @Override
        public int getColumnIndex() {
            return columnIndexDOM;
        }

        @Override
        public T getRow() {
            return grid.getDataSource().getRow(rowIndex);
        }

        @Override
        public Grid<T> getGrid() {
            return grid;
        }

        @Override
        public Widget getWidget(Grid.Column<?, T> column) {
            Widget w = grid.getEditorWidget(column);
            assert w != null;
            return w;
        }

        private void complete(String errorMessage, Collection<Column<?, T>> errorColumns) {
            if (completed) {
                throw new IllegalStateException("An EditorRequest must be completed exactly once");
            }
            completed = true;

            if (errorColumns == null) {
                errorColumns = Collections.emptySet();
            }
            grid.getEditor().setEditorError(errorMessage, errorColumns);
        }

        @Override
        public void success() {
            complete(null, null);
            if (callback != null) {
                callback.onSuccess(this);
            }
        }

        @Override
        public void failure() {
            complete("", null);
            if (callback != null) {
                callback.onError(this);
            }
        }

        @Override
        public boolean isCompleted() {
            return completed;
        }
    }

    /**
     * A wrapper for native DOM events originating from Grid. In addition to the
     * native event, contains a {@link CellReference} instance specifying which
     * cell the event originated from.
     *
     * @since 7.6
     * @param <T>
     *            The row type of the grid
     */
    public static class GridEvent<T> {
        private Event event;
        private EventCellReference<T> cell;
        private boolean handled = false;

        protected GridEvent(Event event, EventCellReference<T> cell) {
            this.event = event;
            this.cell = cell;
        }

        /**
         * Returns the wrapped DOM event.
         *
         * @return the DOM event
         */
        public Event getDomEvent() {
            return event;
        }

        /**
         * Returns the Grid cell this event originated from.
         *
         * @return the event cell
         */
        public EventCellReference<T> getCell() {
            return cell;
        }

        /**
         * Returns the Grid instance this event originated from.
         *
         * @return the grid
         */
        public Grid<T> getGrid() {
            return cell.getGrid();
        }

        /**
         * Check whether this event has already been marked as handled.
         *
         * @return whether this event has already been marked as handled
         */
        public boolean isHandled() {
            return handled;
        }

        /**
         * Set the status of this event. Setting to {@code true} effectively
         * marks this event as having already been handled.
         *
         * @param handled
         */
        public void setHandled(boolean handled) {
            this.handled = handled;
        }
    }

    /**
     * A wrapper for native DOM events related to the {@link Editor Grid editor}
     * .
     *
     * @since 7.6
     * @param <T>
     *            the row type of the grid
     */
    public static class EditorDomEvent<T> extends GridEvent<T> {

        private final Widget editorWidget;

        protected EditorDomEvent(Event event, EventCellReference<T> cell, Widget editorWidget) {
            super(event, cell);
            this.editorWidget = editorWidget;
        }

        /**
         * Returns the editor of the Grid this event originated from.
         *
         * @return the related editor instance
         */
        public Editor<T> getEditor() {
            return getGrid().getEditor();
        }

        /**
         * Returns the currently focused editor widget.
         *
         * @return the focused editor widget or {@code null} if not editable
         */
        public Widget getEditorWidget() {
            return editorWidget;
        }

        /**
         * Returns the row index the editor is open at. If the editor is not
         * open, returns -1.
         *
         * @return the index of the edited row or -1 if editor is not open
         */
        public int getRowIndex() {
            return getEditor().rowIndex;
        }

        /**
         * Returns the DOM column index (excluding hidden columns) the editor
         * was opened at. If the editor is not open, returns -1.
         *
         * @return the column index or -1 if editor is not open
         */
        public int getFocusedColumnIndex() {
            return getEditor().focusedColumnIndexDOM;
        }
    }

    /**
     * An editor UI for Grid rows. A single Grid row at a time can be opened for
     * editing.
     *
     * @since 7.6
     * @param <T>
     *            the row type of the grid
     */
    public static class Editor<T> implements DeferredWorker {

        public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER;
        public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE;

        private static final String ERROR_CLASS_NAME = "error";
        private static final String NOT_EDITABLE_CLASS_NAME = "not-editable";

        /**
         * A handler for events related to the Grid editor. Responsible for
         * opening, moving or closing the editor based on the received event.
         *
         * @since 7.6
         * @author Vaadin Ltd
         * @param <T>
         *            the row type of the grid
         */
        public interface EventHandler<T> {
            /**
             * Handles editor-related events in an appropriate way. Opens,
             * moves, or closes the editor based on the given event.
             *
             * @param event
             *            the received event
             * @return true if the event was handled and nothing else should be
             *         done, false otherwise
             */
            boolean handleEvent(EditorDomEvent<T> event);
        }

        protected enum State {
            INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING
        }

        private Grid<T> grid;
        private EditorHandler<T> handler;
        private EventHandler<T> eventHandler = GWT.create(DefaultEditorEventHandler.class);

        private DivElement editorOverlay = DivElement.as(DOM.createDiv());
        private DivElement cellWrapper = DivElement.as(DOM.createDiv());
        private DivElement frozenCellWrapper = DivElement.as(DOM.createDiv());

        private DivElement messageAndButtonsWrapper = DivElement.as(DOM.createDiv());

        private DivElement messageWrapper = DivElement.as(DOM.createDiv());
        private DivElement buttonsWrapper = DivElement.as(DOM.createDiv());

        // Element which contains the error message for the editor
        // Should only be added to the DOM when there's a message to show
        private DivElement message = DivElement.as(DOM.createDiv());

        private Map<Column<?, T>, Widget> columnToWidget = new HashMap<>();
        private List<HandlerRegistration> focusHandlers = new ArrayList<>();

        private boolean enabled = false;
        private State state = State.INACTIVE;
        private int rowIndex = -1;
        private int focusedColumnIndexDOM = -1;
        private String styleName = null;

        private HandlerRegistration hScrollHandler;

        private final Button saveButton;
        private final Button cancelButton;

        private static final int SAVE_TIMEOUT_MS = 5000;
        private final Timer saveTimeout = new Timer() {
            @Override
            public void run() {
                getLogger().warning(
                        "Editor save action is taking longer than expected (" + SAVE_TIMEOUT_MS + "ms). Does your "
                                + EditorHandler.class.getSimpleName() + " remember to call success() or fail()?");
            }
        };

        private final EditorRequestImpl.RequestCallback<T> saveRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
            @Override
            public void onSuccess(EditorRequest<T> request) {
                if (state == State.SAVING) {
                    cleanup();
                    cancel(true);
                    grid.clearSortOrder();
                }
            }

            @Override
            public void onError(EditorRequest<T> request) {
                if (state == State.SAVING) {
                    cleanup();
                }
            }

            private void cleanup() {
                state = State.ACTIVE;
                setButtonsEnabled(true);
                saveTimeout.cancel();
            }
        };

        private static final int BIND_TIMEOUT_MS = 5000;
        private final Timer bindTimeout = new Timer() {
            @Override
            public void run() {
                getLogger().warning(
                        "Editor bind action is taking longer than expected (" + BIND_TIMEOUT_MS + "ms). Does your "
                                + EditorHandler.class.getSimpleName() + " remember to call success() or fail()?");
            }
        };

        private final EditorRequestImpl.RequestCallback<T> bindRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
            @Override
            public void onSuccess(EditorRequest<T> request) {
                if (state == State.BINDING) {
                    state = State.ACTIVE;
                    bindTimeout.cancel();

                    rowIndex = request.getRowIndex();
                    focusedColumnIndexDOM = request.getColumnIndex();
                    if (focusedColumnIndexDOM >= 0) {
                        // Update internal focus of Grid
                        grid.focusCell(rowIndex, focusedColumnIndexDOM);
                    }

                    showOverlay();
                }
            }

            @Override
            public void onError(EditorRequest<T> request) {
                if (state == State.BINDING) {
                    if (rowIndex == -1) {
                        doCancel();
                    } else {
                        state = State.ACTIVE;
                        // TODO: Maybe restore focus?
                    }
                    bindTimeout.cancel();
                }
            }
        };

        /** A set of all the columns that display an error flag. */
        private final Set<Column<?, T>> columnErrors = new HashSet<>();
        private boolean buffered = true;

        /** Original position of editor */
        private double originalTop;
        /** Original scroll position of grid when editor was opened */
        private double originalScrollTop;
        private RowHandle<T> pinnedRowHandle;

        public Editor() {
            saveButton = new Button();
            saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION);
            saveButton.addClickHandler(event -> {
                save();
                FocusUtil.setFocus(grid, true);
            });

            cancelButton = new Button();
            cancelButton.setText(GridConstants.DEFAULT_CANCEL_CAPTION);
            cancelButton.addClickHandler(event -> {
                cancel();
                FocusUtil.setFocus(grid, true);
            });
        }

        public void setEditorError(String errorMessage, Collection<Column<?, T>> errorColumns) {

            if (errorMessage == null) {
                message.removeFromParent();
            } else {
                message.setInnerText(errorMessage);
                if (message.getParentElement() == null) {
                    messageWrapper.appendChild(message);
                }
            }
            // In unbuffered mode only show message wrapper if there is an error
            if (!isBuffered()) {
                setMessageAndButtonsWrapperVisible(errorMessage != null);
            }

            if (state == State.ACTIVE || state == State.SAVING) {
                for (Column<?, T> c : grid.getColumns()) {
                    grid.getEditor().setEditorColumnError(c, errorColumns.contains(c));
                }
            }
        }

        public int getRow() {
            return rowIndex;
        }

        /**
         * If a cell of this Grid had focus once this editRow call was
         * triggered, the editor component at the previously focused column
         * index will be focused.
         *
         * If a Grid cell was not focused prior to calling this method, it will
         * be equivalent to {@code editRow(rowIndex, -1)}.
         *
         * @see #editRow(int, int)
         */
        public void editRow(int rowIndex) {
            // Focus the last focused column in the editor if grid or its child
            // was focused before the edit request
            Cell focusedCell = grid.cellFocusHandler.getFocusedCell();
            Element focusedElement = WidgetUtil.getFocusedElement();
            if (focusedCell != null && focusedElement != null && grid.getElement().isOrHasChild(focusedElement)) {
                editRow(rowIndex, focusedCell.getColumn());
            } else {
                editRow(rowIndex, -1);
            }
        }

        /**
         * Opens the editor over the row with the given index and attempts to
         * focus the editor widget in the given column index. Does not move
         * focus if the widget is not focusable or if the column index is -1.
         *
         * @param rowIndex
         *            the index of the row to be edited
         * @param columnIndexDOM
         *            the column index (excluding hidden columns) of the editor
         *            widget that should be initially focused or -1 to not set
         *            focus
         *
         * @throws IllegalStateException
         *             if this editor is not enabled
         * @throws IllegalStateException
         *             if this editor is already in edit mode and in buffered
         *             mode
         *
         * @since 7.5
         */
        public void editRow(final int rowIndex, final int columnIndexDOM) {
            if (!enabled) {
                throw new IllegalStateException("Cannot edit row: editor is not enabled");
            }

            if (isWorkPending()) {
                // Request pending a response, don't move try to start another
                // request.
                return;
            }

            if (state != State.INACTIVE && this.rowIndex != rowIndex) {
                if (isBuffered()) {
                    throw new IllegalStateException("Cannot edit row: editor already in edit mode");
                } else if (!columnErrors.isEmpty()) {
                    // Don't move row if errors are present

                    // FIXME: Should attempt bind if error field values have
                    // changed.

                    return;
                }
            }
            if (columnIndexDOM >= grid.getVisibleColumns().size()) {
                throw new IllegalArgumentException(
                        "Edited column index " + columnIndexDOM + " was bigger than visible column count.");
            }

            if (this.rowIndex == rowIndex && focusedColumnIndexDOM == columnIndexDOM) {
                // NO-OP
                return;
            }

            if (this.rowIndex == rowIndex) {
                if (focusedColumnIndexDOM != columnIndexDOM) {
                    if (columnIndexDOM >= grid.getFrozenColumnCount()) {
                        // Scroll to new focused column.
                        grid.getEscalator().scrollToColumn(columnIndexDOM, ScrollDestination.ANY, 0);
                    }

                    focusedColumnIndexDOM = columnIndexDOM;
                }

                updateHorizontalScrollPosition();

                // Update Grid internal focus and focus widget if possible
                if (focusedColumnIndexDOM >= 0) {
                    grid.focusCell(rowIndex, focusedColumnIndexDOM);
                    focusColumn(focusedColumnIndexDOM);
                }

                // No need to request anything from the editor handler.
                return;
            }
            state = State.ACTIVATING;

            grid.scrollToRow(rowIndex, ScrollDestination.ANY, () -> show(rowIndex, columnIndexDOM));
        }

        /**
         * Cancels the currently active edit and hides the editor. Any changes
         * that are not {@link #save() saved} are lost.
         *
         * @throws IllegalStateException
         *             if this editor is not enabled
         * @throws IllegalStateException
         *             if this editor is not in edit mode
         */
        public void cancel() {
            cancel(false);
        }

        private void cancel(boolean afterSave) {
            if (!enabled) {
                throw new IllegalStateException("Cannot cancel edit: editor is not enabled");
            }
            if (state == State.INACTIVE) {
                throw new IllegalStateException("Cannot cancel edit: editor is not in edit mode");
            }
            handler.cancel(new EditorRequestImpl<>(grid, rowIndex, focusedColumnIndexDOM, null), afterSave);
            doCancel();
        }

        private void doCancel() {
            hideOverlay();
            state = State.INACTIVE;
            rowIndex = -1;
            focusedColumnIndexDOM = -1;
            grid.getEscalator().setScrollLocked(Direction.VERTICAL, false);
            updateSelectionCheckboxesAsNeeded(true);
        }

        private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) {
            // FIXME: This is too much guessing. Define a better way to do this.
            if (grid.selectionColumn != null
                    && grid.selectionColumn.getRenderer() instanceof MultiSelectionRenderer) {
                grid.refreshBody();
                HeaderCell cell = grid.getDefaultHeaderRow().getCell(grid.selectionColumn);
                // if lazy provider, then no checkbox
                if (cell.getType() == GridStaticCellType.WIDGET) {
                    CheckBox checkBox = (CheckBox) grid.getDefaultHeaderRow().getCell(grid.selectionColumn)
                            .getWidget();
                    checkBox.setEnabled(isEnabled);
                }
            }
        }

        /**
         * Saves any unsaved changes to the data source and hides the editor.
         *
         * @throws IllegalStateException
         *             if this editor is not enabled
         * @throws IllegalStateException
         *             if this editor is not in edit mode
         */
        public void save() {
            if (!enabled) {
                throw new IllegalStateException("Cannot save: editor is not enabled");
            }
            if (state != State.ACTIVE) {
                throw new IllegalStateException("Cannot save: editor is not in edit mode");
            }

            state = State.SAVING;
            setButtonsEnabled(false);
            saveTimeout.schedule(SAVE_TIMEOUT_MS);
            EditorRequest<T> request = new EditorRequestImpl<>(grid, rowIndex, focusedColumnIndexDOM,
                    saveRequestCallback);
            handler.save(request);
            updateSelectionCheckboxesAsNeeded(true);
        }

        /**
         * Returns the handler responsible for binding data and editor widgets
         * to this editor.
         *
         * @return the editor handler or null if not set
         */
        public EditorHandler<T> getHandler() {
            return handler;
        }

        /**
         * Sets the handler responsible for binding data and editor widgets to
         * this editor.
         *
         * @param rowHandler
         *            the new editor handler
         *
         * @throws IllegalStateException
         *             if this editor is currently in edit mode
         */
        public void setHandler(EditorHandler<T> rowHandler) {
            if (state != State.INACTIVE) {
                throw new IllegalStateException("Cannot set EditorHandler: editor is currently in edit mode");
            }
            handler = rowHandler;
        }

        public boolean isEnabled() {
            return enabled;
        }

        /**
         * Sets the enabled state of this editor.
         *
         * @param enabled
         *            true if enabled, false otherwise
         *
         * @throws IllegalStateException
         *             if in edit mode and trying to disable
         * @throws IllegalStateException
         *             if the editor handler is not set
         */
        public void setEnabled(boolean enabled) {
            if (!enabled && state != State.INACTIVE) {
                throw new IllegalStateException("Cannot disable: editor is in edit mode");
            } else if (enabled && getHandler() == null) {
                throw new IllegalStateException("Cannot enable: EditorHandler not set");
            }
            this.enabled = enabled;
        }

        protected void show(int rowIndex, int columnIndex) {
            if (state == State.ACTIVATING) {
                state = State.BINDING;
                bindTimeout.schedule(BIND_TIMEOUT_MS);
                EditorRequest<T> request = new EditorRequestImpl<>(grid, rowIndex, columnIndex,
                        bindRequestCallback);
                handler.bind(request);
                grid.getEscalator().setScrollLocked(Direction.VERTICAL, isBuffered());
                updateSelectionCheckboxesAsNeeded(false);
            }
        }

        protected void setGrid(final Grid<T> grid) {
            assert grid != null : "Grid cannot be null";
            assert this.grid == null : "Can only attach editor to Grid once";

            this.grid = grid;
        }

        protected State getState() {
            return state;
        }

        protected void setState(State state) {
            this.state = state;
        }

        /**
         * Returns the editor widget associated with the given column. If the
         * editor is not active or the column is not
         * {@link Grid.Column#isEditable() editable}, returns null.
         *
         * @param column
         *            the column
         * @return the widget if the editor is open and the column is editable,
         *         null otherwise
         */
        protected Widget getWidget(Column<?, T> column) {
            return columnToWidget.get(column);
        }

        /**
         * Equivalent to {@code showOverlay()}. The argument is ignored.
         *
         * @param unused
         *            ignored argument
         *
         * @deprecated As of 7.5, use {@link #showOverlay()} instead.
         */
        @Deprecated
        protected void showOverlay(TableRowElement unused) {
            showOverlay();
        }

        /**
         * Opens the editor overlay over the table row indicated by
         * {@link #getRow()}.
         *
         * @since 7.5
         */
        protected void showOverlay() {
            // Ensure overlay is hidden initially
            hideOverlay();
            DivElement gridElement = DivElement.as(grid.getElement());

            TableRowElement tr = grid.getEscalator().getBody().getRowElement(rowIndex);

            hScrollHandler = grid.addScrollHandler(event -> {
                updateHorizontalScrollPosition();
                updateVerticalScrollPosition();
            });

            gridElement.appendChild(editorOverlay);
            editorOverlay.appendChild(frozenCellWrapper);
            editorOverlay.appendChild(cellWrapper);
            editorOverlay.appendChild(messageAndButtonsWrapper);

            updateBufferedStyleName();

            // Add class name with selected modifier if the editor is being
            // opened on selected row, see #11634
            String selectedStylename = styleName + "-selected";
            if (grid.isSelected(grid.getDataSource().getRow(getRow()))) {
                cellWrapper.addClassName(selectedStylename);
            } else {
                cellWrapper.removeClassName(selectedStylename);
            }

            int frozenColumns = grid.getVisibleFrozenColumnCount();
            double frozenColumnsWidth = 0;
            double cellHeight = 0;

            for (int i = 0; i < tr.getCells().getLength(); i++) {
                Element cell = createCell(tr.getCells().getItem(i));
                cellHeight = Math.max(cellHeight,
                        WidgetUtil.getRequiredHeightBoundingClientRectDouble(tr.getCells().getItem(i)));

                Column<?, T> column = grid.getVisibleColumn(i);

                if (i < frozenColumns) {
                    frozenCellWrapper.appendChild(cell);
                    frozenColumnsWidth += WidgetUtil
                            .getRequiredWidthBoundingClientRectDouble(tr.getCells().getItem(i));
                } else {
                    cellWrapper.appendChild(cell);
                }

                if (column.isEditable()) {
                    Widget editor = getHandler().getWidget(column);

                    if (editor != null) {
                        columnToWidget.put(column, editor);
                        grid.attachWidget(editor, cell);
                    }

                    if (i == focusedColumnIndexDOM) {
                        focusColumn(focusedColumnIndexDOM);
                    }
                } else {
                    cell.addClassName(NOT_EDITABLE_CLASS_NAME);
                    cell.addClassName(tr.getCells().getItem(i).getClassName());
                    // If the focused or frozen stylename is present it should
                    // not be inherited by the editor cell as it is not useful
                    // in the editor and would look broken without additional
                    // style rules. This is a bit of a hack.
                    cell.removeClassName(grid.cellFocusStyleName);
                    cell.removeClassName("frozen");

                    if (column == grid.selectionColumn) {
                        // Duplicate selection column CheckBox

                        pinnedRowHandle = grid.getDataSource().getHandle(grid.getDataSource().getRow(rowIndex));
                        pinnedRowHandle.pin();

                        // We need to duplicate the selection CheckBox for the
                        // editor overlay since the original one is hidden by
                        // the overlay
                        final CheckBox checkBox = GWT.create(CheckBox.class);
                        checkBox.setValue(grid.isSelected(pinnedRowHandle.getRow()));
                        checkBox.sinkEvents(Event.ONCLICK);

                        checkBox.addClickHandler(event -> {
                            T row = pinnedRowHandle.getRow();
                            if (grid.isSelected(row)) {
                                grid.deselect(row);
                            } else {
                                grid.select(row);
                            }
                        });
                        grid.attachWidget(checkBox, cell);
                        columnToWidget.put(column, checkBox);

                        // Only enable CheckBox in non-buffered mode
                        checkBox.setEnabled(!isBuffered());

                    } else if (!(column.getRenderer() instanceof WidgetRenderer)) {
                        // Copy non-widget content directly
                        cell.setInnerHTML(tr.getCells().getItem(i).getInnerHTML());
                    }
                }
            }

            setBounds(frozenCellWrapper, 0, 0, frozenColumnsWidth, 0);
            setBounds(cellWrapper, frozenColumnsWidth, 0, tr.getOffsetWidth() - frozenColumnsWidth, cellHeight);

            // Only add these elements once
            if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) {
                messageAndButtonsWrapper.appendChild(messageWrapper);
                messageAndButtonsWrapper.appendChild(buttonsWrapper);
            }

            if (isBuffered()) {
                grid.attachWidget(saveButton, buttonsWrapper);
                grid.attachWidget(cancelButton, buttonsWrapper);
            }

            setMessageAndButtonsWrapperVisible(isBuffered());

            updateHorizontalScrollPosition();

            AbstractRowContainer body = (AbstractRowContainer) grid.getEscalator().getBody();
            double rowTop = body.getRowTop(tr);

            int bodyTop = body.getElement().getAbsoluteTop();
            int gridTop = gridElement.getAbsoluteTop();
            double overlayTop = rowTop + bodyTop - gridTop;

            originalScrollTop = grid.getScrollTop();
            if (!isBuffered() || buttonsShouldBeRenderedBelow(tr)) {
                // Default case, editor buttons are below the edited row
                editorOverlay.getStyle().setTop(overlayTop, Unit.PX);
                originalTop = overlayTop;
                editorOverlay.getStyle().clearBottom();
            } else {
                // Move message and buttons wrapper on top of cell wrapper if
                // there is not enough space visible space under and fix the
                // overlay from the bottom
                editorOverlay.insertFirst(messageAndButtonsWrapper);
                int gridHeight = grid.getElement().getOffsetHeight();
                editorOverlay.getStyle().setBottom(gridHeight - overlayTop - tr.getOffsetHeight(), Unit.PX);
                editorOverlay.getStyle().clearTop();
            }

            // Do not render over the vertical scrollbar
            editorOverlay.getStyle().setWidth(grid.escalator.getInnerWidth(), Unit.PX);
        }

        private void focusColumn(int columnIndexDOM) {
            if (columnIndexDOM < 0 || columnIndexDOM >= grid.getVisibleColumns().size()) {
                // NO-OP
                return;
            }

            Widget editor = getWidget(grid.getVisibleColumn(columnIndexDOM));
            if (editor instanceof Focusable) {
                ((Focusable) editor).focus();
            } else if (editor instanceof com.google.gwt.user.client.ui.Focusable) {
                ((com.google.gwt.user.client.ui.Focusable) editor).setFocus(true);
            } else {
                grid.focus();
            }
        }

        private boolean buttonsShouldBeRenderedBelow(TableRowElement tr) {
            TableSectionElement tfoot = grid.escalator.getFooter().getElement();
            double tfootPageTop = WidgetUtil.getBoundingClientRect(tfoot).getTop();
            double trPageBottom = WidgetUtil.getBoundingClientRect(tr).getBottom();
            int messageAndButtonsHeight = messageAndButtonsWrapper.getOffsetHeight();
            double bottomOfButtons = trPageBottom + messageAndButtonsHeight;

            return bottomOfButtons < tfootPageTop;
        }

        protected void hideOverlay() {
            if (editorOverlay.getParentElement() == null) {
                return;
            }

            if (pinnedRowHandle != null) {
                pinnedRowHandle.unpin();
                pinnedRowHandle = null;
            }

            for (HandlerRegistration r : focusHandlers) {
                r.removeHandler();
            }
            focusHandlers.clear();

            for (Widget w : columnToWidget.values()) {
                setParent(w, null);
            }
            columnToWidget.clear();

            if (isBuffered()) {
                grid.detachWidget(saveButton);
                grid.detachWidget(cancelButton);
            }

            editorOverlay.removeAllChildren();
            cellWrapper.removeAllChildren();
            frozenCellWrapper.removeAllChildren();
            editorOverlay.removeFromParent();

            hScrollHandler.removeHandler();

            clearEditorColumnErrors();
        }

        private void updateBufferedStyleName() {
            if (isBuffered()) {
                editorOverlay.removeClassName("unbuffered");
                editorOverlay.addClassName("buffered");
            } else {
                editorOverlay.removeClassName("buffered");
                editorOverlay.addClassName("unbuffered");
            }
        }

        protected void setStylePrimaryName(String primaryName) {
            if (styleName != null) {
                editorOverlay.removeClassName(styleName);

                cellWrapper.removeClassName(styleName + "-cells");
                frozenCellWrapper.removeClassName(styleName + "-cells");
                messageAndButtonsWrapper.removeClassName(styleName + "-footer");

                messageWrapper.removeClassName(styleName + "-message");
                buttonsWrapper.removeClassName(styleName + "-buttons");

                saveButton.removeStyleName(styleName + "-save");
                cancelButton.removeStyleName(styleName + "-cancel");
            }
            styleName = primaryName + "-editor";
            editorOverlay.setClassName(styleName);

            cellWrapper.setClassName(styleName + "-cells");
            frozenCellWrapper.setClassName(styleName + "-cells frozen");
            messageAndButtonsWrapper.setClassName(styleName + "-footer");

            messageWrapper.setClassName(styleName + "-message");
            buttonsWrapper.setClassName(styleName + "-buttons");

            saveButton.setStyleName(styleName + "-save");
            cancelButton.setStyleName(styleName + "-cancel");
        }

        /**
         * Creates an editor cell corresponding to the given table cell. The
         * returned element is empty and has the same dimensions and position as
         * the table cell.
         *
         * @param td
         *            the table cell used as a reference
         * @return an editor cell corresponding to the given cell
         */
        protected Element createCell(TableCellElement td) {
            DivElement cell = DivElement.as(DOM.createDiv());
            double width = WidgetUtil.getRequiredWidthBoundingClientRectDouble(td);
            double height = WidgetUtil.getRequiredHeightBoundingClientRectDouble(td);
            setBounds(cell, td.getOffsetLeft(), td.getOffsetTop(), width, height);
            return cell;
        }

        private static void setBounds(Element e, double left, double top, double width, double height) {
            Style style = e.getStyle();
            style.setLeft(left, Unit.PX);
            style.setTop(top, Unit.PX);
            style.setWidth(width, Unit.PX);
            style.setHeight(height, Unit.PX);
        }

        private void updateHorizontalScrollPosition() {
            double scrollLeft = grid.getScrollLeft();
            cellWrapper.getStyle().setLeft(frozenCellWrapper.getOffsetWidth() - scrollLeft, Unit.PX);
        }

        /**
         * Moves the editor overlay on scroll so that it stays on top of the
         * edited row. This will also snap the editor to top or bottom of the
         * row container if the edited row is scrolled out of the visible area.
         */
        private void updateVerticalScrollPosition() {
            if (isBuffered()) {
                return;
            }

            double newScrollTop = grid.getScrollTop();

            int gridTop = grid.getElement().getAbsoluteTop();
            int editorHeight = editorOverlay.getOffsetHeight();

            Escalator escalator = grid.getEscalator();
            TableSectionElement header = escalator.getHeader().getElement();
            int footerTop = escalator.getFooter().getElement().getAbsoluteTop();
            int headerBottom = header.getAbsoluteBottom();

            double newTop = originalTop - (newScrollTop - originalScrollTop);

            if (newTop + gridTop < headerBottom) {
                // Snap editor to top of the row container
                newTop = header.getOffsetHeight();
            } else if (newTop + gridTop > footerTop - editorHeight) {
                // Snap editor to the bottom of the row container
                newTop = footerTop - editorHeight - gridTop;
            }

            editorOverlay.getStyle().setTop(newTop, Unit.PX);
        }

        protected void setGridEnabled(boolean enabled) {
            // TODO: This should be informed to handler as well so possible
            // fields can be disabled.
            setButtonsEnabled(enabled);
        }

        private void setButtonsEnabled(boolean enabled) {
            saveButton.setEnabled(enabled);
            cancelButton.setEnabled(enabled);
        }

        public void setSaveCaption(String saveCaption) throws IllegalArgumentException {
            if (saveCaption == null) {
                throw new IllegalArgumentException("Save caption cannot be null");
            }
            saveButton.setText(saveCaption);
        }

        public String getSaveCaption() {
            return saveButton.getText();
        }

        public void setCancelCaption(String cancelCaption) throws IllegalArgumentException {
            if (cancelCaption == null) {
                throw new IllegalArgumentException("Cancel caption cannot be null");
            }
            cancelButton.setText(cancelCaption);
        }

        public String getCancelCaption() {
            return cancelButton.getText();
        }

        public void setEditorColumnError(Column<?, T> column, boolean hasError) {
            if (state != State.ACTIVE && state != State.SAVING) {
                throw new IllegalStateException(
                        "Cannot set cell error " + "status: editor is neither active nor saving.");
            }

            if (isEditorColumnError(column) == hasError) {
                return;
            }

            Element editorCell = getWidget(column).getElement().getParentElement();
            if (hasError) {
                editorCell.addClassName(ERROR_CLASS_NAME);
                columnErrors.add(column);
            } else {
                editorCell.removeClassName(ERROR_CLASS_NAME);
                columnErrors.remove(column);
            }
        }

        public void clearEditorColumnErrors() {

            /*
             * editorOverlay has no children if it's not active, effectively
             * making this loop a NOOP.
             */
            Element e = editorOverlay.getFirstChildElement();
            while (e != null) {
                e.removeClassName(ERROR_CLASS_NAME);
                e = e.getNextSiblingElement();
            }

            columnErrors.clear();
        }

        public boolean isEditorColumnError(Column<?, T> column) {
            return columnErrors.contains(column);
        }

        public void setBuffered(boolean buffered) {
            this.buffered = buffered;
            setMessageAndButtonsWrapperVisible(buffered);
        }

        public boolean isBuffered() {
            return buffered;
        }

        private void setMessageAndButtonsWrapperVisible(boolean visible) {
            if (visible) {
                messageAndButtonsWrapper.getStyle().clearDisplay();
            } else {
                messageAndButtonsWrapper.getStyle().setDisplay(Display.NONE);
            }
        }

        /**
         * Sets the event handler for this Editor.
         *
         * @since 7.6
         * @param handler
         *            the new event handler
         */
        public void setEventHandler(EventHandler<T> handler) {
            eventHandler = handler;
        }

        /**
         * Returns the event handler of this Editor.
         *
         * @since 7.6
         * @return the current event handler
         */
        public EventHandler<T> getEventHandler() {
            return eventHandler;
        }

        @Override
        public boolean isWorkPending() {
            return saveTimeout.isRunning() || bindTimeout.isRunning();
        }

        protected int getElementColumn(Element e) {
            int frozenCells = frozenCellWrapper.getChildCount();
            if (frozenCellWrapper.isOrHasChild(e)) {
                for (int i = 0; i < frozenCells; ++i) {
                    if (frozenCellWrapper.getChild(i).isOrHasChild(e)) {
                        return i;
                    }
                }
            }

            if (cellWrapper.isOrHasChild(e)) {
                for (int i = 0; i < cellWrapper.getChildCount(); ++i) {
                    if (cellWrapper.getChild(i).isOrHasChild(e)) {
                        return i + frozenCells;
                    }
                }
            }

            return -1;
        }
    }

    public abstract static class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
            extends KeyEvent<HANDLER> {

        /**
         * @since 7.7.9
         */
        public AbstractGridKeyEvent() {
        }

        /**
         * @deprecated This constructor's arguments are no longer used. Use the
         *             no-args constructor instead.
         */
        @Deprecated
        public AbstractGridKeyEvent(Grid<?> grid, CellReference<?> targetCell) {
        }

        protected abstract String getBrowserEventType();

        /**
         * Gets the Grid instance for this event, if it originated from a Grid.
         *
         * @return the grid this event originated from, or {@code null} if this
         *         event did not originate from a grid
         */
        public Grid<?> getGrid() {
            EventTarget target = getNativeEvent().getEventTarget();
            if (!Element.is(target)) {
                return null;
            }
            return WidgetUtil.findWidget(Element.as(target), Grid.class, false);
        }

        /**
         * Gets the reference of target cell for this event, if this event
         * originated from a Grid.
         *
         * @return target cell, or {@code null} if this event did not originate
         *         from a grid
         */
        public CellReference<?> getFocusedCell() {
            return getGrid().getEventCell();
        }

        @Override
        protected void dispatch(HANDLER handler) {
            EventTarget target = getNativeEvent().getEventTarget();
            Grid<?> grid = getGrid();
            if (Element.is(target) && grid != null) {
                final RowContainer container = Stream
                        .of(grid.escalator.getHeader(), grid.escalator.getBody(), grid.escalator.getFooter())
                        .filter(c -> c.getCell(target.cast()) != null).findFirst()
                        .orElse(grid.cellFocusHandler.containerWithFocus);

                Section section = Section.FOOTER;
                if (container == grid.escalator.getHeader()) {
                    section = Section.HEADER;
                } else if (container == getGrid().escalator.getBody()) {
                    section = Section.BODY;
                }

                // Don't handle event of child widget unless the column has been
                // explicitly permitted to do so
                if (grid.isElementInChildWidget(Element.as(target))) {
                    Cell cell = container.getCell(target.cast());
                    if (cell != null) {
                        Column<?, ?> column = grid.getVisibleColumn(cell.getColumn());
                        if (column == null || !column.isHandleWidgetEvents()) {
                            return;
                        }
                    }
                }

                doDispatch(handler, section);
            }
        }

        protected abstract void doDispatch(HANDLER handler, Section section);
    }

    public abstract static class AbstractGridMouseEvent<HANDLER extends AbstractGridMouseEventHandler>
            extends MouseEvent<HANDLER> {

        /**
         * @since 7.7.9
         */
        public AbstractGridMouseEvent() {
        }

        /**
         * @deprecated This constructor's arguments are no longer used. Use the
         *             no-args constructor instead.
         */
        @Deprecated
        public AbstractGridMouseEvent(Grid<?> grid, CellReference<?> targetCell) {
        }

        protected abstract String getBrowserEventType();

        /**
         * Gets the Grid instance for this event, if it originated from a Grid.
         *
         * @return the grid this event originated from, or {@code null} if this
         *         event did not originate from a grid
         */
        public Grid<?> getGrid() {
            EventTarget target = getNativeEvent().getEventTarget();
            if (!Element.is(target)) {
                return null;
            }
            return WidgetUtil.findWidget(Element.as(target), Grid.class, false);
        }

        /**
         * Gets the reference of target cell for this event, if this event
         * originated from a Grid.
         *
         * @return target cell, or {@code null} if this event did not originate
         *         from a grid
         */
        public CellReference<?> getTargetCell() {
            Grid<?> grid = getGrid();
            if (grid == null) {
                return null;
            }
            return grid.getEventCell();
        }

        @Override
        protected void dispatch(HANDLER handler) {
            EventTarget target = getNativeEvent().getEventTarget();
            if (!Element.is(target)) {
                // Target is not an element
                return;
            }

            Grid<?> grid = getGrid();
            if (grid == null) {
                // Target is not an element of a grid
                return;
            }

            Element targetElement = Element.as(target);
            if (ignoreEventFromTarget(grid, targetElement)) {
                // Event on this target should be ignored
                return;
            }

            final RowContainer container = grid.escalator.findRowContainer(targetElement);
            if (container == null) {
                // No container for given element
                return;
            }

            Section section = Section.FOOTER;
            if (container == grid.escalator.getHeader()) {
                section = Section.HEADER;
            } else if (container == grid.escalator.getBody()) {
                section = Section.BODY;
            }

            doDispatch(handler, section);
        }

        /**
         * Returns whether the mouse event on the target element should be
         * ignored.
         *
         * @param grid
         *            the {@code Grid} instance from which the event originated
         * @param targetElement
         *            the element from which the event originated
         * @return {@code true} if the event should be ignored, {@code false} if
         *         it should be handled
         * @since 8.2
         */
        protected boolean ignoreEventFromTarget(Grid<?> grid, Element targetElement) {
            boolean childWidget = grid.isElementInChildWidget(targetElement);
            boolean handleWidgetEvent = false;

            RowContainer container = grid.getEscalator().findRowContainer(targetElement);
            if (container != null) {
                Cell cell = container.getCell(targetElement);
                if (cell != null) {
                    Column<?, ?> column = grid.getVisibleColumn(cell.getColumn());
                    handleWidgetEvent = column != null && column.isHandleWidgetEvents();
                }
            }

            return childWidget && !handleWidgetEvent;
        }

        protected abstract void doDispatch(HANDLER handler, Section section);
    }

    private static final String CUSTOM_STYLE_PROPERTY_NAME = "customStyle";

    /**
     * An initial height that is given to new details rows before rendering the
     * appropriate widget that we then can be measure
     *
     * @see Grid.GridSpacerUpdater
     */
    private static final double DETAILS_ROW_INITIAL_HEIGHT = 50;

    private EventCellReference<T> eventCell = new EventCellReference<T>(this);

    private class CellFocusHandler {

        private RowContainer containerWithFocus = escalator.getBody();
        private int rowWithFocus = 0;
        private Range cellFocusRange = Range.withLength(0, 1);
        private int lastFocusedBodyRow = 0;
        private int lastFocusedHeaderRow = 0;
        private int lastFocusedFooterRow = 0;
        private TableCellElement cellWithFocusStyle = null;
        private TableRowElement rowWithFocusStyle = null;

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

        private Cell getFocusedCell() {
            return new Cell(rowWithFocus, cellFocusRange.getStart(), cellWithFocusStyle);
        }

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

            if (cellContainer == containerWithFocus) {
                // Cell is in the current container
                if (cellRow == rowWithFocus && columnHasFocus) {
                    if (cellWithFocusStyle != cell.getElement()) {
                        // Cell is correct but it does not have focused style
                        if (cellWithFocusStyle != null) {
                            // Remove old focus style
                            setStyleName(cellWithFocusStyle, cellFocusStyleName, false);
                        }
                        cellWithFocusStyle = cell.getElement();

                        // Add focus style to correct cell.
                        setStyleName(cellWithFocusStyle, cellFocusStyleName, true);
                    }
                } else if (cellWithFocusStyle == cell.getElement()) {
                    // Due to escalator reusing cells, a new cell has the same
                    // element but is not the focused cell.
                    setStyleName(cellWithFocusStyle, cellFocusStyleName, false);
                    cellWithFocusStyle = null;
                }
            }
        }

        /**
         * Sets focus style for the given row if needed.
         *
         * @param row
         *            a row object
         */
        public void updateFocusedRowStyle(Row row) {
            if (rowWithFocus == row.getRow() && containerWithFocus == escalator.getBody()) {
                if (row.getElement() != rowWithFocusStyle) {
                    // Row should have focus style but does not have it.
                    if (rowWithFocusStyle != null) {
                        setStyleName(rowWithFocusStyle, rowFocusStyleName, false);
                    }
                    rowWithFocusStyle = row.getElement();
                    setStyleName(rowWithFocusStyle, rowFocusStyleName, true);
                }
            } else if (rowWithFocusStyle == row.getElement()
                    || containerWithFocus != escalator.getBody() && rowWithFocusStyle != null) {
                // Remove focus style.
                setStyleName(rowWithFocusStyle, rowFocusStyleName, false);
                rowWithFocusStyle = null;
            }
        }

        /**
         * Sets the currently focused.
         * <p>
         * <em>NOTE:</em> the column index is the index in DOM, not the logical
         * column index which includes hidden columns.
         *
         * @param rowIndex
         *            the index of the row having focus
         * @param columnIndexDOM
         *            the index of the cell having focus
         * @param container
         *            the row container having focus
         */
        private void setCellFocus(int rowIndex, int columnIndexDOM, RowContainer container) {
            if (container == null || rowIndex == rowWithFocus && cellFocusRange.contains(columnIndexDOM)
                    && container == this.containerWithFocus) {
                return;
            }

            int oldRow = rowWithFocus;
            rowWithFocus = rowIndex;
            Range oldRange = cellFocusRange;

            if (container == escalator.getBody()) {
                scrollToRow(rowWithFocus);
                cellFocusRange = Range.withLength(columnIndexDOM, 1);
            } else {
                int i = 0;
                Element cell = container.getRowElement(rowWithFocus).getFirstChildElement();
                do {
                    int colSpan = cell.getPropertyInt(FlyweightCell.COLSPAN_ATTR);
                    Range cellRange = Range.withLength(i, colSpan);
                    if (cellRange.contains(columnIndexDOM)) {
                        cellFocusRange = cellRange;
                        break;
                    }
                    cell = cell.getNextSiblingElement();
                    ++i;
                } while (cell != null);
            }
            if (columnIndexDOM >= escalator.getColumnConfiguration().getFrozenColumnCount()) {
                escalator.scrollToColumn(columnIndexDOM, ScrollDestination.ANY, 10);
            }

            if (this.containerWithFocus == container) {
                if (oldRange.equals(cellFocusRange) && oldRow != rowWithFocus) {
                    refreshRow(oldRow);
                } else {
                    refreshHeader();
                    refreshFooter();
                }
            } else {
                RowContainer oldContainer = this.containerWithFocus;
                this.containerWithFocus = container;

                if (oldContainer == escalator.getBody()) {
                    lastFocusedBodyRow = oldRow;
                } else if (oldContainer == escalator.getHeader()) {
                    lastFocusedHeaderRow = oldRow;
                } else {
                    lastFocusedFooterRow = oldRow;
                }

                if (!oldRange.equals(cellFocusRange)) {
                    refreshHeader();
                    refreshFooter();
                    if (oldContainer == escalator.getBody()) {
                        oldContainer.refreshRows(oldRow, 1);
                    }
                } else {
                    oldContainer.refreshRows(oldRow, 1);
                }
            }
            refreshRow(rowWithFocus);
        }

        /**
         * Sets focus on a cell.
         *
         * <p>
         * <em>Note</em>: cell focus is not the same as JavaScript's
         * {@code document.activeElement}.
         *
         * @param cell
         *            a cell object
         */
        public void setCellFocus(CellReference<T> cell) {
            setCellFocus(cell.getRowIndex(), cell.getColumnIndexDOM(),
                    escalator.findRowContainer(cell.getElement()));
        }

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

        /**
         * Handle events that can move the cell focus.
         */
        public void handleNavigationEvent(Event event, CellReference<T> cell) {
            if (event.getType().equals(BrowserEvents.CLICK)) {
                setCellFocus(cell);
                // Grid should have focus when clicked.
                getElement().focus();
            } else if (event.getType().equals(BrowserEvents.KEYDOWN)) {
                int newRow = rowWithFocus;
                RowContainer newContainer = containerWithFocus;
                int newColumn = cellFocusRange.getStart();

                switch (event.getKeyCode()) {
                case KeyCodes.KEY_DOWN:
                    ++newRow;
                    break;
                case KeyCodes.KEY_UP:
                    --newRow;
                    break;
                case KeyCodes.KEY_RIGHT:
                    if (cellFocusRange.getEnd() >= getVisibleColumns().size()) {
                        return;
                    }
                    newColumn = cellFocusRange.getEnd();
                    break;
                case KeyCodes.KEY_LEFT:
                    if (newColumn == 0) {
                        return;
                    }
                    --newColumn;
                    break;
                case KeyCodes.KEY_TAB:
                    if (event.getShiftKey()) {
                        newContainer = getPreviousContainer(containerWithFocus);
                    } else {
                        newContainer = getNextContainer(containerWithFocus);
                    }

                    if (newContainer == containerWithFocus) {
                        return;
                    }
                    break;
                case KeyCodes.KEY_HOME:
                    if (newContainer.getRowCount() > 0) {
                        newRow = 0;
                    }
                    break;
                case KeyCodes.KEY_END:
                    if (newContainer.getRowCount() > 0) {
                        newRow = newContainer.getRowCount() - 1;
                    }
                    break;
                case KeyCodes.KEY_PAGEDOWN:
                case KeyCodes.KEY_PAGEUP:
                    if (newContainer.getRowCount() > 0) {
                        boolean down = event.getKeyCode() == KeyCodes.KEY_PAGEDOWN;
                        // If there is a visible focused cell, scroll by one
                        // page from its position. Otherwise, use the first or
                        // the last visible row as the scroll start position.
                        // This avoids jumping when using both keyboard and the
                        // scroll bar for scrolling.
                        int firstVisible = getFirstVisibleRowIndex();
                        int lastVisible = getLastVisibleRowIndex();
                        if (newRow < firstVisible || newRow > lastVisible) {
                            newRow = down ? lastVisible : firstVisible;
                        }
                        // Scroll by a little less than the visible area to
                        // account for the possibility that the top and the
                        // bottom row are only partially visible.
                        int moveFocusBy = Math.max(1, lastVisible - firstVisible - 1);
                        moveFocusBy *= down ? 1 : -1;
                        newRow += moveFocusBy;
                        newRow = Math.max(0, Math.min(newContainer.getRowCount() - 1, newRow));
                    }
                    break;
                default:
                    return;
                }

                if (newContainer != containerWithFocus) {
                    if (newContainer == escalator.getBody()) {
                        newRow = lastFocusedBodyRow;
                    } else if (newContainer == escalator.getHeader()) {
                        newRow = lastFocusedHeaderRow;
                    } else {
                        newRow = lastFocusedFooterRow;
                    }
                } else if (newRow < 0) {
                    newContainer = getPreviousContainer(newContainer);

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

                    if (newContainer == containerWithFocus) {
                        newRow = containerWithFocus.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
                     * focused cell.
                     */
                    return;
                }

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

                setCellFocus(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) {
            containerWithFocus.refreshRows(row, 1);
        }

        /**
         * Offsets the focused cell's range.
         *
         * @param offset
         *            offset for fixing focused cell's range
         */
        public void offsetRangeBy(int offset) {
            cellFocusRange = cellFocusRange.offsetBy(offset);
        }

        /**
         * Informs {@link CellFocusHandler} that certain range of rows has been
         * added to the Grid body. {@link CellFocusHandler} will fix indices
         * accordingly.
         *
         * @param added
         *            a range of added rows
         */
        public void rowsAddedToBody(Range added) {
            boolean bodyHasFocus = containerWithFocus == escalator.getBody();
            boolean insertionIsAboveFocusedCell = added.getStart() < rowWithFocus;
            if (bodyHasFocus && insertionIsAboveFocusedCell) {
                rowWithFocus += added.length();
                rowWithFocus = Math.min(rowWithFocus, escalator.getBody().getRowCount() - 1);
                refreshRow(rowWithFocus);
            }
        }

        /**
         * Informs {@link CellFocusHandler} that certain range of rows has been
         * removed from the Grid body. {@link CellFocusHandler} will fix indices
         * accordingly.
         *
         * @param removed
         *            a range of removed rows
         */
        public void rowsRemovedFromBody(Range removed) {
            if (containerWithFocus != escalator.getBody()) {
                return;
            } else if (!removed.contains(rowWithFocus)) {
                if (removed.getStart() > rowWithFocus) {
                    return;
                }
                rowWithFocus = rowWithFocus - removed.length();
            } else {
                if (containerWithFocus.getRowCount() > removed.getEnd()) {
                    rowWithFocus = removed.getStart();
                } else if (removed.getStart() > 0) {
                    rowWithFocus = removed.getStart() - 1;
                } else {
                    if (escalator.getHeader().getRowCount() > 0) {
                        rowWithFocus = Math.min(lastFocusedHeaderRow, escalator.getHeader().getRowCount() - 1);
                        containerWithFocus = escalator.getHeader();
                    } else if (escalator.getFooter().getRowCount() > 0) {
                        rowWithFocus = Math.min(lastFocusedFooterRow, escalator.getFooter().getRowCount() - 1);
                        containerWithFocus = escalator.getFooter();
                    }
                }
            }
            refreshRow(rowWithFocus);
        }
    }

    public final class SelectionColumn extends Column<Boolean, T>
            implements GridEnabledHandler, GridSelectionAllowedHandler {

        private boolean initDone = false;
        private boolean selected = false;
        private CheckBox selectAllCheckBox;
        private boolean selectAllCheckBoxVisible;
        private HeaderCell selectionCell;

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

            addEnabledHandler(this);
            addSelectionAllowedHandler(this);
        }

        void initDone() {
            setWidth(-1);

            setEditable(false);
            setResizable(false);
            updateEnable();

            initDone = true;
        }

        @Override
        protected void setDefaultHeaderContent(HeaderCell selectionCell) {
            this.selectionCell = selectionCell;

            if (selectAllCheckBox == null) {
                // there is no checkbox yet -> create it
                selectAllCheckBox = GWT.create(CheckBox.class);
                selectAllCheckBox.setStylePrimaryName(getStylePrimaryName() + SELECT_ALL_CHECKBOX_CLASSNAME);
                // label of checkbox should only be visible for assistive
                // devices
                selectAllCheckBox.addStyleName("v-assistive-device-only-label");
                selectAllCheckBox.addValueChangeHandler(event -> {
                    selected = event.getValue();
                    fireEvent(new SelectAllEvent<>(getSelectionModel(), selected));
                });
                selectAllCheckBox.setText("Selects all rows of the table.");
                selectAllCheckBox.setValue(selected);

                addHeaderClickHandler(this::onHeaderClickEvent);

                // Select all with space when "select all" cell is active
                addHeaderKeyUpHandler(this::onHeaderKeyUpEvent);

            } else {
                // checkbox exists, but default header row has changed -> clear
                // rows
                for (HeaderRow row : header.getRows()) {
                    if (row.getCell(this).getType() == GridStaticCellType.WIDGET) {
                        // Detach from old header.
                        row.getCell(this).setText("");
                    }
                }
            }

            // attach the checkbox to default row depending on visibility
            doSetSelectAllCheckBoxVisible();
        }

        @Override
        public Column<Boolean, T> setWidth(double pixels) {
            if (pixels != getWidth() && initDone) {
                throw new UnsupportedOperationException("The selection " + "column cannot be modified after init");
            } else {
                super.setWidth(pixels);
            }

            return this;
        }

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

        @Override
        public Column<Boolean, T> setExpandRatio(int ratio) {
            throw new UnsupportedOperationException("can't change the expand ratio of the selection column");
        }

        @Override
        public int getExpandRatio() {
            return 0;
        }

        @Override
        public Column<Boolean, T> setMaximumWidth(double pixels) {
            throw new UnsupportedOperationException("can't change the maximum width of the selection column");
        }

        @Override
        public double getMaximumWidth() {
            return -1;
        }

        @Override
        public Column<Boolean, T> setMinimumWidth(double pixels) {
            throw new UnsupportedOperationException("can't change the minimum width of the selection column");
        }

        @Override
        public double getMinimumWidth() {
            return -1;
        }

        @Override
        public Column<Boolean, T> setEditable(boolean editable) {
            if (initDone) {
                throw new UnsupportedOperationException("can't set the selection column editable");
            }
            super.setEditable(editable);
            return this;
        }

        @Override
        public void onEnabled(boolean enabled) {
            updateEnable();
        }

        /**
         * Sets the select all checkbox visible in the default header row for
         * selection column.
         *
         * @param selectAllCheckBoxVisible
         *            {@code true} for visible, {@code false} for not
         */
        public void setSelectAllCheckBoxVisible(boolean selectAllCheckBoxVisible) {
            if (this.selectAllCheckBoxVisible != selectAllCheckBoxVisible) {
                this.selectAllCheckBoxVisible = selectAllCheckBoxVisible;
                doSetSelectAllCheckBoxVisible();
            }
        }

        /**
         * Returns whether the select all checkbox is visible or not.
         *
         * @return {@code true} for visible, {@code false} for not
         */
        public boolean isSelectAllCheckBoxVisible() {
            return selectAllCheckBoxVisible;
        }

        /**
         * Returns the select all checkbox, which is present in the default
         * header if the used selection model is of type
         * {@link SelectionModelWithSelectionColumn}.
         *
         * To handle select all, add {@link SelectAllHandler} the grid with
         * {@link #addSelectAllHandler(SelectAllHandler)}.
         *
         * @return the select all checkbox, or an empty optional if not in use
         */
        public Optional<CheckBox> getSelectAllCheckBox() {
            return Optional.ofNullable(selectionColumn == null ? null : selectionColumn.selectAllCheckBox);
        }

        /**
         * Sets the select all checkbox visible or hidden.
         */
        protected void doSetSelectAllCheckBoxVisible() {
            if (selectAllCheckBox == null || selectionCell == null) {
                // There is no default header row to display select all checkbox
                return;
            }

            if (selectAllCheckBoxVisible) {
                selectionCell.setWidget(selectAllCheckBox);
            } else {
                selectionCell.setText("");
            }
        }

        private void updateEnable() {
            if (selectAllCheckBox != null) {
                selectAllCheckBox.setEnabled(isEnabled() && getSelectionModel().isSelectionAllowed());
            }
        }

        private void onHeaderClickEvent(GridClickEvent event) {
            if (selectAllCheckBox.isEnabled()) {
                CellReference<?> targetCell = event.getTargetCell();
                int defaultRowIndex = getHeader().getRows().indexOf(getDefaultHeaderRow());

                if (targetCell.getColumnIndex() == 0 && targetCell.getRowIndex() == defaultRowIndex) {
                    selectAllCheckBox.setValue(!selectAllCheckBox.getValue(), true);
                }
            }
        }

        private void onHeaderKeyUpEvent(GridKeyUpEvent event) {
            if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE || !selectAllCheckBox.isEnabled()) {
                return;
            }
            HeaderRow targetHeaderRow = getHeader().getRow(event.getFocusedCell().getRowIndex());
            if (!targetHeaderRow.isDefault()) {
                return;
            }
            if (event.getFocusedCell().getColumn() == SelectionColumn.this) {
                // Send events to ensure state is updated
                selectAllCheckBox.setValue(!selectAllCheckBox.getValue(), true);
            }
        }

        @Override
        public void onSelectionAllowed(GridSelectionAllowedEvent event) {
            updateEnable();
        }

    }

    /**
     * Helper class for performing sorting through the user interface. Controls
     * the sort() method, reporting USER as the event originator. This is a
     * completely internal class, and is, as such, safe to re-name should a more
     * descriptive name come to mind.
     */
    private final class UserSorter {

        private final Timer timer;
        private boolean scheduledMultisort;
        private Column<?, T> column;

        private UserSorter() {
            timer = new Timer() {

                @Override
                public void run() {
                    scheduledMultisort = true;
                }
            };
        }

        /**
         * Toggle sorting for a cell. If the multisort parameter is set to true,
         * the cell's sort order is modified as a natural part of a multi-sort
         * chain. If false, the sorting order is set to ASCENDING for that
         * cell's column. If that column was already the only sorted column in
         * the Grid, the sort direction is flipped.
         *
         * @param cell
         *            a valid cell reference
         * @param multisort
         *            whether the sort command should act as a multi-sort stack
         *            or not
         */
        public void sort(Column<?, ?> column, boolean multisort) {

            if (!columns.contains(column)) {
                throw new IllegalArgumentException("Given column is not a column in this grid. " + column);
            }

            if (!column.isSortable()) {
                return;
            }

            final SortOrder so = getSortOrder(column);

            if (multisort) {

                // If the sort order exists, replace existing value with its
                // opposite
                if (so != null) {
                    final int idx = sortOrder.indexOf(so);
                    sortOrder.set(idx, so.getOpposite());
                } else {
                    // If it doesn't, just add a new sort order to the end of
                    // the list
                    sortOrder.add(new SortOrder(column));
                }

            } else {

                // Since we're doing single column sorting, first clear the
                // list. Then, if the sort order existed, add its opposite,
                // otherwise just add a new sort value

                int items = sortOrder.size();
                sortOrder.clear();
                if (so != null && items == 1) {
                    sortOrder.add(so.getOpposite());
                } else {
                    sortOrder.add(new SortOrder(column));
                }
            }

            // sortOrder has been changed; tell the Grid to re-sort itself by
            // user request.
            Grid.this.sort(true);
        }

        /**
         * Invoked on touchstart, marks itself that we will perform sorting on
         * touchend. By default single sort is performed, however on long touch
         * we will perform multitouch.
         *
         * Actual sorting is only performed after {@link #onTouchEnd()} is
         * invoked.
         *
         * @param delay
         *            delay, in milliseconds
         */
        public void awaitForTouchEnd(int delay) {
            cancelAwaitForTouchEnd();
            column = eventCell.getColumn();
            scheduledMultisort = false;
            timer.schedule(delay);
        }

        /**
         * Notifies that the finger has been lifted from the tablet/mobile.
         * Depending on how much time has passed, we need to perform singlesort
         * or multisort.
         *
         * Does nothing if the await has been canceled by a call to
         * {@link #cancelAwaitForTouchEnd()}.
         */
        public void onTouchEnd() {
            if (column != null) {
                sort(column, scheduledMultisort);
                cancelAwaitForTouchEnd();
            }
        }

        /**
         * Cancel a scheduled sort.
         */
        public void cancelAwaitForTouchEnd() {
            timer.cancel();
            column = null;
        }

    }

    /**
     * @see Grid#autoColumnWidthsRecalculator
     */
    private class AutoColumnWidthsRecalculator {
        private double lastCalculatedInnerWidth = -1;
        private double lastCalculatedInnerHeight = -1;

        private final ScheduledCommand calculateCommand = new ScheduledCommand() {

            @Override
            public void execute() {
                if (!isScheduled) {
                    // something cancelled running this.
                    return;
                }

                if (header.markAsDirty || footer.markAsDirty) {
                    if (rescheduleCount < 10) {
                        /*
                         * Headers and footers are rendered as finally, this way
                         * we re-schedule this loop as finally, at the end of
                         * the queue, so that the headers have a chance to
                         * render themselves.
                         */
                        Scheduler.get().scheduleFinally(this);
                        rescheduleCount++;
                    } else {
                        /*
                         * We've tried too many times reschedule finally. Seems
                         * like something is being deferred. Let the queue
                         * execute and retry again.
                         */
                        rescheduleCount = 0;
                        Scheduler.get().scheduleDeferred(this);
                    }
                } else if (currentDataAvailable.isEmpty() && dataSource.isWaitingForData()) {
                    Scheduler.get().scheduleDeferred(this);
                } else {
                    calculate();
                }
            }
        };

        private int rescheduleCount = 0;
        private boolean isScheduled;

        /**
         * Calculates and applies column widths, taking into account fixed
         * widths and column expand rules
         *
         * @param immediately
         *            <code>true</code> if the widths should be executed
         *            immediately (ignoring lazy loading completely), or
         *            <code>false</code> if the command should be run after a
         *            while (duplicate non-immediately invocations are ignored).
         * @see Column#setWidth(double)
         * @see Column#setExpandRatio(int)
         * @see Column#setMinimumWidth(double)
         * @see Column#setMaximumWidth(double)
         */
        public void schedule() {
            if (!isScheduled && isAttached()) {
                isScheduled = true;
                Scheduler.get().scheduleFinally(calculateCommand);
            }
        }

        private void calculate() {
            isScheduled = false;
            rescheduleCount = 0;

            assert !(currentDataAvailable.isEmpty() && dataSource
                    .isWaitingForData()) : "Trying to calculate column widths without data while data is still being fetched.";

            // Make SelectAllCheckbox visible
            getSelectionColumn().ifPresent(col -> {
                if (getDefaultHeaderRow() == null)
                    return;
                HeaderCell headerCell = getDefaultHeaderRow().getCell(col);
                if (headerCell.getType().equals(GridStaticCellType.WIDGET)) {
                    // SelectAllCheckbox is present already
                    return;
                }
                headerCell.setWidget(col.selectAllCheckBox);
                refreshHeader(); // Paint.
            });

            if (columnsAreGuaranteedToBeWiderThanGrid()) {
                applyColumnWidths();
            } else {
                applyColumnWidthsWithExpansion();
            }

            // Hide the SelectAllCheckbox if needed
            getSelectionColumn().ifPresent(SelectionColumn::doSetSelectAllCheckBoxVisible);

            // Update latest width to prevent recalculate on height change.
            lastCalculatedInnerWidth = escalator.getInnerWidth();
            lastCalculatedInnerHeight = getEscalatorInnerHeight();
        }

        private boolean columnsAreGuaranteedToBeWiderThanGrid() {
            double freeSpace = escalator.getInnerWidth();
            for (Column<?, ?> column : getVisibleColumns()) {
                if (column.getWidth() >= 0) {
                    freeSpace -= column.getWidth();
                } else if (column.getMinimumWidth() >= 0) {
                    freeSpace -= column.getMinimumWidth();
                }
            }
            return freeSpace < 0;
        }

        @SuppressWarnings("boxing")
        private void applyColumnWidths() {

            /* Step 1: Apply all column widths as they are. */

            Map<Integer, Double> selfWidths = new LinkedHashMap<>();
            List<Column<?, T>> columns = getVisibleColumns();
            for (int index = 0; index < columns.size(); index++) {
                selfWidths.put(index, columns.get(index).getWidth());
            }
            Grid.this.escalator.getColumnConfiguration().setColumnWidths(selfWidths);

            /*
             * Step 2: Make sure that each column ends up obeying their min/max
             * width constraints if defined as autowidth. If constraints are
             * violated, fix it.
             */

            Map<Integer, Double> constrainedWidths = new LinkedHashMap<>();
            for (int index = 0; index < columns.size(); index++) {
                Column<?, T> column = columns.get(index);

                boolean hasAutoWidth = column.getWidth() < 0;
                if (!hasAutoWidth) {
                    continue;
                }

                // TODO: bug: these don't honor the CSS max/min. :(
                double actualWidth = column.getWidthActual();
                if (actualWidth < getMinWidth(column)) {
                    constrainedWidths.put(index, column.getMinimumWidth());
                } else if (actualWidth > getMaxWidth(column)) {
                    constrainedWidths.put(index, column.getMaximumWidth());
                }
            }
            Grid.this.escalator.getColumnConfiguration().setColumnWidths(constrainedWidths);
        }

        private void applyColumnWidthsWithExpansion() {
            boolean defaultExpandRatios = true;
            int totalRatios = 0;
            double reservedPixels = 0;
            final Set<Column<?, T>> columnsToExpand = new HashSet<>();
            List<Column<?, T>> nonFixedColumns = new ArrayList<>();
            Map<Integer, Double> columnSizes = new HashMap<>();
            final List<Column<?, T>> visibleColumns = getVisibleColumns();

            /*
             * Set all fixed widths and also calculate the size-to-fit widths
             * for the autocalculated columns.
             *
             * This way we know with how many pixels we have left to expand the
             * rest.
             */
            for (Column<?, T> column : visibleColumns) {
                final double widthAsIs = column.getWidth();
                final boolean isFixedWidth = widthAsIs >= 0;
                // Check for max width just to be sure we don't break the limits
                final double widthFixed = Math.max(Math.min(getMaxWidth(column), widthAsIs),
                        column.getMinimumWidth());
                defaultExpandRatios = defaultExpandRatios
                        && (column.getExpandRatio() == -1 || column == selectionColumn);

                if (isFixedWidth) {
                    columnSizes.put(visibleColumns.indexOf(column), widthFixed);
                    reservedPixels += widthFixed;
                } else {
                    nonFixedColumns.add(column);
                    columnSizes.put(visibleColumns.indexOf(column), -1.0d);
                }
            }

            setColumnSizes(columnSizes);

            for (Column<?, T> column : nonFixedColumns) {
                final int expandRatio = defaultExpandRatios ? 1 : column.getExpandRatio();
                final double maxWidth = getMaxWidth(column);
                double newWidth;
                if (column.isMinimumWidthFromContent()) {
                    newWidth = Math.min(maxWidth, column.getWidthActual());
                } else {
                    newWidth = 0;
                }

                boolean shouldExpand = newWidth < maxWidth && expandRatio > 0 && column != selectionColumn;
                if (shouldExpand) {
                    totalRatios += expandRatio;
                    columnsToExpand.add(column);
                }
                reservedPixels += newWidth;
                columnSizes.put(visibleColumns.indexOf(column), newWidth);
            }

            /*
             * Now that we know how many pixels we need at the very least, we
             * can distribute the remaining pixels to all columns according to
             * their expand ratios.
             */
            double pixelsToDistribute = escalator.getInnerWidth() - reservedPixels;
            if (pixelsToDistribute <= 0 || totalRatios <= 0) {
                if (pixelsToDistribute <= 0) {
                    // Set column sizes for expanding columns
                    setColumnSizes(columnSizes);
                }

                return;
            }

            /*
             * Check for columns that hit their max width. Adjust
             * pixelsToDistribute and totalRatios accordingly. Recheck. Stop
             * when no new columns hit their max width
             */
            boolean aColumnHasMaxedOut;
            do {
                aColumnHasMaxedOut = false;
                final double widthPerRatio = pixelsToDistribute / totalRatios;
                final Iterator<Column<?, T>> i = columnsToExpand.iterator();
                while (i.hasNext()) {
                    final Column<?, T> column = i.next();
                    final int expandRatio = getExpandRatio(column, defaultExpandRatios);
                    final int columnIndex = visibleColumns.indexOf(column);
                    final double autoWidth = columnSizes.get(columnIndex);
                    final double maxWidth = getMaxWidth(column);
                    double expandedWidth = autoWidth + widthPerRatio * expandRatio;

                    if (maxWidth <= expandedWidth) {
                        i.remove();
                        totalRatios -= expandRatio;
                        aColumnHasMaxedOut = true;
                        pixelsToDistribute -= maxWidth - autoWidth;
                        columnSizes.put(columnIndex, maxWidth);
                    }
                }
            } while (aColumnHasMaxedOut);

            if (totalRatios <= 0 && columnsToExpand.isEmpty()) {
                setColumnSizes(columnSizes);
                return;
            }
            assert pixelsToDistribute > 0 : "We've run out of pixels to distribute (" + pixelsToDistribute
                    + "px to " + totalRatios + " ratios between " + columnsToExpand.size() + " columns)";
            assert totalRatios > 0 && !columnsToExpand.isEmpty() : "Bookkeeping out of sync. Ratios: " + totalRatios
                    + " Columns: " + columnsToExpand.size();

            /*
             * If we still have anything left, distribute the remaining pixels
             * to the remaining columns.
             */
            final double widthPerRatio;
            int leftOver = 0;
            if (BrowserInfo.getBrowserString().contains("PhantomJS")) {
                // These browsers report subpixels as integers. this usually
                // results into issues..
                widthPerRatio = (int) (pixelsToDistribute / totalRatios);
                leftOver = (int) (pixelsToDistribute - widthPerRatio * totalRatios);
            } else {
                widthPerRatio = pixelsToDistribute / totalRatios;
            }
            for (Column<?, T> column : columnsToExpand) {
                final int expandRatio = getExpandRatio(column, defaultExpandRatios);
                final int columnIndex = visibleColumns.indexOf(column);
                final double autoWidth = columnSizes.get(columnIndex);
                double totalWidth = autoWidth + widthPerRatio * expandRatio;
                if (leftOver > 0) {
                    totalWidth += 1;
                    leftOver--;
                }
                columnSizes.put(columnIndex, totalWidth);

                totalRatios -= expandRatio;
            }
            assert totalRatios == 0 : "Bookkeeping error: there were still some ratios left undistributed: "
                    + totalRatios;

            /*
             * Check the guarantees for minimum width and scoot back the columns
             * that don't care.
             */
            boolean minWidthsCausedReflows;
            do {
                minWidthsCausedReflows = false;

                /*
                 * First, let's check which columns were too cramped, and expand
                 * them. Also keep track on how many pixels we grew - we need to
                 * remove those pixels from other columns
                 */
                double pixelsToRemoveFromOtherColumns = 0;
                for (Column<?, T> column : visibleColumns) {
                    /*
                     * We can't iterate over columnsToExpand, even though that
                     * would be convenient. This is because some column without
                     * an expand ratio might still have a min width - those
                     * wouldn't show up in that set.
                     */

                    double minWidth = getMinWidth(column);
                    final int columnIndex = visibleColumns.indexOf(column);
                    double currentWidth = columnSizes.get(columnIndex);
                    boolean hasAutoWidth = column.getWidth() < 0;
                    if (hasAutoWidth && currentWidth < minWidth) {
                        columnSizes.put(columnIndex, minWidth);
                        pixelsToRemoveFromOtherColumns += minWidth - currentWidth;
                        minWidthsCausedReflows = true;

                        /*
                         * Remove this column form the set if it exists. This
                         * way we make sure that it doesn't get shrunk in the
                         * next step.
                         */
                        columnsToExpand.remove(column);
                    }
                }

                /*
                 * Now we need to shrink the remaining columns according to
                 * their ratios. Recalculate the sum of remaining ratios.
                 */
                totalRatios = 0;
                for (Column<?, ?> column : columnsToExpand) {
                    totalRatios += getExpandRatio(column, defaultExpandRatios);
                }
                final double pixelsToRemovePerRatio = pixelsToRemoveFromOtherColumns / totalRatios;
                for (Column<?, T> column : columnsToExpand) {
                    final double pixelsToRemove = pixelsToRemovePerRatio
                            * getExpandRatio(column, defaultExpandRatios);
                    int colIndex = visibleColumns.indexOf(column);
                    columnSizes.put(colIndex, columnSizes.get(colIndex) - pixelsToRemove);
                }

            } while (minWidthsCausedReflows);

            // Finally set all the column sizes.
            setColumnSizes(columnSizes);
        }

        private void setColumnSizes(Map<Integer, Double> columnSizes) {
            // Set all widths at once
            escalator.getColumnConfiguration().setColumnWidths(columnSizes);
        }

        private int getExpandRatio(Column<?, ?> column, boolean defaultExpandRatios) {
            int expandRatio = column.getExpandRatio();
            if (expandRatio > 0) {
                return expandRatio;
            } else if (expandRatio < 0) {
                assert defaultExpandRatios : "No columns should've expanded";
                return 1;
            } else {
                assert false : "this method should've not been called at all if expandRatio is 0";
                return 0;
            }
        }

        /**
         * Returns the maximum width of the column, or {@link Double#MAX_VALUE}
         * if defined as negative.
         */
        private double getMaxWidth(Column<?, ?> column) {
            double maxWidth = column.getMaximumWidth();
            if (maxWidth >= 0) {
                return maxWidth;
            } else {
                return Double.MAX_VALUE;
            }
        }

        /**
         * Returns the minimum width of the column, or {@link Double#MIN_VALUE}
         * if defined as negative.
         */
        private double getMinWidth(Column<?, ?> column) {
            double minWidth = column.getMinimumWidth();
            if (minWidth >= 0) {
                return minWidth;
            } else {
                return Double.MIN_VALUE;
            }
        }

        /**
         * Check whether the auto width calculation is currently scheduled.
         *
         * @return <code>true</code> if auto width calculation is currently
         *         scheduled
         */
        public boolean isScheduled() {
            return isScheduled;
        }
    }

    private class GridSpacerUpdater implements SpacerUpdater {

        private static final String STRIPE_CLASSNAME = "stripe";

        private final Map<Element, Widget> elementToWidgetMap = new HashMap<>();

        @Override
        public void init(Spacer spacer) {
            initTheming(spacer);

            int rowIndex = spacer.getRow();

            Widget detailsWidget = null;
            try {
                detailsWidget = detailsGenerator.getDetails(rowIndex);
            } catch (Throwable e) {
                getLogger().log(Level.SEVERE, "Exception while generating details for row " + rowIndex, e);
            }

            final double spacerHeight;
            Element spacerElement = spacer.getElement();
            if (detailsWidget == null) {
                spacerElement.removeAllChildren();
                spacerHeight = DETAILS_ROW_INITIAL_HEIGHT;
            } else {
                Element element = detailsWidget.getElement();
                spacerElement.appendChild(element);
                setParent(detailsWidget, Grid.this);
                Widget previousWidget = elementToWidgetMap.put(element, detailsWidget);

                assert previousWidget == null : "Overwrote a pre-existing widget on row " + rowIndex
                        + " without proper removal first.";

                /*
                 * Once we have the content properly inside the DOM, we should
                 * re-measure it to make sure that it's the correct height.
                 *
                 * This is rather tricky, since the row (tr) will get the
                 * height, but the spacer cell (td) has the borders, which
                 * should go on top of the previous row and next row.
                 */
                final double contentHeight;
                if (detailsGenerator instanceof HeightAwareDetailsGenerator) {
                    HeightAwareDetailsGenerator sadg = (HeightAwareDetailsGenerator) detailsGenerator;
                    contentHeight = sadg.getDetailsHeight(rowIndex);
                } else {
                    contentHeight = WidgetUtil.getRequiredHeightBoundingClientRectDouble(element);
                }
                double borderTopAndBottomHeight = WidgetUtil.getBorderTopAndBottomThickness(spacerElement);
                double measuredHeight = 0d;
                if (contentHeight > 0) {
                    measuredHeight = contentHeight + borderTopAndBottomHeight;
                } else {
                    Scheduler.get().scheduleFinally(() -> {
                        // make sure the spacer hasn't got removed
                        if (spacer.getElement().getParentElement() != null) {
                            // re-check the height
                            double confirmedContentHeight = WidgetUtil
                                    .getRequiredHeightBoundingClientRectDouble(element);
                            if (confirmedContentHeight > 0) {
                                double confirmedMeasuredHeight = confirmedContentHeight
                                        + WidgetUtil.getBorderTopAndBottomThickness(spacer.getElement());
                                escalator.getBody().setSpacer(spacer.getRow(), confirmedMeasuredHeight);
                                if (getHeightMode() == HeightMode.UNDEFINED) {
                                    setHeightByRows(getEscalator().getBody().getRowCount());
                                }
                            }
                        }
                    });
                }
                assert getElement().isOrHasChild(
                        spacerElement) : "The spacer element wasn't in the DOM during measurement, but was assumed to be.";
                spacerHeight = measuredHeight;
            }

            escalator.getBody().setSpacer(rowIndex, spacerHeight);
            if (getHeightMode() == HeightMode.UNDEFINED) {
                setHeightByRows(getEscalator().getBody().getRowCount());
            }
        }

        @Override
        public void destroy(Spacer spacer) {
            Element spacerElement = spacer.getElement();

            assert getElement().isOrHasChild(spacerElement) : "Trying "
                    + "to destroy a spacer that is not connected to this " + "Grid's DOM. (row: " + spacer.getRow()
                    + ", element: " + spacerElement + ")";

            Widget detailsWidget = elementToWidgetMap.remove(spacerElement.getFirstChildElement());

            if (detailsWidget != null) {
                /*
                 * The widget may be null here if the previous generator
                 * returned a null widget.
                 */

                assert spacerElement.getFirstChild() != null : "The "
                        + "details row to destroy did not contain a widget - "
                        + "probably removed by something else without " + "permission? (row: " + spacer.getRow()
                        + ", element: " + spacerElement + ")";

                setParent(detailsWidget, null);
                spacerElement.removeAllChildren();
                if (getHeightMode() == HeightMode.UNDEFINED) {
                    // update spacer height
                    escalator.getBody().setSpacer(spacer.getRow(), 0);
                    setHeightByRows(getEscalator().getBody().getRowCount());
                }
            }
        }

        private void initTheming(Spacer spacer) {
            Element spacerRoot = spacer.getElement();

            if (spacer.getRow() % 2 == 1) {
                spacerRoot.getParentElement().addClassName(STRIPE_CLASSNAME);
            } else {
                spacerRoot.getParentElement().removeClassName(STRIPE_CLASSNAME);
            }
        }

    }

    /**
     * Sidebar displaying toggles for hidable columns and custom widgets
     * provided by the application.
     * <p>
     * The button for opening the sidebar is automatically visible inside the
     * grid, if it contains any column hiding options or custom widgets. The
     * column hiding toggles and custom widgets become visible once the sidebar
     * has been opened.
     *
     * @since 7.5.0
     */
    private static class Sidebar extends Composite implements HasEnabled {

        private final ClickHandler openCloseButtonHandler = event -> {
            if (!isOpen()) {
                open();
            } else {
                close();
            }
        };

        private final FlowPanel rootContainer;

        private final FlowPanel content;

        private final MenuBar menuBar;

        private final Button openCloseButton;

        private final Grid<?> grid;

        private Overlay overlay;

        private Sidebar(Grid<?> grid) {
            this.grid = grid;

            rootContainer = new FlowPanel();
            initWidget(rootContainer);

            openCloseButton = new Button();

            openCloseButton.addClickHandler(openCloseButtonHandler);

            rootContainer.add(openCloseButton);

            content = new FlowPanel() {
                @Override
                public boolean remove(Widget w) {
                    // Check here to catch child.removeFromParent() calls
                    boolean removed = super.remove(w);
                    if (removed) {
                        updateVisibility();
                    }

                    return removed;
                }
            };

            createOverlay();

            menuBar = new MenuBar(true) {

                @Override
                public MenuItem insertItem(MenuItem item, int beforeIndex) throws IndexOutOfBoundsException {
                    if (getParent() == null) {
                        content.insert(this, 0);
                        updateVisibility();
                    }
                    return super.insertItem(item, beforeIndex);
                }

                @Override
                public void removeItem(MenuItem item) {
                    super.removeItem(item);
                    if (getItems().isEmpty()) {
                        menuBar.removeFromParent();
                    }
                }

                @Override
                public void onBrowserEvent(Event event) {
                    // selecting a item with enter will lose the focus and
                    // selected item, which means that further keyboard
                    // selection won't work unless we do this:
                    if (event.getTypeInt() == Event.ONKEYDOWN && event.getKeyCode() == KeyCodes.KEY_ENTER) {
                        final MenuItem item = getSelectedItem();
                        super.onBrowserEvent(event);
                        Scheduler.get().scheduleDeferred(() -> {
                            selectItem(item);
                            focus();
                        });

                    } else {
                        super.onBrowserEvent(event);
                    }
                }
            };
            KeyDownHandler keyDownHandler = event -> {
                if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
                    close();
                }
            };
            openCloseButton.addDomHandler(keyDownHandler, KeyDownEvent.getType());
            menuBar.addDomHandler(keyDownHandler, KeyDownEvent.getType());
        }

        /**
         * Creates and initializes the overlay.
         */
        private void createOverlay() {
            overlay = GWT.create(Overlay.class);
            overlay.setOwner(grid);
            overlay.setAutoHideEnabled(true);
            overlay.addStyleDependentName("popup");
            overlay.add(content);
            overlay.addAutoHidePartner(rootContainer.getElement());
            overlay.addCloseHandler(new CloseHandler<PopupPanel>() {
                @Override
                public void onClose(CloseEvent<PopupPanel> event) {
                    removeStyleName("open");
                    addStyleName("closed");
                }
            });
            overlay.setFitInWindow(true);
        }

        /**
         * Opens the sidebar if not yet opened. Opening the sidebar has no
         * effect if it is empty.
         */
        public void open() {
            if (!isOpen() && isInDOM()) {
                addStyleName("open");
                removeStyleName("closed");
                overlay.showRelativeTo(rootContainer);
            }
        }

        /**
         * Closes the sidebar if not yet closed.
         */
        public void close() {
            overlay.hide();
        }

        /**
         * Returns whether the sidebar is open or not.
         *
         * @return <code>true</code> if open, <code>false</code> if not
         */
        public boolean isOpen() {
            return overlay != null && overlay.isShowing();
        }

        @Override
        public void setStylePrimaryName(String styleName) {
            super.setStylePrimaryName(styleName);
            overlay.setStylePrimaryName(styleName);
            content.setStylePrimaryName(styleName + "-content");
            openCloseButton.setStylePrimaryName(styleName + "-button");
            if (isOpen()) {
                addStyleName("open");
                removeStyleName("closed");
            } else {
                removeStyleName("open");
                addStyleName("closed");
            }
        }

        @Override
        public void addStyleName(String style) {
            super.addStyleName(style);
            overlay.addStyleName(style);
        }

        @Override
        public void removeStyleName(String style) {
            super.removeStyleName(style);
            overlay.removeStyleName(style);
        }

        private void setHeightToHeaderCellHeight() {
            RowContainer header = grid.escalator.getHeader();
            if (header.getRowCount() == 0 || !header.getRowElement(0).hasChildNodes()) {
                getLogger().info("No header cell available when calculating sidebar button height");
                openCloseButton.setHeight(header.getDefaultRowHeight() + "px");

                return;
            }

            Element firstHeaderCell = header.getRowElement(0).getFirstChildElement();
            double height = WidgetUtil.getRequiredHeightBoundingClientRectDouble(firstHeaderCell)
                    - WidgetUtil.measureVerticalBorder(getElement()) / 2;
            openCloseButton.setHeight(height + "px");
        }

        private void updateVisibility() {
            final boolean hasWidgets = content.getWidgetCount() > 0;
            final boolean isVisible = isInDOM();
            if (isVisible && !hasWidgets) {
                Grid.setParent(this, null);
                getElement().removeFromParent();
            } else if (!isVisible && hasWidgets) {
                close();
                grid.getElement().appendChild(getElement());
                Grid.setParent(this, grid);
                // border calculation won't work until attached
                setHeightToHeaderCellHeight();
            }
        }

        private boolean isInDOM() {
            return getParent() != null;
        }

        @Override
        protected void onAttach() {
            super.onAttach();
            // make sure the button will get correct height if the button should
            // be visible when the grid is rendered the first time.
            Scheduler.get().scheduleDeferred(() -> setHeightToHeaderCellHeight());
        }

        @Override
        public boolean isEnabled() {
            return openCloseButton.isEnabled();
        }

        @Override
        public void setEnabled(boolean enabled) {
            if (!enabled && isOpen()) {
                close();
            }

            openCloseButton.setEnabled(enabled);
        }
    }

    /**
     * UI and functionality related to hiding columns with toggles in the
     * sidebar.
     */
    private final class ColumnHider {

        /** Map from columns to their hiding toggles, component might change */
        private Map<Column<?, T>, MenuItem> columnToHidingToggleMap = new HashMap<>();

        /**
         * When column is being hidden with a toggle, do not refresh toggles for
         * no reason. Also helps for keeping the keyboard navigation working.
         */
        private boolean hidingColumn;

        private void updateColumnHidable(final Column<?, T> column) {
            if (column.isHidable()) {
                MenuItem toggle = columnToHidingToggleMap.get(column);
                if (toggle == null) {
                    toggle = createToggle(column);
                }
                toggle.setStyleName("hidden", column.isHidden());
            } else if (columnToHidingToggleMap.containsKey(column)) {
                sidebar.menuBar.removeItem(columnToHidingToggleMap.remove(column));
            }
            updateTogglesOrder();
        }

        private MenuItem createToggle(final Column<?, T> column) {
            MenuItem toggle = new MenuItem(createHTML(column), true, () -> {
                hidingColumn = true;
                column.setHidden(!column.isHidden(), true);
                hidingColumn = false;
            });
            toggle.addStyleName("column-hiding-toggle");
            columnToHidingToggleMap.put(column, toggle);
            return toggle;
        }

        private String createHTML(Column<?, T> column) {
            final StringBuilder buf = new StringBuilder();
            buf.append("<span class=\"");
            if (column.isHidden()) {
                buf.append("v-off");
            } else {
                buf.append("v-on");
            }
            buf.append("\"><div>");
            String caption = column.getHidingToggleCaption();
            if (caption == null) {
                caption = column.headerCaption;
            }
            buf.append(caption);
            buf.append("</div></span>");

            return buf.toString();
        }

        private void updateTogglesOrder() {
            if (!hidingColumn) {
                int lastIndex = 0;
                for (Column<?, T> column : getColumns()) {
                    if (column.isHidable()) {
                        final MenuItem menuItem = columnToHidingToggleMap.get(column);
                        sidebar.menuBar.removeItem(menuItem);
                        sidebar.menuBar.insertItem(menuItem, lastIndex++);
                    }
                }
            }
        }

        private void updateHidingToggle(Column<?, T> column) {
            if (column.isHidable()) {
                MenuItem toggle = columnToHidingToggleMap.get(column);
                toggle.setHTML(createHTML(column));
                toggle.setStyleName("hidden", column.isHidden());
            } // else we can just ignore
        }

        private void removeColumnHidingToggle(Column<?, T> column) {
            sidebar.menuBar.removeItem(columnToHidingToggleMap.get(column));
        }

    }

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

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

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

    private final Sidebar sidebar = new Sidebar(this);

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

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

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

    /**
     * The number of frozen columns, 0 freezes the selection column if
     * displayed, -1 also prevents selection col from freezing.
     */
    private int frozenColumnCount = 0;

    /**
     * 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<>();

    private Renderer<Boolean> selectColumnRenderer = null;

    private SelectionColumn selectionColumn;

    private String rowStripeStyleName;
    private String rowHasDataStyleName;
    private String rowSelectedStyleName;
    private String cellFocusStyleName;
    private String rowFocusStyleName;

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

    protected final CellFocusHandler cellFocusHandler;

    private final UserSorter sorter = new UserSorter();

    private final Editor<T> editor = GWT.create(Editor.class);

    /**
     * The cell a click event originated from
     * <p>
     * This is a workaround to make Chrome work like Firefox. In Chrome,
     * normally if you start a drag on one cell and release on:
     * <ul>
     * <li>that same cell, the click event is that <code>&lt;td></code>.
     * <li>a cell on that same row, the click event is the parent
     * <code>&lt;tr></code>.
     * <li>a cell on another row, the click event is the table section ancestor
     * ({@code <thead>}, {@code <tbody>} or {@code <tfoot>}).
     * </ul>
     *
     * @see #onBrowserEvent(Event)
     */
    private Cell cellOnPrevMouseDown;

    /**
     * A scheduled command to re-evaluate the widths of <em>all columns</em>
     * that have calculated widths. Most probably called because
     * minwidth/maxwidth/expandratio has changed.
     */
    private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator();

    private boolean enabled = true;

    private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
    private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater();
    /** A set keeping track of the indices of all currently open details */
    private Set<Integer> visibleDetails = new HashSet<>();
    /** A set of indices of details to reopen after detach and on attach */
    private final Set<Integer> reattachVisibleDetails = new HashSet<>();

    private boolean columnReorderingAllowed;

    private ColumnHider columnHider = new ColumnHider();

    private DragAndDropHandler dndHandler = new DragAndDropHandler();

    private AutoScroller autoScroller = new AutoScroller(this);

    private ColumnResizeMode columnResizeMode = ColumnResizeMode.ANIMATED;

    private final List<GridEventHandler<T>> browserEventHandlers = new ArrayList<>();

    private CellStyleGenerator<T> cellStyleGenerator;
    private RowStyleGenerator<T> rowStyleGenerator;
    private RowReference<T> rowReference = new RowReference<>(this);
    private CellReference<T> cellReference = new CellReference<>(rowReference);
    private RendererCellReference rendererCellReference = new RendererCellReference(
            (RowReference<Object>) rowReference);

    private boolean refreshBodyRequested = false;

    private DragAndDropHandler.DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() {

        private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() {

            @Override
            public void onAutoScroll(int scrollDiff) {
                autoScrollX = scrollDiff;
                onDragUpdate(null);
            }

            @Override
            public void onAutoScrollReachedMin() {
                // make sure the drop marker is visible on the left
                autoScrollX = 0;
                updateDragDropMarker(clientX);
            }

            @Override
            public void onAutoScrollReachedMax() {
                // make sure the drop marker is visible on the right
                autoScrollX = 0;
                updateDragDropMarker(clientX);
            }
        };
        /**
         * Elements for displaying the dragged column(s) and drop marker
         * properly
         */
        private Element table;
        private Element tableHeader;
        /** Marks the column drop location */
        private Element dropMarker;
        /** A copy of the dragged column(s), moves with cursor. */
        private Element dragElement;
        /** Tracks index of the column whose left side the drop would occur */
        private int latestColumnDropIndex;
        /**
         * Map of possible drop positions for the column and the corresponding
         * column index.
         */
        private final TreeMap<Double, Integer> possibleDropPositions = new TreeMap<>();
        /**
         * Makes sure that drag cancel doesn't cause anything unwanted like sort
         */
        private HandlerRegistration columnSortPreventRegistration;

        private int clientX;

        /** How much the grid is being auto scrolled while dragging. */
        private int autoScrollX;

        /** Captures the value of the focused column before reordering */
        private int focusedColumnIndex;

        /** Offset caused by the drag and drop marker width */
        private double dropMarkerWidthOffset;

        private void initHeaderDragElementDOM() {
            if (table == null) {
                tableHeader = DOM.createTHead();
                dropMarker = DOM.createDiv();
                tableHeader.appendChild(dropMarker);
                table = DOM.createTable();
                table.appendChild(tableHeader);
                table.setClassName("header-drag-table");
            }
            // update the style names on each run in case primary name has been
            // modified
            tableHeader.setClassName(escalator.getHeader().getElement().getClassName());
            dropMarker.setClassName(getStylePrimaryName() + "-drop-marker");
            int topOffset = 0;
            for (int i = 0; i < eventCell.getRowIndex(); i++) {
                topOffset += escalator.getHeader().getRowElement(i).getFirstChildElement().getOffsetHeight();
            }
            tableHeader.getStyle().setTop(topOffset, Unit.PX);

            getElement().appendChild(table);

            dropMarkerWidthOffset = WidgetUtil.getRequiredWidthBoundingClientRectDouble(dropMarker) / 2;
        }

        @Override
        public void onDragUpdate(Event e) {
            if (e != null) {
                clientX = WidgetUtil.getTouchOrMouseClientX(e);
                autoScrollX = 0;
            }
            resolveDragElementHorizontalPosition(clientX);
            updateDragDropMarker(clientX);
        }

        private void updateDragDropMarker(final int clientX) {
            final double scrollLeft = getScrollLeft();
            final double cursorXCoordinate = clientX - escalator.getHeader().getElement().getAbsoluteLeft();
            final Entry<Double, Integer> cellEdgeOnRight = possibleDropPositions.ceilingEntry(cursorXCoordinate);
            final Entry<Double, Integer> cellEdgeOnLeft = possibleDropPositions.floorEntry(cursorXCoordinate);
            final double diffToRightEdge = cellEdgeOnRight == null ? Double.MAX_VALUE
                    : cellEdgeOnRight.getKey() - cursorXCoordinate;
            final double diffToLeftEdge = cellEdgeOnLeft == null ? Double.MAX_VALUE
                    : cursorXCoordinate - cellEdgeOnLeft.getKey();

            double dropMarkerLeft = 0 - scrollLeft;
            if (diffToRightEdge > diffToLeftEdge) {
                latestColumnDropIndex = cellEdgeOnLeft.getValue();
                dropMarkerLeft += cellEdgeOnLeft.getKey();
            } else {
                latestColumnDropIndex = cellEdgeOnRight.getValue();
                dropMarkerLeft += cellEdgeOnRight.getKey();
            }

            dropMarkerLeft += autoScrollX;

            final double frozenColumnsWidth = autoScroller.getFrozenColumnsWidth();
            final double rightBoundaryForDrag = getSidebarBoundaryComparedTo(dropMarkerLeft);
            final int visibleColumns = getVisibleColumns().size();

            // First check if the drop marker should move left because of the
            // sidebar opening button. this only the case if the grid is
            // scrolled to the right
            if (latestColumnDropIndex == visibleColumns && rightBoundaryForDrag < dropMarkerLeft
                    && dropMarkerLeft <= escalator.getInnerWidth()) {
                dropMarkerLeft = rightBoundaryForDrag - dropMarkerWidthOffset;
            } else if (
            // Check if the drop marker shouldn't be shown at all
            dropMarkerLeft < frozenColumnsWidth
                    || dropMarkerLeft > Math.min(rightBoundaryForDrag, escalator.getInnerWidth())
                    || dropMarkerLeft < 0) {
                dropMarkerLeft = -10000000;
            }
            dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX);
        }

        private void resolveDragElementHorizontalPosition(final int clientX) {
            double left = clientX - table.getAbsoluteLeft();

            // Do not show the drag element beyond a spanned header cell
            // limitation
            final Double leftBound = possibleDropPositions.firstKey();
            final Double rightBound = possibleDropPositions.lastKey();
            final double scrollLeft = getScrollLeft();
            if (left + scrollLeft < leftBound) {
                left = leftBound - scrollLeft + autoScrollX;
            } else if (left + scrollLeft > rightBound) {
                left = rightBound - scrollLeft + autoScrollX;
            }

            // Do not show the drag element beyond the grid
            final double sidebarBoundary = getSidebarBoundaryComparedTo(left);
            final double gridBoundary = escalator.getInnerWidth();
            final double rightBoundary = Math.min(sidebarBoundary, gridBoundary);

            // Do not show on left of the frozen columns (even if scrolled)
            final int frozenColumnsWidth = (int) autoScroller.getFrozenColumnsWidth();

            left = Math.max(frozenColumnsWidth, Math.min(left, rightBoundary));

            left -= dragElement.getClientWidth() / 2;
            dragElement.getStyle().setLeft(left, Unit.PX);
        }

        private boolean isSidebarOnDraggedRow() {
            return eventCell.getRowIndex() == 0 && sidebar.isInDOM() && !sidebar.isOpen();
        }

        /**
         * Returns the sidebar left coordinate, in relation to the grid. Or
         * Double.MAX_VALUE if it doesn't cause a boundary.
         */
        private double getSidebarBoundaryComparedTo(double left) {
            if (isSidebarOnDraggedRow()) {
                double absoluteLeft = left + getElement().getAbsoluteLeft();
                double sidebarLeft = sidebar.getElement().getAbsoluteLeft();
                double diff = absoluteLeft - sidebarLeft;

                if (diff > 0) {
                    return left - diff;
                }
            }
            return Double.MAX_VALUE;
        }

        @Override
        public boolean onDragStart(Event e) {
            calculatePossibleDropPositions();

            if (possibleDropPositions.isEmpty()) {
                return false;
            }

            initHeaderDragElementDOM();
            // needs to clone focus and sorting indicators too (UX)
            dragElement = DOM.clone(eventCell.getElement(), true);
            dragElement.getStyle().clearWidth();
            dropMarker.getStyle().setProperty("height", dragElement.getStyle().getHeight());
            tableHeader.appendChild(dragElement);
            // mark the column being dragged for styling
            eventCell.getElement().addClassName("dragged");
            // mark the floating cell, for styling & testing
            dragElement.addClassName("dragged-column-header");

            // start the auto scroll handler
            autoScroller.setScrollArea(60);
            autoScroller.start(e, ScrollAxis.HORIZONTAL, autoScrollerCallback);
            return true;
        }

        @Override
        public void onDragEnd() {
            table.removeFromParent();
            dragElement.removeFromParent();
            eventCell.getElement().removeClassName("dragged");
        }

        @Override
        public void onDrop() {
            final int draggedColumnIndex = eventCell.getColumnIndex();
            final int colspan = header.getRow(eventCell.getRowIndex()).getCell(eventCell.getColumn()).getColspan();
            if (latestColumnDropIndex != draggedColumnIndex
                    && latestColumnDropIndex != draggedColumnIndex + colspan) {
                List<Column<?, T>> columns = getColumns();
                List<Column<?, T>> reordered = new ArrayList<>();
                if (draggedColumnIndex < latestColumnDropIndex) {
                    reordered.addAll(columns.subList(0, draggedColumnIndex));
                    reordered.addAll(columns.subList(draggedColumnIndex + colspan, latestColumnDropIndex));
                    reordered.addAll(columns.subList(draggedColumnIndex, draggedColumnIndex + colspan));
                    reordered.addAll(columns.subList(latestColumnDropIndex, columns.size()));
                } else {
                    reordered.addAll(columns.subList(0, latestColumnDropIndex));
                    reordered.addAll(columns.subList(draggedColumnIndex, draggedColumnIndex + colspan));
                    reordered.addAll(columns.subList(latestColumnDropIndex, draggedColumnIndex));
                    reordered.addAll(columns.subList(draggedColumnIndex + colspan, columns.size()));
                }
                // since setColumnOrder will add it anyway!
                reordered.remove(selectionColumn);

                // capture focused cell column before reorder
                Cell focusedCell = cellFocusHandler.getFocusedCell();
                if (focusedCell != null) {
                    // take hidden columns into account
                    focusedColumnIndex = getColumns().indexOf(getVisibleColumn(focusedCell.getColumn()));
                }

                Column<?, T>[] array = reordered.toArray(new Column[reordered.size()]);
                setColumnOrder(true, array);
                transferCellFocusOnDrop();
            } // else
              // no
              // reordering
        }

        private void transferCellFocusOnDrop() {
            final Cell focusedCell = cellFocusHandler.getFocusedCell();
            if (focusedCell != null && focusedCell.getElement() != null) {
                final int focusedColumnIndexDOM = focusedCell.getColumn();
                final int focusedRowIndex = focusedCell.getRow();
                final int draggedColumnIndex = eventCell.getColumnIndex();
                // transfer focus if it was effected by the new column order
                final RowContainer rowContainer = escalator.findRowContainer(focusedCell.getElement());
                if (focusedColumnIndex == draggedColumnIndex) {
                    // move with the dragged column
                    int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1
                            : latestColumnDropIndex;
                    // remove hidden columns from indexing
                    adjustedDropIndex = getVisibleColumns().indexOf(getColumn(adjustedDropIndex));
                    cellFocusHandler.setCellFocus(focusedRowIndex, adjustedDropIndex, rowContainer);
                } else if (latestColumnDropIndex <= focusedColumnIndex && draggedColumnIndex > focusedColumnIndex) {
                    cellFocusHandler.setCellFocus(focusedRowIndex, focusedColumnIndexDOM + 1, rowContainer);
                } else if (latestColumnDropIndex > focusedColumnIndex && draggedColumnIndex < focusedColumnIndex) {
                    cellFocusHandler.setCellFocus(focusedRowIndex, focusedColumnIndexDOM - 1, rowContainer);
                }
            }
        }

        @Override
        public void onDragCancel() {
            // cancel next click so that we may prevent column sorting if
            // mouse was released on top of the dragged cell
            if (columnSortPreventRegistration == null) {
                columnSortPreventRegistration = Event.addNativePreviewHandler(event -> {
                    if (event.getTypeInt() == Event.ONCLICK) {
                        event.cancel();
                        event.getNativeEvent().preventDefault();
                        columnSortPreventRegistration.removeHandler();
                        columnSortPreventRegistration = null;
                    }
                });
            }
            autoScroller.stop();
        }

        /**
         * Returns the amount of frozen columns. The selection column is always
         * considered frozen, since it can't be moved.
         */
        private int getSelectionAndFrozenColumnCount() {
            // no matter if selection column is frozen or not, it is considered
            // frozen for column dnd reorder
            if (getSelectionModel() instanceof SelectionModelWithSelectionColumn) {
                return Math.max(0, getFrozenColumnCount()) + 1;
            } else {
                return Math.max(0, getFrozenColumnCount());
            }
        }

        @SuppressWarnings("boxing")
        private void calculatePossibleDropPositions() {
            possibleDropPositions.clear();

            final int draggedColumnIndex = eventCell.getColumnIndex();
            final StaticRow<?> draggedCellRow = header.getRow(eventCell.getRowIndex());
            final int draggedColumnRightIndex = draggedColumnIndex
                    + draggedCellRow.getCell(eventCell.getColumn()).getColspan();
            final int frozenColumns = getSelectionAndFrozenColumnCount();
            final Range draggedCellRange = Range.between(draggedColumnIndex, draggedColumnRightIndex);
            /*
             * If the dragged cell intersects with a spanned cell in any other
             * header or footer row, then the drag is limited inside that
             * spanned cell. The same rules apply: the cell can't be dropped
             * inside another spanned cell. The left and right bounds keep track
             * of the edges of the most limiting spanned cell.
             */
            int leftBound = -1;
            int rightBound = getColumnCount() + 1;

            final HashSet<Integer> unavailableColumnDropIndices = new HashSet<>();
            final List<StaticRow<?>> rows = new ArrayList<>();
            rows.addAll(header.getRows());
            rows.addAll(footer.getRows());
            for (StaticRow<?> row : rows) {
                if (!row.hasSpannedCells()) {
                    continue;
                }
                final boolean isDraggedCellRow = row.equals(draggedCellRow);
                for (int cellColumnIndex = frozenColumns; cellColumnIndex < getColumnCount(); cellColumnIndex++) {
                    StaticCell cell = row.getCell(getColumn(cellColumnIndex));
                    int colspan = cell.getColspan();
                    if (colspan <= 1) {
                        continue;
                    }
                    final int cellColumnRightIndex = cellColumnIndex
                            + row.getSizeOfCellGroup(getColumn(cellColumnIndex));
                    final Range cellRange = Range.between(cellColumnIndex, cellColumnRightIndex);
                    final boolean intersects = draggedCellRange.intersects(cellRange);
                    if (intersects && !isDraggedCellRow) {
                        // if the currently iterated cell is inside or same as
                        // the dragged cell, then it doesn't restrict the drag
                        if (cellRange.isSubsetOf(draggedCellRange)) {
                            cellColumnIndex = cellColumnRightIndex - 1;
                            continue;
                        }
                        /*
                         * if the dragged cell is a spanned cell and it crosses
                         * with the currently iterated cell without sharing
                         * either start or end then not possible to drag the
                         * cell.
                         */
                        if (!draggedCellRange.isSubsetOf(cellRange)) {
                            return;
                        }
                        // the spanned cell overlaps the dragged cell (but is
                        // not the dragged cell)
                        if (cellColumnIndex <= draggedColumnIndex && cellColumnIndex > leftBound) {
                            leftBound = cellColumnIndex;
                        }
                        if (cellColumnRightIndex < rightBound) {
                            rightBound = cellColumnRightIndex;
                        }
                        cellColumnIndex = cellColumnRightIndex - 1;
                    } else {
                        // can't drop inside a spanned cell, or this is the
                        // dragged cell
                        while (colspan > 1) {
                            cellColumnIndex++;
                            colspan--;
                            unavailableColumnDropIndices.add(cellColumnIndex);
                        }
                    }
                }
            }

            if (leftBound == rightBound - 1) {
                return;
            }

            double position = autoScroller.getFrozenColumnsWidth();
            // iterate column indices and add possible drop positions
            for (int i = frozenColumns; i < getColumnCount(); i++) {
                Column<?, T> column = getColumn(i);
                if (!unavailableColumnDropIndices.contains(i) && !column.isHidden()) {
                    if (leftBound != -1) {
                        if (i >= leftBound && i <= rightBound) {
                            possibleDropPositions.put(position, i);
                        }
                    } else {
                        possibleDropPositions.put(position, i);
                    }
                }
                position += column.getWidthActual();
            }

            if (leftBound == -1) {
                // add the right side of the last column as columns.size()
                possibleDropPositions.put(position, getColumnCount());
            }
        }

    };

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

        /**
         * Default renderer for GridColumns. Renders everything into text
         * through {@link Object#toString()}.
         */
        private final class DefaultTextRenderer implements Renderer<Object> {
            boolean warned = false;
            private static final String DEFAULT_RENDERER_WARNING = "This column uses a dummy default TextRenderer. "
                    + "A more suitable renderer should be set using the setRenderer() method.";

            @Override
            public void render(RendererCellReference cell, Object data) {
                if (!warned && !(data instanceof String)) {
                    getLogger().warning(Column.this + ": " + DEFAULT_RENDERER_WARNING);
                    warned = true;
                }

                final String text;
                if (data == null) {
                    text = "";
                } else {
                    text = data.toString();
                }

                cell.getElement().setInnerText(text);
            }
        }

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

        /**
         * Width of column in pixels as {@link #setWidth(double)} has been
         * called
         */
        private double widthUser = GridConstants.DEFAULT_COLUMN_WIDTH_PX;

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

        private boolean sortable = false;

        private boolean editable = true;

        private boolean resizable = true;

        private boolean hidden = false;

        private boolean hidable = false;

        private String headerCaption = "";

        private String assistiveCaption = null;

        private String hidingToggleCaption = null;

        private boolean handleWidgetEvents = false;

        private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH;
        private double maximumWidthPx = GridConstants.DEFAULT_MAX_WIDTH;
        private int expandRatio = GridConstants.DEFAULT_EXPAND_RATIO;
        private boolean minimumWidthFromContent = true;

        /**
         * Constructs a new column with a simple TextRenderer.
         */
        public Column() {
            setRenderer(new DefaultTextRenderer());
        }

        /**
         * Constructs a new column with a simple TextRenderer.
         *
         * @param caption
         *            The header caption for this column
         *
         * @throws IllegalArgumentException
         *             if given header caption is null
         */
        public Column(String caption) throws IllegalArgumentException {
            this();
            setHeaderCaption(caption);
        }

        /**
         * Constructs a new column with a custom renderer.
         *
         * @param renderer
         *            The renderer to use for rendering the cells
         *
         * @throws IllegalArgumentException
         *             if given Renderer is null
         */
        public Column(Renderer<? super C> renderer) throws IllegalArgumentException {
            setRenderer(renderer);
        }

        /**
         * Constructs a new column with a custom renderer.
         *
         * @param renderer
         *            The renderer to use for rendering the cells
         * @param caption
         *            The header caption for this column
         *
         * @throws IllegalArgumentException
         *             if given Renderer or header caption is null
         */
        public Column(String caption, Renderer<? super C> renderer) throws IllegalArgumentException {
            this(renderer);
            setHeaderCaption(caption);
        }

        /**
         * Internally used by the grid to set itself
         *
         * @param grid
         */
        private void setGrid(Grid<T> grid) {
            if (this.grid != null && grid != null) {
                // Trying to replace grid
                throw new IllegalStateException(
                        "Column already is attached " + "to a grid. Remove the column first from the grid "
                                + "and then add it. (in: " + toString() + ")");
            }

            if (this.grid != null) {
                this.grid.recalculateColumnWidths();
            }
            this.grid = grid;
            if (this.grid != null) {
                this.grid.recalculateColumnWidths();
            }
        }

        /**
         * Sets a header caption for this column.
         *
         * @param caption
         *            The header caption for this column
         * @return the column itself
         *
         */
        public Column<C, T> setHeaderCaption(String caption) {
            if (caption == null) {
                caption = "";
            }

            if (!this.headerCaption.equals(caption)) {
                this.headerCaption = caption;
                if (grid != null) {
                    updateHeader();
                }
            }

            return this;
        }

        /**
         * Returns the current header caption for this column.
         *
         * @since 7.6
         * @return the header caption string
         */
        public String getHeaderCaption() {
            return headerCaption;
        }

        /**
         * Sets the header aria-label for this column.
         *
         * @param caption
         *            The header aria-label for this column
         * @return the column itself
         *
         * @since 8.2
         */
        public Column<C, T> setAssistiveCaption(String caption) {
            if (!Objects.equals(this.assistiveCaption, caption)) {
                this.assistiveCaption = caption;
                if (grid != null) {
                    grid.getHeader().requestSectionRefresh();
                }
            }

            return this;
        }

        /**
         * Returns the current header aria-label for this column.
         *
         * @return the header aria-label string
         *
         * @since 8.2
         */
        public String getAssistiveCaption() {
            return assistiveCaption;
        }

        private void updateHeader() {
            HeaderRow row = grid.getHeader().getDefaultRow();
            if (row != null) {
                setDefaultHeaderContent(row.getCell(this));
                if (isHidable()) {
                    grid.columnHider.updateHidingToggle(this);
                }
            }
        }

        /**
         * Returns the data that should be rendered into the cell. By default
         * returning Strings and Widgets are supported. If the return type is a
         * String then it will be treated as preformatted text.
         * <p>
         * To support other types you will need to pass a custom renderer to the
         * column via the column constructor.
         *
         * @param row
         *            The row object that provides the cell content.
         *
         * @return The cell content
         */
        public abstract C getValue(T row);

        /**
         * The renderer to render the cell with. By default renders the data as
         * a String or adds the widget into the cell if the column type is of
         * widget type.
         *
         * @return The renderer to render the cell content with
         */
        public Renderer<? super C> getRenderer() {
            return bodyRenderer;
        }

        /**
         * Sets a custom {@link Renderer} for this column.
         *
         * @param renderer
         *            The renderer to use for rendering the cells
         * @return the column itself
         *
         * @throws IllegalArgumentException
         *             if given Renderer is null
         */
        public Column<C, T> setRenderer(Renderer<? super C> renderer) throws IllegalArgumentException {
            if (renderer == null) {
                throw new IllegalArgumentException("Renderer cannot be null.");
            }

            if (renderer != bodyRenderer) {
                // Variables used to restore removed column.
                boolean columnRemoved = false;
                double widthInConfiguration = 0.0d;
                ColumnConfiguration conf = null;
                int index = 0;

                if (!isHidden() && grid != null
                        && (bodyRenderer instanceof WidgetRenderer || renderer instanceof WidgetRenderer)) {
                    // Column needs to be recreated.
                    index = grid.getVisibleColumns().indexOf(this);
                    conf = grid.escalator.getColumnConfiguration();
                    widthInConfiguration = conf.getColumnWidth(index);

                    conf.removeColumns(index, 1);
                    columnRemoved = true;
                }

                // Complex renderers need to be destroyed.
                if (bodyRenderer instanceof ComplexRenderer) {
                    ((ComplexRenderer) bodyRenderer).destroy();
                }

                bodyRenderer = renderer;

                if (columnRemoved) {
                    // Restore the column.
                    conf.insertColumns(index, 1);
                    conf.setColumnWidth(index, widthInConfiguration);
                }

                if (!isHidden() && grid != null) {
                    grid.requestRefreshBody();
                }
            }
            return this;
        }

        /**
         * Sets the pixel width of the column. Use a negative value for the grid
         * to autosize column based on content and available space.
         * <p>
         * This action is done "finally", once the current execution loop
         * returns. This is done to reduce overhead of unintentionally always
         * recalculate all columns, when modifying several columns at once.
         * <p>
         * If the column is currently {@link #isHidden() hidden}, then this set
         * width has effect only once the column has been made visible again.
         *
         * @param pixels
         *            the width in pixels or negative for auto sizing
         * @return this column
         */
        public Column<C, T> setWidth(double pixels) {
            if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) {
                widthUser = pixels;
                if (!isHidden()) {
                    scheduleColumnWidthRecalculator();
                }
            }
            return this;
        }

        void doSetWidth(double pixels) {
            assert !isHidden() : "applying width for a hidden column";
            if (grid != null) {
                int index = grid.getVisibleColumns().indexOf(this);
                ColumnConfiguration conf = grid.escalator.getColumnConfiguration();
                conf.setColumnWidth(index, pixels);
            }
        }

        /**
         * Returns the pixel width of the column as given by the user.
         * <p>
         * <em>Note:</em> If a negative value was given to
         * {@link #setWidth(double)}, that same negative value is returned here.
         * <p>
         * <em>Note:</em> Returns the value, even if the column is currently
         * {@link #isHidden() hidden}.
         *
         * @return pixel width of the column, or a negative number if the column
         *         width has been automatically calculated.
         * @see #setWidth(double)
         * @see #getWidthActual()
         */
        public double getWidth() {
            return widthUser;
        }

        /**
         * Returns the effective pixel width of the column.
         * <p>
         * This differs from {@link #getWidth()} only when the column has been
         * automatically resized, or when the column is currently
         * {@link #isHidden() hidden}, when the value is 0.
         *
         * @return pixel width of the column.
         */
        public double getWidthActual() {
            if (isHidden()) {
                return 0;
            }
            return grid.escalator.getColumnConfiguration()
                    .getColumnWidthActual(grid.getVisibleColumns().indexOf(this));
        }

        void reapplyWidth() {
            scheduleColumnWidthRecalculator();
        }

        /**
         * Sets whether the column should be sortable by the user. The grid can
         * be sorted by a sortable column by clicking or tapping the column's
         * default header. Programmatic sorting using the Grid#sort methods is
         * not affected by this setting.
         *
         * @param sortable
         *            {@code true} if the user should be able to sort the
         *            column, {@code false} otherwise
         * @return the column itself
         */
        public Column<C, T> setSortable(boolean sortable) {
            if (this.sortable != sortable) {
                this.sortable = sortable;
                if (grid != null) {
                    grid.getHeader().requestSectionRefresh();
                }
            }
            return this;
        }

        /**
         * Returns whether the user can sort the grid by this column.
         * <p>
         * <em>Note:</em> it is possible to sort by this column programmatically
         * using the Grid#sort methods regardless of the returned value.
         *
         * @return {@code true} if the column is sortable by the user,
         *         {@code false} otherwise
         */
        public boolean isSortable() {
            return sortable;
        }

        /**
         * Sets whether this column can be resized by the user.
         *
         * @since 7.6
         *
         * @param resizable
         *            {@code true} if this column should be resizable,
         *            {@code false} otherwise
         * @return this column
         */
        public Column<C, T> setResizable(boolean resizable) {
            if (this.resizable != resizable) {
                this.resizable = resizable;
                if (grid != null) {
                    grid.getHeader().requestSectionRefresh();
                }
            }
            return this;
        }

        /**
         * Returns whether this column can be resized by the user. Default is
         * {@code true}.
         * <p>
         * <em>Note:</em> the column can be programmatically resized using
         * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
         * of the returned value.
         *
         * @since 7.6
         *
         * @return {@code true} if this column is resizable, {@code false}
         *         otherwise
         */
        public boolean isResizable() {
            return resizable;
        }

        /**
         * Hides or shows the column. By default columns are visible before
         * explicitly hiding them.
         *
         * @since 7.5.0
         * @param hidden
         *            <code>true</code> to hide the column, <code>false</code>
         *            to show
         * @return this column
         */
        public Column<C, T> setHidden(boolean hidden) {
            setHidden(hidden, false);
            return this;
        }

        private void setHidden(boolean hidden, boolean userOriginated) {
            if (this.hidden != hidden) {
                if (grid == null) {
                    // Not yet attached so just update the flag so that a column
                    // can initially be hidden
                    this.hidden = hidden;
                    return;
                }
                if (hidden) {
                    grid.escalator.getColumnConfiguration().removeColumns(grid.getVisibleColumns().indexOf(this),
                            1);
                    this.hidden = hidden;
                } else {
                    this.hidden = hidden;

                    final int columnIndex = grid.getVisibleColumns().indexOf(this);
                    grid.escalator.getColumnConfiguration().insertColumns(columnIndex, 1);

                    // make sure column is set to frozen if it needs to be,
                    // escalator doesn't handle situation where the added column
                    // would be the last frozen column
                    int gridFrozenColumns = grid.getFrozenColumnCount();
                    int escalatorFrozenColumns = grid.escalator.getColumnConfiguration().getFrozenColumnCount();
                    if (gridFrozenColumns > escalatorFrozenColumns && escalatorFrozenColumns == columnIndex) {
                        grid.escalator.getColumnConfiguration().setFrozenColumnCount(++escalatorFrozenColumns);
                    }
                }
                grid.columnHider.updateHidingToggle(this);
                grid.header.updateColSpans();
                grid.footer.updateColSpans();
                scheduleColumnWidthRecalculator();
                this.grid.fireEvent(new ColumnVisibilityChangeEvent<>(this, hidden, userOriginated));
            }
        }

        /**
         * Returns whether this column is hidden. Default is {@code false}.
         *
         * @since 7.5.0
         * @return {@code true} if the column is currently hidden, {@code false}
         *         otherwise
         */
        public boolean isHidden() {
            return hidden;
        }

        /**
         * Set whether it is possible for the user to hide this column or not.
         * Default is {@code false}.
         * <p>
         * <em>Note:</em> it is still possible to hide the column
         * programmatically using {@link #setHidden(boolean)}.
         *
         * @since 7.5.0
         * @param hidable
         *            {@code true} the user can hide this column, {@code false}
         *            otherwise
         * @return this column
         */
        public Column<C, T> setHidable(boolean hidable) {
            if (this.hidable != hidable) {
                this.hidable = hidable;
                grid.columnHider.updateColumnHidable(this);
            }
            return this;
        }

        /**
         * Is it possible for the the user to hide this column. Default is
         * {@code false}.
         * <p>
         * <em>Note:</em> the column can be programmatically hidden using
         * {@link #setHidden(boolean)} regardless of the returned value.
         *
         * @since 7.5.0
         * @return <code>true</code> if the user can hide the column,
         *         <code>false</code> if not
         */
        public boolean isHidable() {
            return hidable;
        }

        /**
         * Sets the hiding toggle's caption for this column. Shown in the toggle
         * for this column in the grid's sidebar when the column is
         * {@link #isHidable() hidable}.
         * <p>
         * The default value is <code>null</code>. In this case the header
         * caption is used, see {@link #setHeaderCaption(String)}.
         *
         * @since 7.5.0
         * @param hidingToggleCaption
         *            the caption for the hiding toggle for this column
         * @return this column
         */
        public Column<C, T> setHidingToggleCaption(String hidingToggleCaption) {
            this.hidingToggleCaption = hidingToggleCaption;
            if (isHidable()) {
                grid.columnHider.updateHidingToggle(this);
            }
            return this;
        }

        /**
         * Gets the hiding toggle caption for this column.
         *
         * @since 7.5.0
         * @see #setHidingToggleCaption(String)
         * @return the hiding toggle's caption for this column
         */
        public String getHidingToggleCaption() {
            return hidingToggleCaption;
        }

        @Override
        public String toString() {
            String details = "";

            if (headerCaption != null && !headerCaption.isEmpty()) {
                details += "header:\"" + headerCaption + "\" ";
            } else {
                details += "header:empty ";
            }

            if (grid != null) {
                int index = grid.getColumns().indexOf(this);
                if (index != -1) {
                    details += "attached:#" + index + " ";
                } else {
                    details += "attached:unindexed ";
                }
            } else {
                details += "detached ";
            }

            details += "sortable:" + sortable + " ";

            return getClass().getSimpleName() + "[" + details.trim() + "]";
        }

        /**
         * Sets the minimum width for this column.
         * <p>
         * This defines the minimum guaranteed pixel width of the column
         * <em>when it is set to expand</em>.
         * <p>
         * This action is done "finally", once the current execution loop
         * returns. This is done to reduce overhead of unintentionally always
         * recalculate all columns, when modifying several columns at once.
         *
         * @param pixels
         *            the minimum width
         * @return this column
         */
        public Column<C, T> setMinimumWidth(double pixels) {
            final double maxwidth = getMaximumWidth();
            if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
                throw new IllegalArgumentException(
                        "New minimum width (" + pixels + ") was greater than maximum width (" + maxwidth + ")");
            }

            if (minimumWidthPx != pixels) {
                minimumWidthPx = pixels;
                scheduleColumnWidthRecalculator();
            }
            return this;
        }

        /**
         * Sets whether the width of the contents in the column should be
         * considered minimum width for this column.
         * <p>
         * If this is set to <code>true</code> (default for backwards
         * compatibility), then a column will not shrink to smaller than the
         * width required to show the contents available when calculating the
         * widths (first N rows).
         * <p>
         * If this is set to <code>false</code>, then a column will shrink if
         * necessary to the minimum width defined by
         * {@link #setMinimumWidth(double)} <em>when it is set to expand</em>.
         *
         * @param minimumWidthFromContent
         *            <code>true</code> to reserve space for all contents,
         *            <code>false</code> to allow the column to shrink smaller
         *            than the contents
         * @since 8.1
         */
        public void setMinimumWidthFromContent(boolean minimumWidthFromContent) {
            this.minimumWidthFromContent = minimumWidthFromContent;
        }

        /**
         * Gets whether the width of the contents in the column should be
         * considered minimum width for this column.
         *
         * @return <code>true</code> to reserve space for all contents,
         *         <code>false</code> to allow the column to shrink smaller than
         *         the contents
         * @since 8.1
         */
        public boolean isMinimumWidthFromContent() {
            return minimumWidthFromContent;
        }

        /**
         * Sets the maximum width for this column.
         * <p>
         * This defines the maximum allowed pixel width of the column <em>when
         * it is set to expand</em>.
         * <p>
         * This action is done "finally", once the current execution loop
         * returns. This is done to reduce overhead of unintentionally always
         * recalculate all columns, when modifying several columns at once.
         *
         * @param pixels
         *            the maximum width
         * @return this column
         */
        public Column<C, T> setMaximumWidth(double pixels) {
            final double minwidth = getMinimumWidth();
            if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
                throw new IllegalArgumentException(
                        "New maximum width (" + pixels + ") was less than minimum width (" + minwidth + ")");
            }

            if (maximumWidthPx != pixels) {
                maximumWidthPx = pixels;
                scheduleColumnWidthRecalculator();
            }
            return this;
        }

        /**
         * Sets the ratio with which the column expands.
         * <p>
         * By default, all columns expand equally (treated as if all of them had
         * an expand ratio of 1). Once at least one column gets a defined expand
         * ratio, the implicit expand ratio is removed, and only the defined
         * expand ratios are taken into account.
         * <p>
         * If a column has a defined width ({@link #setWidth(double)}), it
         * overrides this method's effects.
         * <p>
         * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
         * and 2, respectively. The column with a <strong>ratio of 0 is exactly
         * as wide as its contents requires</strong>. The column with a ratio of
         * 1 is as wide as it needs, <strong>plus a third of any excess
         * space</strong>, bceause we have 3 parts total, and this column
         * reservs only one of those. The column with a ratio of 2, is as wide
         * as it needs to be, <strong>plus two thirds</strong> of the excess
         * width.
         * <p>
         * This action is done "finally", once the current execution loop
         * returns. This is done to reduce overhead of unintentionally always
         * recalculate all columns, when modifying several columns at once.
         *
         * @param ratio
         *            the expand ratio of this column. {@code 0} to not have it
         *            expand at all. A negative number to clear the expand
         *            value.
         * @return this column
         */
        public Column<C, T> setExpandRatio(int ratio) {
            if (expandRatio != ratio) {
                expandRatio = ratio;
                scheduleColumnWidthRecalculator();
            }
            return this;
        }

        /**
         * Clears the column's expand ratio.
         * <p>
         * Same as calling {@link #setExpandRatio(int) setExpandRatio(-1)}
         *
         * @return this column
         */
        public Column<C, T> clearExpandRatio() {
            return setExpandRatio(-1);
        }

        /**
         * Gets the minimum width for this column.
         *
         * @return the minimum width for this column
         * @see #setMinimumWidth(double)
         */
        public double getMinimumWidth() {
            return minimumWidthPx;
        }

        /**
         * Gets the maximum width for this column.
         *
         * @return the maximum width for this column
         * @see #setMaximumWidth(double)
         */
        public double getMaximumWidth() {
            return maximumWidthPx;
        }

        /**
         * Gets the expand ratio for this column.
         *
         * @return the expand ratio for this column
         * @see #setExpandRatio(int)
         */
        public int getExpandRatio() {
            return expandRatio;
        }

        /**
         * Sets whether the values in this column should be editable by the user
         * when the row editor is active. By default columns are editable.
         *
         * @param editable
         *            {@code true} to set this column editable, {@code false}
         *            otherwise
         * @return this column
         *
         * @throws IllegalStateException
         *             if the editor is currently active
         *
         * @see Grid#editRow(int)
         * @see Grid#isEditorActive()
         */
        public Column<C, T> setEditable(boolean editable) {
            if (editable != this.editable && grid != null && grid.isEditorActive()) {
                throw new IllegalStateException("Cannot change column editable status while the editor is active");
            }
            this.editable = editable;
            return this;
        }

        /**
         * Returns whether the values in this column are editable by the user
         * when the row editor is active.
         *
         * @return {@code true} if this column is editable, {@code false}
         *         otherwise
         *
         * @see #setEditable(boolean)
         */
        public boolean isEditable() {
            return editable;
        }

        private void scheduleColumnWidthRecalculator() {
            if (grid != null) {
                grid.recalculateColumnWidths();
            } else {
                /*
                 * NOOP
                 *
                 * Since setGrid() will call reapplyWidths as the colum is
                 * attached to a grid, it will call setWidth, which, in turn,
                 * will call this method again. Therefore, it's guaranteed that
                 * the recalculation is scheduled eventually, once the column is
                 * attached to a grid.
                 */
            }
        }

        /**
         * Resets the default header cell contents to column header captions.
         *
         * @since 7.5.1
         * @param cell
         *            default header cell for this column
         */
        protected void setDefaultHeaderContent(HeaderCell cell) {
            cell.setText(headerCaption);
        }

        /**
         * Returns whether Grid should handle events from Widgets in this
         * Column.
         *
         * @return {@code true} to handle events from widgets; {@code false} to
         *         not
         * @since 8.3
         */
        public boolean isHandleWidgetEvents() {
            return handleWidgetEvents;
        }

        /**
         * Sets whether Grid should handle events from Widgets in this Column.
         *
         * @param handleWidgetEvents
         *            {@code true} to let grid handle events from widgets;
         *            {@code false} to not
         *
         * @since 8.3
         */
        public void setHandleWidgetEvents(boolean handleWidgetEvents) {
            this.handleWidgetEvents = handleWidgetEvents;
        }

    }

    protected class BodyUpdater implements EscalatorUpdater {

        @Override
        public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
            int rowIndex = row.getRow();
            rowReference.set(rowIndex, getDataSource().getRow(rowIndex), row.getElement());
            for (FlyweightCell cell : cellsToAttach) {
                Renderer<?> renderer = findRenderer(cell);
                if (renderer instanceof ComplexRenderer) {
                    try {
                        Column<?, T> column = getVisibleColumn(cell.getColumn());
                        rendererCellReference.set(cell, getColumns().indexOf(column), column);
                        ((ComplexRenderer<?>) renderer).init(rendererCellReference);
                    } catch (RuntimeException e) {
                        getLogger().log(Level.SEVERE, "Error initing cell in column " + cell.getColumn(), e);
                    }
                }
            }
        }

        @Override
        public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
            for (FlyweightCell cell : attachedCells) {
                Renderer<?> renderer = findRenderer(cell);
                if (renderer instanceof WidgetRenderer) {
                    try {
                        WidgetRenderer<?, ?> widgetRenderer = (WidgetRenderer<?, ?>) renderer;

                        Widget widget = widgetRenderer.createWidget();
                        assert widget != null : "WidgetRenderer.createWidget() returned null. It should return a widget.";
                        assert widget
                                .getParent() == null : "WidgetRenderer.createWidget() returned a widget which already is attached.";
                        assert cell.getElement()
                                .getChildCount() == 0 : "Cell content should be empty when adding Widget";

                        // Physical attach
                        cell.getElement().appendChild(widget.getElement());

                        // Logical attach
                        setParent(widget, Grid.this);
                    } catch (RuntimeException e) {
                        getLogger().log(Level.SEVERE, "Error attaching child widget in column " + cell.getColumn(),
                                e);
                    }
                }
            }
        }

        @Override
        public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
            int rowIndex = row.getRow();
            TableRowElement rowElement = row.getElement();
            T rowData = dataSource.getRow(rowIndex);

            boolean hasData = rowData != null;

            /*
             * TODO could be more efficient to build a list of all styles that
             * should be used and update the element only once instead of
             * attempting to update only the ones that have changed.
             */

            // Assign stylename for rows with data
            boolean usedToHaveData = rowElement.hasClassName(rowHasDataStyleName);

            if (usedToHaveData != hasData) {
                setStyleName(rowElement, rowHasDataStyleName, hasData);
            }

            boolean isEvenIndex = row.getRow() % 2 == 0;
            setStyleName(rowElement, rowStripeStyleName, !isEvenIndex);

            rowReference.set(rowIndex, rowData, rowElement);

            boolean isSelected = hasData && isSelected(rowData);
            if (Grid.this.selectionModel.isSelectionAllowed()) {
                rowElement.setAttribute("aria-selected", String.valueOf(isSelected));
            } else {
                rowElement.removeAttribute("aria-selected");
            }

            if (hasData) {
                setStyleName(rowElement, rowSelectedStyleName, isSelected);
                if (rowStyleGenerator != null) {
                    try {
                        String rowStylename = rowStyleGenerator.getStyle(rowReference);
                        setCustomStyleName(rowElement, rowStylename);
                    } catch (RuntimeException e) {
                        getLogger().log(Level.SEVERE, "Error generating styles for row " + row.getRow(), e);
                    }
                } else {
                    // Remove in case there was a generator previously
                    setCustomStyleName(rowElement, null);
                }
            } else if (usedToHaveData) {
                setStyleName(rowElement, rowSelectedStyleName, false);

                setCustomStyleName(rowElement, null);
            }

            cellFocusHandler.updateFocusedRowStyle(row);

            for (FlyweightCell cell : cellsToUpdate) {
                Column<?, T> column = getVisibleColumn(cell.getColumn());
                final int columnIndex = getColumns().indexOf(column);

                assert column != null : "Column was not found from cell (" + cell.getColumn() + "," + cell.getRow()
                        + ")";

                cellFocusHandler.updateFocusedCellStyle(cell, escalator.getBody());

                if (hasData && cellStyleGenerator != null) {
                    try {
                        cellReference.set(cell.getColumn(), columnIndex, column);
                        String generatedStyle = cellStyleGenerator.getStyle(cellReference);
                        setCustomStyleName(cell.getElement(), generatedStyle);
                    } catch (RuntimeException e) {
                        getLogger().log(Level.SEVERE,
                                "Error generating style for cell in column " + cell.getColumn(), e);
                    }
                } else if (hasData || usedToHaveData) {
                    setCustomStyleName(cell.getElement(), null);
                }

                Renderer renderer = column.getRenderer();

                try {
                    rendererCellReference.set(cell, columnIndex, column);
                    if (renderer instanceof ComplexRenderer) {
                        // Hide cell content if needed
                        ComplexRenderer clxRenderer = (ComplexRenderer) renderer;
                        if (hasData) {
                            if (!usedToHaveData) {
                                // Prepare cell for rendering
                                clxRenderer.setContentVisible(rendererCellReference, true);
                            }

                            Object value = column.getValue(rowData);
                            clxRenderer.render(rendererCellReference, value);

                        } else {
                            // Prepare cell for no data
                            clxRenderer.setContentVisible(rendererCellReference, false);
                        }

                    } else if (hasData) {
                        // Simple renderers just render
                        Object value = column.getValue(rowData);
                        renderer.render(rendererCellReference, value);

                    } else {
                        // Clear cell if there is no data
                        cell.getElement().removeAllChildren();
                    }
                } catch (RuntimeException e) {
                    getLogger().log(Level.SEVERE, "Error rendering cell in column " + cell.getColumn(), e);
                }
            }
        }

        @Override
        public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
            for (FlyweightCell cell : cellsToDetach) {
                Renderer<?> renderer = findRenderer(cell);
                if (renderer instanceof WidgetRenderer) {
                    try {
                        Widget w = WidgetUtil.findWidget(cell.getElement().getFirstChildElement());
                        if (w != null) {

                            // Logical detach
                            setParent(w, null);

                            // Physical detach
                            cell.getElement().removeChild(w.getElement());
                        }
                    } catch (RuntimeException e) {
                        getLogger().log(Level.SEVERE, "Error detaching widget in column " + cell.getColumn(), e);
                    }
                }
            }
        }

        @Override
        public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
            int rowIndex = row.getRow();
            // Passing null row data since it might not exist in the data source
            // any more
            rowReference.set(rowIndex, null, row.getElement());
            for (FlyweightCell cell : detachedCells) {
                Renderer<?> renderer = findRenderer(cell);
                if (renderer instanceof ComplexRenderer) {
                    try {
                        Column<?, T> column = getVisibleColumn(cell.getColumn());
                        rendererCellReference.set(cell, getColumns().indexOf(column), column);
                        ((ComplexRenderer) renderer).destroy(rendererCellReference);
                    } catch (RuntimeException e) {
                        getLogger().log(Level.SEVERE, "Error destroying cell in column " + cell.getColumn(), e);
                    }
                }
            }
        }
    }

    protected class StaticSectionUpdater implements EscalatorUpdater {

        private StaticSection<?> section;
        private RowContainer container;

        public StaticSectionUpdater(StaticSection<?> section, RowContainer container) {
            super();
            this.section = section;
            this.container = container;
        }

        @Override
        public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
            StaticSection.StaticRow<?> staticRow = section.getRow(row.getRow());
            final List<Column<?, T>> columns = getVisibleColumns();

            setCustomStyleName(row.getElement(), staticRow.getStyleName());

            for (FlyweightCell cell : cellsToUpdate) {
                final StaticSection.StaticCell metadata = staticRow.getCell(columns.get(cell.getColumn()));

                // Decorate default row with sorting indicators
                if (staticRow instanceof HeaderRow) {
                    addAriaLabelToHeaderRow(cell);
                    addSortingIndicatorsToHeaderRow((HeaderRow) staticRow, cell);
                }

                // Assign colspan to cell before rendering
                cell.setColSpan(metadata.getColspan());

                Element td = cell.getElement();
                td.removeAllChildren();
                setCustomStyleName(td, metadata.getStyleName());

                Element content;
                // Wrap text or html content in default header to isolate
                // the content from the possible column resize drag handle
                // next to it
                if (metadata.getType() != GridStaticCellType.WIDGET) {
                    content = DOM.createDiv();

                    if (staticRow instanceof HeaderRow) {
                        content.setClassName(getStylePrimaryName() + "-column-header-content");
                        if (((HeaderRow) staticRow).isDefault()) {
                            content.setClassName(content.getClassName() + " " + getStylePrimaryName()
                                    + "-column-default-header-content");
                        }
                    } else if (staticRow instanceof FooterRow) {
                        content.setClassName(getStylePrimaryName() + "-column-footer-content");
                    } else {
                        getLogger().severe("Unhandled static row type " + staticRow.getClass().getCanonicalName());
                    }

                    td.appendChild(content);
                } else {
                    content = td;
                }

                switch (metadata.getType()) {
                case TEXT:
                    content.setInnerText(metadata.getText());
                    break;
                case HTML:
                    content.setInnerHTML(metadata.getHtml());
                    break;
                case WIDGET:
                    preDetach(row, Arrays.asList(cell));
                    content.setInnerHTML("");
                    postAttach(row, Arrays.asList(cell));
                    break;
                }

                // XXX: Should add only once in preAttach/postAttach or when
                // resizable status changes
                // Only add resize handles to default header row for now
                if (columns.get(cell.getColumn()).isResizable() && staticRow instanceof HeaderRow
                        && ((HeaderRow) staticRow).isDefault()) {

                    final DivElement resizeElement = Document.get().createDivElement();
                    resizeElement.addClassName(getStylePrimaryName() + "-column-resize-simple-indicator");

                    final int column = cell.getColumn();
                    final DragHandle dragger = new DragHandle(getStylePrimaryName() + "-column-resize-handle");
                    dragger.addTo(td);

                    // Common functionality for drag handle callback
                    // implementations
                    abstract class AbstractDHCallback implements DragHandleCallback {
                        protected Column<?, T> col = getVisibleColumn(column);
                        protected double initialWidth = 0;
                        protected double minCellWidth;
                        protected double width;

                        protected void dragStarted() {
                            initialWidth = col.getWidthActual();
                            width = initialWidth;

                            minCellWidth = escalator.getMinCellWidth(getVisibleColumns().indexOf(col));
                            for (Column<?, T> c : getVisibleColumns()) {
                                if (selectionColumn == c) {
                                    // Don't modify selection column.
                                    continue;
                                }

                                if (c.getWidth() < 0) {
                                    c.setWidth(c.getWidthActual());
                                    fireEvent(new ColumnResizeEvent<>(c));
                                }
                            }

                            WidgetUtil.setTextSelectionEnabled(getElement(), false);
                        }

                        protected void dragEnded() {
                            WidgetUtil.setTextSelectionEnabled(getElement(), true);
                        }
                    }

                    final DragHandleCallback simpleResizeMode = new AbstractDHCallback() {
                        @Override
                        protected void dragEnded() {
                            super.dragEnded();
                            dragger.getElement().removeChild(resizeElement);
                        }

                        @Override
                        public void onStart() {
                            dragStarted();
                            dragger.getElement().appendChild(resizeElement);
                            resizeElement.getStyle().setLeft(
                                    (dragger.getElement().getOffsetWidth() - resizeElement.getOffsetWidth()) * .5,
                                    Unit.PX);
                            resizeElement.getStyle().setHeight(col.grid.getOffsetHeight(), Unit.PX);
                        }

                        @Override
                        public void onUpdate(double deltaX, double deltaY) {
                            width = Math.max(minCellWidth, initialWidth + deltaX);
                            resizeElement.getStyle().setLeft(
                                    (dragger.getElement().getOffsetWidth() - resizeElement.getOffsetWidth()) * .5
                                            + (width - initialWidth),
                                    Unit.PX);
                        }

                        @Override
                        public void onCancel() {
                            dragEnded();
                        }

                        @Override
                        public void onComplete() {
                            dragEnded();
                            col.setWidth(width);

                            // Need to wait for column width recalculation
                            // scheduled by setWidth() before firing the event
                            Scheduler.get().scheduleDeferred(() -> fireEvent(new ColumnResizeEvent<>(col)));
                        }
                    };

                    final DragHandleCallback animatedResizeMode = new AbstractDHCallback() {
                        @Override
                        public void onStart() {
                            dragStarted();
                        }

                        @Override
                        public void onUpdate(double deltaX, double deltaY) {
                            width = Math.max(minCellWidth, initialWidth + deltaX);
                            col.setWidth(width);
                        }

                        @Override
                        public void onCancel() {
                            dragEnded();
                            col.setWidth(initialWidth);
                        }

                        @Override
                        public void onComplete() {
                            dragEnded();
                            col.setWidth(width);
                            fireEvent(new ColumnResizeEvent<>(col));
                        }
                    };

                    // DragHandle gets assigned a 'master callback' that
                    // delegates
                    // functionality to the correct case-specific implementation
                    dragger.setCallback(new DragHandleCallback() {

                        private DragHandleCallback currentCallback;

                        @Override
                        public void onStart() {
                            switch (getColumnResizeMode()) {
                            case SIMPLE:
                                currentCallback = simpleResizeMode;
                                break;
                            case ANIMATED:
                                currentCallback = animatedResizeMode;
                                break;
                            default:
                                throw new UnsupportedOperationException(
                                        "Support for current column resize mode is not yet implemented");
                            }

                            currentCallback.onStart();
                        }

                        @Override
                        public void onUpdate(double deltaX, double deltaY) {
                            currentCallback.onUpdate(deltaX, deltaY);
                        }

                        @Override
                        public void onCancel() {
                            currentCallback.onCancel();
                        }

                        @Override
                        public void onComplete() {
                            currentCallback.onComplete();
                        }
                    });
                }

                cellFocusHandler.updateFocusedCellStyle(cell, container);
            }
        }

        private void addAriaLabelToHeaderRow(FlyweightCell cell) {

            Element cellElement = cell.getElement();

            final Column<?, T> column = getVisibleColumn(cell.getColumn());

            if (column.getAssistiveCaption() != null) {
                cellElement.setAttribute("aria-label", column.getAssistiveCaption());
            } else {
                cellElement.removeAttribute("aria-label");
            }
        }

        private void addSortingIndicatorsToHeaderRow(HeaderRow headerRow, FlyweightCell cell) {

            Element cellElement = cell.getElement();

            boolean sortedBefore = cellElement.hasClassName("sort-asc") || cellElement.hasClassName("sort-desc");

            cleanup(cell);
            if (!headerRow.isDefault()) {
                // Nothing more to do if not in the default row
                return;
            }

            final Column<?, T> column = getVisibleColumn(cell.getColumn());
            SortOrder sortingOrder = getSortOrder(column);
            boolean sortable = column.isSortable();

            if (sortable) {
                cellElement.addClassName("sortable");
                cellElement.setAttribute("aria-sort", "none");
            }

            if (!sortable || sortingOrder == null) {
                // Only apply sorting indicators to sortable header columns
                return;
            }

            if (SortDirection.ASCENDING == sortingOrder.getDirection()) {
                cellElement.addClassName("sort-asc");
                cellElement.setAttribute("aria-sort", "ascending");
            } else {
                cellElement.addClassName("sort-desc");
                cellElement.setAttribute("aria-sort", "descending");
            }

            int sortIndex = Grid.this.getSortOrder().indexOf(sortingOrder);
            if (sortIndex > -1 && Grid.this.getSortOrder().size() > 1) {
                // Show sort order indicator if column is
                // sorted and other sorted columns also exists.
                cellElement.setAttribute("sort-order", String.valueOf(sortIndex + 1));
                cellElement.setAttribute("aria-sort", "other");
            }

            if (!sortedBefore) {
                verifyColumnWidth(column);
            }
        }

        /**
         * Sort indicator requires a bit more space from the cell than normally.
         * This method check that the now sorted column has enough width.
         *
         * @param column
         *            sorted column
         */
        private void verifyColumnWidth(Column<?, T> column) {
            int colIndex = getColumns().indexOf(column);
            double minWidth = escalator.getMinCellWidth(colIndex);
            if (column.getWidthActual() < minWidth) {
                // Fix column size
                escalator.getColumnConfiguration().setColumnWidth(colIndex, minWidth);

                fireEvent(new ColumnResizeEvent<>(column));
            }
        }

        /**
         * Finds the sort order for this column
         */
        private SortOrder getSortOrder(Column<?, ?> column) {
            for (SortOrder order : Grid.this.getSortOrder()) {
                if (order.getColumn() == column) {
                    return order;
                }
            }
            return null;
        }

        private void cleanup(FlyweightCell cell) {
            Element cellElement = cell.getElement();
            cellElement.removeAttribute("sort-order");
            cellElement.removeAttribute("aria-sort");
            cellElement.removeClassName("sort-desc");
            cellElement.removeClassName("sort-asc");
            cellElement.removeClassName("sortable");
        }

        @Override
        public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
        }

        @Override
        public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
            StaticSection.StaticRow<?> gridRow = section.getRow(row.getRow());
            List<Column<?, T>> columns = getVisibleColumns();

            for (FlyweightCell cell : attachedCells) {
                StaticSection.StaticCell metadata = gridRow.getCell(columns.get(cell.getColumn()));
                /*
                 * If the cell contains widgets that are not currently attached
                 * then attach them now.
                 */
                if (GridStaticCellType.WIDGET.equals(metadata.getType())) {
                    final Widget widget = metadata.getWidget();
                    if (widget != null && !widget.isAttached()) {
                        getGrid().attachWidget(metadata.getWidget(), cell.getElement());
                    }
                }
            }
        }

        @Override
        public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
            if (section.getRowCount() > row.getRow()) {
                StaticSection.StaticRow<?> gridRow = section.getRow(row.getRow());
                List<Column<?, T>> columns = getVisibleColumns();
                for (FlyweightCell cell : cellsToDetach) {
                    StaticSection.StaticCell metadata = gridRow.getCell(columns.get(cell.getColumn()));

                    if (GridStaticCellType.WIDGET.equals(metadata.getType()) && metadata.getWidget() != null
                            && metadata.getWidget().isAttached()) {

                        getGrid().detachWidget(metadata.getWidget());
                    }
                }
            }
        }

        protected Grid getGrid() {
            return section.grid;
        }

        @Override
        public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
        }
    };

    /**
     * Creates a new instance.
     */
    public Grid() {
        initWidget(escalator);
        getElement().setTabIndex(0);
        cellFocusHandler = new CellFocusHandler();

        setStylePrimaryName(STYLE_NAME);
        setAriaRole("grid");

        escalator.getHeader().setEscalatorUpdater(createHeaderUpdater());
        escalator.getBody().setEscalatorUpdater(createBodyUpdater());
        escalator.getFooter().setEscalatorUpdater(createFooterUpdater());

        header.setGrid(this);
        HeaderRow defaultRow = header.appendRow();
        header.setDefaultRow(defaultRow);

        footer.setGrid(this);

        editor.setGrid(this);

        setSelectionModel(new SelectionModel.NoSelectionModel<>());

        escalator.getBody().setSpacerUpdater(gridSpacerUpdater);

        escalator.addScrollHandler(event -> fireEvent(new ScrollEvent()));

        escalator.addRowVisibilityChangeHandler(event -> {
            if (dataSource != null && dataSource.size() != 0) {
                dataSource.ensureAvailability(event.getFirstVisibleRow(), event.getVisibleRowCount());
            }
        });

        // Default action on SelectionEvents. Refresh the body so changed
        // become visible.
        addSelectionHandler(new SelectionHandler<T>() {

            @Override
            public void onSelect(SelectionEvent<T> event) {
                refreshBody();
            }
        });

        addSpacerIndexChangedHandler(new SpacerIndexChangedHandler() {
            @Override
            public void onSpacerIndexChanged(SpacerIndexChangedEvent event) {
                // remove old index and add new index
                visibleDetails.remove(event.getOldIndex());
                visibleDetails.add(event.getNewIndex());
            }
        });

        // Sink header events and key events
        sinkEvents(getHeader().getConsumedEvents());
        sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP, BrowserEvents.KEYPRESS,
                BrowserEvents.DBLCLICK, BrowserEvents.MOUSEDOWN, BrowserEvents.CLICK));

        // Make ENTER and SHIFT+ENTER in the header perform sorting
        addHeaderKeyUpHandler(event -> {
            if (event.getNativeKeyCode() != KeyCodes.KEY_ENTER) {
                return;
            }
            if (getHeader().getRow(event.getFocusedCell().getRowIndex()).isDefault()) {
                // Only sort for enter on the default header
                sorter.sort(event.getFocusedCell().getColumn(), event.isShiftKeyDown());
            }
        });

        browserEventHandlers.addAll(Arrays.asList(
                // Opening, closing and navigating in the editor
                new EditorEventHandler(),
                // Keyboard and click handlers, Escalator events
                new SuperEventHandler(),
                // Column reordering via header drag&drop
                new HeaderCellDragStartHandler(),
                // Column sorting via header click
                new HeaderDefaultRowEventHandler(),
                // Invoking event-aware renderers
                new RendererEventHandler(),
                // Moving cell focus by keyboard or mouse
                new CellFocusEventHandler()));
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public void setEnabled(boolean enabled) {
        if (enabled == this.enabled) {
            return;
        }

        this.enabled = enabled;
        getElement().setTabIndex(enabled ? 0 : -1);

        // Editor save and cancel buttons need to be disabled.
        boolean editorOpen = editor.getState() != State.INACTIVE;
        if (editorOpen) {
            editor.setGridEnabled(enabled);
        }

        sidebar.setEnabled(enabled);

        getEscalator().setScrollLocked(Direction.VERTICAL, !enabled || editorOpen);
        getEscalator().setScrollLocked(Direction.HORIZONTAL, !enabled);

        fireEvent(new GridEnabledEvent(enabled));
    }

    /**
     * Sets the column resize mode to use. The default mode is
     * {@link ColumnResizeMode.ANIMATED}.
     *
     * @param mode
     *            a ColumnResizeMode value
     *
     * @since 7.7.5
     */
    public void setColumnResizeMode(ColumnResizeMode mode) {
        columnResizeMode = mode;
    }

    /**
     * Returns the current column resize mode. The default mode is
     * {@link ColumnResizeMode.ANIMATED}.
     *
     * @return a ColumnResizeMode value
     * @since 7.7.5
     */
    public ColumnResizeMode getColumnResizeMode() {
        return columnResizeMode;
    }

    @Override
    public void setStylePrimaryName(String style) {
        super.setStylePrimaryName(style);
        escalator.setStylePrimaryName(style);
        editor.setStylePrimaryName(style);
        sidebar.setStylePrimaryName(style + "-sidebar");
        sidebar.addStyleName("v-contextmenu");

        String rowStyle = getStylePrimaryName() + "-row";
        rowHasDataStyleName = rowStyle + "-has-data";
        rowSelectedStyleName = rowStyle + "-selected";
        rowStripeStyleName = rowStyle + "-stripe";

        cellFocusStyleName = getStylePrimaryName() + "-cell-focused";
        rowFocusStyleName = getStylePrimaryName() + "-row-focused";

        if (isAttached()) {
            refreshHeader();
            requestRefreshBody();
            refreshFooter();
        }
    }

    /**
     * Adds the given role as 'role="$param"' to the <code>&lt;table&gt;</code>
     * element of the grid.
     *
     * @param role
     *            the role param
     * @since 8.2
     */
    protected void setAriaRole(String role) {
        escalator.getTable().setAttribute("role", role);
    }

    /**
     * Creates the escalator updater used to update the header rows in this
     * grid. The updater is invoked when header rows or columns are added or
     * removed, or the content of existing header cells is changed.
     *
     * @return the new header updater instance
     *
     * @see GridHeader
     * @see Grid#getHeader()
     */
    protected EscalatorUpdater createHeaderUpdater() {
        return new StaticSectionUpdater(header, escalator.getHeader());
    }

    /**
     * Creates the escalator updater used to update the body rows in this grid.
     * The updater is invoked when body rows or columns are added or removed,
     * the content of body cells is changed, or the body is scrolled to expose
     * previously hidden content.
     *
     * @return the new body updater instance
     */
    protected EscalatorUpdater createBodyUpdater() {
        return new BodyUpdater();
    }

    /**
     * Creates the escalator updater used to update the footer rows in this
     * grid. The updater is invoked when header rows or columns are added or
     * removed, or the content of existing header cells is changed.
     *
     * @return the new footer updater instance
     *
     * @see GridFooter
     * @see #getFooter()
     */
    protected EscalatorUpdater createFooterUpdater() {
        return new StaticSectionUpdater(footer, escalator.getFooter());
    }

    /**
     * Refreshes header or footer rows on demand
     *
     * @param rows
     *            The row container
     * @param firstRowIsVisible
     *            is the first row visible
     * @param isHeader
     *            <code>true</code> if we refreshing the header, else assumed
     *            the footer
     */
    private void refreshRowContainer(RowContainer rows, StaticSection<?> section) {

        // Add or Remove rows on demand
        int rowDiff = section.getVisibleRowCount() - rows.getRowCount();
        if (rowDiff > 0) {
            rows.insertRows(0, rowDiff);
        } else if (rowDiff < 0) {
            rows.removeRows(0, -rowDiff);
        }

        // Refresh all the rows
        if (rows.getRowCount() > 0) {
            rows.refreshRows(0, rows.getRowCount());
        }
    }

    /**
     * Focus a body cell by row and column index.
     *
     * @param rowIndex
     *            index of row to focus
     * @param columnIndexDOM
     *            index (excluding hidden columns) of cell to focus
     */
    void focusCell(int rowIndex, int columnIndexDOM) {
        final Range rowRange = Range.between(0, dataSource.size());
        final Range columnRange = Range.between(0, getVisibleColumns().size());

        assert rowRange.contains(rowIndex) : "Illegal row index. Should be in range " + rowRange;
        assert columnRange.contains(columnIndexDOM) : "Illegal column index. Should be in range " + columnRange;

        if (rowRange.contains(rowIndex) && columnRange.contains(columnIndexDOM)) {
            cellFocusHandler.setCellFocus(rowIndex, columnIndexDOM, escalator.getBody());
            WidgetUtil.focus(getElement());
        }
    }

    /**
     * Refreshes all header rows
     */
    void refreshHeader() {
        refreshRowContainer(escalator.getHeader(), header);
    }

    /**
     * Refreshes all body rows
     */
    private void refreshBody() {
        escalator.getBody().refreshRows(0, escalator.getBody().getRowCount());
    }

    /**
     * Request delayed refresh of all body rows.
     *
     * @since 8.1
     */
    public void requestRefreshBody() {
        if (!refreshBodyRequested) {
            refreshBodyRequested = true;
            Scheduler.get().scheduleFinally(() -> {
                refreshBodyRequested = false;
                refreshBody();
            });
        }
    }

    /**
     * Refreshes all footer rows
     */
    void refreshFooter() {
        refreshRowContainer(escalator.getFooter(), footer);
    }

    /**
     * Adds columns as the last columns in the grid.
     *
     * @param columns
     *            the columns to add
     */
    public void addColumns(Column<?, T>... columns) {
        if (columns.length == 0) {
            // Nothing to add.
            return;
        }
        addColumnsSkipSelectionColumnCheck(Arrays.asList(columns), getVisibleColumns().size());
    }

    /**
     * Adds a column as the last column in the grid.
     *
     * @param column
     *            the column to add
     * @return given column
     */
    public <C extends Column<?, T>> C addColumn(C column) {
        addColumn(column, getColumnCount());
        return column;
    }

    /**
     * Inserts a column into a specific position in the grid.
     *
     * @param index
     *            the index where the column should be inserted into
     * @param column
     *            the column to add
     * @return given column
     *
     * @throws IllegalStateException
     *             if Grid's current selection model renders a selection column,
     *             and {@code index} is 0.
     */
    public <C extends Column<?, T>> C addColumn(C column, int index) {
        if (column == selectionColumn) {
            throw new IllegalArgumentException("The selection column many " + "not be added manually");
        } else if (selectionColumn != null && index == 0) {
            throw new IllegalStateException("A column cannot be inserted " + "before the selection column");
        }

        addColumnsSkipSelectionColumnCheck(Collections.singleton(column), index);
        return column;
    }

    private void addColumnsSkipSelectionColumnCheck(Collection<Column<?, T>> columnsToAdd, int startIndex) {
        AtomicInteger index = new AtomicInteger(startIndex);
        columnsToAdd.forEach(col -> {
            // Register column with grid
            columns.add(index.getAndIncrement(), col);

            header.addColumn(col);
            footer.addColumn(col);

            // Register this grid instance with the column
            col.setGrid(this);
        });

        escalator.getColumnConfiguration().insertColumns(startIndex,
                (int) columnsToAdd.stream().filter(col -> !col.isHidden()).count());

        columnsToAdd.forEach(col -> {
            // Reapply column width
            col.reapplyWidth();

            // Sink all renderer events
            Set<String> events = new HashSet<>();
            events.addAll(getConsumedEventsForRenderer(col.getRenderer()));

            if (col.isHidable()) {
                columnHider.updateColumnHidable(col);
            }

            sinkEvents(events);
        });
    }

    private void sinkEvents(Collection<String> events) {
        assert events != null;

        int eventsToSink = 0;
        for (String typeName : events) {
            int typeInt = Event.getTypeInt(typeName);
            if (typeInt < 0) {
                // Type not recognized by typeInt
                sinkBitlessEvent(typeName);
            } else {
                eventsToSink |= typeInt;
            }
        }

        if (eventsToSink > 0) {
            sinkEvents(eventsToSink);
        }
    }

    private Renderer<?> findRenderer(FlyweightCell cell) {
        Column<?, T> column = getVisibleColumn(cell.getColumn());
        assert column != null : "Could not find column at index:" + cell.getColumn();
        return column.getRenderer();
    }

    /**
     * Removes a column from the grid.
     *
     * @param column
     *            the column to remove
     */
    public void removeColumn(Column<?, T> column) {
        if (column != null && column.equals(selectionColumn)) {
            throw new IllegalArgumentException("The selection column may not be removed manually.");
        }

        removeColumnSkipSelectionColumnCheck(column);
    }

    private void removeColumnSkipSelectionColumnCheck(Column<?, T> column) {
        int columnIndex = columns.indexOf(column);

        // Remove from column configuration
        int visibleColumnIndex = getVisibleColumns().indexOf(column);
        if (visibleColumnIndex < 0) {
            assert column.isHidden();
            // Hidden columns are not included in Escalator
        } else {
            getEscalator().getColumnConfiguration().removeColumns(visibleColumnIndex, 1);
        }

        header.removeColumn(column);
        footer.removeColumn(column);

        // de-register column with grid
        ((Column<?, T>) column).setGrid(null);

        columns.remove(columnIndex);

        if (column.isHidable()) {
            columnHider.removeColumnHidingToggle(column);
        }

        updateFrozenColumns();
    }

    /**
     * Returns the amount of columns in the grid.
     * <p>
     * <em>NOTE:</em> this includes the hidden columns in the count.
     *
     * @return The number of columns in the grid
     */
    public int getColumnCount() {
        return columns.size();
    }

    /**
     * Returns a list columns in the grid, including hidden columns.
     * <p>
     * For currently visible columns, use {@link #getVisibleColumns()}.
     *
     * @return A unmodifiable list of the columns in the grid
     */
    public List<Column<?, T>> getColumns() {
        return Collections.unmodifiableList(new ArrayList<>(columns));
    }

    /**
     * Returns a list of the currently visible columns in the grid.
     * <p>
     * No {@link Column#isHidden() hidden} columns included.
     *
     * @since 7.5.0
     * @return A unmodifiable list of the currently visible columns in the grid
     */
    public List<Column<?, T>> getVisibleColumns() {
        List<Column<?, T>> visible = new ArrayList<>();
        for (Column<?, T> c : columns) {
            if (!c.isHidden()) {
                visible.add(c);
            }
        }
        return Collections.unmodifiableList(visible);
    }

    /**
     * Returns a column by its index in the grid.
     * <p>
     * <em>NOTE:</em> The indexing includes hidden columns.
     *
     * @param index
     *            the index of the column
     * @return The column in the given index
     * @throws IllegalArgumentException
     *             if the column index does not exist in the grid
     */
    public Column<?, T> getColumn(int index) throws IllegalArgumentException {
        if (index < 0 || index >= columns.size()) {
            throw new IllegalStateException("Column not found.");
        }
        return columns.get(index);
    }

    private Column<?, T> getVisibleColumn(int index) throws IllegalArgumentException {
        List<Column<?, T>> visibleColumns = getVisibleColumns();
        if (index < 0 || index >= visibleColumns.size()) {
            throw new IllegalStateException("Column not found.");
        }
        return visibleColumns.get(index);
    }

    /**
     * Returns the header section of this grid. The default header contains a
     * single row displaying the column captions.
     *
     * @return the header
     */
    protected Header getHeader() {
        return header;
    }

    /**
     * Gets the header row at given index.
     *
     * @param rowIndex
     *            0 based index for row. Counted from top to bottom
     * @return header row at given index
     * @throws IllegalArgumentException
     *             if no row exists at given index
     */
    public HeaderRow getHeaderRow(int rowIndex) {
        return header.getRow(rowIndex);
    }

    /**
     * Inserts a new row at the given position to the header section. Shifts the
     * row currently at that position and any subsequent rows down (adds one to
     * their indices).
     *
     * @param index
     *            the position at which to insert the row
     * @return the new row
     *
     * @throws IllegalArgumentException
     *             if the index is less than 0 or greater than row count
     * @see #appendHeaderRow()
     * @see #prependHeaderRow()
     * @see #removeHeaderRow(HeaderRow)
     * @see #removeHeaderRow(int)
     */
    public HeaderRow addHeaderRowAt(int index) {
        return header.addRowAt(index);
    }

    /**
     * Adds a new row at the bottom of the header section.
     *
     * @return the new row
     * @see #prependHeaderRow()
     * @see #addHeaderRowAt(int)
     * @see #removeHeaderRow(HeaderRow)
     * @see #removeHeaderRow(int)
     */
    public HeaderRow appendHeaderRow() {
        return header.appendRow();
    }

    /**
     * Returns the current default row of the header section. The default row is
     * a special header row providing a user interface for sorting columns.
     * Setting a header caption for column updates cells in the default header.
     *
     * @return the default row or null if no default row set
     */
    public HeaderRow getDefaultHeaderRow() {
        return header.getDefaultRow();
    }

    /**
     * Gets the row count for the header section.
     *
     * @return row count
     */
    public int getHeaderRowCount() {
        return header.getRowCount();
    }

    /**
     * Adds a new row at the top of the header section.
     *
     * @return the new row
     * @see #appendHeaderRow()
     * @see #addHeaderRowAt(int)
     * @see #removeHeaderRow(HeaderRow)
     * @see #removeHeaderRow(int)
     */
    public HeaderRow prependHeaderRow() {
        return header.prependRow();
    }

    /**
     * Removes the given row from the header section.
     *
     * @param row
     *            the row to be removed
     *
     * @throws IllegalArgumentException
     *             if the row does not exist in this section
     * @see #removeHeaderRow(int)
     * @see #addHeaderRowAt(int)
     * @see #appendHeaderRow()
     * @see #prependHeaderRow()
     */
    public void removeHeaderRow(HeaderRow row) {
        header.removeRow(row);
    }

    /**
     * Removes the row at the given position from the header section.
     *
     * @param rowIndex
     *            the position of the row
     *
     * @throws IllegalArgumentException
     *             if no row exists at given index
     * @see #removeHeaderRow(HeaderRow)
     * @see #addHeaderRowAt(int)
     * @see #appendHeaderRow()
     * @see #prependHeaderRow()
     */
    public void removeHeaderRow(int rowIndex) {
        header.removeRow(rowIndex);
    }

    /**
     * Sets the default row of the header. The default row is a special header
     * row providing a user interface for sorting columns.
     * <p>
     * Note: Setting the default header row will reset all cell contents to
     * Column defaults.
     *
     * @param row
     *            the new default row, or null for no default row
     *
     * @throws IllegalArgumentException
     *             header does not contain the row
     */
    public void setDefaultHeaderRow(HeaderRow row) {
        header.setDefaultRow(row);
    }

    /**
     * Sets the visibility of the header section.
     *
     * @param visible
     *            true to show header section, false to hide
     */
    public void setHeaderVisible(boolean visible) {
        header.setVisible(visible);
    }

    /**
     * Returns the visibility of the header section.
     *
     * @return true if visible, false otherwise.
     */
    public boolean isHeaderVisible() {
        return header.isVisible();
    }

    /* Grid Footers */

    /**
     * Returns the footer section of this grid. The default footer is empty.
     *
     * @return the footer
     */
    protected Footer getFooter() {
        return footer;
    }

    /**
     * Gets the footer row at given index.
     *
     * @param rowIndex
     *            0 based index for row. Counted from top to bottom
     * @return footer row at given index
     * @throws IllegalArgumentException
     *             if no row exists at given index
     */
    public FooterRow getFooterRow(int rowIndex) {
        return footer.getRow(rowIndex);
    }

    /**
     * Inserts a new row at the given position to the footer section. Shifts the
     * row currently at that position and any subsequent rows down (adds one to
     * their indices).
     *
     * @param index
     *            the position at which to insert the row
     * @return the new row
     *
     * @throws IllegalArgumentException
     *             if the index is less than 0 or greater than row count
     * @see #appendFooterRow()
     * @see #prependFooterRow()
     * @see #removeFooterRow(FooterRow)
     * @see #removeFooterRow(int)
     */
    public FooterRow addFooterRowAt(int index) {
        return footer.addRowAt(index);
    }

    /**
     * Adds a new row at the bottom of the footer section.
     *
     * @return the new row
     * @see #prependFooterRow()
     * @see #addFooterRowAt(int)
     * @see #removeFooterRow(FooterRow)
     * @see #removeFooterRow(int)
     */
    public FooterRow appendFooterRow() {
        return footer.appendRow();
    }

    /**
     * Gets the row count for the footer.
     *
     * @return row count
     */
    public int getFooterRowCount() {
        return footer.getRowCount();
    }

    /**
     * Adds a new row at the top of the footer section.
     *
     * @return the new row
     * @see #appendFooterRow()
     * @see #addFooterRowAt(int)
     * @see #removeFooterRow(FooterRow)
     * @see #removeFooterRow(int)
     */
    public FooterRow prependFooterRow() {
        return footer.prependRow();
    }

    /**
     * Removes the given row from the footer section.
     *
     * @param row
     *            the row to be removed
     *
     * @throws IllegalArgumentException
     *             if the row does not exist in this section
     * @see #removeFooterRow(int)
     * @see #addFooterRowAt(int)
     * @see #appendFooterRow()
     * @see #prependFooterRow()
     */
    public void removeFooterRow(FooterRow row) {
        footer.removeRow(row);
    }

    /**
     * Removes the row at the given position from the footer section.
     *
     * @param rowIndex
     *            the position of the row
     *
     * @throws IllegalArgumentException
     *             if no row exists at given index
     * @see #removeFooterRow(FooterRow)
     * @see #addFooterRowAt(int)
     * @see #appendFooterRow()
     * @see #prependFooterRow()
     */
    public void removeFooterRow(int rowIndex) {
        footer.removeRow(rowIndex);
    }

    /**
     * Sets the visibility of the footer section.
     *
     * @param visible
     *            true to show footer section, false to hide
     */
    public void setFooterVisible(boolean visible) {
        footer.setVisible(visible);
    }

    /**
     * Returns the visibility of the footer section.
     *
     * @return true if visible, false otherwise.
     */
    public boolean isFooterVisible() {
        return footer.isVisible();
    }

    public Editor<T> getEditor() {
        return editor;
    }

    /**
     * Gets the {@link Escalator} used by this Grid instance.
     *
     * @return the escalator instance, never <code>null</code>
     */
    public Escalator getEscalator() {
        return escalator;
    }

    /**
     * {@inheritDoc}
     * <p>
     * <em>Note:</em> This method will change the widget's size in the browser
     * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}.
     *
     * @see #setHeightMode(HeightMode)
     */
    @Override
    public void setHeight(String height) {
        escalator.setHeight(height);
    }

    @Override
    public void setWidth(String width) {
        escalator.setWidth(width);
    }

    /**
     * Sets the data source used by this grid.
     *
     * @param dataSource
     *            the data source to use, not null
     * @throws IllegalArgumentException
     *             if <code>dataSource</code> is <code>null</code>
     */
    public void setDataSource(final DataSource<T> dataSource) throws IllegalArgumentException {
        if (dataSource == null) {
            throw new IllegalArgumentException("dataSource can't be null.");
        }

        if (changeHandler != null) {
            changeHandler.remove();
            changeHandler = null;
        }

        this.dataSource = dataSource;
        changeHandler = dataSource.addDataChangeHandler(new DataChangeHandler() {
            @Override
            public void dataUpdated(int firstIndex, int numberOfItems) {
                escalator.getBody().refreshRows(firstIndex, numberOfItems);
            }

            @Override
            public void dataRemoved(int firstIndex, int numberOfItems) {
                for (int i = 0; i < numberOfItems; ++i) {
                    visibleDetails.remove(firstIndex + i);
                }
                escalator.getBody().removeRows(firstIndex, numberOfItems);
                Range removed = Range.withLength(firstIndex, numberOfItems);
                cellFocusHandler.rowsRemovedFromBody(removed);
            }

            @Override
            public void dataAdded(int firstIndex, int numberOfItems) {
                escalator.getBody().insertRows(firstIndex, numberOfItems);
                Range added = Range.withLength(firstIndex, numberOfItems);
                cellFocusHandler.rowsAddedToBody(added);
            }

            @Override
            public void dataAvailable(int firstIndex, int numberOfItems) {
                currentDataAvailable = Range.withLength(firstIndex, numberOfItems);
                fireEvent(new DataAvailableEvent(currentDataAvailable));
            }

            @Override
            public void resetDataAndSize(int newSize) {
                RowContainer body = escalator.getBody();
                int oldSize = body.getRowCount();

                // Hide all details.
                Set<Integer> oldDetails = new HashSet<>(visibleDetails);
                for (int i : oldDetails) {
                    setDetailsVisible(i, false);
                }

                if (newSize > oldSize) {
                    if (oldSize == 0 && !isHeaderVisible()) {
                        // Fixes framework/issues/11607
                        // Need to recalculate column widths when the
                        // first row is added to a non-header grid,
                        // otherwise the checkbox will be aligned in a
                        // wrong place.
                        recalculateColumnWidths();
                    }
                    body.insertRows(oldSize, newSize - oldSize);
                    cellFocusHandler.rowsAddedToBody(Range.withLength(oldSize, newSize - oldSize));
                } else if (newSize < oldSize) {
                    body.removeRows(newSize, oldSize - newSize);
                    cellFocusHandler.rowsRemovedFromBody(Range.withLength(newSize, oldSize - newSize));
                }

                if (newSize > 0) {
                    Range visibleRowRange = escalator.getVisibleRowRange();
                    dataSource.ensureAvailability(visibleRowRange.getStart(), visibleRowRange.length());
                } else {
                    // We won't expect any data more data updates, so
                    // just make
                    // the bookkeeping happy
                    dataAvailable(0, 0);
                }

                assert body.getRowCount() == newSize;
            }
        });

        int previousRowCount = escalator.getBody().getRowCount();
        if (previousRowCount != 0) {
            escalator.getBody().removeRows(0, previousRowCount);
        }

        setEscalatorSizeFromDataSource();
    }

    private void setEscalatorSizeFromDataSource() {
        assert escalator.getBody().getRowCount() == 0;

        int size = dataSource.size();
        if (size > 0) {
            escalator.getBody().insertRows(0, size);
        }
    }

    /**
     * Gets the {@Link DataSource} for this Grid.
     *
     * @return the data source used by this grid
     */
    public DataSource<T> getDataSource() {
        return dataSource;
    }

    /**
     * Sets the number of frozen columns in this grid. Setting the count to 0
     * means that no data columns will be frozen, but the built-in selection
     * checkbox column will still be frozen if it's in use. Setting the count to
     * -1 will also disable the selection column.
     * <p>
     * The default value is 0.
     *
     * @param numberOfColumns
     *            the number of columns that should be frozen
     *
     * @throws IllegalArgumentException
     *             if the column count is < -1 or > the number of visible
     *             columns
     */
    public void setFrozenColumnCount(int numberOfColumns) {
        if (numberOfColumns < -1 || numberOfColumns > getColumnCount()) {
            throw new IllegalArgumentException(
                    "count must be between -1 and the current number of columns (" + getColumnCount() + ")");
        }

        frozenColumnCount = numberOfColumns;
        updateFrozenColumns();
    }

    private void updateFrozenColumns() {
        escalator.getColumnConfiguration().setFrozenColumnCount(getVisibleFrozenColumnCount());
    }

    private int getVisibleFrozenColumnCount() {
        int numberOfColumns = getFrozenColumnCount();

        // for the escalator the hidden columns are not in the frozen column
        // count, but for grid they are. thus need to convert the index
        for (int i = 0; i < frozenColumnCount; i++) {
            if (i >= getColumnCount() || getColumn(i).isHidden()) {
                numberOfColumns--;
            }
        }

        if (numberOfColumns == -1) {
            numberOfColumns = 0;
        } else if (selectionColumn != null) {
            numberOfColumns++;
        }
        return numberOfColumns;
    }

    /**
     * Gets the number of frozen columns in this grid. 0 means that no data
     * columns will be frozen, but the built-in selection checkbox column will
     * still be frozen if it's in use. -1 means that not even the selection
     * column is frozen.
     * <p>
     * <em>NOTE:</em> This includes {@link Column#isHidden() hidden columns} in
     * the count.
     *
     * @return the number of frozen columns
     */
    public int getFrozenColumnCount() {
        return frozenColumnCount;
    }

    public HandlerRegistration addRowVisibilityChangeHandler(RowVisibilityChangeHandler handler) {
        /*
         * Reusing Escalator's RowVisibilityChangeHandler, since a scroll
         * concept is too abstract. e.g. the event needs to be re-sent when the
         * widget is resized.
         */
        return escalator.addRowVisibilityChangeHandler(handler);
    }

    /**
     * Scrolls to a certain row, using {@link ScrollDestination#ANY}.
     * <p>
     * If the details for that row are visible, those will be taken into account
     * as well.
     *
     * @param rowIndex
     *            zero-based index of the row to scroll to.
     * @throws IllegalArgumentException
     *             if rowIndex is below zero, or above the maximum value
     *             supported by the data source.
     */
    public void scrollToRow(int rowIndex) throws IllegalArgumentException {
        scrollToRow(rowIndex, ScrollDestination.ANY, GridConstants.DEFAULT_PADDING);
    }

    /**
     * Scrolls to a certain row, using user-specified scroll destination.
     * <p>
     * If the details for that row are visible, those will be taken into account
     * as well.
     *
     * @param rowIndex
     *            zero-based index of the row to scroll to.
     * @param destination
     *            desired destination placement of scrolled-to-row. See
     *            {@link ScrollDestination} for more information.
     * @throws IllegalArgumentException
     *             if rowIndex is below zero, or above the maximum value
     *             supported by the data source.
     */
    public void scrollToRow(int rowIndex, ScrollDestination destination) throws IllegalArgumentException {
        scrollToRow(rowIndex, destination,
                destination == ScrollDestination.MIDDLE ? 0 : GridConstants.DEFAULT_PADDING);
    }

    /**
     * Helper method for making sure desired row is visible and it is properly
     * rendered.
     *
     * @param rowIndex
     *            the row to look for
     * @param destination
     *            the desired scroll destination
     * @param callback
     *            the callback command to execute when row is available
     * @since 8.4
     */
    public void scrollToRow(int rowIndex, ScrollDestination destination, Runnable callback) {
        waitUntilVisible(rowIndex, destination, callback);
    }

    /**
     * Helper method for making sure desired row is visible and it is properly
     * rendered.
     *
     * @param rowIndex
     *            the row to look for
     * @param whenRendered
     *            the callback command to execute when row is available
     * @since 8.4
     */
    public void scrollToRow(int rowIndex, Runnable whenRendered) {
        scrollToRow(rowIndex, ScrollDestination.ANY, whenRendered);
    }

    /**
     * Scrolls to a certain row using only user-specified parameters.
     * <p>
     * If the details for that row are visible, those will be taken into account
     * as well.
     *
     * @param rowIndex
     *            zero-based index of the row to scroll to.
     * @param destination
     *            desired destination placement of scrolled-to-row. See
     *            {@link ScrollDestination} for more information.
     * @param paddingPx
     *            number of pixels to overscroll. Behavior depends on
     *            destination.
     * @throws IllegalArgumentException
     *             if {@code destination} is {@link ScrollDestination#MIDDLE}
     *             and padding is nonzero, because having a padding on a
     *             centered row is undefined behavior, or if rowIndex is below
     *             zero or above the row count of the data source.
     */
    private void scrollToRow(int rowIndex, ScrollDestination destination, int paddingPx)
            throws IllegalArgumentException {
        int maxsize = escalator.getBody().getRowCount() - 1;

        if (rowIndex < 0) {
            throw new IllegalArgumentException("Row index (" + rowIndex + ") is below zero!");
        }

        if (rowIndex > maxsize) {
            throw new IllegalArgumentException("Row index (" + rowIndex + ") is above maximum (" + maxsize + ")!");
        }

        escalator.scrollToRowAndSpacer(rowIndex, destination, paddingPx);
    }

    /**
     * Helper method for scrolling and making sure row is visible.
     *
     * @param rowIndex
     *            the row index to make visible
     * @param destination
     *            the desired scroll destination
     * @param whenVisible
     *            the callback method to call when row is visible
     */
    private void waitUntilVisible(int rowIndex, ScrollDestination destination, Runnable whenVisible) {
        boolean waitForCache = false;
        if (getDataSource().getRow(rowIndex) == null) {
            // not yet in cache, wait for this to change
            waitForCache = true;

            Reference<Registration> registration = new Reference<>();
            registration.set(getDataSource().addDataChangeHandler(new DataChangeHandler() {
                @Override
                public void resetDataAndSize(int estimatedNewDataSize) {
                    // data set changed, cancel the operation
                    registration.get().remove();
                }

                @Override
                public void dataUpdated(int firstRowIndex, int numberOfRows) {
                    // NOP
                }

                @Override
                public void dataRemoved(int firstRowIndex, int numberOfRows) {
                    // data set changed, cancel the operation
                    registration.get().remove();
                }

                @Override
                public void dataAvailable(int firstRowIndex, int numberOfRows) {
                    // if new available range contains the row,
                    // try again
                    if (Range.withLength(firstRowIndex, numberOfRows).contains(rowIndex)) {
                        registration.get().remove();
                        waitUntilVisible(rowIndex, destination, whenVisible);
                    }
                }

                @Override
                public void dataAdded(int firstRowIndex, int numberOfRows) {
                    // data set changed, cancel the operation
                    registration.get().remove();
                }
            }));
        }

        scrollToRow(rowIndex, destination);

        if (!waitForCache) {
            // all necessary adjustments done, time to perform
            whenVisible.run();
        }
    }

    /**
     * Scrolls to the beginning of the very first row.
     */
    public void scrollToStart() {
        if (getEscalator().getBody().getRowCount() > 0) {
            scrollToRow(0, ScrollDestination.START);
        }
    }

    /**
     * Scrolls to the end of the very last row.
     */
    public void scrollToEnd() {
        if (getEscalator().getBody().getRowCount() > 0) {
            scrollToRow(escalator.getBody().getRowCount() - 1, ScrollDestination.END);
        }
    }

    /**
     * Sets the vertical scroll offset.
     *
     * @param px
     *            the number of pixels this grid should be scrolled down
     */
    public void setScrollTop(double px) {
        escalator.setScrollTop(px);
    }

    /**
     * Gets the vertical scroll offset.
     *
     * @return the number of pixels this grid is scrolled down
     */
    public double getScrollTop() {
        return escalator.getScrollTop();
    }

    /**
     * Sets the horizontal scroll offset.
     *
     * @since 7.5.0
     * @param px
     *            the number of pixels this grid should be scrolled right
     */
    public void setScrollLeft(double px) {
        escalator.setScrollLeft(px);
    }

    /**
     * Gets the horizontal scroll offset.
     *
     * @return the number of pixels this grid is scrolled to the right
     */
    public double getScrollLeft() {
        return escalator.getScrollLeft();
    }

    /**
     * Returns the height of the scrollable area in pixels.
     *
     * @since 7.5.0
     * @return the height of the scrollable area in pixels
     */
    public double getScrollHeight() {
        return escalator.getScrollHeight();
    }

    /**
     * Returns the width of the scrollable area in pixels.
     *
     * @since 7.5.0
     * @return the width of the scrollable area in pixels.
     */
    public double getScrollWidth() {
        return escalator.getScrollWidth();
    }

    private static final Logger getLogger() {
        return Logger.getLogger(Grid.class.getName());
    }

    /**
     * Sets the number of rows that should be visible in Grid's body, while
     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
     * <p>
     * If Grid is currently not in {@link HeightMode#ROW}, the given value is
     * remembered, and applied once the mode is applied.
     *
     * @param rows
     *            The height in terms of number of rows displayed in Grid's
     *            body. If Grid doesn't contain enough rows, white space is
     *            displayed instead.
     * @throws IllegalArgumentException
     *             if {@code rows} is zero or less
     * @throws IllegalArgumentException
     *             if {@code rows} is {@link Double#isInifinite(double)
     *             infinite}
     * @throws IllegalArgumentException
     *             if {@code rows} is {@link Double#isNaN(double) NaN}
     *
     * @see #setHeightMode(HeightMode)
     */
    public void setHeightByRows(double rows) throws IllegalArgumentException {
        escalator.setHeightByRows(rows);
    }

    /**
     * Gets the amount of rows in Grid's body that are shown, while
     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
     * <p>
     * By default, it is {@value Escalator#DEFAULT_HEIGHT_BY_ROWS}.
     *
     * @return the amount of rows that should be shown in Grid's body, while in
     *         {@link HeightMode#ROW}.
     * @see #setHeightByRows(double)
     */
    public double getHeightByRows() {
        return escalator.getHeightByRows();
    }

    /**
     * Defines the mode in which the Grid widget's height is calculated.
     * <p>
     * If {@link HeightMode#CSS} is given, Grid will respect the values given
     * via {@link #setHeight(String)}, and behave as a traditional Widget.
     * <p>
     * If {@link HeightMode#ROW} is given, Grid will make sure that the body
     * will display as many rows as {@link #getHeightByRows()} defines.
     * <em>Note:</em> If headers/footers are inserted or removed, the widget
     * will resize itself to still display the required amount of rows in its
     * body. It also takes the horizontal scrollbar into account.
     *
     * @param heightMode
     *            the mode in to which Grid should be set
     */
    public void setHeightMode(HeightMode heightMode) {
        /*
         * This method is a workaround for the fact that Vaadin re-applies
         * widget dimensions (height/width) on each state change event. The
         * original design was to have setHeight an setHeightByRow be equals,
         * and whichever was called the latest was considered in effect.
         *
         * But, because of Vaadin always calling setHeight on the widget, this
         * approach doesn't work.
         */

        escalator.setHeightMode(heightMode);
    }

    /**
     * Returns the current {@link HeightMode} the Grid is in.
     * <p>
     * Defaults to {@link HeightMode#CSS}.
     *
     * @return the current HeightMode
     */
    public HeightMode getHeightMode() {
        return escalator.getHeightMode();
    }

    private Set<String> getConsumedEventsForRenderer(Renderer<?> renderer) {
        Set<String> events = new HashSet<>();
        if (renderer instanceof ComplexRenderer) {
            Collection<String> consumedEvents = ((ComplexRenderer<?>) renderer).getConsumedEvents();
            if (consumedEvents != null) {
                events.addAll(consumedEvents);
            }
        }
        return events;
    }

    @Override
    public void onBrowserEvent(Event event) {
        if (!isEnabled()) {
            return;
        }

        String eventType = event.getType();

        if (eventType.equals(BrowserEvents.FOCUS) || eventType.equals(BrowserEvents.BLUR)) {
            super.onBrowserEvent(event);
            return;
        }

        EventTarget target = event.getEventTarget();

        if (!Element.is(target) || isOrContainsInSpacer(Element.as(target))) {
            return;
        }

        Element element = Element.as(target);
        RowContainer container = escalator.findRowContainer(element);
        Cell cell;

        if (container == null) {
            if (eventType.equals(BrowserEvents.KEYDOWN) || eventType.equals(BrowserEvents.KEYUP)
                    || eventType.equals(BrowserEvents.KEYPRESS)) {
                cell = cellFocusHandler.getFocusedCell();
                container = cellFocusHandler.containerWithFocus;
            } else {
                // Click might be in an editor cell, should still map.
                if (editor.editorOverlay != null && editor.editorOverlay.isOrHasChild(element)) {
                    container = escalator.getBody();
                    int rowIndex = editor.getRow();
                    int colIndex = editor.getElementColumn(element);

                    if (colIndex < 0) {
                        // Click in editor, but not for any column.
                        return;
                    }

                    try {
                        TableCellElement cellElement = container.getRowElement(rowIndex).getCells()
                                .getItem(colIndex);

                        cell = new Cell(rowIndex, colIndex, cellElement);
                    } catch (IllegalStateException exception) {
                        // IllegalStateException may occur if user has scrolled Grid so
                        // that Escalator has updated, and row under Editor is no longer
                        // there
                        return;
                    }
                } else {
                    if (escalator.getElement().isOrHasChild(element)) {
                        eventCell.set(new Cell(-1, -1, null), Section.BODY);
                        // Fire native events.
                        super.onBrowserEvent(event);
                    }
                    return;
                }
            }
        } else {
            cell = container.getCell(element);
            if (eventType.equals(BrowserEvents.MOUSEDOWN)) {
                cellOnPrevMouseDown = cell;
            } else if (cell == null && eventType.equals(BrowserEvents.CLICK)) {
                /*
                 * Chrome has an interesting idea on click targets (see
                 * cellOnPrevMouseDown javadoc). Firefox, on the other hand, has
                 * the mousedown target as the click target.
                 */
                cell = cellOnPrevMouseDown;
            }
        }

        if (cell == null) {
            getLogger().log(Level.WARNING, "received " + eventType + "-event with a null cell target");
            return;
        }
        eventCell.set(cell, getSectionFromContainer(container));

        GridEvent<T> gridEvent = new GridEvent<>(event, eventCell);
        for (GridEventHandler<T> handler : browserEventHandlers) {
            handler.onEvent(gridEvent);
        }
    }

    private Section getSectionFromContainer(RowContainer container) {
        assert container != null : "RowContainer should not be null";

        if (container == escalator.getBody()) {
            return Section.BODY;
        } else if (container == escalator.getFooter()) {
            return Section.FOOTER;
        } else if (container == escalator.getHeader()) {
            return Section.HEADER;
        }
        assert false : "RowContainer was not header, footer or body.";
        return null;
    }

    private boolean isOrContainsInSpacer(Node node) {
        Node n = node;
        while (n != null && n != getElement()) {
            boolean isElement = Element.is(n);
            if (isElement) {
                String className = Element.as(n).getClassName();

                // Also check whether className is indeed a string. For
                // SVGElement it may be of type SVGAnimatedString.
                // https://developer.mozilla.org/en-US/docs/Web/API/Element/className#Notes
                if (WidgetUtil.isString(className) && className.contains(getStylePrimaryName() + "-spacer")) {
                    return true;
                }
            }
            n = n.getParentNode();
        }
        return false;
    }

    private boolean isElementInChildWidget(Element e) {
        Widget w = WidgetUtil.findWidget(e);

        if (w == this) {
            return false;
        }

        /*
         * If e is directly inside this grid, but the grid is wrapped in a
         * Composite, findWidget is not going to find this, only the wrapper.
         * Thus we need to check its parents to see if we encounter this; if we
         * don't, the found widget is actually a parent of this, so we should
         * return false.
         */
        while (w != null && w != this) {
            w = w.getParent();
        }
        return w != null;
    }

    private class EditorEventHandler implements GridEventHandler<T> {

        @Override
        public void onEvent(GridEvent<T> event) {
            if (!isEditorEnabled()) {
                return;
            }

            Widget widget;
            if (editor.focusedColumnIndexDOM < 0) {
                widget = null;
            } else {
                widget = editor.getWidget(getVisibleColumn(editor.focusedColumnIndexDOM));
            }

            EditorDomEvent<T> editorEvent = new EditorDomEvent<>(event.getDomEvent(), event.getCell(), widget);

            event.setHandled(getEditor().getEventHandler().handleEvent(editorEvent));
        }
    };

    private class SuperEventHandler implements GridEventHandler<T> {

        @Override
        public void onEvent(GridEvent<T> event) {
            if (event.isHandled()) {
                return;
            }
            Grid.super.onBrowserEvent(event.getDomEvent());
        }
    };

    private abstract class AbstractGridEventHandler implements GridEventHandler<T> {

        @Override
        public void onEvent(GridEvent<T> event) {
            if (event.isHandled()) {
                return;
            }
            event.setHandled(isElementInChildWidget(Element.as(event.getDomEvent().getEventTarget())));
        }
    };

    private class RendererEventHandler extends AbstractGridEventHandler {

        @Override
        public void onEvent(GridEvent<T> event) {
            super.onEvent(event);
            if (event.isHandled()) {
                return;
            }
            if (!event.getCell().isBody()) {
                return;
            }

            Column<?, T> gridColumn = event.getCell().getColumn();
            boolean enterKey = event.getDomEvent().getType().equals(BrowserEvents.KEYDOWN)
                    && event.getDomEvent().getKeyCode() == KeyCodes.KEY_ENTER;
            boolean doubleClick = event.getDomEvent().getType().equals(BrowserEvents.DBLCLICK);

            if (gridColumn.getRenderer() instanceof ComplexRenderer) {
                ComplexRenderer<?> cplxRenderer = (ComplexRenderer<?>) gridColumn.getRenderer();
                if (cplxRenderer.getConsumedEvents().contains(event.getDomEvent().getType())) {
                    if (cplxRenderer.onBrowserEvent(event.getCell(), event.getDomEvent())) {
                        event.setHandled(true);
                    }
                }

                // Calls onActivate if KeyDown and Enter or double click
                if ((enterKey || doubleClick) && cplxRenderer.onActivate(event.getCell())) {
                    event.setHandled(true);
                }
            }
        }
    };

    private class CellFocusEventHandler extends AbstractGridEventHandler {

        @Override
        public void onEvent(GridEvent<T> event) {
            super.onEvent(event);
            if (event.isHandled()) {
                return;
            }

            Collection<String> navigation = cellFocusHandler.getNavigationEvents();
            if (navigation.contains(event.getDomEvent().getType())) {
                cellFocusHandler.handleNavigationEvent(event.getDomEvent(), event.getCell());
            }
        }
    };

    private class HeaderCellDragStartHandler extends AbstractGridEventHandler {

        @Override
        public void onEvent(GridEvent<T> event) {
            super.onEvent(event);
            if (event.isHandled()) {
                return;
            }
            if (!isColumnReorderingAllowed()) {
                return;
            }
            if (!event.getCell().isHeader()) {
                return;
            }
            int offset = 0; // apply offset depending on selection column, see
                            // #10546
            if (getSelectionColumn().isPresent()) {
                offset = -1;
            }
            if (event.getCell().getColumnIndex() + offset < getFrozenColumnCount()) {
                return;
            }

            if (event.getDomEvent().getTypeInt() == Event.ONMOUSEDOWN
                    && event.getDomEvent().getButton() == NativeEvent.BUTTON_LEFT
                    || event.getDomEvent().getTypeInt() == Event.ONTOUCHSTART) {
                dndHandler.onDragStartOnDraggableElement(event.getDomEvent(), headerCellDndCallback);
                event.getDomEvent().preventDefault();
                event.getDomEvent().stopPropagation();

                // fixes https://github.com/vaadin/framework/issues/8632
                // don't mark the event as handled, in order for the next
                // handler in the handler chain (HeaderDefaultRowEventHandler)
                // to be able to receive it. This should be safe since the next
                // handlers in the chain (RendererEventHandler and
                // CellFocusEventHandler) do not react to header touches/clicks.

                // event.setHandled(true);
            }
        }
    };

    private class HeaderDefaultRowEventHandler extends AbstractGridEventHandler {

        private Point rowEventTouchStartingPoint;

        @Override
        public void onEvent(GridEvent<T> event) {
            super.onEvent(event);
            if (event.isHandled()) {
                return;
            }
            if (!event.getCell().isHeader()) {
                return;
            }
            if (!getHeader().getRow(event.getCell().getRowIndex()).isDefault()) {
                return;
            }
            if (!event.getCell().getColumn().isSortable()) {
                // Only handle sorting events if the column is sortable
                return;
            }

            if (BrowserEvents.MOUSEDOWN.equals(event.getDomEvent().getType())
                    && event.getDomEvent().getShiftKey()) {
                // Don't select text when shift clicking on a header.
                event.getDomEvent().preventDefault();
            }

            if (BrowserEvents.TOUCHSTART.equals(event.getDomEvent().getType())) {
                if (event.getDomEvent().getTouches().length() > 1) {
                    return;
                }

                event.getDomEvent().preventDefault();

                Touch touch = event.getDomEvent().getChangedTouches().get(0);
                rowEventTouchStartingPoint = new Point(touch.getClientX(), touch.getClientY());

                sorter.awaitForTouchEnd(GridConstants.LONG_TAP_DELAY);

                event.setHandled(true);
            } else if (BrowserEvents.TOUCHMOVE.equals(event.getDomEvent().getType())) {
                if (event.getDomEvent().getTouches().length() > 1) {
                    return;
                }

                if (rowEventTouchStartingPoint == null) {
                    return;
                }

                event.getDomEvent().preventDefault();

                Touch touch = event.getDomEvent().getChangedTouches().get(0);
                double diffX = Math.abs(touch.getClientX() - rowEventTouchStartingPoint.getX());
                double diffY = Math.abs(touch.getClientY() - rowEventTouchStartingPoint.getY());

                // Cancel long tap if finger strays too far from
                // starting point
                if (diffX > GridConstants.LONG_TAP_THRESHOLD || diffY > GridConstants.LONG_TAP_THRESHOLD) {
                    sorter.cancelAwaitForTouchEnd();
                }

                event.setHandled(true);
            } else if (BrowserEvents.TOUCHEND.equals(event.getDomEvent().getType())) {
                if (event.getDomEvent().getTouches().length() > 1) {
                    return;
                }

                if (rowEventTouchStartingPoint == null) {
                    return;
                }

                sorter.onTouchEnd();

                event.setHandled(true);
            } else if (BrowserEvents.TOUCHCANCEL.equals(event.getDomEvent().getType())) {
                if (event.getDomEvent().getTouches().length() > 1) {
                    return;
                }

                sorter.cancelAwaitForTouchEnd();

                event.setHandled(true);
            } else if (BrowserEvents.CLICK.equals(event.getDomEvent().getType())) {

                sorter.sort(event.getCell().getColumn(), event.getDomEvent().getShiftKey());
            }
        }
    };

    @Override
    @SuppressWarnings("deprecation")
    public com.google.gwt.user.client.Element getSubPartElement(String subPart) {

        /*
         * handles details[] (translated to spacer[] for Escalator), cell[],
         * header[] and footer[]
         */

        // "#header[0][0]/DRAGhANDLE"
        Element escalatorElement = escalator.getSubPartElement(subPart.replaceFirst("^details\\[", "spacer["));

        if (escalatorElement != null) {

            int detailIdx = subPart.indexOf("/");
            if (detailIdx > 0) {
                String detail = subPart.substring(detailIdx + 1);
                getLogger().severe("Looking up detail from index " + detailIdx + " onward: \"" + detail + "\"");
                if (detail.equalsIgnoreCase("content")) {
                    // XXX: Fix this to look up by class name!
                    return DOM.asOld(Element.as(escalatorElement.getChild(0)));
                }
                if (detail.equalsIgnoreCase("draghandle")) {
                    // XXX: Fix this to look up by class name!
                    return DOM.asOld(Element.as(escalatorElement.getChild(1)));
                }
            }

            return DOM.asOld(escalatorElement);
        }

        SubPartArguments args = SubPartArguments.create(subPart);
        Element editor = getSubPartElementEditor(args);
        if (editor != null) {
            return DOM.asOld(editor);
        }

        return null;
    }

    private Element getSubPartElementEditor(SubPartArguments args) {

        if (!args.getType().equalsIgnoreCase("editor") || editor.getState() != State.ACTIVE) {
            return null;
        }

        if (args.getIndicesLength() == 0) {
            return editor.editorOverlay;
        } else if (args.getIndicesLength() == 1) {
            int index = args.getIndex(0);
            if (index >= columns.size()) {
                return null;
            }

            escalator.scrollToColumn(index, ScrollDestination.ANY, 0);
            Widget widget = editor.getWidget(columns.get(index));

            if (widget != null) {
                return widget.getElement();
            }

            // No widget for the column.
            return null;
        }

        return null;
    }

    @Override
    @SuppressWarnings("deprecation")
    public String getSubPartName(com.google.gwt.user.client.Element subElement) {

        String escalatorStructureName = escalator.getSubPartName(subElement);
        if (escalatorStructureName != null) {
            return escalatorStructureName.replaceFirst("^spacer", "details");
        }

        String editorName = getSubPartNameEditor(subElement);
        if (editorName != null) {
            return editorName;
        }

        return null;
    }

    private String getSubPartNameEditor(Element subElement) {

        if (editor.getState() != State.ACTIVE || !editor.editorOverlay.isOrHasChild(subElement)) {
            return null;
        }

        int i = 0;
        for (Column<?, T> column : columns) {
            if (editor.getWidget(column).getElement().isOrHasChild(subElement)) {
                return "editor[" + i + "]";
            }
            ++i;
        }

        return "editor";
    }

    private void setSelectColumnRenderer(final Renderer<Boolean> selectColumnRenderer) {
        if (this.selectColumnRenderer == selectColumnRenderer) {
            return;
        }

        if (this.selectColumnRenderer != null) {
            if (this.selectColumnRenderer instanceof ComplexRenderer) {
                // End of Life for the old selection column renderer.
                ((ComplexRenderer<?>) this.selectColumnRenderer).destroy();
            }

            // Clear field so frozen column logic in the remove method knows
            // what to do
            Column<?, T> colToRemove = selectionColumn;
            selectionColumn = null;
            removeColumnSkipSelectionColumnCheck(colToRemove);
            cellFocusHandler.offsetRangeBy(-1);
        }

        this.selectColumnRenderer = selectColumnRenderer;

        if (selectColumnRenderer != null) {
            cellFocusHandler.offsetRangeBy(1);
            selectionColumn = new SelectionColumn(selectColumnRenderer);

            addColumnsSkipSelectionColumnCheck(Collections.singleton(selectionColumn), 0);

            selectionColumn.initDone();
        } else {
            selectionColumn = null;
            requestRefreshBody();
        }

        updateFrozenColumns();
    }

    /**
     * Sets the current selection model.
     *
     * @param selectionModel
     *            a selection model implementation.
     * @throws IllegalArgumentException
     *             if selection model argument is null
     */
    public void setSelectionModel(SelectionModel<T> selectionModel) {

        if (selectionModel == null) {
            throw new IllegalArgumentException("Selection model can't be null");
        }

        this.selectionModel = selectionModel;
        if (selectionModel instanceof SelectionModelWithSelectionColumn) {
            setSelectColumnRenderer(((SelectionModelWithSelectionColumn) selectionModel).getRenderer());
        } else {
            setSelectColumnRenderer(null);
        }

        if (this.selectionModel.isMultiSelectionAllowed()) {
            escalator.getTable().setAttribute("aria-multiselectable", "true");
        } else if (this.selectionModel.isSelectionAllowed()) {
            escalator.getTable().setAttribute("aria-multiselectable", "false");
        } else {
            escalator.getTable().removeAttribute("aria-multiselectable");
        }
        // Refresh rendered rows to update selection, if it has changed
        requestRefreshBody();
    }

    /**
     * Gets a reference to the current selection model.
     *
     * @return the currently used SelectionModel instance.
     */
    public SelectionModel<T> getSelectionModel() {
        return selectionModel;
    }

    /**
     * Returns if a row is selected.
     *
     * @param row
     *            a row object
     * @return {@code true}, if the current selection model considers the
     *         provided row object selected.
     */
    public boolean isSelected(T row) {
        return selectionModel.isSelected(row);
    }

    /**
     * Selects a row using the current selection model.
     * <p>
     * Only selection models implementing {@link SelectionModel.Single} and
     * {@link SelectionModel.Multi} are supported; for anything else, an
     * exception will be thrown.
     *
     * @param row
     *            a row object
     * @throws IllegalStateException
     *             if the current selection model is not an instance of
     *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
     */
    public void select(T row) {
        if (getSelectionModel().isSelectionAllowed()) {
            getSelectionModel().select(row);
        }
    }

    /**
     * Deselects a row using the current selection model.
     * <p>
     * Only selection models implementing {@link SelectionModel.Single} and
     * {@link SelectionModel.Multi} are supported; for anything else, an
     * exception will be thrown.
     *
     * @param row
     *            a row object
     * @throws IllegalStateException
     *             if the current selection model is not an instance of
     *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
     */
    public void deselect(T row) {
        if (getSelectionModel().isSelectionAllowed()) {
            getSelectionModel().deselect(row);
        }
    }

    /**
     * Deselects all rows using the current selection model.
     *
     * @throws IllegalStateException
     *             if the current selection model is not an instance of
     *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
     */
    public void deselectAll() {
        getSelectionModel().deselectAll();
    }

    @Override
    public HandlerRegistration addSelectionHandler(final SelectionHandler<T> handler) {
        return addHandler(handler, SelectionEvent.getType());
    }

    /**
     * Sets the current sort order using the fluid Sort API. Read the
     * documentation for {@link Sort} for more information.
     *
     * @param s
     *            a sort instance
     */
    public void sort(Sort s) {
        setSortOrder(s.build());
    }

    /**
     * Sorts the Grid data in ascending order along one column.
     *
     * @param column
     *            a grid column reference
     */
    public <C> void sort(Column<C, T> column) {
        sort(column, SortDirection.ASCENDING);
    }

    /**
     * Sorts the Grid data along one column.
     *
     * @param column
     *            a grid column reference
     * @param direction
     *            a sort direction value
     */
    public <C> void sort(Column<C, T> column, SortDirection direction) {
        sort(Sort.by(column, direction));
    }

    /**
     * Sets the sort order to use. Setting this causes the Grid to re-sort
     * itself.
     *
     * @param order
     *            a sort order list. If set to null, the sort order is cleared.
     */
    public void setSortOrder(List<SortOrder> order) {
        setSortOrder(order, false);
    }

    /**
     * Clears the sort order and indicators without re-sorting.
     */
    private void clearSortOrder() {
        sortOrder.clear();
        refreshHeader();
    }

    private void setSortOrder(List<SortOrder> order, boolean userOriginated) {
        if (order != sortOrder) {
            sortOrder.clear();
            if (order != null) {
                sortOrder.addAll(order);
            }
        }
        sort(userOriginated);
    }

    /**
     * Get a copy of the current sort order array.
     *
     * @return a copy of the current sort order array
     */
    public List<SortOrder> getSortOrder() {
        return Collections.unmodifiableList(sortOrder);
    }

    /**
     * Finds the sorting order for this column
     */
    private SortOrder getSortOrder(Column<?, ?> column) {
        for (SortOrder order : getSortOrder()) {
            if (order.getColumn() == column) {
                return order;
            }
        }
        return null;
    }

    /**
     * Register a GWT event handler for a sorting event. This handler gets
     * called whenever this Grid needs its data source to provide data sorted in
     * a specific order.
     *
     * @param handler
     *            a sort event handler
     * @return the registration for the event
     */
    public HandlerRegistration addSortHandler(SortHandler<T> handler) {
        return addHandler(handler, SortEvent.getType());
    }

    /**
     * Register a GWT event handler for a select all event. This handler gets
     * called whenever Grid needs all rows selected.
     * <p>
     * In case the select all checkbox is not visible in the
     * {@link SelectionColumn}, it will be come visible after adding the
     * handler.
     *
     * @param handler
     *            a select all event handler
     * @return the registration for the event
     */
    public HandlerRegistration addSelectAllHandler(SelectAllHandler<T> handler) {
        HandlerRegistration registration = addHandler(handler, SelectAllEvent.getType());
        return registration;
    }

    /**
     * Register a GWT event handler for a data available event. This handler
     * gets called whenever the {@link DataSource} for this Grid has new data
     * available.
     * <p>
     * This handle will be fired with the current available data after
     * registration is done.
     *
     * @param handler
     *            a data available event handler
     * @return the registration for the event
     */
    public HandlerRegistration addDataAvailableHandler(final DataAvailableHandler handler) {
        // Deferred call to handler with current row range
        Scheduler.get().scheduleFinally(() -> {
            if (!dataSource.isWaitingForData()) {
                handler.onDataAvailable(new DataAvailableEvent(currentDataAvailable));
            }
        });
        return addHandler(handler, DataAvailableEvent.TYPE);
    }

    /**
     * Register a BodyKeyDownHandler to this Grid. The event for this handler is
     * fired when a KeyDown event occurs while cell focus is in the Body of this
     * Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addBodyKeyDownHandler(BodyKeyDownHandler handler) {
        return addHandler(handler, GridKeyDownEvent.TYPE);
    }

    /**
     * Register a BodyKeyUpHandler to this Grid. The event for this handler is
     * fired when a KeyUp event occurs while cell focus is in the Body of this
     * Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addBodyKeyUpHandler(BodyKeyUpHandler handler) {
        return addHandler(handler, GridKeyUpEvent.TYPE);
    }

    /**
     * Register a BodyKeyPressHandler to this Grid. The event for this handler
     * is fired when a KeyPress event occurs while cell focus is in the Body of
     * this Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addBodyKeyPressHandler(BodyKeyPressHandler handler) {
        return addHandler(handler, GridKeyPressEvent.TYPE);
    }

    /**
     * Register a HeaderKeyDownHandler to this Grid. The event for this handler
     * is fired when a KeyDown event occurs while cell focus is in the Header of
     * this Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addHeaderKeyDownHandler(HeaderKeyDownHandler handler) {
        return addHandler(handler, GridKeyDownEvent.TYPE);
    }

    /**
     * Register a HeaderKeyUpHandler to this Grid. The event for this handler is
     * fired when a KeyUp event occurs while cell focus is in the Header of this
     * Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addHeaderKeyUpHandler(HeaderKeyUpHandler handler) {
        return addHandler(handler, GridKeyUpEvent.TYPE);
    }

    /**
     * Register a HeaderKeyPressHandler to this Grid. The event for this handler
     * is fired when a KeyPress event occurs while cell focus is in the Header
     * of this Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addHeaderKeyPressHandler(HeaderKeyPressHandler handler) {
        return addHandler(handler, GridKeyPressEvent.TYPE);
    }

    /**
     * Register a FooterKeyDownHandler to this Grid. The event for this handler
     * is fired when a KeyDown event occurs while cell focus is in the Footer of
     * this Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addFooterKeyDownHandler(FooterKeyDownHandler handler) {
        return addHandler(handler, GridKeyDownEvent.TYPE);
    }

    /**
     * Register a FooterKeyUpHandler to this Grid. The event for this handler is
     * fired when a KeyUp event occurs while cell focus is in the Footer of this
     * Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addFooterKeyUpHandler(FooterKeyUpHandler handler) {
        return addHandler(handler, GridKeyUpEvent.TYPE);
    }

    /**
     * Register a FooterKeyPressHandler to this Grid. The event for this handler
     * is fired when a KeyPress event occurs while cell focus is in the Footer
     * of this Grid.
     *
     * @param handler
     *            the key handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addFooterKeyPressHandler(FooterKeyPressHandler handler) {
        return addHandler(handler, GridKeyPressEvent.TYPE);
    }

    /**
     * Register a BodyClickHandler to this Grid. The event for this handler is
     * fired when a Click event occurs in the Body of this Grid.
     *
     * @param handler
     *            the click handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addBodyClickHandler(BodyClickHandler handler) {
        return addHandler(handler, GridClickEvent.TYPE);
    }

    /**
     * Register a HeaderClickHandler to this Grid. The event for this handler is
     * fired when a Click event occurs in the Header of this Grid.
     *
     * @param handler
     *            the click handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addHeaderClickHandler(HeaderClickHandler handler) {
        return addHandler(handler, GridClickEvent.TYPE);
    }

    /**
     * Register a FooterClickHandler to this Grid. The event for this handler is
     * fired when a Click event occurs in the Footer of this Grid.
     *
     * @param handler
     *            the click handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addFooterClickHandler(FooterClickHandler handler) {
        return addHandler(handler, GridClickEvent.TYPE);
    }

    /**
     * Register a BodyDoubleClickHandler to this Grid. The event for this
     * handler is fired when a double click event occurs in the Body of this
     * Grid.
     *
     * @param handler
     *            the double click handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addBodyDoubleClickHandler(BodyDoubleClickHandler handler) {
        return addHandler(handler, GridDoubleClickEvent.TYPE);
    }

    /**
     * Register a HeaderDoubleClickHandler to this Grid. The event for this
     * handler is fired when a double click event occurs in the Header of this
     * Grid.
     *
     * @param handler
     *            the double click handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addHeaderDoubleClickHandler(HeaderDoubleClickHandler handler) {
        return addHandler(handler, GridDoubleClickEvent.TYPE);
    }

    /**
     * Register a FooterDoubleClickHandler to this Grid. The event for this
     * handler is fired when a double click event occurs in the Footer of this
     * Grid.
     *
     * @param handler
     *            the double click handler to register
     * @return the registration for the event
     */
    public HandlerRegistration addFooterDoubleClickHandler(FooterDoubleClickHandler handler) {
        return addHandler(handler, GridDoubleClickEvent.TYPE);
    }

    /**
     * Register a column reorder handler to this Grid. The event for this
     * handler is fired when the Grid's columns are reordered.
     *
     * @since 7.5.0
     * @param handler
     *            the handler for the event
     * @return the registration for the event
     */
    public HandlerRegistration addColumnReorderHandler(ColumnReorderHandler<T> handler) {
        return addHandler(handler, ColumnReorderEvent.getType());
    }

    /**
     * Register a column visibility change handler to this Grid. The event for
     * this handler is fired when the Grid's columns change visibility.
     *
     * @since 7.5.0
     * @param handler
     *            the handler for the event
     * @return the registration for the event
     */
    public HandlerRegistration addColumnVisibilityChangeHandler(ColumnVisibilityChangeHandler<T> handler) {
        return addHandler(handler, ColumnVisibilityChangeEvent.getType());
    }

    /**
     * Register a column resize handler to this Grid. The event for this handler
     * is fired when the Grid's columns are resized.
     *
     * @since 7.6
     * @param handler
     *            the handler for the event
     * @return the registration for the event
     */
    public HandlerRegistration addColumnResizeHandler(ColumnResizeHandler<T> handler) {
        return addHandler(handler, ColumnResizeEvent.getType());
    }

    /**
     * Register a enabled status change handler to this Grid. The event for this
     * handler is fired when the Grid changes from disabled to enabled and
     * vice-versa.
     *
     * @param handler
     *            the handler for the event
     * @return the registration for the event
     */
    public HandlerRegistration addEnabledHandler(GridEnabledHandler handler) {
        return addHandler(handler, GridEnabledEvent.TYPE);
    }

    /**
     * Register a selection allowed status change handler to this Grid. The
     * event for this handler is fired when the Grid changes selection allowed
     * state.
     *
     * @param handler
     *            the handler for the event
     * @return the registration for the event
     */
    public HandlerRegistration addSelectionAllowedHandler(GridSelectionAllowedHandler handler) {
        return addHandler(handler, GridSelectionAllowedEvent.TYPE);
    }

    public HandlerRegistration addRowHeightChangedHandler(RowHeightChangedHandler handler) {
        return escalator.addHandler(handler, RowHeightChangedEvent.TYPE);
    }

    /**
     * Adds a spacer visibility changed handler to the underlying escalator.
     *
     * @param handler
     *            the handler to be called when a spacer's visibility changes
     * @return the registration object with which the handler can be removed
     * @since 8.3.2
     */
    public HandlerRegistration addSpacerVisibilityChangedHandler(SpacerVisibilityChangedHandler handler) {
        return escalator.addHandler(handler, SpacerVisibilityChangedEvent.TYPE);
    }

    /**
     * Adds a spacer index changed handler to the underlying escalator.
     *
     * @param handler
     *            the handler to be called when a spacer's index changes
     * @return the registration object with which the handler can be removed
     * @since 8.9
     */
    public HandlerRegistration addSpacerIndexChangedHandler(SpacerIndexChangedHandler handler) {
        return escalator.addHandler(handler, SpacerIndexChangedEvent.TYPE);
    }

    /**
     * Adds a low-level DOM event handler to this Grid. The handler is inserted
     * into the given position in the list of handlers. The handlers are invoked
     * in order. If the
     * {@link GridEventHandler#onEvent(Event, EventCellReference) onEvent}
     * method of a handler returns true, subsequent handlers are not invoked.
     *
     * @param index
     *            the index to insert the handler to
     * @param handler
     *            the handler to add
     */
    public void addBrowserEventHandler(int index, GridEventHandler<T> handler) {
        browserEventHandlers.add(index, handler);
    }

    /**
     * Apply sorting to data source.
     */
    private void sort(boolean userOriginated) {
        refreshHeader();
        fireEvent(new SortEvent<>(this, Collections.unmodifiableList(sortOrder), userOriginated));
    }

    private int getLastVisibleRowIndex() {
        int lastRowIndex = escalator.getVisibleRowRange().getEnd();
        int footerTop = escalator.getFooter().getElement().getAbsoluteTop();
        Element lastRow;

        do {
            lastRow = escalator.getBody().getRowElement(--lastRowIndex);
        } while (lastRow.getAbsoluteTop() > footerTop);

        return lastRowIndex;
    }

    private int getFirstVisibleRowIndex() {
        int firstRowIndex = escalator.getVisibleRowRange().getStart();
        int headerBottom = escalator.getHeader().getElement().getAbsoluteBottom();
        Element firstRow = escalator.getBody().getRowElement(firstRowIndex);

        while (firstRow.getAbsoluteBottom() < headerBottom) {
            firstRow = escalator.getBody().getRowElement(++firstRowIndex);
        }

        return firstRowIndex;
    }

    /**
     * Adds a scroll handler to this grid.
     *
     * @param handler
     *            the scroll handler to add
     * @return a handler registration for the registered scroll handler
     */
    public HandlerRegistration addScrollHandler(ScrollHandler handler) {
        return addHandler(handler, ScrollEvent.TYPE);
    }

    @Override
    public boolean isWorkPending() {
        return escalator.isWorkPending() || dataSource.isWaitingForData()
                || autoColumnWidthsRecalculator.isScheduled() || editor.isWorkPending();
    }

    /**
     * Returns whether columns can be reordered with drag and drop.
     *
     * @since 7.5.0
     * @return <code>true</code> if columns can be reordered, false otherwise
     */
    public boolean isColumnReorderingAllowed() {
        return columnReorderingAllowed;
    }

    /**
     * Sets whether column reordering with drag and drop is allowed or not.
     *
     * @since 7.5.0
     * @param columnReorderingAllowed
     *            specifies whether column reordering is allowed
     */
    public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
        this.columnReorderingAllowed = columnReorderingAllowed;
    }

    /**
     * Sets a new column order for the grid. All columns which are not ordered
     * here will remain in the order they were before as the last columns of
     * grid.
     *
     * @param orderedColumns
     *            array of columns in wanted order
     */
    public void setColumnOrder(Column<?, T>... orderedColumns) {
        setColumnOrder(false, orderedColumns);
    }

    private void setColumnOrder(boolean isUserOriginated, Column<?, T>... orderedColumns) {
        List<Column<?, T>> newOrder = new ArrayList<>();
        if (selectionColumn != null) {
            newOrder.add(selectionColumn);
        }

        int i = 0;
        for (Column<?, T> column : orderedColumns) {
            if (columns.contains(column)) {
                newOrder.add(column);
                ++i;
            } else {
                throw new IllegalArgumentException("Given column at index " + i + " does not exist in Grid");
            }
        }

        if (columns.size() != newOrder.size()) {
            columns.stream().filter(col -> !newOrder.contains(col)).forEach(newOrder::add);
        }

        if (columns.equals(newOrder)) {
            // No changes in order.
            return;
        }

        // Prepare event for firing
        ColumnReorderEvent<T> event = new ColumnReorderEvent<>(columns, newOrder, isUserOriginated);

        // Trigger ComplexRenderer.destroy for old content
        ColumnConfiguration conf = getEscalator().getColumnConfiguration();
        conf.removeColumns(0, conf.getColumnCount());

        // Update the order
        columns = newOrder;

        // Do ComplexRenderer.init and render new content
        conf.insertColumns(0, getVisibleColumns().size());

        // Number of frozen columns should be kept same #16901
        updateFrozenColumns();

        // Update column widths.
        for (Column<?, T> column : columns) {
            column.reapplyWidth();
        }

        // Recalculate all the colspans
        for (HeaderRow row : header.getRows()) {
            row.calculateColspans();
        }
        for (FooterRow row : footer.getRows()) {
            row.calculateColspans();
        }

        columnHider.updateTogglesOrder();

        fireEvent(event);
    }

    /**
     * Sets the style generator that is used for generating styles for cells.
     *
     * @param cellStyleGenerator
     *            the cell style generator to set, or <code>null</code> to
     *            remove a previously set generator
     */
    public void setCellStyleGenerator(CellStyleGenerator<T> cellStyleGenerator) {
        this.cellStyleGenerator = cellStyleGenerator;
        requestRefreshBody();
    }

    /**
     * Gets the style generator that is used for generating styles for cells.
     *
     * @return the cell style generator, or <code>null</code> if no generator is
     *         set
     */
    public CellStyleGenerator<T> getCellStyleGenerator() {
        return cellStyleGenerator;
    }

    /**
     * Sets the style generator that is used for generating styles for rows.
     *
     * @param rowStyleGenerator
     *            the row style generator to set, or <code>null</code> to remove
     *            a previously set generator
     */
    public void setRowStyleGenerator(RowStyleGenerator<T> rowStyleGenerator) {
        this.rowStyleGenerator = rowStyleGenerator;
        requestRefreshBody();
    }

    /**
     * Gets the style generator that is used for generating styles for rows.
     *
     * @return the row style generator, or <code>null</code> if no generator is
     *         set
     */
    public RowStyleGenerator<T> getRowStyleGenerator() {
        return rowStyleGenerator;
    }

    private static void setCustomStyleName(Element element, String styleName) {
        assert element != null;

        String oldStyleName = element.getPropertyString(CUSTOM_STYLE_PROPERTY_NAME);

        if (!SharedUtil.equals(oldStyleName, styleName)) {
            if (oldStyleName != null && !oldStyleName.isEmpty()) {
                element.removeClassName(oldStyleName);
            }
            if (styleName != null && !styleName.isEmpty()) {
                element.addClassName(styleName);
            }
            element.setPropertyString(CUSTOM_STYLE_PROPERTY_NAME, styleName);
        }

    }

    /**
     * Opens the editor over the row with the given index.
     *
     * @param rowIndex
     *            the index of the row to be edited
     *
     * @throws IllegalStateException
     *             if the editor is not enabled
     * @throws IllegalStateException
     *             if the editor is already in edit mode
     */
    public void editRow(int rowIndex) {
        editor.editRow(rowIndex);
    }

    /**
     * Returns whether the editor is currently open on some row.
     *
     * @return {@code true} if the editor is active, {@code false} otherwise.
     */
    public boolean isEditorActive() {
        return editor.getState() != State.INACTIVE;
    }

    /**
     * Saves any unsaved changes in the editor to the data source.
     *
     * @throws IllegalStateException
     *             if the editor is not enabled
     * @throws IllegalStateException
     *             if the editor is not in edit mode
     */
    public void saveEditor() {
        editor.save();
    }

    /**
     * Cancels the currently active edit and hides the editor. Any changes that
     * are not {@link #saveEditor() saved} are lost.
     *
     * @throws IllegalStateException
     *             if the editor is not enabled
     * @throws IllegalStateException
     *             if the editor is not in edit mode
     */
    public void cancelEditor() {
        editor.cancel();
    }

    /**
     * Returns the handler responsible for binding data and editor widgets to
     * the editor.
     *
     * @return the editor handler or null if not set
     */
    public EditorHandler<T> getEditorHandler() {
        return editor.getHandler();
    }

    /**
     * Sets the handler responsible for binding data and editor widgets to the
     * editor.
     *
     * @param handler
     *            the new editor handler
     *
     * @throws IllegalStateException
     *             if the editor is currently in edit mode
     */
    public void setEditorHandler(EditorHandler<T> handler) {
        editor.setHandler(handler);
    }

    /**
     * Returns the enabled state of the editor.
     *
     * @return true if editing is enabled, false otherwise
     */
    public boolean isEditorEnabled() {
        return editor.isEnabled();
    }

    /**
     * Sets the enabled state of the editor.
     *
     * @param enabled
     *            true to enable editing, false to disable
     *
     * @throws IllegalStateException
     *             if in edit mode and trying to disable
     * @throws IllegalStateException
     *             if the editor handler is not set
     */
    public void setEditorEnabled(boolean enabled) {
        editor.setEnabled(enabled);
    }

    /**
     * Returns the editor widget associated with the given column. If the editor
     * is not active, returns null.
     *
     * @param column
     *            the column
     * @return the widget if the editor is open, null otherwise
     */
    public Widget getEditorWidget(Column<?, T> column) {
        return editor.getWidget(column);
    }

    /**
     * Sets the caption on the save button in the Grid editor.
     *
     * @param saveCaption
     *            the caption to set
     * @throws IllegalArgumentException
     *             if {@code saveCaption} is {@code null}
     */
    public void setEditorSaveCaption(String saveCaption) throws IllegalArgumentException {
        editor.setSaveCaption(saveCaption);
    }

    /**
     * Gets the current caption on the save button in the Grid editor.
     *
     * @return the current caption on the save button
     */
    public String getEditorSaveCaption() {
        return editor.getSaveCaption();
    }

    /**
     * Sets the caption on the cancel button in the Grid editor.
     *
     * @param cancelCaption
     *            the caption to set
     * @throws IllegalArgumentException
     *             if {@code cancelCaption} is {@code null}
     */
    public void setEditorCancelCaption(String cancelCaption) throws IllegalArgumentException {
        editor.setCancelCaption(cancelCaption);
    }

    /**
     * Gets the caption on the cancel button in the Grid editor.
     *
     * @return the current caption on the cancel button
     */
    public String getEditorCancelCaption() {
        return editor.getCancelCaption();
    }

    @Override
    protected void onAttach() {
        super.onAttach();

        // Grid was just attached to DOM. Column widths should be
        // calculated.
        recalculateColumnWidths();

        if (getEscalator().getBody().getRowCount() == 0 && dataSource != null) {
            setEscalatorSizeFromDataSource();
        }

        for (int row : reattachVisibleDetails) {
            setDetailsVisible(row, true);
        }
        reattachVisibleDetails.clear();
    }

    @Override
    protected void onDetach() {
        Set<Integer> details = new HashSet<>(visibleDetails);
        reattachVisibleDetails.clear();
        reattachVisibleDetails.addAll(details);
        for (int row : details) {
            setDetailsVisible(row, false);
        }
        super.onDetach();
    }

    @Override
    public void onResize() {
        super.onResize();

        /*
         * Delay calculation to be deferred so Escalator can do it's magic.
         */
        Scheduler.get().scheduleFinally(() -> {
            if (escalator.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) {
                recalculateColumnWidths();
            }

            // Vertical resizing could make editor positioning invalid so it
            // needs to be recalculated on resize
            if (isEditorActive()) {
                editor.updateVerticalScrollPosition();
            }

            // if there is a resize, we need to refresh the body to avoid an
            // off-by-one error which occurs when the user scrolls all the
            // way to the bottom.
            refreshBody();
        });
    }

    private double getEscalatorInnerHeight() {
        return new ComputedStyle(getEscalator().getTableWrapper()).getHeightIncludingBorderPadding();
    }

    /**
     * Grid does not support adding Widgets this way.
     * <p>
     * This method is implemented only because removing widgets from Grid (added
     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
     *
     * @param w
     *            irrelevant
     * @throws UnsupportedOperationException
     *             always
     */
    @Override
    @Deprecated
    public void add(Widget w) {
        throw new UnsupportedOperationException("Cannot add widgets to Grid with this method");
    }

    /**
     * Grid does not support clearing Widgets this way.
     * <p>
     * This method is implemented only because removing widgets from Grid (added
     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
     *
     * @throws UnsupportedOperationException
     *             always
     */
    @Override
    @Deprecated
    public void clear() {
        throw new UnsupportedOperationException("Cannot clear widgets from Grid this way");
    }

    /**
     * Grid does not support iterating through Widgets this way.
     * <p>
     * This method is implemented only because removing widgets from Grid (added
     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
     *
     * @return never
     * @throws UnsupportedOperationException
     *             always
     */
    @Override
    @Deprecated
    public Iterator<Widget> iterator() {
        throw new UnsupportedOperationException("Cannot iterate through widgets in Grid this way");
    }

    /**
     * Grid does not support removing Widgets this way.
     * <p>
     * This method is implemented only because removing widgets from Grid (added
     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
     *
     * @return always <code>false</code>
     */
    @Override
    @Deprecated
    public boolean remove(Widget w) {
        /*
         * This is the method that is the sole reason to have Grid implement
         * HasWidget - when Vaadin removes a Component from the hierarchy, the
         * corresponding Widget will call removeFromParent() on itself. GWT will
         * check there that its parent (i.e. Grid) implements HasWidgets, and
         * will call this remove(Widget) method.
         *
         * tl;dr: all this song and dance to make sure GWT's sanity checks
         * aren't triggered, even though they effectively do nothing interesting
         * from Grid's perspective.
         */
        return false;
    }

    /**
     * Accesses the package private method Widget#setParent()
     *
     * @param widget
     *            The widget to access
     * @param parent
     *            The parent to set
     */
    private static final native void setParent(Widget widget, Grid<?> parent)
    /*-{
    widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent);
    }-*/;

    private static final native void onAttach(Widget widget)
    /*-{
    widget.@Widget::onAttach()();
    }-*/;

    private static final native void onDetach(Widget widget)
    /*-{
    widget.@Widget::onDetach()();
    }-*/;

    @Override
    protected void doAttachChildren() {
        if (sidebar.getParent() == this) {
            onAttach(sidebar);
        }
    }

    @Override
    protected void doDetachChildren() {
        if (sidebar.getParent() == this) {
            onDetach(sidebar);
        }
    }

    private void attachWidget(Widget w, Element parent) {
        assert w.getParent() == null;

        parent.appendChild(w.getElement());
        setParent(w, this);
    }

    private void detachWidget(Widget w) {
        assert w.getParent() == this;

        setParent(w, null);
        w.getElement().removeFromParent();
    }

    /**
     * Resets all cached pixel sizes and reads new values from the DOM. This
     * methods should be used e.g. when styles affecting the dimensions of
     * elements in this grid have been changed.
     */
    public void resetSizesFromDom() {
        getEscalator().resetSizesFromDom();
    }

    /**
     * Sets a new details generator for row details.
     * <p>
     * The currently opened row details will be re-rendered.
     *
     * @since 7.5.0
     * @param detailsGenerator
     *            the details generator to set
     * @throws IllegalArgumentException
     *             if detailsGenerator is <code>null</code>;
     */
    public void setDetailsGenerator(DetailsGenerator detailsGenerator) throws IllegalArgumentException {

        if (detailsGenerator == null) {
            throw new IllegalArgumentException("Details generator may not be null");
        }

        for (Integer index : visibleDetails) {
            setDetailsVisible(index, false);
        }

        this.detailsGenerator = detailsGenerator;

        // this will refresh all visible spacers
        escalator.getBody().setSpacerUpdater(gridSpacerUpdater);
    }

    /**
     * Gets the current details generator for row details.
     *
     * @since 7.5.0
     * @return the detailsGenerator the current details generator
     */
    public DetailsGenerator getDetailsGenerator() {
        return detailsGenerator;
    }

    /**
     * Shows or hides the details for a specific row.
     * <p>
     * This method does nothing if trying to set show already-visible details,
     * or hide already-hidden details.
     *
     * @since 7.5.0
     * @param rowIndex
     *            the index of the affected row
     * @param visible
     *            <code>true</code> to show the details, or <code>false</code>
     *            to hide them
     * @see #isDetailsVisible(int)
     */
    public void setDetailsVisible(int rowIndex, boolean visible) {
        if (DetailsGenerator.NULL.equals(detailsGenerator)) {
            return;
        }

        Integer rowIndexInteger = Integer.valueOf(rowIndex);

        /*
         * We want to prevent opening a details row twice, so any subsequent
         * openings (or closings) of details is a NOOP.
         *
         * When a details row is opened, it is given an arbitrary height
         * (because Escalator requires a height upon opening). Only when it's
         * opened, Escalator will ask the generator to generate a widget, which
         * we then can measure. When measured, we correct the initial height by
         * the original height.
         *
         * Without this check, we would override the measured height, and revert
         * back to the initial, arbitrary, height which would most probably be
         * wrong.
         *
         * see GridSpacerUpdater.init for implementation details.
         *
         * The order of operations isn't entirely stable. Sometimes Escalator
         * knows about the spacer visibility updates first and doesn't need
         * updating again but Grid's visibleDetails set still does.
         */

        boolean isVisible = isDetailsVisible(rowIndex);
        boolean isVisibleInEscalator = escalator.getBody().spacerExists(rowIndex);
        if (visible) {
            if (!isVisibleInEscalator) {
                escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT);
            }
            if (!isVisible) {
                visibleDetails.add(rowIndexInteger);
            }
        } else {
            if (isVisibleInEscalator) {
                escalator.getBody().setSpacer(rowIndex, -1);
            }
            if (isVisible) {
                visibleDetails.remove(rowIndexInteger);
            }
        }
    }

    /**
     * Check whether the details for a row is visible or not.
     *
     * @since 7.5.0
     * @param rowIndex
     *            the index of the row for which to check details
     * @return <code>true</code> if the details for the given row is visible
     * @see #setDetailsVisible(int, boolean)
     */
    public boolean isDetailsVisible(int rowIndex) {
        return visibleDetails.contains(Integer.valueOf(rowIndex));
    }

    /**
     * Update details row height.
     *
     * @since 8.1.3
     * @param rowIndex
     *            the index of the row for which to update details height
     * @param height
     *            new height of the details row
     */
    public void setDetailsHeight(int rowIndex, double height) {
        escalator.getBody().setSpacer(rowIndex, height);
    }

    /**
     * Requests that the column widths should be recalculated.
     * <p>
     * The actual recalculation is not necessarily done immediately so you
     * cannot rely on the columns being the correct width after the call
     * returns.
     *
     * @since 7.4.1
     */
    public void recalculateColumnWidths() {
        autoColumnWidthsRecalculator.schedule();
    }

    /**
     * Gets the customizable menu bar that is by default used for toggling
     * column hidability. The application developer is allowed to add their
     * custom items to the end of the menu, but should try to avoid modifying
     * the items in the beginning of the menu that control the column hiding if
     * any columns are marked as hidable. A toggle for opening the menu will be
     * displayed whenever the menu contains at least one item.
     *
     * @since 7.5.0
     * @return the menu bar
     */
    public MenuBar getSidebarMenu() {
        return sidebar.menuBar;
    }

    /**
     * Tests whether the sidebar menu is currently open.
     *
     * @since 7.5.0
     * @see #getSidebarMenu()
     * @return <code>true</code> if the sidebar is open; <code>false</code> if
     *         it is closed
     */
    public boolean isSidebarOpen() {
        return sidebar.isOpen();
    }

    /**
     * Sets whether the sidebar menu is open.
     *
     *
     * @since 7.5.0
     * @see #getSidebarMenu()
     * @see #isSidebarOpen()
     * @param sidebarOpen
     *            <code>true</code> to open the sidebar; <code>false</code> to
     *            close it
     */
    public void setSidebarOpen(boolean sidebarOpen) {
        if (sidebarOpen) {
            sidebar.open();
        } else {
            sidebar.close();
        }
    }

    @Override
    public int getTabIndex() {
        return FocusUtil.getTabIndex(this);
    }

    @Override
    public void setAccessKey(char key) {
        FocusUtil.setAccessKey(this, key);
    }

    @Override
    public void setFocus(boolean focused) {
        FocusUtil.setFocus(this, focused);
    }

    @Override
    public void setTabIndex(int index) {
        FocusUtil.setTabIndex(this, index);
    }

    @Override
    public void focus() {
        setFocus(true);
    }

    /**
     * Sets the buffered editor mode.
     *
     * @since 7.6
     * @param editorBuffered
     *            <code>true</code> to enable buffered editor,
     *            <code>false</code> to disable it
     */
    public void setEditorBuffered(boolean editorBuffered) {
        editor.setBuffered(editorBuffered);
    }

    /**
     * Gets the buffered editor mode.
     *
     * @since 7.6
     * @return <code>true</code> if buffered editor is enabled,
     *         <code>false</code> otherwise
     */
    public boolean isEditorBuffered() {
        return editor.isBuffered();
    }

    /**
     * Returns the {@link EventCellReference} for the latest event fired from
     * this Grid.
     * <p>
     * Note: This cell reference will be updated when firing the next event.
     *
     * @since 7.5
     * @return event cell reference
     */
    public EventCellReference<T> getEventCell() {
        return eventCell;
    }

    /**
     * Returns a CellReference for the cell to which the given element belongs
     * to.
     *
     * @since 7.6
     * @param element
     *            Element to find from the cell's content.
     * @return CellReference or <code>null</code> if cell was not found.
     */
    public CellReference<T> getCellReference(Element element) {
        RowContainer container = getEscalator().findRowContainer(element);
        if (container != null) {
            Cell cell = container.getCell(element);
            if (cell != null) {
                EventCellReference<T> cellRef = new EventCellReference<>(this);
                cellRef.set(cell, getSectionFromContainer(container));
                return cellRef;
            }
        }
        return null;
    }

    /**
     * Returns the selection column for the grid if the selection model is of
     * type {@link SelectionModelWithSelectionColumn}.
     *
     * @return the select all checkbox, or an empty optional if not in use
     */
    public Optional<SelectionColumn> getSelectionColumn() {
        return Optional.ofNullable(selectionColumn);
    }
}