diff options
author | John Ahlroos <john@vaadin.com> | 2014-03-18 15:19:59 +0000 |
---|---|---|
committer | John Ahlroos <john@vaadin.com> | 2014-03-18 15:19:59 +0000 |
commit | cca2172654699f9e2f79e8b36c70700c248da8f2 (patch) | |
tree | 2122cdf70d91f7533de4f3f0e31b10fec1792289 /client | |
parent | 4420f52578e245045677f88852f1ba3f405e88a3 (diff) | |
download | vaadin-framework-cca2172654699f9e2f79e8b36c70700c248da8f2.tar.gz vaadin-framework-cca2172654699f9e2f79e8b36c70700c248da8f2.zip |
Revert "Merge branch 'master' into grid"
This reverts commit 4420f52578e245045677f88852f1ba3f405e88a3.
Change-Id: I06effe06f245baaeb499071917c359eb34cc55ea
Diffstat (limited to 'client')
29 files changed, 0 insertions, 9141 deletions
diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java deleted file mode 100644 index 127eb80696..0000000000 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright 2000-2013 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.data; - -import java.util.HashMap; -import java.util.List; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.vaadin.client.Profiler; -import com.vaadin.shared.ui.grid.Range; - -/** - * Base implementation for data sources that fetch data from a remote system. - * This class takes care of caching data and communicating with the data source - * user. An implementation of this class should override - * {@link #requestRows(int, int)} to trigger asynchronously loading of data. - * When data is received from the server, new row data should be passed to - * {@link #setRowData(int, List)}. {@link #setEstimatedSize(int)} should be used - * based on estimations of how many rows are available. - * - * @since 7.2 - * @author Vaadin Ltd - * @param <T> - * the row type - */ -public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { - - private boolean requestPending = false; - - private boolean coverageCheckPending = false; - - private Range requestedAvailability = Range.between(0, 0); - - private Range cached = Range.between(0, 0); - - private final HashMap<Integer, T> rowCache = new HashMap<Integer, T>(); - - private DataChangeHandler dataChangeHandler; - - private int estimatedSize; - - private final ScheduledCommand coverageChecker = new ScheduledCommand() { - @Override - public void execute() { - coverageCheckPending = false; - checkCacheCoverage(); - } - }; - - /** - * Sets the estimated number of rows in the data source. - * - * @param estimatedSize - * the estimated number of available rows - */ - protected void setEstimatedSize(int estimatedSize) { - // TODO update dataChangeHandler if size changes - this.estimatedSize = estimatedSize; - } - - private void ensureCoverageCheck() { - if (!coverageCheckPending) { - coverageCheckPending = true; - Scheduler.get().scheduleDeferred(coverageChecker); - } - } - - @Override - public void ensureAvailability(int firstRowIndex, int numberOfRows) { - requestedAvailability = Range.withLength(firstRowIndex, numberOfRows); - - /* - * Don't request any data right away since the data might be included in - * a message that has been received but not yet fully processed. - */ - ensureCoverageCheck(); - } - - private void checkCacheCoverage() { - if (requestPending) { - // Anyone clearing requestPending should run this method again - return; - } - - Profiler.enter("AbstractRemoteDataSource.checkCacheCoverage"); - - if (!requestedAvailability.intersects(cached) || cached.isEmpty()) { - /* - * Simple case: no overlap between cached data and needed data. - * Clear the cache and request new data - */ - rowCache.clear(); - cached = Range.between(0, 0); - - handleMissingRows(requestedAvailability); - } else { - discardStaleCacheEntries(); - - // Might need more rows -> request them - Range[] availabilityPartition = requestedAvailability - .partitionWith(cached); - handleMissingRows(availabilityPartition[0]); - handleMissingRows(availabilityPartition[2]); - } - - Profiler.leave("AbstractRemoteDataSource.checkCacheCoverage"); - } - - private void discardStaleCacheEntries() { - Range[] cacheParition = cached.partitionWith(requestedAvailability); - dropFromCache(cacheParition[0]); - cached = cacheParition[1]; - dropFromCache(cacheParition[2]); - } - - private void dropFromCache(Range range) { - for (int i = range.getStart(); i < range.getEnd(); i++) { - rowCache.remove(Integer.valueOf(i)); - } - } - - private void handleMissingRows(Range range) { - if (range.isEmpty()) { - return; - } - requestPending = true; - requestRows(range.getStart(), range.length()); - } - - /** - * Triggers fetching rows from the remote data source. - * {@link #setRowData(int, List)} should be invoked with data for the - * requested rows when they have been received. - * - * @param firstRowIndex - * the index of the first row to fetch - * @param numberOfRows - * the number of rows to fetch - */ - protected abstract void requestRows(int firstRowIndex, int numberOfRows); - - @Override - public int getEstimatedSize() { - return estimatedSize; - } - - @Override - public T getRow(int rowIndex) { - return rowCache.get(Integer.valueOf(rowIndex)); - } - - @Override - public void setDataChangeHandler(DataChangeHandler dataChangeHandler) { - this.dataChangeHandler = dataChangeHandler; - - if (dataChangeHandler != null && !cached.isEmpty()) { - // Push currently cached data to the implementation - dataChangeHandler.dataUpdated(cached.getStart(), cached.length()); - } - } - - /** - * Informs this data source that updated data has been sent from the server. - * - * @param firstRowIndex - * the index of the first received row - * @param rowData - * a list of rows, starting from <code>firstRowIndex</code> - */ - protected void setRowData(int firstRowIndex, List<T> rowData) { - requestPending = false; - - Profiler.enter("AbstractRemoteDataSource.setRowData"); - - Range received = Range.withLength(firstRowIndex, rowData.size()); - - Range[] partition = received.partitionWith(requestedAvailability); - - Range newUsefulData = partition[1]; - if (!newUsefulData.isEmpty()) { - // Update the parts that are actually inside - for (int i = newUsefulData.getStart(); i < newUsefulData.getEnd(); i++) { - rowCache.put(Integer.valueOf(i), rowData.get(i - firstRowIndex)); - } - - if (dataChangeHandler != null) { - Profiler.enter("AbstractRemoteDataSource.setRowData notify dataChangeHandler"); - dataChangeHandler.dataUpdated(newUsefulData.getStart(), - newUsefulData.length()); - Profiler.leave("AbstractRemoteDataSource.setRowData notify dataChangeHandler"); - } - - // Potentially extend the range - if (cached.isEmpty()) { - cached = newUsefulData; - } else { - discardStaleCacheEntries(); - - /* - * everything might've become stale so we need to re-check for - * emptiness. - */ - if (!cached.isEmpty()) { - cached = cached.combineWith(newUsefulData); - } else { - cached = newUsefulData; - } - } - } - - if (!partition[0].isEmpty() || !partition[2].isEmpty()) { - /* - * FIXME - * - * Got data that we might need in a moment if the container is - * updated before the widget settings. Support for this will be - * implemented later on. - */ - } - - // Eventually check whether all needed rows are now available - ensureCoverageCheck(); - - Profiler.leave("AbstractRemoteDataSource.setRowData"); - } - - /** - * Informs this data source that the server has removed data. - * - * @param firstRowIndex - * the index of the first removed row - * @param count - * the number of removed rows, starting from - * <code>firstRowIndex</code> - */ - protected void removeRowData(int firstRowIndex, int count) { - Profiler.enter("AbstractRemoteDataSource.removeRowData"); - - // pack the cached data - for (int i = 0; i < count; i++) { - Integer oldIndex = Integer.valueOf(firstRowIndex + count + i); - if (rowCache.containsKey(oldIndex)) { - Integer newIndex = Integer.valueOf(firstRowIndex + i); - rowCache.put(newIndex, rowCache.remove(oldIndex)); - } - } - - Range removedRange = Range.withLength(firstRowIndex, count); - if (removedRange.intersects(cached)) { - Range[] partitions = cached.partitionWith(removedRange); - Range remainsBefore = partitions[0]; - Range transposedRemainsAfter = partitions[2].offsetBy(-removedRange - .length()); - cached = remainsBefore.combineWith(transposedRemainsAfter); - } - estimatedSize -= count; - dataChangeHandler.dataRemoved(firstRowIndex, count); - checkCacheCoverage(); - - Profiler.leave("AbstractRemoteDataSource.removeRowData"); - } - - /** - * Informs this data source that new data has been inserted from the server. - * - * @param firstRowIndex - * the destination index of the new row data - * @param count - * the number of rows inserted - */ - protected void insertRowData(int firstRowIndex, int count) { - Profiler.enter("AbstractRemoteDataSource.insertRowData"); - - if (cached.contains(firstRowIndex)) { - int oldCacheEnd = cached.getEnd(); - /* - * We need to invalidate the cache from the inserted row onwards, - * since the cache wants to be a contiguous range. It doesn't - * support holes. - * - * If holes were supported, we could shift the higher part of - * "cached" and leave a hole the size of "count" in the middle. - */ - cached = cached.splitAt(firstRowIndex)[0]; - - for (int i = firstRowIndex; i < oldCacheEnd; i++) { - rowCache.remove(Integer.valueOf(i)); - } - } - - else if (firstRowIndex < cached.getStart()) { - Range oldCached = cached; - cached = cached.offsetBy(count); - - for (int i = 0; i < rowCache.size(); i++) { - Integer oldIndex = Integer.valueOf(oldCached.getEnd() - i); - Integer newIndex = Integer.valueOf(cached.getEnd() - i); - rowCache.put(newIndex, rowCache.remove(oldIndex)); - } - } - - estimatedSize += count; - dataChangeHandler.dataAdded(firstRowIndex, count); - checkCacheCoverage(); - - Profiler.leave("AbstractRemoteDataSource.insertRowData"); - } -} diff --git a/client/src/com/vaadin/client/data/DataChangeHandler.java b/client/src/com/vaadin/client/data/DataChangeHandler.java deleted file mode 100644 index 4c4cc7656d..0000000000 --- a/client/src/com/vaadin/client/data/DataChangeHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2000-2013 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.data; - -/** - * Callback interface used by {@link DataSource} to inform its user about - * updates to the data. - * - * @since 7.2 - * @author Vaadin Ltd - */ -public interface DataChangeHandler { - /** - * Called when the contents of the data source has changed. If the number of - * rows has changed or if rows have been moved around, - * {@link #dataAdded(int, int)} or {@link #dataRemoved(int, int)} should - * ideally be used instead. - * - * @param firstRowIndex - * the index of the first changed row - * @param numberOfRows - * the number of changed rows - */ - public void dataUpdated(int firstRowIndex, int numberOfRows); - - /** - * Called when rows have been removed from the data source. - * - * @param firstRowIndex - * the index that the first removed row had prior to removal - * @param numberOfRows - * the number of removed rows - */ - public void dataRemoved(int firstRowIndex, int numberOfRows); - - /** - * Called when the new rows have been added to the container. - * - * @param firstRowIndex - * the index of the first added row - * @param numberOfRows - * the number of added rows - */ - public void dataAdded(int firstRowIndex, int numberOfRows); -} diff --git a/client/src/com/vaadin/client/data/DataSource.java b/client/src/com/vaadin/client/data/DataSource.java deleted file mode 100644 index 9179b6d03d..0000000000 --- a/client/src/com/vaadin/client/data/DataSource.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2000-2013 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.data; - -/** - * Source of data for widgets showing lazily loaded data based on indexable - * items (e.g. rows) of a specified type. The data source is a lazy view into a - * larger data set. - * - * @since 7.2 - * @author Vaadin Ltd - * @param <T> - * the row type - */ -public interface DataSource<T> { - /** - * Informs the data source that data for the given range is needed. A data - * source only has one active region at a time, so calling this method - * discards the previously set range. - * <p> - * This method triggers lazy loading of data if necessary. The change - * handler registered using {@link #setDataChangeHandler(DataChangeHandler)} - * is informed when new data has been loaded. - * - * @param firstRowIndex - * the index of the first needed row - * @param numberOfRows - * the number of needed rows - */ - public void ensureAvailability(int firstRowIndex, int numberOfRows); - - /** - * Retrieves the data for the row at the given index. If the row data is not - * available, returns <code>null</code>. - * <p> - * This method does not trigger loading of unavailable data. - * {@link #ensureAvailability(int, int)} should be used to signal what data - * will be needed. - * - * @param rowIndex - * the index of the row to retrieve data for - * @return data for the row; or <code>null</code> if no data is available - */ - public T getRow(int rowIndex); - - /** - * Returns the current best guess for the number of rows in the container. - * - * @return the current estimation of the container size - */ - public int getEstimatedSize(); - - /** - * Sets a data change handler to inform when data is updated, added or - * removed. - * - * @param dataChangeHandler - * the data change handler - */ - public void setDataChangeHandler(DataChangeHandler dataChangeHandler); - -} diff --git a/client/src/com/vaadin/client/data/RpcDataSourceConnector.java b/client/src/com/vaadin/client/data/RpcDataSourceConnector.java deleted file mode 100644 index 4d22c10197..0000000000 --- a/client/src/com/vaadin/client/data/RpcDataSourceConnector.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2000-2013 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.data; - -import java.util.List; - -import com.vaadin.client.ServerConnector; -import com.vaadin.client.extensions.AbstractExtensionConnector; -import com.vaadin.client.ui.grid.GridConnector; -import com.vaadin.shared.data.DataProviderRpc; -import com.vaadin.shared.data.DataProviderState; -import com.vaadin.shared.data.DataRequestRpc; -import com.vaadin.shared.ui.Connect; - -/** - * Connects a Vaadin server-side container data source to a Grid. This is - * currently implemented as an Extension hardcoded to support a specific - * connector type. This will be changed once framework support for something - * more flexible has been implemented. - * - * @since 7.2 - * @author Vaadin Ltd - */ -@Connect(com.vaadin.data.RpcDataProviderExtension.class) -public class RpcDataSourceConnector extends AbstractExtensionConnector { - - private final AbstractRemoteDataSource<String[]> dataSource = new AbstractRemoteDataSource<String[]>() { - @Override - protected void requestRows(int firstRowIndex, int numberOfRows) { - getRpcProxy(DataRequestRpc.class).requestRows(firstRowIndex, - numberOfRows); - } - }; - - @Override - protected void extend(ServerConnector target) { - dataSource.setEstimatedSize(getState().containerSize); - ((GridConnector) target).getWidget().setDataSource(dataSource); - - registerRpc(DataProviderRpc.class, new DataProviderRpc() { - @Override - public void setRowData(int firstRow, List<String[]> rows) { - dataSource.setRowData(firstRow, rows); - } - - @Override - public void removeRowData(int firstRow, int count) { - dataSource.removeRowData(firstRow, count); - } - - @Override - public void insertRowData(int firstRow, int count) { - dataSource.insertRowData(firstRow, count); - } - }); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractConnector#getState() - */ - @Override - public DataProviderState getState() { - return (DataProviderState) super.getState(); - } -} diff --git a/client/src/com/vaadin/client/ui/grid/Cell.java b/client/src/com/vaadin/client/ui/grid/Cell.java deleted file mode 100644 index 3d42f082a6..0000000000 --- a/client/src/com/vaadin/client/ui/grid/Cell.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.ui.HasOneWidget; - -/** - * A representation of a single cell. - * <p> - * A Cell instance will be provided to the {@link EscalatorUpdater} responsible - * for rendering the cells in a certain {@link RowContainer}. - * - * @since 7.2 - * @author Vaadin Ltd - */ -public interface Cell extends HasOneWidget { - - /** - * Gets the index of the row this cell is in. - * - * @return the index of the row this cell is in - */ - public int getRow(); - - /** - * Gets the index of the column this cell is in. - * - * @return the index of the column this cell is in - */ - public int getColumn(); - - /** - * Gets the root element for this cell. The {@link EscalatorUpdater} may - * update the class names of the element, add inline styles and freely - * modify the contents. - * <p> - * Avoid modifying the dimensions, positioning or colspan of the cell - * element. - * - * @return The root element for this cell. Never <code>null</code>. - */ - public Element getElement(); - - /** - * Sets the column span of the cell. - * <p> - * This will overwrite any possible "colspan" attribute in the current - * element (i.e. the object returned by {@link #getElement()}). This will - * also handle internal bookkeeping, skip the rendering of any affected - * adjacent cells, and make sure that the current cell's dimensions are - * handled correctly. - * - * @param numberOfCells - * the number of cells to span to the right, or <code>1</code> to - * unset any column spans - * @throws IllegalArgumentException - * if <code>numberOfCells < 1</code> - */ - public void setColSpan(int numberOfCells) throws IllegalArgumentException; -}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/grid/ColumnConfiguration.java b/client/src/com/vaadin/client/ui/grid/ColumnConfiguration.java deleted file mode 100644 index 64104164cd..0000000000 --- a/client/src/com/vaadin/client/ui/grid/ColumnConfiguration.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -/** - * A representation of the columns in an instance of {@link Escalator}. - * - * @since 7.2 - * @author Vaadin Ltd - * @see Escalator#getColumnConfiguration() - */ -public interface ColumnConfiguration { - - /** - * Removes columns at a certain index. - * <p> - * If any of the removed columns were frozen, the number of frozen columns - * will be reduced by the number of the removed columns that were frozen. - * - * @param index - * the index of the first column to be removed - * @param numberOfColumns - * the number of rows to remove, starting from the index - * @throws IndexOutOfBoundsException - * if any integer in the range - * <code>[index..(index+numberOfColumns)]</code> is not an - * existing column index. - * @throws IllegalArgumentException - * if <code>numberOfColumns</code> is less than 1. - */ - public void removeColumns(int index, int numberOfColumns) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Adds columns at a certain index. - * <p> - * The new columns will be inserted between the column at the index, and the - * column before (an index of 0 means that the columns are inserted at the - * beginning). Therefore, the columns at the index and afterwards will be - * moved to the right. - * <p> - * The contents of the inserted columns will be queried from the respective - * cell renderers in the header, body and footer. - * <p> - * If there are frozen columns and the first added column is to the left of - * the last frozen column, the number of frozen columns will be increased by - * the number of inserted columns. - * <p> - * <em>Note:</em> Only the contents of the inserted columns will be - * rendered. If inserting new columns affects the contents of existing - * columns, {@link RowContainer#refreshRows(int, int)} needs to be called as - * appropriate. - * - * @param index - * the index of the column before which new columns are inserted, - * or {@link #getColumnCount()} to add new columns at the end - * @param numberOfColumns - * the number of columns to insert after the <code>index</code> - * @throws IndexOutOfBoundsException - * if <code>index</code> is not an integer in the range - * <code>[0..{@link #getColumnCount()}]</code> - * @throws IllegalArgumentException - * if {@code numberOfColumns} is less than 1. - */ - public void insertColumns(int index, int numberOfColumns) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Returns the number of columns in the escalator. - * - * @return the number of columns in the escalator - */ - public int getColumnCount(); - - /** - * Sets the number of leftmost columns that are not affected by horizontal - * scrolling. - * - * @param count - * the number of columns to freeze - * - * @throws IllegalArgumentException - * if the column count is < 0 or > the number of columns - * - */ - public void setFrozenColumnCount(int count) throws IllegalArgumentException; - - /** - * Get the number of leftmost columns that are not affected by horizontal - * scrolling. - * - * @return the number of frozen columns - */ - public int getFrozenColumnCount(); - - /** - * Sets (or unsets) an explicit width for a column. - * - * @param index - * the index of the column for which to set a width - * @param px - * the number of pixels the indicated column should be, or a - * negative number to let the escalator decide - * @throws IllegalArgumentException - * if <code>index</code> is not a valid column index - */ - public void setColumnWidth(int index, int px) - throws IllegalArgumentException; - - /** - * Returns the user-defined width of a column. - * - * @param index - * the index of the column for which to retrieve the width - * @return the column's width in pixels, or a negative number if the width - * is implicitly decided by the escalator - * @throws IllegalArgumentException - * if <code>index</code> is not a valid column index - */ - public int getColumnWidth(int index) throws IllegalArgumentException; - - /** - * Returns the actual width of a column. - * - * @param index - * the index of the column for which to retrieve the width - * @return the column's actual width in pixels - * @throws IllegalArgumentException - * if <code>index</code> is not a valid column index - */ - public int getColumnWidthActual(int index) throws IllegalArgumentException; -}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/grid/ColumnGroup.java b/client/src/com/vaadin/client/ui/grid/ColumnGroup.java deleted file mode 100644 index e48656bc6b..0000000000 --- a/client/src/com/vaadin/client/ui/grid/ColumnGroup.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import com.vaadin.client.ui.grid.renderers.TextRenderer; - -/** - * Column groups are used to group columns together for adding common auxiliary - * headers and footers. Columns groups are added to {@link ColumnGroupRow - * ColumnGroupRows}. - * - * @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.2 - * @author Vaadin Ltd - */ -public class ColumnGroup<T> { - - /** - * The text shown in the header - */ - private String header; - - /** - * The text shown in the footer - */ - private String footer; - - /** - * Renders the header cells for the column group - */ - private Renderer<String> headerRenderer = new TextRenderer(); - - /** - * Renders the footer cells for the column group - */ - private Renderer<String> footerRenderer = new TextRenderer(); - - /** - * The columns included in the group when also accounting for subgroup - * columns - */ - private final List<GridColumn<?, T>> columns; - - /** - * The grid associated with the column group - */ - private final Grid<T> grid; - - /** - * Constructs a new column group - */ - ColumnGroup(Grid<T> grid, Collection<GridColumn<?, T>> columns) { - if (columns == null) { - throw new IllegalArgumentException( - "columns cannot be null. Pass an empty list instead."); - } - this.grid = grid; - this.columns = Collections - .unmodifiableList(new ArrayList<GridColumn<?, T>>(columns)); - } - - /** - * Gets the header text. - * - * @return the header text - */ - public String getHeaderCaption() { - return header; - } - - /** - * Sets the text shown in the header. - * - * @param header - * the header to set - */ - public void setHeaderCaption(String header) { - this.header = header; - grid.refreshHeader(); - } - - /** - * Gets the text shown in the footer. - * - * @return the text in the footer - */ - public String getFooterCaption() { - return footer; - } - - /** - * Sets the text displayed in the footer. - * - * @param footer - * the footer to set - */ - public void setFooterCaption(String footer) { - this.footer = footer; - grid.refreshFooter(); - } - - /** - * Returns all column in this group. It includes the subgroups columns as - * well. - * - * @return unmodifiable list of columns - */ - public List<GridColumn<?, T>> getColumns() { - return columns; - } - - /** - * Returns the renderer used for rendering the header cells - * - * @return a renderer that renders header cells - */ - public Renderer<String> getHeaderRenderer() { - return headerRenderer; - } - - /** - * Sets the renderer that renders header cells. - * - * @param renderer - * The renderer to use for rendering header cells. Must not be - * null. - * @throws IllegalArgumentException - * thrown when renderer is null - */ - public void setHeaderRenderer(Renderer<String> renderer) { - if (renderer == null) { - throw new IllegalArgumentException("Renderer cannot be null."); - } - this.headerRenderer = renderer; - grid.refreshHeader(); - } - - /** - * Returns the renderer used for rendering the footer cells - * - * @return a renderer that renders footer cells - */ - public Renderer<String> getFooterRenderer() { - return footerRenderer; - } - - /** - * Sets the renderer that renders footer cells. - * - * @param renderer - * The renderer to use for rendering footer cells. Must not be - * null. - * @throws IllegalArgumentException - * thrown when renderer is null - */ - public void setFooterRenderer(Renderer<String> renderer) { - if (renderer == null) { - throw new IllegalArgumentException("Renderer cannot be null."); - } - this.footerRenderer = renderer; - grid.refreshFooter(); - } -} diff --git a/client/src/com/vaadin/client/ui/grid/ColumnGroupRow.java b/client/src/com/vaadin/client/ui/grid/ColumnGroupRow.java deleted file mode 100644 index ebe4db508c..0000000000 --- a/client/src/com/vaadin/client/ui/grid/ColumnGroupRow.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * A column group row represents an auxiliary header or footer row added to the - * grid. A column group row includes column groups that group columns together. - * - * @param <T> - * Row type - * @since 7.2 - * @author Vaadin Ltd - */ -public class ColumnGroupRow<T> { - - /** - * The column groups in this row - */ - private List<ColumnGroup<T>> groups = new ArrayList<ColumnGroup<T>>(); - - /** - * The grid associated with the column row - */ - private final Grid<T> grid; - - /** - * Is the header shown - */ - private boolean headerVisible = true; - - /** - * Is the footer shown - */ - private boolean footerVisible = false; - - /** - * Constructs a new column group row - * - * @param grid - * Grid associated with this column - * - */ - ColumnGroupRow(Grid<T> grid) { - this.grid = grid; - } - - /** - * Add a new group to the row by using column instances. - * - * @param columns - * The columns that should belong to the group - * @return a column group representing the collection of columns added to - * the group. - */ - public ColumnGroup<T> addGroup(GridColumn<?, T>... columns) - throws IllegalArgumentException { - - for (GridColumn<?, T> column : columns) { - if (isColumnGrouped(column)) { - throw new IllegalArgumentException("Column " - + String.valueOf(column.getHeaderCaption()) - + " already belongs to another group."); - } - } - - validateNewGroupProperties(Arrays.asList(columns)); - - ColumnGroup<T> group = new ColumnGroup<T>(grid, Arrays.asList(columns)); - groups.add(group); - grid.refreshHeader(); - grid.refreshFooter(); - return group; - } - - private void validateNewGroupProperties(Collection<GridColumn<?, T>> columns) { - - int rowIndex = grid.getColumnGroupRows().indexOf(this); - int parentRowIndex = rowIndex - 1; - - // Get the parent row of this row. - ColumnGroupRow<T> parentRow = null; - if (parentRowIndex > -1) { - parentRow = grid.getColumnGroupRows().get(parentRowIndex); - } - - if (parentRow == null) { - // A parentless row is always valid and is usually the first row - // added to the grid - return; - } - - for (GridColumn<?, T> column : columns) { - if (parentRow.hasColumnBeenGrouped(column)) { - /* - * If a property has been grouped in the parent row then all of - * the properties in the parent group also needs to be included - * in the child group for the groups to be valid - */ - ColumnGroup parentGroup = parentRow.getGroupForColumn(column); - if (!columns.containsAll(parentGroup.getColumns())) { - throw new IllegalArgumentException( - "Grouped properties overlaps previous grouping bounderies"); - } - } - } - } - - private boolean hasColumnBeenGrouped(GridColumn<?, T> column) { - return getGroupForColumn(column) != null; - } - - private ColumnGroup<T> getGroupForColumn(GridColumn<?, T> column) { - for (ColumnGroup<T> group : groups) { - if (group.getColumns().contains(column)) { - return group; - } - } - return null; - } - - /** - * Add a new group to the row by using other already greated groups - * - * @param groups - * The subgroups of the group. - * @return a column group representing the collection of columns added to - * the group. - * - */ - public ColumnGroup<T> addGroup(ColumnGroup<T>... groups) - throws IllegalArgumentException { - assert groups != null : "groups cannot be null"; - - Set<GridColumn<?, T>> columns = new HashSet<GridColumn<?, T>>(); - for (ColumnGroup<T> group : groups) { - columns.addAll(group.getColumns()); - } - - validateNewGroupProperties(columns); - - ColumnGroup<T> group = new ColumnGroup<T>(grid, columns); - this.groups.add(group); - grid.refreshHeader(); - grid.refreshFooter(); - return group; - } - - /** - * Removes a group from the row. - * - * @param group - * The group to remove - */ - public void removeGroup(ColumnGroup<T> group) { - groups.remove(group); - grid.refreshHeader(); - grid.refreshFooter(); - } - - /** - * Get the groups in the row - * - * @return unmodifiable list of groups in this row - */ - public List<ColumnGroup<T>> getGroups() { - return Collections.unmodifiableList(groups); - } - - /** - * Is the header visible for the row. - * - * @return <code>true</code> if header is visible - */ - public boolean isHeaderVisible() { - return headerVisible; - } - - /** - * Sets the header visible for the row. - * - * @param visible - * should the header be shown - */ - public void setHeaderVisible(boolean visible) { - headerVisible = visible; - grid.refreshHeader(); - } - - /** - * Is the footer visible for the row. - * - * @return <code>true</code> if footer is visible - */ - public boolean isFooterVisible() { - return footerVisible; - } - - /** - * Sets the footer visible for the row. - * - * @param visible - * should the footer be shown - */ - public void setFooterVisible(boolean visible) { - footerVisible = visible; - grid.refreshFooter(); - } - - /** - * Iterates all the column groups and checks if the columns alread has been - * added to a group. - */ - private boolean isColumnGrouped(GridColumn<?, T> column) { - for (ColumnGroup<T> group : groups) { - if (group.getColumns().contains(column)) { - return true; - } - } - return false; - } -} diff --git a/client/src/com/vaadin/client/ui/grid/Escalator.java b/client/src/com/vaadin/client/ui/grid/Escalator.java deleted file mode 100644 index 6112d6b139..0000000000 --- a/client/src/com/vaadin/client/ui/grid/Escalator.java +++ /dev/null @@ -1,4042 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.animation.client.AnimationScheduler; -import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; -import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; -import com.google.gwt.core.client.Duration; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -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.event.shared.HandlerRegistration; -import com.google.gwt.logging.client.LogConfiguration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.UIObject; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.Profiler; -import com.vaadin.client.Util; -import com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle; -import com.vaadin.client.ui.grid.PositionFunction.AbsolutePosition; -import com.vaadin.client.ui.grid.PositionFunction.Translate3DPosition; -import com.vaadin.client.ui.grid.PositionFunction.TranslatePosition; -import com.vaadin.client.ui.grid.PositionFunction.WebkitTranslate3DPosition; -import com.vaadin.client.ui.grid.ScrollbarBundle.HorizontalScrollbarBundle; -import com.vaadin.client.ui.grid.ScrollbarBundle.VerticalScrollbarBundle; -import com.vaadin.shared.ui.grid.Range; -import com.vaadin.shared.ui.grid.ScrollDestination; -import com.vaadin.shared.util.SharedUtil; - -/*- - - Maintenance Notes! Reading these might save your day. - - - == Row Container Structure - - AbstractRowContainer - |-- AbstractStaticRowContainer - | |-- HeaderRowContainer - | `-- FooterContainer - `-- BodyRowContainer - - AbstractRowContainer is intended to contain all common logic - between RowContainers. It manages the bookkeeping of row - count, makes sure that all individual cells are rendered - the same way, and so on. - - AbstractStaticRowContainer has some special logic that is - required by all RowContainers that don't scroll (hence the - word "static"). HeaderRowContainer and FooterRowContainer - are pretty thin special cases of a StaticRowContainer - (mostly relating to positioning of the root element). - - BodyRowContainer could also be split into an additional - "AbstractScrollingRowContainer", but I felt that no more - inner classes were needed. So it contains both logic - required for making things scroll about, and equivalent - special cases for layouting, as are found in - Header/FooterRowContainers. - - - == The Three Indices - - Each RowContainer can be thought to have three levels of - indices for any given displayed row (but the distinction - matters primarily for the BodyRowContainer, because of the - way it scrolls through data): - - - Logical index - - Physical (or DOM) index - - Visual index - - LOGICAL INDEX is the index that is linked to the data - source. If you want your data source to represent a SQL - database with 10 000 rows, the 7 000:th row in the SQL has a - logical index of 6 999, since the index is 0-based (unless - that data source does some funky logic). - - PHYSICAL INDEX is the index for a row that you see in a - browser's DOM inspector. If your row is the second <tr> - element within a <tbody> tag, it has a physical index of 1 - (because of 0-based indices). In Header and - FooterRowContainers, you are safe to assume that the logical - index is the same as the physical index. But because the - BodyRowContainer never displays large data sources entirely - in the DOM, a physical index usually has no apparent direct - relationship with its logical index. - - VISUAL INDEX is the index relating to the order that you - see a row in, in the browser, as it is rendered. The - topmost row is 0, the second is 1, and so on. The visual - index is similar to the physical index in the sense that - Header and FooterRowContainers can assume a 1:1 - relationship between visual index and logical index. And - again, BodyRowContainer has no such relationship. The - body's visual index has additionally no apparent - relationship with its physical index. Because the <tr> tags - are reused in the body and visually repositioned with CSS - as the user scrolls, the relationship between physical - index and visual index is quickly broken. You can get an - element's visual index via the field - BodyRowContainer.visualRowOrder. - - */ - -/** - * A workaround-class for GWT and JSNI. - * <p> - * GWT is unable to handle some method calls to Java methods in inner-classes - * from within JSNI blocks. Having that inner class implement a non-inner-class - * (or interface), makes it possible for JSNI to indirectly refer to the inner - * class, by invoking methods and fields in the non-inner-class. - * - * @see Escalator.Scroller - */ -abstract class JsniWorkaround { - /** - * A JavaScript function that handles the scroll DOM event, and passes it on - * to Java code. - * - * @see #createScrollListenerFunction(Escalator) - * @see Escalator#onScroll(double,double) - * @see Escalator.Scroller#onScroll(double, double) - */ - protected final JavaScriptObject scrollListenerFunction; - - /** - * A JavaScript function that handles the mousewheel DOM event, and passes - * it on to Java code. - * - * @see #createMousewheelListenerFunction(Escalator) - * @see Escalator#onScroll(double,double) - * @see Escalator.Scroller#onScroll(double, double) - */ - protected final JavaScriptObject mousewheelListenerFunction; - - /** - * A JavaScript function that handles the touch start DOM event, and passes - * it on to Java code. - * - * @see TouchHandlerBundle#touchStart(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) - */ - protected JavaScriptObject touchStartFunction; - - /** - * A JavaScript function that handles the touch move DOM event, and passes - * it on to Java code. - * - * @see TouchHandlerBundle#touchMove(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) - */ - protected JavaScriptObject touchMoveFunction; - - /** - * A JavaScript function that handles the touch end and cancel DOM events, - * and passes them on to Java code. - * - * @see TouchHandlerBundle#touchEnd(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) - */ - protected JavaScriptObject touchEndFunction; - - protected JsniWorkaround(final Escalator escalator) { - scrollListenerFunction = createScrollListenerFunction(escalator); - mousewheelListenerFunction = createMousewheelListenerFunction(escalator); - - final TouchHandlerBundle bundle = new TouchHandlerBundle(escalator); - touchStartFunction = bundle.getTouchStartHandler(); - touchMoveFunction = bundle.getTouchMoveHandler(); - touchEndFunction = bundle.getTouchEndHandler(); - } - - /** - * A method that constructs the JavaScript function that will be stored into - * {@link #scrollListenerFunction}. - * - * @param esc - * a reference to the current instance of {@link Escalator} - * @see Escalator#onScroll(double,double) - */ - protected abstract JavaScriptObject createScrollListenerFunction( - Escalator esc); - - /** - * A method that constructs the JavaScript function that will be stored into - * {@link #mousewheelListenerFunction}. - * - * @param esc - * a reference to the current instance of {@link Escalator} - * @see Escalator#onScroll(double,double) - */ - protected abstract JavaScriptObject createMousewheelListenerFunction( - Escalator esc); -} - -/** - * A low-level table-like widget that features a scrolling virtual viewport and - * lazily generated rows. - * - * @since 7.2 - * @author Vaadin Ltd - */ -public class Escalator extends Widget { - - // todo comments legend - /* - * [[optimize]]: There's an opportunity to rewrite the code in such a way - * that it _might_ perform better (rememeber to measure, implement, - * re-measure) - */ - /* - * [[rowheight]]: This code will require alterations that are relevant for - * being able to support variable row heights. NOTE: these bits can most - * often also be identified by searching for code reading the ROW_HEIGHT_PX - * constant. - */ - /* - * [[API]]: Implementing this suggestion would require a change in the - * public API. These suggestions usually don't come lightly. - */ - /* - * [[mpixscroll]]: This code will require alterations that are relevant for - * supporting the scrolling through more pixels than some browsers normally - * would support. (i.e. when we support more than "a million" pixels in the - * escalator DOM). NOTE: these bits can most often also be identified by - * searching for code that call scrollElem.getScrollTop();. - */ - - /** - * A utility class that contains utility methods that are usually called - * from JSNI. - * <p> - * The methods are moved in this class to minimize the amount of JSNI code - * as much as feasible. - */ - static class JsniUtil { - public static class TouchHandlerBundle { - - /** - * A <a href= - * "http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsOverlay.html" - * >JavaScriptObject overlay</a> for the <a - * href="http://www.w3.org/TR/touch-events/">JavaScript - * TouchEvent</a> object. - * <p> - * This needs to be used in the touch event handlers, since GWT's - * {@link com.google.gwt.event.dom.client.TouchEvent TouchEvent} - * can't be cast from the JSNI call, and the - * {@link com.google.gwt.dom.client.NativeEvent NativeEvent} isn't - * properly populated with the correct values. - */ - private final static class CustomTouchEvent extends - JavaScriptObject { - protected CustomTouchEvent() { - } - - public native NativeEvent getNativeEvent() - /*-{ - return this; - }-*/; - - public native int getPageX() - /*-{ - return this.targetTouches[0].pageX; - }-*/; - - public native int getPageY() - /*-{ - return this.targetTouches[0].pageY; - }-*/; - } - - private double touches = 0; - private int lastX = 0; - private int lastY = 0; - private double lastTime = 0; - private boolean snappedScrollEnabled = true; - private double deltaX = 0; - private double deltaY = 0; - - private final Escalator escalator; - private CustomTouchEvent latestTouchMoveEvent; - private AnimationCallback mover = new AnimationCallback() { - @Override - public void execute(double timestamp) { - if (touches != 1) { - return; - } - - final int x = latestTouchMoveEvent.getPageX(); - final int y = latestTouchMoveEvent.getPageY(); - deltaX = x - lastX; - deltaY = y - lastY; - lastX = x; - lastY = y; - lastTime = Duration.currentTimeMillis(); - - // snap the scroll to the major axes, at first. - if (snappedScrollEnabled) { - final double oldDeltaX = deltaX; - final double oldDeltaY = deltaY; - - /* - * Scrolling snaps to 40 degrees vs. flick scroll's 30 - * degrees, since slow movements have poor resolution - - * it's easy to interpret a slight angle as a steep - * angle, since the sample rate is "unnecessarily" high. - * 40 simply felt better than 30. - */ - final double[] snapped = Escalator.snapDeltas(deltaX, - deltaY, RATIO_OF_40_DEGREES); - deltaX = snapped[0]; - deltaY = snapped[1]; - - /* - * if the snap failed once, let's follow the pointer - * from now on. - */ - if (oldDeltaX != 0 && deltaX == oldDeltaX - && oldDeltaY != 0 && deltaY == oldDeltaY) { - snappedScrollEnabled = false; - } - } - - moveScrollFromEvent(escalator, -deltaX, -deltaY, - latestTouchMoveEvent.getNativeEvent()); - } - }; - private AnimationHandle animationHandle; - - public TouchHandlerBundle(final Escalator escalator) { - this.escalator = escalator; - } - - public native JavaScriptObject getTouchStartHandler() - /*-{ - // we need to store "this", since it won't be preserved on call. - var self = this; - return $entry(function (e) { - self.@com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle::touchStart(*)(e); - }); - }-*/; - - public native JavaScriptObject getTouchMoveHandler() - /*-{ - // we need to store "this", since it won't be preserved on call. - var self = this; - return $entry(function (e) { - self.@com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle::touchMove(*)(e); - }); - }-*/; - - public native JavaScriptObject getTouchEndHandler() - /*-{ - // we need to store "this", since it won't be preserved on call. - var self = this; - return $entry(function (e) { - self.@com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle::touchEnd(*)(e); - }); - }-*/; - - public void touchStart(final CustomTouchEvent event) { - touches++; - if (touches != 1) { - return; - } - - escalator.scroller.cancelFlickScroll(); - - lastX = event.getPageX(); - lastY = event.getPageY(); - - snappedScrollEnabled = true; - } - - public void touchMove(final CustomTouchEvent event) { - /* - * since we only use the getPageX/Y, and calculate the diff - * within the handler, we don't need to calculate any - * intermediate deltas. - */ - latestTouchMoveEvent = event; - - if (animationHandle != null) { - animationHandle.cancel(); - } - animationHandle = AnimationScheduler.get() - .requestAnimationFrame(mover, escalator.bodyElem); - event.getNativeEvent().preventDefault(); - } - - public void touchEnd(@SuppressWarnings("unused") - final CustomTouchEvent event) { - touches--; - - if (touches == 0) { - escalator.scroller.handleFlickScroll(deltaX, deltaY, - lastTime); - } - } - } - - public static void moveScrollFromEvent(final Escalator escalator, - final double deltaX, final double deltaY, - final NativeEvent event) { - - if (!Double.isNaN(deltaX)) { - escalator.horizontalScrollbar.setScrollPosByDelta((int) deltaX); - } - - if (!Double.isNaN(deltaY)) { - escalator.verticalScrollbar.setScrollPosByDelta((int) deltaY); - } - - /* - * TODO: only prevent if not scrolled to end/bottom. Or no? UX team - * needs to decide. - */ - final boolean warrantedYScroll = deltaY != 0 - && escalator.verticalScrollbar.showsScrollHandle(); - final boolean warrantedXScroll = deltaX != 0 - && escalator.horizontalScrollbar.showsScrollHandle(); - if (warrantedYScroll || warrantedXScroll) { - event.preventDefault(); - } - } - } - - /** - * The animation callback that handles the animation of a touch-scrolling - * flick with inertia. - */ - private class FlickScrollAnimator implements AnimationCallback { - private static final double MIN_MAGNITUDE = 0.005; - private static final double MAX_SPEED = 7; - - private double velX; - private double velY; - private double prevTime = 0; - private int millisLeft; - private double xFric; - private double yFric; - - private boolean cancelled = false; - private int lastLeft; - private int lastTop; - - /** - * Creates a new animation callback to handle touch-scrolling flick with - * inertia. - * - * @param deltaX - * the last scrolling delta in the x-axis in a touchmove - * @param deltaY - * the last scrolling delta in the y-axis in a touchmove - * @param lastTime - * the timestamp of the last touchmove - */ - public FlickScrollAnimator(final double deltaX, final double deltaY, - final double lastTime) { - final double currentTimeMillis = Duration.currentTimeMillis(); - velX = Math.max(Math.min(deltaX / (currentTimeMillis - lastTime), - MAX_SPEED), -MAX_SPEED); - velY = Math.max(Math.min(deltaY / (currentTimeMillis - lastTime), - MAX_SPEED), -MAX_SPEED); - - lastLeft = horizontalScrollbar.getScrollPos(); - lastTop = verticalScrollbar.getScrollPos(); - - /* - * If we're scrolling mainly in one of the four major directions, - * and only a teeny bit to any other side, snap the scroll to that - * major direction instead. - */ - final double[] snapDeltas = Escalator.snapDeltas(velX, velY, - RATIO_OF_30_DEGREES); - velX = snapDeltas[0]; - velY = snapDeltas[1]; - - if (velX * velX + velY * velY > MIN_MAGNITUDE) { - millisLeft = 1500; - xFric = velX / millisLeft; - yFric = velY / millisLeft; - } else { - millisLeft = 0; - } - - } - - @Override - public void execute(final double timestamp) { - if (millisLeft <= 0 || cancelled) { - scroller.currentFlickScroller = null; - return; - } - - if (prevTime == 0) { - prevTime = timestamp; - AnimationScheduler.get().requestAnimationFrame(this); - return; - } - - int currentLeft = horizontalScrollbar.getScrollPos(); - int currentTop = verticalScrollbar.getScrollPos(); - - final double timeDiff = timestamp - prevTime; - double left = currentLeft - velX * timeDiff; - setScrollLeft((int) left); - velX -= xFric * timeDiff; - - double top = currentTop - velY * timeDiff; - setScrollTop(top); - velY -= yFric * timeDiff; - - cancelBecauseOfEdgeOrCornerMaybe(); - - prevTime = timestamp; - millisLeft -= timeDiff; - lastLeft = currentLeft; - lastTop = currentTop; - AnimationScheduler.get().requestAnimationFrame(this); - } - - private void cancelBecauseOfEdgeOrCornerMaybe() { - if (lastLeft == horizontalScrollbar.getScrollPos() - && lastTop == verticalScrollbar.getScrollPos()) { - cancel(); - } - } - - public void cancel() { - cancelled = true; - } - } - - /** - * ScrollDestination case-specific handling logic. - */ - private static double getScrollPos(final ScrollDestination destination, - final double targetStartPx, final double targetEndPx, - final double viewportStartPx, final double viewportEndPx, - final int padding) { - - final double viewportLength = viewportEndPx - viewportStartPx; - - switch (destination) { - - /* - * Scroll as little as possible to show the target element. If the - * element fits into view, this works as START or END depending on the - * current scroll position. If the element does not fit into view, this - * works as START. - */ - case ANY: { - final double startScrollPos = targetStartPx - padding; - final double endScrollPos = targetEndPx + padding - viewportLength; - - if (startScrollPos < viewportStartPx) { - return startScrollPos; - } else if (targetEndPx + padding > viewportEndPx) { - return endScrollPos; - } else { - // NOOP, it's already visible - return viewportStartPx; - } - } - - /* - * Scrolls so that the element is shown at the end of the viewport. The - * viewport will, however, not scroll before its first element. - */ - case END: { - return targetEndPx + padding - viewportLength; - } - - /* - * Scrolls so that the element is shown in the middle of the viewport. - * The viewport will, however, not scroll beyond its contents, given - * more elements than what the viewport is able to show at once. Under - * no circumstances will the viewport scroll before its first element. - */ - case MIDDLE: { - final double targetMiddle = targetStartPx - + (targetEndPx - targetStartPx) / 2; - return targetMiddle - viewportLength / 2; - } - - /* - * Scrolls so that the element is shown at the start of the viewport. - * The viewport will, however, not scroll beyond its contents. - */ - case START: { - return targetStartPx - padding; - } - - /* - * Throw an error if we're here. This can only mean that - * ScrollDestination has been carelessly amended.. - */ - default: { - throw new IllegalArgumentException( - "Internal: ScrollDestination has been modified, " - + "but Escalator.getScrollPos has not been updated " - + "to match new values."); - } - } - - } - - /** An inner class that handles all logic related to scrolling. */ - private class Scroller extends JsniWorkaround { - private double lastScrollTop = 0; - private double lastScrollLeft = 0; - /** - * The current flick scroll animator. This is <code>null</code> if the - * view isn't animating a flick scroll at the moment. - */ - private FlickScrollAnimator currentFlickScroller; - - public Scroller() { - super(Escalator.this); - } - - @Override - protected native JavaScriptObject createScrollListenerFunction( - Escalator esc) - /*-{ - var vScroll = esc.@com.vaadin.client.ui.grid.Escalator::verticalScrollbar; - var vScrollElem = vScroll.@com.vaadin.client.ui.grid.ScrollbarBundle::getElement()(); - - var hScroll = esc.@com.vaadin.client.ui.grid.Escalator::horizontalScrollbar; - var hScrollElem = hScroll.@com.vaadin.client.ui.grid.ScrollbarBundle::getElement()(); - - return $entry(function(e) { - var target = e.target || e.srcElement; // IE8 uses e.scrElement - - // in case the scroll event was native (i.e. scrollbars were dragged, or - // the scrollTop/Left was manually modified), the bundles have old cache - // values. We need to make sure that the caches are kept up to date. - if (target === vScrollElem) { - vScroll.@com.vaadin.client.ui.grid.ScrollbarBundle::updateScrollPosFromDom()(); - } else if (target === hScrollElem) { - hScroll.@com.vaadin.client.ui.grid.ScrollbarBundle::updateScrollPosFromDom()(); - } else { - $wnd.console.error("unexpected scroll target: "+target); - } - - esc.@com.vaadin.client.ui.grid.Escalator::onScroll()(); - }); - }-*/; - - @Override - protected native JavaScriptObject createMousewheelListenerFunction( - Escalator esc) - /*-{ - return $entry(function(e) { - var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX; - var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY; - - // IE8 has only delta y - if (isNaN(deltaY)) { - deltaY = -0.5*e.wheelDelta; - } - - @com.vaadin.client.ui.grid.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e); - }); - }-*/; - - /** - * Recalculates the virtual viewport represented by the scrollbars, so - * that the sizes of the scroll handles appear correct in the browser - */ - public void recalculateScrollbarsForVirtualViewport() { - int scrollContentHeight = body.calculateEstimatedTotalRowHeight(); - int scrollContentWidth = columnConfiguration.calculateRowWidth(); - - double tableWrapperHeight = heightOfEscalator; - double tableWrapperWidth = widthOfEscalator; - - boolean verticalScrollNeeded = scrollContentHeight > tableWrapperHeight - - header.heightOfSection - footer.heightOfSection; - boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth; - - // One dimension got scrollbars, but not the other. Recheck time! - if (verticalScrollNeeded != horizontalScrollNeeded) { - if (!verticalScrollNeeded && horizontalScrollNeeded) { - verticalScrollNeeded = scrollContentHeight > tableWrapperHeight - - header.heightOfSection - - footer.heightOfSection - - horizontalScrollbar.getScrollbarThickness(); - } else { - horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth - - verticalScrollbar.getScrollbarThickness(); - } - } - - // let's fix the table wrapper size, since it's now stable. - if (verticalScrollNeeded) { - tableWrapperWidth -= verticalScrollbar.getScrollbarThickness(); - } - if (horizontalScrollNeeded) { - tableWrapperHeight -= horizontalScrollbar - .getScrollbarThickness(); - } - tableWrapper.getStyle().setHeight(tableWrapperHeight, Unit.PX); - tableWrapper.getStyle().setWidth(tableWrapperWidth, Unit.PX); - - verticalScrollbar.setOffsetSize((int) (tableWrapperHeight - - footer.heightOfSection - header.heightOfSection)); - verticalScrollbar.setScrollSize(scrollContentHeight); - - /* - * If decreasing the amount of frozen columns, and scrolled to the - * right, the scroll position might reset. So we need to remember - * the scroll position, and re-apply it once the scrollbar size has - * been adjusted. - */ - int prevScrollPos = horizontalScrollbar.getScrollPos(); - - int unfrozenPixels = columnConfiguration - .getCalculatedColumnsWidth(Range.between( - columnConfiguration.getFrozenColumnCount(), - columnConfiguration.getColumnCount())); - int frozenPixels = scrollContentWidth - unfrozenPixels; - double hScrollOffsetWidth = tableWrapperWidth - frozenPixels; - horizontalScrollbar.setOffsetSize((int) hScrollOffsetWidth); - horizontalScrollbar.setScrollSize(unfrozenPixels); - horizontalScrollbar.getElement().getStyle() - .setLeft(frozenPixels, Unit.PX); - horizontalScrollbar.setScrollPos(prevScrollPos); - } - - /** - * Logical scrolling event handler for the entire widget. - * - * @param scrollLeft - * the current number of pixels that the user has scrolled - * from left - * @param scrollTop - * the current number of pixels that the user has scrolled - * from the top - */ - public void onScroll() { - if (internalScrollEventCalls > 0) { - internalScrollEventCalls--; - return; - } - - final int scrollLeft = horizontalScrollbar.getScrollPos(); - final int scrollTop = verticalScrollbar.getScrollPos(); - - if (lastScrollLeft != scrollLeft) { - for (int i = 0; i < columnConfiguration.frozenColumns; i++) { - header.updateFreezePosition(i, scrollLeft); - body.updateFreezePosition(i, scrollLeft); - footer.updateFreezePosition(i, scrollLeft); - } - - position.set(headElem, -scrollLeft, 0); - - /* - * TODO [[optimize]]: cache this value in case the instanceof - * check has undesirable overhead. This could also be a - * candidate for some deferred binding magic so that e.g. - * AbsolutePosition is not even considered in permutations that - * we know support something better. That would let the compiler - * completely remove the entire condition since it knows that - * the if will never be true. - */ - if (position instanceof AbsolutePosition) { - /* - * we don't want to put "top: 0" on the footer, since it'll - * render wrong, as we already have - * "bottom: $footer-height". - */ - footElem.getStyle().setLeft(-scrollLeft, Unit.PX); - } else { - position.set(footElem, -scrollLeft, 0); - } - - lastScrollLeft = scrollLeft; - } - - body.setBodyScrollPosition(scrollLeft, scrollTop); - - lastScrollTop = scrollTop; - body.updateEscalatorRowsOnScroll(); - /* - * TODO [[optimize]]: Might avoid a reflow by first calculating new - * scrolltop and scrolleft, then doing the escalator magic based on - * those numbers and only updating the positions after that. - */ - } - - public native void attachScrollListener(Element element) - /* - * Attaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - element.addEventListener("scroll", this.@com.vaadin.client.ui.grid.JsniWorkaround::scrollListenerFunction); - } else { - element.attachEvent("onscroll", this.@com.vaadin.client.ui.grid.JsniWorkaround::scrollListenerFunction); - } - }-*/; - - public native void detachScrollListener(Element element) - /* - * Attaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - element.removeEventListener("scroll", this.@com.vaadin.client.ui.grid.JsniWorkaround::scrollListenerFunction); - } else { - element.detachEvent("onscroll", this.@com.vaadin.client.ui.grid.JsniWorkaround::scrollListenerFunction); - } - }-*/; - - public native void attachMousewheelListener(Element element) - /* - * Attaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - // firefox likes "wheel", while others use "mousewheel" - var eventName = element.onwheel===undefined?"mousewheel":"wheel"; - element.addEventListener(eventName, this.@com.vaadin.client.ui.grid.JsniWorkaround::mousewheelListenerFunction); - } else { - // IE8 - element.attachEvent("onmousewheel", this.@com.vaadin.client.ui.grid.JsniWorkaround::mousewheelListenerFunction); - } - }-*/; - - public native void detachMousewheelListener(Element element) - /* - * Detaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - // firefox likes "wheel", while others use "mousewheel" - var eventName = element.onwheel===undefined?"mousewheel":"wheel"; - element.removeEventListener(eventName, this.@com.vaadin.client.ui.grid.JsniWorkaround::mousewheelListenerFunction); - } else { - // IE8 - element.detachEvent("onmousewheel", this.@com.vaadin.client.ui.grid.JsniWorkaround::mousewheelListenerFunction); - } - }-*/; - - public native void attachTouchListeners(Element element) - /* - * Detaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - element.addEventListener("touchstart", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchStartFunction); - element.addEventListener("touchmove", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchMoveFunction); - element.addEventListener("touchend", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchEndFunction); - element.addEventListener("touchcancel", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchEndFunction); - } else { - // this would be IE8, but we don't support it with touch - } - }-*/; - - public native void detachTouchListeners(Element element) - /* - * Detaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.removeEventListener) { - element.removeEventListener("touchstart", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchStartFunction); - element.removeEventListener("touchmove", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchMoveFunction); - element.removeEventListener("touchend", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchEndFunction); - element.removeEventListener("touchcancel", this.@com.vaadin.client.ui.grid.JsniWorkaround::touchEndFunction); - } else { - // this would be IE8, but we don't support it with touch - } - }-*/; - - private void cancelFlickScroll() { - if (currentFlickScroller != null) { - currentFlickScroller.cancel(); - } - } - - /** - * Handles a touch-based flick scroll. - * - * @param deltaX - * the last scrolling delta in the x-axis in a touchmove - * @param deltaY - * the last scrolling delta in the y-axis in a touchmove - * @param lastTime - * the timestamp of the last touchmove - */ - public void handleFlickScroll(double deltaX, double deltaY, - double lastTime) { - currentFlickScroller = new FlickScrollAnimator(deltaX, deltaY, - lastTime); - AnimationScheduler.get() - .requestAnimationFrame(currentFlickScroller); - } - - public void scrollToColumn(final int columnIndex, - final ScrollDestination destination, final int padding) { - assert columnIndex >= columnConfiguration.frozenColumns : "Can't scroll to a frozen column"; - - /* - * To cope with frozen columns, we just pretend those columns are - * not there at all when calculating the position of the target - * column and the boundaries of the viewport. The resulting - * scrollLeft will be correct without compensation since the DOM - * structure effectively means that scrollLeft also ignores the - * frozen columns. - */ - final int frozenPixels = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(0, - columnConfiguration.frozenColumns)); - - final int targetStartPx = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(0, columnIndex)) - - frozenPixels; - final int targetEndPx = targetStartPx - + columnConfiguration.getColumnWidthActual(columnIndex); - - final int viewportStartPx = getScrollLeft(); - int viewportEndPx = viewportStartPx + getElement().getOffsetWidth() - - frozenPixels; - if (verticalScrollbar.showsScrollHandle()) { - viewportEndPx -= Util.getNativeScrollbarSize(); - } - - final double scrollLeft = getScrollPos(destination, targetStartPx, - targetEndPx, viewportStartPx, viewportEndPx, padding); - - /* - * note that it doesn't matter if the scroll would go beyond the - * content, since the browser will adjust for that, and everything - * fall into line accordingly. - */ - setScrollLeft((int) scrollLeft); - } - - public void scrollToRow(final int rowIndex, - final ScrollDestination destination, final int padding) { - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ - final int targetStartPx = body.getDefaultRowHeight() * rowIndex; - final int targetEndPx = targetStartPx + body.getDefaultRowHeight(); - - final double viewportStartPx = getScrollTop(); - final double viewportEndPx = viewportStartPx - + body.calculateHeight(); - - final double scrollTop = getScrollPos(destination, targetStartPx, - targetEndPx, viewportStartPx, viewportEndPx, padding); - - /* - * note that it doesn't matter if the scroll would go beyond the - * content, since the browser will adjust for that, and everything - * falls into line accordingly. - */ - setScrollTop(scrollTop); - } - } - - private abstract class AbstractRowContainer implements RowContainer { - - private EscalatorUpdater updater = EscalatorUpdater.NULL; - - private int rows; - - /** - * The table section element ({@code <thead>}, {@code <tbody>} or - * {@code <tfoot>}) the rows (i.e. {@code <tr>} tags) are contained in. - */ - protected final Element root; - - /** The height of the combined rows in the DOM. */ - protected double heightOfSection = -1; - - /** - * The primary style name of the escalator. Most commonly provided by - * Escalator as "v-escalator". - */ - private String primaryStyleName = null; - - /** - * A map containing cached values of an element's current top position. - * <p> - * Don't use this field directly, because it will not take proper care - * of all the bookkeeping required. - * - * @deprecated Use {@link #setRowPosition(Element, int, int)}, - * {@link #getRowTop(Element)} and - * {@link #removeRowPosition(Element)} instead. - */ - @Deprecated - private final Map<Element, Integer> rowTopPositionMap = new HashMap<Element, Integer>(); - - private boolean defaultRowHeightShouldBeAutodetected = true; - - private int defaultRowHeight = INITIAL_DEFAULT_ROW_HEIGHT; - - public AbstractRowContainer(final Element rowContainerElement) { - root = rowContainerElement; - } - - /** - * Gets the tag name of an element to represent a cell in a row. - * <p> - * Usually {@code "th"} or {@code "td"}. - * <p> - * <em>Note:</em> To actually <em>create</em> such an element, use - * {@link #createCellElement()} instead. - * - * @return the tag name for the element to represent cells as - * @see #createCellElement() - */ - protected abstract String getCellElementTagName(); - - @Override - public EscalatorUpdater getEscalatorUpdater() { - return updater; - } - - /** - * {@inheritDoc} - * <p> - * <em>Implementation detail:</em> This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for rows or columns - * when this method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - public void setEscalatorUpdater(final EscalatorUpdater escalatorUpdater) { - if (escalatorUpdater == null) { - throw new IllegalArgumentException( - "escalator updater cannot be null"); - } - - updater = escalatorUpdater; - - if (hasColumnAndRowData() && getRowCount() > 0) { - refreshRows(0, getRowCount()); - } - } - - /** - * {@inheritDoc} - * <p> - * <em>Implementation detail:</em> This method does no DOM modifications - * (i.e. is very cheap to call) if there are no rows in the DOM when - * this method is called. - * - * @see #hasSomethingInDom() - */ - @Override - public void removeRows(final int index, final int numberOfRows) { - assertArgumentsAreValidAndWithinRange(index, numberOfRows); - - rows -= numberOfRows; - - if (!isAttached()) { - return; - } - - if (hasSomethingInDom()) { - paintRemoveRows(index, numberOfRows); - } - } - - protected abstract void paintRemoveRows(final int index, - final int numberOfRows); - - private void assertArgumentsAreValidAndWithinRange(final int index, - final int numberOfRows) throws IllegalArgumentException, - IndexOutOfBoundsException { - if (numberOfRows < 1) { - throw new IllegalArgumentException( - "Number of rows must be 1 or greater (was " - + numberOfRows + ")"); - } - - if (index < 0 || index + numberOfRows > getRowCount()) { - throw new IndexOutOfBoundsException("The given " - + "row range (" + index + ".." + (index + numberOfRows) - + ") was outside of the current number of rows (" - + getRowCount() + ")"); - } - } - - @Override - public int getRowCount() { - return rows; - } - - /** - * {@inheritDoc} - * <p> - * <em>Implementation detail:</em> This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for columns when - * this method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - public void insertRows(final int index, final int numberOfRows) { - if (index < 0 || index > getRowCount()) { - throw new IndexOutOfBoundsException("The given index (" + index - + ") was outside of the current number of rows (0.." - + getRowCount() + ")"); - } - - if (numberOfRows < 1) { - throw new IllegalArgumentException( - "Number of rows must be 1 or greater (was " - + numberOfRows + ")"); - } - - rows += numberOfRows; - - /* - * only add items in the DOM if the widget itself is attached to the - * DOM. We can't calculate sizes otherwise. - */ - if (isAttached()) { - paintInsertRows(index, numberOfRows); - } - } - - /** - * Actually add rows into the DOM, now that everything can be - * calculated. - * - * @param visualIndex - * the DOM index to add rows into - * @param numberOfRows - * the number of rows to insert - * @return a list of the added row elements - */ - protected List<Element> paintInsertRows(final int visualIndex, - final int numberOfRows) { - assert isAttached() : "Can't paint rows if Escalator is not attached"; - - final List<Element> addedRows = new ArrayList<Element>(); - - if (numberOfRows < 1) { - return addedRows; - } - - Node referenceRow; - if (root.getChildCount() != 0 && visualIndex != 0) { - // get the row node we're inserting stuff after - referenceRow = root.getChild(visualIndex - 1); - } else { - // index is 0, so just prepend. - referenceRow = null; - } - - for (int row = visualIndex; row < visualIndex + numberOfRows; row++) { - final int rowHeight = getDefaultRowHeight(); - final Element tr = DOM.createTR(); - addedRows.add(tr); - tr.addClassName(getStylePrimaryName() + "-row"); - referenceRow = insertAfterReferenceAndUpdateIt(root, tr, - referenceRow); - - for (int col = 0; col < columnConfiguration.getColumnCount(); col++) { - final int colWidth = columnConfiguration - .getColumnWidthActual(col); - final Element cellElem = createCellElement(rowHeight, - colWidth); - tr.appendChild(cellElem); - - // Set stylename and position if new cell is frozen - if (col < columnConfiguration.frozenColumns) { - cellElem.addClassName("frozen"); - position.set(cellElem, scroller.lastScrollLeft, 0); - } - } - - refreshRow(tr, row); - } - reapplyRowWidths(); - - recalculateSectionHeight(); - - return addedRows; - } - - private Node insertAfterReferenceAndUpdateIt(final Element parent, - final Element elem, final Node referenceNode) { - if (referenceNode != null) { - parent.insertAfter(elem, referenceNode); - } else { - /* - * referencenode being null means we have offset 0, i.e. make it - * the first row - */ - /* - * TODO [[optimize]]: Is insertFirst or append faster for an - * empty root? - */ - parent.insertFirst(elem); - } - return elem; - } - - abstract protected void recalculateSectionHeight(); - - /** - * Returns the estimated height of all rows in the row container. - * <p> - * The estimate is promised to be correct as long as there are no rows - * with calculated heights. - */ - protected int calculateEstimatedTotalRowHeight() { - return getDefaultRowHeight() * getRowCount(); - } - - /** - * {@inheritDoc} - * <p> - * <em>Implementation detail:</em> This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for columns when - * this method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - public void refreshRows(final int index, final int numberOfRows) { - Profiler.enter("Escalator.AbstractRowContainer.refreshRows"); - - assertArgumentsAreValidAndWithinRange(index, numberOfRows); - - if (!isAttached()) { - return; - } - - /* - * TODO [[rowheight]]: even if no rows are evaluated in the current - * viewport, the heights of some unrendered rows might change in a - * refresh. This would cause the scrollbar to be adjusted (in - * scrollHeight and/or scrollTop). Do we want to take this into - * account? - */ - if (hasColumnAndRowData()) { - /* - * TODO [[rowheight]]: nudge rows down with - * refreshRowPositions() as needed - */ - for (int row = index; row < index + numberOfRows; row++) { - final Node tr = getTrByVisualIndex(row); - refreshRow(tr, row); - } - } - - Profiler.leave("Escalator.AbstractRowContainer.refreshRows"); - } - - void refreshRow(final Node tr, final int logicalRowIndex) { - flyweightRow.setup((Element) tr, logicalRowIndex, - columnConfiguration.getCalculatedColumnWidths()); - updater.updateCells(flyweightRow, flyweightRow.getCells()); - - /* - * the "assert" guarantees that this code is run only during - * development/debugging. - */ - assert flyweightRow.teardown(); - } - - /** - * Create and setup an empty cell element. - * - * @param width - * the width of the cell, in pixels - * @param height - * the height of the cell, in pixels - * - * @return a set-up empty cell element - */ - @SuppressWarnings("hiding") - public Element createCellElement(final int height, final int width) { - final Element cellElem = DOM.createElement(getCellElementTagName()); - cellElem.getStyle().setHeight(height, Unit.PX); - cellElem.getStyle().setWidth(width, Unit.PX); - cellElem.addClassName(getStylePrimaryName() + "-cell"); - return cellElem; - } - - /** - * Gets the child element that is visually at a certain index - * - * @param index - * the index of the element to retrieve - * @return the element at position {@code index} - * @throws IndexOutOfBoundsException - * if {@code index} is not valid within {@link #root} - */ - abstract protected Element getTrByVisualIndex(int index) - throws IndexOutOfBoundsException; - - protected void paintRemoveColumns(final int offset, - final int numberOfColumns, - final List<ColumnConfigurationImpl.Column> removedColumns) { - final NodeList<Node> childNodes = root.getChildNodes(); - for (int visualRowIndex = 0; visualRowIndex < childNodes - .getLength(); visualRowIndex++) { - final Node tr = childNodes.getItem(visualRowIndex); - - for (int column = 0; column < numberOfColumns; column++) { - Element cellElement = tr.getChild(offset).cast(); - detachPossibleWidgetFromCell(cellElement); - cellElement.removeFromParent(); - } - } - reapplyRowWidths(); - - final int firstRemovedColumnLeft = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(0, offset)); - final boolean columnsWereRemovedFromLeftOfTheViewport = scroller.lastScrollLeft > firstRemovedColumnLeft; - - if (columnsWereRemovedFromLeftOfTheViewport) { - int removedColumnsPxAmount = 0; - for (ColumnConfigurationImpl.Column removedColumn : removedColumns) { - removedColumnsPxAmount += removedColumn - .getCalculatedWidth(); - } - final int leftByDiff = (int) (scroller.lastScrollLeft - removedColumnsPxAmount); - final int newScrollLeft = Math.max(firstRemovedColumnLeft, - leftByDiff); - horizontalScrollbar.setScrollPos(newScrollLeft); - } - - // this needs to be after the scroll position adjustment above. - scroller.recalculateScrollbarsForVirtualViewport(); - - /* - * Because we might remove columns where affected by colspans, it's - * easiest to simply redraw everything when columns are modified. - * - * Yes, this is a TODO [[optimize]]. - */ - if (getRowCount() > 0 - && getColumnConfiguration().getColumnCount() > 0) { - refreshRows(0, getRowCount()); - } - } - - void detachPossibleWidgetFromCell(Node cellNode) { - // Detach possible widget - Widget widget = getWidgetFromCell(cellNode); - if (widget != null) { - // Orphan. - setParent(widget, null); - - // Physical detach. - cellNode.removeChild(widget.getElement()); - } - } - - protected void paintInsertColumns(final int offset, - final int numberOfColumns, boolean frozen) { - final NodeList<Node> childNodes = root.getChildNodes(); - - for (int row = 0; row < childNodes.getLength(); row++) { - final int rowHeight = getDefaultRowHeight(); - final Element tr = getTrByVisualIndex(row); - - Node referenceCell; - if (offset != 0) { - referenceCell = tr.getChild(offset - 1); - } else { - referenceCell = null; - } - - for (int col = offset; col < offset + numberOfColumns; col++) { - final int colWidth = columnConfiguration - .getColumnWidthActual(col); - final Element cellElem = createCellElement(rowHeight, - colWidth); - referenceCell = insertAfterReferenceAndUpdateIt(tr, - cellElem, referenceCell); - } - } - reapplyRowWidths(); - - if (frozen) { - for (int col = offset; col < offset + numberOfColumns; col++) { - setColumnFrozen(col, true); - } - } - - // this needs to be before the scrollbar adjustment. - scroller.recalculateScrollbarsForVirtualViewport(); - - int pixelsToInsertedColumn = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(0, offset)); - final boolean columnsWereAddedToTheLeftOfViewport = scroller.lastScrollLeft > pixelsToInsertedColumn; - - if (columnsWereAddedToTheLeftOfViewport) { - int insertedColumnsWidth = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(offset, - numberOfColumns)); - horizontalScrollbar - .setScrollPos((int) (scroller.lastScrollLeft + insertedColumnsWidth)); - } - - /* - * Because we might insert columns where affected by colspans, it's - * easiest to simply redraw everything when columns are modified. - * - * Yes, this is a TODO [[optimize]]. - */ - if (getRowCount() > 0 - && getColumnConfiguration().getColumnCount() > 1) { - refreshRows(0, getRowCount()); - } - } - - public void setColumnFrozen(int column, boolean frozen) { - final NodeList<Node> childNodes = root.getChildNodes(); - - for (int row = 0; row < childNodes.getLength(); row++) { - final Element tr = childNodes.getItem(row).cast(); - - Element cell = (Element) tr.getChild(column); - if (frozen) { - cell.addClassName("frozen"); - } else { - cell.removeClassName("frozen"); - position.reset(cell); - } - } - - if (frozen) { - updateFreezePosition(column, scroller.lastScrollLeft); - } - } - - public void updateFreezePosition(int column, double scrollLeft) { - final NodeList<Node> childNodes = root.getChildNodes(); - - for (int row = 0; row < childNodes.getLength(); row++) { - final Element tr = childNodes.getItem(row).cast(); - - Element cell = (Element) tr.getChild(column); - position.set(cell, scrollLeft, 0); - } - } - - /** - * Iterates through all the cells in a column and returns the width of - * the widest element in this RowContainer. - * - * @param index - * the index of the column to inspect - * @return the pixel width of the widest element in the indicated column - */ - public int calculateMaxColWidth(int index) { - Element row = root.getFirstChildElement(); - int maxWidth = 0; - while (row != null) { - final Element cell = (Element) row.getChild(index); - final boolean isVisible = !cell.getStyle().getDisplay() - .equals(Display.NONE.getCssName()); - if (isVisible) { - maxWidth = Math.max(maxWidth, cell.getScrollWidth()); - } - row = row.getNextSiblingElement(); - } - return maxWidth; - } - - /** - * Reapplies all the cells' widths according to the calculated widths in - * the column configuration. - */ - public void reapplyColumnWidths() { - Element row = root.getFirstChildElement(); - while (row != null) { - Element cell = row.getFirstChildElement(); - int columnIndex = 0; - while (cell != null) { - @SuppressWarnings("hiding") - final int width = getCalculatedColumnWidthWithColspan(cell, - columnIndex); - - /* - * TODO Should Escalator implement ProvidesResize at some - * point, this is where we need to do that. - */ - cell.getStyle().setWidth(width, Unit.PX); - - cell = cell.getNextSiblingElement(); - columnIndex++; - } - row = row.getNextSiblingElement(); - } - - reapplyRowWidths(); - } - - private int getCalculatedColumnWidthWithColspan(final Element cell, - final int columnIndex) { - final int colspan = cell.getPropertyInt(FlyweightCell.COLSPAN_ATTR); - Range spannedColumns = Range.withLength(columnIndex, colspan); - - /* - * Since browsers don't explode with overflowing colspans, escalator - * shouldn't either. - */ - if (spannedColumns.getEnd() > columnConfiguration.getColumnCount()) { - spannedColumns = Range.between(columnIndex, - columnConfiguration.getColumnCount()); - } - return columnConfiguration - .getCalculatedColumnsWidth(spannedColumns); - } - - /** - * Applies the total length of the columns to each row element. - * <p> - * <em>Note:</em> In contrast to {@link #reapplyColumnWidths()}, this - * method only modifies the width of the {@code <tr>} element, not the - * cells within. - */ - protected void reapplyRowWidths() { - int rowWidth = columnConfiguration.calculateRowWidth(); - - com.google.gwt.dom.client.Element row = root.getFirstChildElement(); - while (row != null) { - row.getStyle().setWidth(rowWidth, Unit.PX); - row = row.getNextSiblingElement(); - } - } - - /** - * The primary style name for the container. - * - * @param primaryStyleName - * the style name to use as prefix for all row and cell style - * names. - */ - protected void setStylePrimaryName(String primaryStyleName) { - String oldStyle = getStylePrimaryName(); - if (SharedUtil.equals(oldStyle, primaryStyleName)) { - return; - } - - this.primaryStyleName = primaryStyleName; - - // Update already rendered rows and cells - Node row = root.getFirstChild(); - while (row != null) { - Element rowElement = row.cast(); - UIObject.setStylePrimaryName(rowElement, primaryStyleName - + "-row"); - Node cell = row.getFirstChild(); - while (cell != null) { - Element cellElement = cell.cast(); - UIObject.setStylePrimaryName(cellElement, primaryStyleName - + "-cell"); - cell = cell.getNextSibling(); - } - row = row.getNextSibling(); - } - } - - /** - * Returns the primary style name of the container. - * - * @return The primary style name or <code>null</code> if not set. - */ - protected String getStylePrimaryName() { - return primaryStyleName; - } - - @Override - public void setDefaultRowHeight(int px) throws IllegalArgumentException { - if (px < 1) { - throw new IllegalArgumentException("Height must be positive. " - + px + " was given."); - } - - defaultRowHeightShouldBeAutodetected = false; - defaultRowHeight = px; - reapplyDefaultRowHeights(); - } - - @Override - public int getDefaultRowHeight() { - return defaultRowHeight; - } - - /** - * The default height of rows has (most probably) changed. - * <p> - * Make sure that the displayed rows with a default height are updated - * in height and top position. - * <p> - * <em>Note:</em>This implementation should not call - * {@link Escalator#recalculateElementSizes()} - it is done by the - * discretion of the caller of this method. - */ - protected abstract void reapplyDefaultRowHeights(); - - protected void reapplyRowHeight(final Element tr, final int heightPx) { - Element cellElem = tr.getFirstChildElement().cast(); - while (cellElem != null) { - cellElem.getStyle().setHeight(heightPx, Unit.PX); - cellElem = cellElem.getNextSiblingElement(); - } - - /* - * no need to apply height to tr-element, it'll be resized - * implicitly. - */ - } - - @SuppressWarnings("boxing") - protected void setRowPosition(final Element tr, final int x, final int y) { - position.set(tr, x, y); - rowTopPositionMap.put(tr, y); - } - - @SuppressWarnings("boxing") - protected int getRowTop(final Element tr) { - return rowTopPositionMap.get(tr); - } - - protected void removeRowPosition(Element tr) { - rowTopPositionMap.remove(tr); - } - - public void autodetectRowHeight() { - Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { - - @Override - public void execute() { - if (defaultRowHeightShouldBeAutodetected && isAttached()) { - final Element detectionTr = DOM.createTR(); - detectionTr - .setClassName(getStylePrimaryName() + "-row"); - - final Element cellElem = DOM - .createElement(getCellElementTagName()); - cellElem.setClassName(getStylePrimaryName() + "-cell"); - cellElem.setInnerHTML("foo"); - - detectionTr.appendChild(cellElem); - root.appendChild(detectionTr); - defaultRowHeight = Math.max(1, - cellElem.getOffsetHeight()); - root.removeChild(detectionTr); - - if (root.hasChildNodes()) { - reapplyDefaultRowHeights(); - } - - defaultRowHeightShouldBeAutodetected = false; - } - } - }); - } - } - - private abstract class AbstractStaticRowContainer extends - AbstractRowContainer { - public AbstractStaticRowContainer(final Element headElement) { - super(headElement); - } - - @Override - protected void paintRemoveRows(final int index, final int numberOfRows) { - for (int i = index; i < index + numberOfRows; i++) { - final Element tr = (Element) root.getChild(index); - for (int c = 0; c < tr.getChildCount(); c++) { - detachPossibleWidgetFromCell((Element) tr.getChild(c) - .cast()); - } - tr.removeFromParent(); - } - recalculateSectionHeight(); - } - - @Override - protected Element getTrByVisualIndex(final int index) - throws IndexOutOfBoundsException { - if (index >= 0 && index < root.getChildCount()) { - return (Element) root.getChild(index); - } else { - throw new IndexOutOfBoundsException("No such visual index: " - + index); - } - } - - @Override - public void insertRows(int index, int numberOfRows) { - super.insertRows(index, numberOfRows); - recalculateElementSizes(); - } - - @Override - public void removeRows(int index, int numberOfRows) { - super.removeRows(index, numberOfRows); - recalculateElementSizes(); - } - - @Override - protected void reapplyDefaultRowHeights() { - if (root.getChildCount() == 0) { - return; - } - - Profiler.enter("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights"); - - Element tr = root.getFirstChildElement().cast(); - while (tr != null) { - reapplyRowHeight(tr, getDefaultRowHeight()); - tr = tr.getNextSiblingElement(); - } - - /* - * Because all rows are immediately displayed in the static row - * containers, the section's overall height has most probably - * changed. - */ - recalculateSectionHeight(); - - Profiler.leave("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights"); - } - - @Override - protected void recalculateSectionHeight() { - Profiler.enter("Escalator.AbstractStaticRowContainer.recalculateSectionHeight"); - - int newHeight = calculateEstimatedTotalRowHeight(); - if (newHeight != heightOfSection) { - heightOfSection = newHeight; - sectionHeightCalculated(); - body.verifyEscalatorCount(); - } - - Profiler.leave("Escalator.AbstractStaticRowContainer.recalculateSectionHeight"); - } - - /** - * Informs the row container that the height of its respective table - * section has changed. - * <p> - * These calculations might affect some layouting logic, such as the - * body is being offset by the footer, the footer needs to be readjusted - * according to its height, and so on. - * <p> - * A table section is either header, body or footer. - */ - protected abstract void sectionHeightCalculated(); - } - - private class HeaderRowContainer extends AbstractStaticRowContainer { - public HeaderRowContainer(final Element headElement) { - super(headElement); - } - - @Override - protected void sectionHeightCalculated() { - bodyElem.getStyle().setMarginTop(heightOfSection, Unit.PX); - verticalScrollbar.getElement().getStyle() - .setTop(heightOfSection, Unit.PX); - } - - @Override - protected String getCellElementTagName() { - return "th"; - } - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - UIObject.setStylePrimaryName(root, primaryStyleName + "-header"); - } - } - - private class FooterRowContainer extends AbstractStaticRowContainer { - public FooterRowContainer(final Element footElement) { - super(footElement); - } - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - UIObject.setStylePrimaryName(root, primaryStyleName + "-footer"); - } - - @Override - protected String getCellElementTagName() { - return "td"; - } - - @Override - protected void sectionHeightCalculated() { - int vscrollHeight = (int) Math.floor(heightOfEscalator - - header.heightOfSection - footer.heightOfSection); - - final boolean horizontalScrollbarNeeded = columnConfiguration - .calculateRowWidth() > widthOfEscalator; - if (horizontalScrollbarNeeded) { - vscrollHeight -= horizontalScrollbar.getScrollbarThickness(); - } - - verticalScrollbar.setOffsetSize(vscrollHeight); - } - } - - private class BodyRowContainer extends AbstractRowContainer { - /* - * TODO [[optimize]]: check whether a native JsArray might be faster - * than LinkedList - */ - /** - * The order in which row elements are rendered visually in the browser, - * with the help of CSS tricks. Usually has nothing to do with the DOM - * order. - */ - private final LinkedList<Element> visualRowOrder = new LinkedList<Element>(); - - /** - * The logical index of the topmost row. - * - * @deprecated Use the accessors {@link #setTopRowLogicalIndex(int)}, - * {@link #updateTopRowLogicalIndex(int)} and - * {@link #getTopRowLogicalIndex()} instead - */ - @Deprecated - private int topRowLogicalIndex = 0; - - private void setTopRowLogicalIndex(int topRowLogicalIndex) { - if (LogConfiguration.loggingIsEnabled(Level.INFO)) { - Logger.getLogger("Escalator.BodyRowContainer").fine( - "topRowLogicalIndex: " + this.topRowLogicalIndex - + " -> " + topRowLogicalIndex); - } - assert topRowLogicalIndex >= 0 : "topRowLogicalIndex became negative"; - /* - * if there's a smart way of evaluating and asserting the max index, - * this would be a nice place to put it. I haven't found out an - * effective and generic solution. - */ - - this.topRowLogicalIndex = topRowLogicalIndex; - } - - private int getTopRowLogicalIndex() { - return topRowLogicalIndex; - } - - private void updateTopRowLogicalIndex(int diff) { - setTopRowLogicalIndex(topRowLogicalIndex + diff); - } - - public BodyRowContainer(final Element bodyElement) { - super(bodyElement); - } - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - UIObject.setStylePrimaryName(root, primaryStyleName + "-body"); - } - - public void updateEscalatorRowsOnScroll() { - if (visualRowOrder.isEmpty()) { - return; - } - - boolean rowsWereMoved = false; - - final int topRowPos = getRowTop(visualRowOrder.getFirst()); - // TODO [[mpixscroll]] - final int scrollTop = tBodyScrollTop; - final int viewportOffset = topRowPos - scrollTop; - - /* - * TODO [[optimize]] this if-else can most probably be refactored - * into a neater block of code - */ - - if (viewportOffset > 0) { - // there's empty room on top - - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - int originalRowsToMove = (int) Math.ceil(viewportOffset - / (double) getDefaultRowHeight()); - int rowsToMove = Math.min(originalRowsToMove, - root.getChildCount()); - - final int end = root.getChildCount(); - final int start = end - rowsToMove; - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - final int logicalRowIndex = scrollTop / getDefaultRowHeight(); - moveAndUpdateEscalatorRows(Range.between(start, end), 0, - logicalRowIndex); - - updateTopRowLogicalIndex(-originalRowsToMove); - } - - else if (viewportOffset + getDefaultRowHeight() <= 0) { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - - /* - * the viewport has been scrolled more than the topmost visual - * row. - */ - - /* - * Using the fact that integer division has implicit - * floor-function to our advantage here. - */ - int originalRowsToMove = Math.abs(viewportOffset - / getDefaultRowHeight()); - int rowsToMove = Math.min(originalRowsToMove, - root.getChildCount()); - - int logicalRowIndex; - if (rowsToMove < root.getChildCount()) { - /* - * We scroll so little that we can just keep adding the rows - * below the current escalator - */ - logicalRowIndex = getLogicalRowIndex(visualRowOrder - .getLast()) + 1; - } else { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - /* - * Since we're moving all escalator rows, we need to - * calculate the first logical row index from the scroll - * position. - */ - logicalRowIndex = scrollTop / getDefaultRowHeight(); - } - - /* - * Since we're moving the viewport downwards, the visual index - * is always at the bottom. Note: Due to how - * moveAndUpdateEscalatorRows works, this will work out even if - * we move all the rows, and try to place them "at the end". - */ - final int targetVisualIndex = root.getChildCount(); - - // make sure that we don't move rows over the data boundary - boolean aRowWasLeftBehind = false; - if (logicalRowIndex + rowsToMove > getRowCount()) { - /* - * TODO [[rowheight]]: with constant row heights, there's - * always exactly one row that will be moved beyond the data - * source, when viewport is scrolled to the end. This, - * however, isn't guaranteed anymore once row heights start - * varying. - */ - rowsToMove--; - aRowWasLeftBehind = true; - } - - moveAndUpdateEscalatorRows(Range.between(0, rowsToMove), - targetVisualIndex, logicalRowIndex); - - if (aRowWasLeftBehind) { - /* - * To keep visualRowOrder as a spatially contiguous block of - * rows, let's make sure that the one row we didn't move - * visually still stays with the pack. - */ - final Range strayRow = Range.withOnly(0); - - /* - * We cannot trust getLogicalRowIndex, because it hasn't yet - * been updated. But since we're leaving rows behind, it - * means we've scrolled to the bottom. So, instead, we - * simply count backwards from the end. - */ - final int topLogicalIndex = getRowCount() - - visualRowOrder.size(); - moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex); - } - - final int naiveNewLogicalIndex = getTopRowLogicalIndex() - + originalRowsToMove; - final int maxLogicalIndex = getRowCount() - - visualRowOrder.size(); - setTopRowLogicalIndex(Math.min(naiveNewLogicalIndex, - maxLogicalIndex)); - } - - if (rowsWereMoved) { - fireRowVisibilityChangeEvent(); - } - } - - @Override - protected List<Element> paintInsertRows(final int index, - final int numberOfRows) { - if (numberOfRows == 0) { - return Collections.emptyList(); - } - - /* - * TODO: this method should probably only add physical rows, and not - * populate them - let everything be populated as appropriate by the - * logic that follows. - * - * This also would lead to the fact that paintInsertRows wouldn't - * need to return anything. - */ - final List<Element> addedRows = fillAndPopulateEscalatorRowsIfNeeded( - index, numberOfRows); - - /* - * insertRows will always change the number of rows - update the - * scrollbar sizes. - */ - scroller.recalculateScrollbarsForVirtualViewport(); - - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ - final boolean addedRowsAboveCurrentViewport = index - * getDefaultRowHeight() < getScrollTop(); - final boolean addedRowsBelowCurrentViewport = index - * getDefaultRowHeight() > getScrollTop() - + calculateHeight(); - - if (addedRowsAboveCurrentViewport) { - /* - * We need to tweak the virtual viewport (scroll handle - * positions, table "scroll position" and row locations), but - * without re-evaluating any rows. - */ - - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - final int yDelta = numberOfRows * getDefaultRowHeight(); - adjustScrollPosIgnoreEvents(yDelta); - updateTopRowLogicalIndex(numberOfRows); - } - - else if (addedRowsBelowCurrentViewport) { - // NOOP, we already recalculated scrollbars. - } - - else { // some rows were added inside the current viewport - - final int unupdatedLogicalStart = index + addedRows.size(); - final int visualOffset = getLogicalRowIndex(visualRowOrder - .getFirst()); - - /* - * At this point, we have added new escalator rows, if so - * needed. - * - * If more rows were added than the new escalator rows can - * account for, we need to start to spin the escalator to update - * the remaining rows aswell. - */ - final int rowsStillNeeded = numberOfRows - addedRows.size(); - final Range unupdatedVisual = convertToVisual(Range.withLength( - unupdatedLogicalStart, rowsStillNeeded)); - final int end = root.getChildCount(); - final int start = end - unupdatedVisual.length(); - final int visualTargetIndex = unupdatedLogicalStart - - visualOffset; - moveAndUpdateEscalatorRows(Range.between(start, end), - visualTargetIndex, unupdatedLogicalStart); - - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - // move the surrounding rows to their correct places. - int rowTop = (unupdatedLogicalStart + (end - start)) - * getDefaultRowHeight(); - final ListIterator<Element> i = visualRowOrder - .listIterator(visualTargetIndex + (end - start)); - while (i.hasNext()) { - final Element tr = i.next(); - setRowPosition(tr, 0, rowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - rowTop += getDefaultRowHeight(); - } - - fireRowVisibilityChangeEvent(); - } - return addedRows; - } - - /** - * Move escalator rows around, and make sure everything gets - * appropriately repositioned and repainted. - * - * @param visualSourceRange - * the range of rows to move to a new place - * @param visualTargetIndex - * the visual index where the rows will be placed to - * @param logicalTargetIndex - * the logical index to be assigned to the first moved row - * @throws IllegalArgumentException - * if any of <code>visualSourceRange.getStart()</code>, - * <code>visualTargetIndex</code> or - * <code>logicalTargetIndex</code> is a negative number; or - * if <code>visualTargetInfo</code> is greater than the - * number of escalator rows. - */ - private void moveAndUpdateEscalatorRows(final Range visualSourceRange, - final int visualTargetIndex, final int logicalTargetIndex) - throws IllegalArgumentException { - - if (visualSourceRange.isEmpty()) { - return; - } - - if (visualSourceRange.getStart() < 0) { - throw new IllegalArgumentException( - "Logical source start must be 0 or greater (was " - + visualSourceRange.getStart() + ")"); - } else if (logicalTargetIndex < 0) { - throw new IllegalArgumentException( - "Logical target must be 0 or greater"); - } else if (visualTargetIndex < 0) { - throw new IllegalArgumentException( - "Visual target must be 0 or greater"); - } else if (visualTargetIndex > root.getChildCount()) { - throw new IllegalArgumentException( - "Visual target must not be greater than the number of escalator rows"); - } else if (logicalTargetIndex + visualSourceRange.length() > getRowCount()) { - final int logicalEndIndex = logicalTargetIndex - + visualSourceRange.length() - 1; - throw new IllegalArgumentException( - "Logical target leads to rows outside of the data range (" - + logicalTargetIndex + ".." + logicalEndIndex - + ")"); - } - - /* - * Since we move a range into another range, the indices might move - * about. Having 10 rows, if we move 0..1 to index 10 (to the end of - * the collection), the target range will end up being 8..9, instead - * of 10..11. - * - * This applies only if we move elements forward in the collection, - * not backward. - */ - final int adjustedVisualTargetIndex; - if (visualSourceRange.getStart() < visualTargetIndex) { - adjustedVisualTargetIndex = visualTargetIndex - - visualSourceRange.length(); - } else { - adjustedVisualTargetIndex = visualTargetIndex; - } - - if (visualSourceRange.getStart() != adjustedVisualTargetIndex) { - - /* - * Reorder the rows to their correct places within - * visualRowOrder (unless rows are moved back to their original - * places) - */ - - /* - * TODO [[optimize]]: move whichever set is smaller: the ones - * explicitly moved, or the others. So, with 10 escalator rows, - * if we are asked to move idx[0..8] to the end of the list, - * it's faster to just move idx[9] to the beginning. - */ - - final List<Element> removedRows = new ArrayList<Element>( - visualSourceRange.length()); - for (int i = 0; i < visualSourceRange.length(); i++) { - final Element tr = visualRowOrder.remove(visualSourceRange - .getStart()); - removedRows.add(tr); - } - visualRowOrder.addAll(adjustedVisualTargetIndex, removedRows); - } - - { // Refresh the contents of the affected rows - final ListIterator<Element> iter = visualRowOrder - .listIterator(adjustedVisualTargetIndex); - for (int logicalIndex = logicalTargetIndex; logicalIndex < logicalTargetIndex - + visualSourceRange.length(); logicalIndex++) { - final Element tr = iter.next(); - refreshRow(tr, logicalIndex); - } - } - - { // Reposition the rows that were moved - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - int newRowTop = logicalTargetIndex * getDefaultRowHeight(); - - final ListIterator<Element> iter = visualRowOrder - .listIterator(adjustedVisualTargetIndex); - for (int i = 0; i < visualSourceRange.length(); i++) { - final Element tr = iter.next(); - setRowPosition(tr, 0, newRowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - newRowTop += getDefaultRowHeight(); - } - } - } - - /** - * Adjust the scroll position without having the scroll handler have any - * side-effects. - * <p> - * <em>Note:</em> {@link Scroller#onScroll(double, double)} - * <em>will</em> be triggered, but it will not do anything, with the - * help of {@link Escalator#internalScrollEventCalls}. - * - * @param yDelta - * the delta of pixels to scrolls. A positive value moves the - * viewport downwards, while a negative value moves the - * viewport upwards - */ - public void adjustScrollPosIgnoreEvents(final int yDelta) { - if (yDelta == 0) { - return; - } - - internalScrollEventCalls++; - verticalScrollbar.setScrollPosByDelta(yDelta); - - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ - final int rowTopPos = yDelta - yDelta % getDefaultRowHeight(); - for (final Element tr : visualRowOrder) { - setRowPosition(tr, 0, getRowTop(tr) + rowTopPos); - } - setBodyScrollPosition(tBodyScrollLeft, tBodyScrollTop + yDelta); - } - - /** - * Adds new physical escalator rows to the DOM at the given index if - * there's still a need for more escalator rows. - * <p> - * If Escalator already is at (or beyond) max capacity, this method does - * nothing to the DOM. - * - * @param index - * the index at which to add new escalator rows. - * <em>Note:</em>It is assumed that the index is both the - * visual index and the logical index. - * @param numberOfRows - * the number of rows to add at <code>index</code> - * @return a list of the added rows - */ - private List<Element> fillAndPopulateEscalatorRowsIfNeeded( - final int index, final int numberOfRows) { - - final int escalatorRowsStillFit = getMaxEscalatorRowCapacity() - - root.getChildCount(); - final int escalatorRowsNeeded = Math.min(numberOfRows, - escalatorRowsStillFit); - - if (escalatorRowsNeeded > 0) { - - final List<Element> addedRows = super.paintInsertRows(index, - escalatorRowsNeeded); - visualRowOrder.addAll(index, addedRows); - - /* - * We need to figure out the top positions for the rows we just - * added. - */ - for (int i = 0; i < addedRows.size(); i++) { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - setRowPosition(addedRows.get(i), 0, (index + i) - * getDefaultRowHeight()); - } - - /* Move the other rows away from above the added escalator rows */ - for (int i = index + addedRows.size(); i < visualRowOrder - .size(); i++) { - final Element tr = visualRowOrder.get(i); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - setRowPosition(tr, 0, i * getDefaultRowHeight()); - } - - return addedRows; - } else { - return new ArrayList<Element>(); - } - } - - private int getMaxEscalatorRowCapacity() { - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ - final int maxEscalatorRowCapacity = (int) Math - .ceil(calculateHeight() / getDefaultRowHeight()) + 1; - - /* - * maxEscalatorRowCapacity can become negative if the headers and - * footers start to overlap. This is a crazy situation, but Vaadin - * blinks the components a lot, so it's feasible. - */ - return Math.max(0, maxEscalatorRowCapacity); - } - - @Override - protected void paintRemoveRows(final int index, final int numberOfRows) { - - final Range viewportRange = Range.withLength( - getLogicalRowIndex(visualRowOrder.getFirst()), - visualRowOrder.size()); - - final Range removedRowsRange = Range - .withLength(index, numberOfRows); - - final Range[] partitions = removedRowsRange - .partitionWith(viewportRange); - final Range removedAbove = partitions[0]; - final Range removedLogicalInside = partitions[1]; - final Range removedVisualInside = convertToVisual(removedLogicalInside); - - /* - * TODO: extract the following if-block to a separate method. I'll - * leave this be inlined for now, to make linediff-based code - * reviewing easier. Probably will be moved in the following patch - * set. - */ - - /* - * Adjust scroll position in one of two scenarios: - * - * 1) Rows were removed above. Then we just need to adjust the - * scrollbar by the height of the removed rows. - * - * 2) There are no logical rows above, and at least the first (if - * not more) visual row is removed. Then we need to snap the scroll - * position to the first visible row (i.e. reset scroll position to - * absolute 0) - * - * The logic is optimized in such a way that the - * adjustScrollPosIgnoreEvents is called only once, to avoid extra - * reflows, and thus the code might seem a bit obscure. - */ - final boolean firstVisualRowIsRemoved = !removedVisualInside - .isEmpty() && removedVisualInside.getStart() == 0; - - if (!removedAbove.isEmpty() || firstVisualRowIsRemoved) { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - final int yDelta = removedAbove.length() - * getDefaultRowHeight(); - final int firstLogicalRowHeight = getDefaultRowHeight(); - final boolean removalScrollsToShowFirstLogicalRow = verticalScrollbar - .getScrollPos() - yDelta < firstLogicalRowHeight; - - if (removedVisualInside.isEmpty() - && (!removalScrollsToShowFirstLogicalRow || !firstVisualRowIsRemoved)) { - /* - * rows were removed from above the viewport, so all we need - * to do is to adjust the scroll position to account for the - * removed rows - */ - adjustScrollPosIgnoreEvents(-yDelta); - } else if (removalScrollsToShowFirstLogicalRow) { - /* - * It seems like we've removed all rows from above, and also - * into the current viewport. This means we'll need to even - * out the scroll position to exactly 0 (i.e. adjust by the - * current negative scrolltop, presto!), so that it isn't - * aligned funnily - */ - adjustScrollPosIgnoreEvents(-verticalScrollbar - .getScrollPos()); - } - } - - // ranges evaluated, let's do things. - if (!removedVisualInside.isEmpty()) { - int escalatorRowCount = bodyElem.getChildCount(); - - /* - * If we're left with less rows than the number of escalators, - * remove the unused ones. - */ - final int escalatorRowsToRemove = escalatorRowCount - - getRowCount(); - if (escalatorRowsToRemove > 0) { - for (int i = 0; i < escalatorRowsToRemove; i++) { - final Element tr = visualRowOrder - .remove(removedVisualInside.getStart()); - for (int c = 0; c < tr.getChildCount(); c++) { - detachPossibleWidgetFromCell((Element) tr.getChild( - c).cast()); - } - tr.removeFromParent(); - removeRowPosition(tr); - } - escalatorRowCount -= escalatorRowsToRemove; - - /* - * Because we're removing escalator rows, we don't have - * anything to scroll by. Let's make sure the viewport is - * scrolled to top, to render any rows possibly left above. - */ - body.setBodyScrollPosition(tBodyScrollLeft, 0); - - /* - * We might have removed some rows from the middle, so let's - * make sure we're not left with any holes. Also remember: - * visualIndex == logicalIndex applies now. - */ - final int dirtyRowsStart = removedLogicalInside.getStart(); - for (int i = dirtyRowsStart; i < escalatorRowCount; i++) { - final Element tr = visualRowOrder.get(i); - /* - * FIXME [[rowheight]]: coded to work only with default - * row heights - will not work with variable row heights - */ - setRowPosition(tr, 0, i * getDefaultRowHeight()); - } - - /* - * this is how many rows appeared into the viewport from - * below - */ - final int rowsToUpdateDataOn = numberOfRows - - escalatorRowsToRemove; - final int start = Math.max(0, escalatorRowCount - - rowsToUpdateDataOn); - final int end = escalatorRowCount; - for (int i = start; i < end; i++) { - final Element tr = visualRowOrder.get(i); - refreshRow(tr, i); - } - } - - else { - // No escalator rows need to be removed. - - /* - * Two things (or a combination thereof) can happen: - * - * 1) We're scrolled to the bottom, the last rows are - * removed. SOLUTION: moveAndUpdateEscalatorRows the - * bottommost rows, and place them at the top to be - * refreshed. - * - * 2) We're scrolled somewhere in the middle, arbitrary rows - * are removed. SOLUTION: moveAndUpdateEscalatorRows the - * removed rows, and place them at the bottom to be - * refreshed. - * - * Since a combination can also happen, we need to handle - * this in a smart way, all while avoiding - * double-refreshing. - */ - - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - final int contentBottom = getRowCount() - * getDefaultRowHeight(); - final int viewportBottom = (int) (tBodyScrollTop + calculateHeight()); - if (viewportBottom <= contentBottom) { - /* - * We're in the middle of the row container, everything - * is added to the bottom - */ - paintRemoveRowsAtMiddle(removedLogicalInside, - removedVisualInside, 0); - } - - else if (contentBottom - + (numberOfRows * getDefaultRowHeight()) - - viewportBottom < getDefaultRowHeight()) { - /* - * FIXME [[rowheight]]: above coded to work only with - * default row heights - will not work with variable row - * heights - */ - - /* - * We're at the end of the row container, everything is - * added to the top. - */ - paintRemoveRowsAtBottom(removedLogicalInside, - removedVisualInside); - updateTopRowLogicalIndex(-removedLogicalInside.length()); - } - - else { - /* - * We're in a combination, where we need to both scroll - * up AND show new rows at the bottom. - * - * Example: Scrolled down to show the second to last - * row. Remove two. Viewport scrolls up, revealing the - * row above row. The last element collapses up and into - * view. - * - * Reminder: this use case handles only the case when - * there are enough escalator rows to still render a - * full view. I.e. all escalator rows will _always_ be - * populated - */ - /*- - * 1 1 |1| <- newly rendered - * |2| |2| |2| - * |3| ==> |*| ==> |5| <- newly rendered - * |4| |*| - * 5 5 - * - * 1 1 |1| <- newly rendered - * |2| |*| |4| - * |3| ==> |*| ==> |5| <- newly rendered - * |4| |4| - * 5 5 - */ - - /* - * STEP 1: - * - * reorganize deprecated escalator rows to bottom, but - * don't re-render anything yet - */ - /*- - * 1 1 1 - * |2| |*| |4| - * |3| ==> |*| ==> |*| - * |4| |4| |*| - * 5 5 5 - */ - int newTop = getRowTop(visualRowOrder - .get(removedVisualInside.getStart())); - for (int i = 0; i < removedVisualInside.length(); i++) { - final Element tr = visualRowOrder - .remove(removedVisualInside.getStart()); - visualRowOrder.addLast(tr); - } - - for (int i = removedVisualInside.getStart(); i < escalatorRowCount; i++) { - final Element tr = visualRowOrder.get(i); - setRowPosition(tr, 0, newTop); - - /* - * FIXME [[rowheight]]: coded to work only with - * default row heights - will not work with variable - * row heights - */ - newTop += getDefaultRowHeight(); - } - - /* - * STEP 2: - * - * manually scroll - */ - /*- - * 1 |1| <-- newly rendered (by scrolling) - * |4| |4| - * |*| ==> |*| - * |*| - * 5 5 - */ - final double newScrollTop = contentBottom - - calculateHeight(); - setScrollTop(newScrollTop); - /* - * Manually call the scroll handler, so we get immediate - * effects in the escalator. - */ - scroller.onScroll(); - internalScrollEventCalls++; - - /* - * Move the bottommost (n+1:th) escalator row to top, - * because scrolling up doesn't handle that for us - * automatically - */ - moveAndUpdateEscalatorRows( - Range.withOnly(escalatorRowCount - 1), - 0, - getLogicalRowIndex(visualRowOrder.getFirst()) - 1); - updateTopRowLogicalIndex(-1); - - /* - * STEP 3: - * - * update remaining escalator rows - */ - /*- - * |1| |1| - * |4| ==> |4| - * |*| |5| <-- newly rendered - * - * 5 - */ - - /* - * FIXME [[rowheight]]: coded to work only with default - * row heights - will not work with variable row heights - */ - final int rowsScrolled = (int) (Math - .ceil((viewportBottom - (double) contentBottom) - / getDefaultRowHeight())); - final int start = escalatorRowCount - - (removedVisualInside.length() - rowsScrolled); - final Range visualRefreshRange = Range.between(start, - escalatorRowCount); - final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder - .getFirst()) + start; - // in-place move simply re-renders the rows. - moveAndUpdateEscalatorRows(visualRefreshRange, start, - logicalTargetIndex); - } - } - } - - updateTopRowLogicalIndex(-removedAbove.length()); - - /* - * this needs to be done after the escalator has been shrunk down, - * or it won't work correctly (due to setScrollTop invocation) - */ - scroller.recalculateScrollbarsForVirtualViewport(); - - fireRowVisibilityChangeEvent(); - } - - private void paintRemoveRowsAtMiddle(final Range removedLogicalInside, - final Range removedVisualInside, final int logicalOffset) { - /*- - * : : : - * |2| |2| |2| - * |3| ==> |*| ==> |4| - * |4| |4| |6| <- newly rendered - * : : : - */ - - final int escalatorRowCount = visualRowOrder.size(); - - final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder - .getLast()) - - (removedVisualInside.length() - 1) - + logicalOffset; - moveAndUpdateEscalatorRows(removedVisualInside, escalatorRowCount, - logicalTargetIndex); - - // move the surrounding rows to their correct places. - final ListIterator<Element> iterator = visualRowOrder - .listIterator(removedVisualInside.getStart()); - - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ - int rowTop = (removedLogicalInside.getStart() + logicalOffset) - * getDefaultRowHeight(); - for (int i = removedVisualInside.getStart(); i < escalatorRowCount - - removedVisualInside.length(); i++) { - final Element tr = iterator.next(); - setRowPosition(tr, 0, rowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - rowTop += getDefaultRowHeight(); - } - } - - private void paintRemoveRowsAtBottom(final Range removedLogicalInside, - final Range removedVisualInside) { - /*- - * : - * : : |4| <- newly rendered - * |5| |5| |5| - * |6| ==> |*| ==> |7| - * |7| |7| - */ - - final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder - .getFirst()) - removedVisualInside.length(); - moveAndUpdateEscalatorRows(removedVisualInside, 0, - logicalTargetIndex); - - // move the surrounding rows to their correct places. - final ListIterator<Element> iterator = visualRowOrder - .listIterator(removedVisualInside.getEnd()); - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ - int rowTop = removedLogicalInside.getStart() - * getDefaultRowHeight(); - while (iterator.hasNext()) { - final Element tr = iterator.next(); - setRowPosition(tr, 0, rowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - rowTop += getDefaultRowHeight(); - } - } - - private int getLogicalRowIndex(final Element element) { - assert element.getParentNode() == root : "The given element isn't a row element in the body"; - int internalIndex = visualRowOrder.indexOf(element); - return getTopRowLogicalIndex() + internalIndex; - } - - @Override - protected void recalculateSectionHeight() { - // NOOP for body, since it doesn't make any sense. - } - - /** - * Adjusts the row index and number to be relevant for the current - * virtual viewport. - * <p> - * It converts a logical range of rows index to the matching visual - * range, truncating the resulting range with the viewport. - * <p> - * <ul> - * <li>Escalator contains logical rows 0..100 - * <li>Current viewport showing logical rows 20..29 - * <li>convertToVisual([20..29]) → [0..9] - * <li>convertToVisual([15..24]) → [0..4] - * <li>convertToVisual([25..29]) → [5..9] - * <li>convertToVisual([26..39]) → [6..9] - * <li>convertToVisual([0..5]) → [0..-1] <em>(empty)</em> - * <li>convertToVisual([35..1]) → [0..-1] <em>(empty)</em> - * <li>convertToVisual([0..100]) → [0..9] - * </ul> - * - * @return a logical range converted to a visual range, truncated to the - * current viewport. The first visual row has the index 0. - */ - private Range convertToVisual(final Range logicalRange) { - if (logicalRange.isEmpty()) { - return logicalRange; - } else if (visualRowOrder.isEmpty()) { - // empty range - return Range.withLength(0, 0); - } - - /* - * TODO [[rowheight]]: these assumptions will be totally broken with - * variable row heights. - */ - final int maxEscalatorRows = getMaxEscalatorRowCapacity(); - final int currentTopRowIndex = getLogicalRowIndex(visualRowOrder - .getFirst()); - - final Range[] partitions = logicalRange.partitionWith(Range - .withLength(currentTopRowIndex, maxEscalatorRows)); - final Range insideRange = partitions[1]; - return insideRange.offsetBy(-currentTopRowIndex); - } - - @Override - protected String getCellElementTagName() { - return "td"; - } - - /** - * Calculates the height of the {@code <tbody>} as it should be rendered - * in the DOM. - */ - private double calculateHeight() { - final int tableHeight = tableWrapper.getOffsetHeight(); - final double footerHeight = footer.heightOfSection; - final double headerHeight = header.heightOfSection; - return tableHeight - footerHeight - headerHeight; - } - - @Override - public void refreshRows(final int index, final int numberOfRows) { - Profiler.enter("Escalator.BodyRowContainer.refreshRows"); - - final Range visualRange = convertToVisual(Range.withLength(index, - numberOfRows)); - - if (!visualRange.isEmpty()) { - final int firstLogicalRowIndex = getLogicalRowIndex(visualRowOrder - .getFirst()); - for (int rowNumber = visualRange.getStart(); rowNumber < visualRange - .getEnd(); rowNumber++) { - refreshRow(visualRowOrder.get(rowNumber), - firstLogicalRowIndex + rowNumber); - } - } - - Profiler.leave("Escalator.BodyRowContainer.refreshRows"); - } - - @Override - protected Element getTrByVisualIndex(final int index) - throws IndexOutOfBoundsException { - if (index >= 0 && index < visualRowOrder.size()) { - return visualRowOrder.get(index); - } else { - throw new IndexOutOfBoundsException("No such visual index: " - + index); - } - } - - private void setBodyScrollPosition(final int scrollLeft, - final int scrollTop) { - tBodyScrollLeft = scrollLeft; - tBodyScrollTop = scrollTop; - position.set(bodyElem, -tBodyScrollLeft, -tBodyScrollTop); - } - - /** - * Make sure that there is a correct amount of escalator rows: Add more - * if needed, or remove any superfluous ones. - * <p> - * This method should be called when e.g. the height of the Escalator - * changes. - * <p> - * <em>Note:</em> This method will make sure that the escalator rows are - * placed in the proper places. By default new rows are added below, but - * if the content is scrolled down, the rows are populated on top - * instead. - */ - public void verifyEscalatorCount() { - /* - * This method indeed has a smell very similar to paintRemoveRows - * and paintInsertRows. - * - * Unfortunately, those the code can't trivially be shared, since - * there are some slight differences in the respective - * responsibilities. The "paint" methods fake the addition and - * removal of rows, and make sure to either push existing data out - * of view, or draw new data into view. Only in some special cases - * will the DOM element count change. - * - * This method, however, has the explicit responsibility to verify - * that when "something" happens, we still have the correct amount - * of escalator rows in the DOM, and if not, we make sure to modify - * that count. Only in some special cases do we need to take into - * account other things than simply modifying the DOM element count. - */ - - Profiler.enter("Escalator.BodyRowContainer.verifyEscalatorCount"); - - if (!isAttached()) { - return; - } - - final int maxEscalatorRows = getMaxEscalatorRowCapacity(); - final int neededEscalatorRows = Math.min(maxEscalatorRows, - body.getRowCount()); - final int neededEscalatorRowsDiff = neededEscalatorRows - - visualRowOrder.size(); - - if (neededEscalatorRowsDiff > 0) { - // needs more - - /* - * This is a workaround for the issue where we might be scrolled - * to the bottom, and the widget expands beyond the content - * range - */ - - final int index = visualRowOrder.size(); - final int nextLastLogicalIndex; - if (!visualRowOrder.isEmpty()) { - nextLastLogicalIndex = getLogicalRowIndex(visualRowOrder - .getLast()) + 1; - } else { - nextLastLogicalIndex = 0; - } - - final boolean contentWillFit = nextLastLogicalIndex < getRowCount() - - neededEscalatorRowsDiff; - if (contentWillFit) { - final List<Element> addedRows = fillAndPopulateEscalatorRowsIfNeeded( - index, neededEscalatorRowsDiff); - - /* - * Since fillAndPopulateEscalatorRowsIfNeeded operates on - * the assumption that index == visual index == logical - * index, we thank for the added escalator rows, but since - * they're painted in the wrong CSS position, we need to - * move them to their actual locations. - * - * Note: this is the second (see body.paintInsertRows) - * occasion where fillAndPopulateEscalatorRowsIfNeeded would - * behave "more correctly" if it only would add escalator - * rows to the DOM and appropriate bookkeping, and not - * actually populate them :/ - */ - moveAndUpdateEscalatorRows( - Range.withLength(index, addedRows.size()), index, - nextLastLogicalIndex); - } else { - /* - * TODO [[optimize]] - * - * We're scrolled so far down that all rows can't be simply - * appended at the end, since we might start displaying - * escalator rows that don't exist. To avoid the mess that - * is body.paintRemoveRows, this is a dirty hack that dumbs - * the problem down to a more basic and already-solved - * problem: - * - * 1) scroll all the way up 2) add the missing escalator - * rows 3) scroll back to the original position. - * - * Letting the browser scroll back to our original position - * will automatically solve any possible overflow problems, - * since the browser will not allow us to scroll beyond the - * actual content. - */ - - final double oldScrollTop = getScrollTop(); - setScrollTop(0); - scroller.onScroll(); - fillAndPopulateEscalatorRowsIfNeeded(index, - neededEscalatorRowsDiff); - setScrollTop(oldScrollTop); - scroller.onScroll(); - internalScrollEventCalls++; - } - } - - else if (neededEscalatorRowsDiff < 0) { - // needs less - - final ListIterator<Element> iter = visualRowOrder - .listIterator(visualRowOrder.size()); - for (int i = 0; i < -neededEscalatorRowsDiff; i++) { - final Element last = iter.previous(); - for (int c = 0; c < last.getChildCount(); c++) { - detachPossibleWidgetFromCell((Element) last.getChild(c) - .cast()); - } - last.removeFromParent(); - iter.remove(); - } - - /* - * If we were scrolled to the bottom so that we didn't have an - * extra escalator row at the bottom, we'll probably end up with - * blank space at the bottom of the escalator, and one extra row - * above the header. - * - * Experimentation idea #1: calculate "scrollbottom" vs content - * bottom and remove one row from top, rest from bottom. This - * FAILED, since setHeight has already happened, thus we never - * will detect ourselves having been scrolled all the way to the - * bottom. - */ - - if (!visualRowOrder.isEmpty()) { - final int firstRowTop = getRowTop(visualRowOrder.getFirst()); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - final double firstRowMinTop = tBodyScrollTop - - getDefaultRowHeight(); - if (firstRowTop < firstRowMinTop) { - final int newLogicalIndex = getLogicalRowIndex(visualRowOrder - .getLast()) + 1; - moveAndUpdateEscalatorRows(Range.withOnly(0), - visualRowOrder.size(), newLogicalIndex); - } - } - } - - if (neededEscalatorRowsDiff != 0) { - fireRowVisibilityChangeEvent(); - } - - Profiler.leave("Escalator.BodyRowContainer.verifyEscalatorCount"); - } - - @Override - protected void reapplyDefaultRowHeights() { - if (visualRowOrder.isEmpty()) { - return; - } - - /* - * As an intermediate step between hard-coded row heights to crazily - * varying row heights, Escalator will support the modification of - * the default row height (which is applied to all rows). - * - * This allows us to do some assumptions and simplifications for - * now. This code is intended to be quite short-lived, but gives - * insight into what needs to be done when row heights change in the - * body, in a general sense. - * - * TODO [[rowheight]] remove this comment once row heights may - * genuinely vary. - */ - - Profiler.enter("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); - - /* step 1: resize and reposition rows */ - for (int i = 0; i < visualRowOrder.size(); i++) { - Element tr = visualRowOrder.get(i); - reapplyRowHeight(tr, getDefaultRowHeight()); - - final int logicalIndex = getTopRowLogicalIndex() + i; - setRowPosition(tr, 0, logicalIndex * getDefaultRowHeight()); - } - - /* - * step 2: move scrollbar so that it corresponds to its previous - * place - */ - - /* - * This ratio needs to be calculated with the scrollsize (not max - * scroll position) in order to align the top row with the new - * scroll position. - */ - double scrollRatio = (double) verticalScrollbar.getScrollPos() - / (double) verticalScrollbar.getScrollSize(); - scroller.recalculateScrollbarsForVirtualViewport(); - internalScrollEventCalls++; - verticalScrollbar.setScrollPos((int) (getDefaultRowHeight() - * getRowCount() * scrollRatio)); - setBodyScrollPosition(horizontalScrollbar.getScrollPos(), - verticalScrollbar.getScrollPos()); - scroller.onScroll(); - - /* step 3: make sure we have the correct amount of escalator rows. */ - verifyEscalatorCount(); - - /* - * TODO [[rowheight]] This simply doesn't work with variable rows - * heights. - */ - setTopRowLogicalIndex(getRowTop(visualRowOrder.getFirst()) - / getDefaultRowHeight()); - - Profiler.leave("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); - } - } - - private class ColumnConfigurationImpl implements ColumnConfiguration { - public class Column { - private static final int DEFAULT_COLUMN_WIDTH_PX = 100; - - private int definedWidth = -1; - private int calculatedWidth = DEFAULT_COLUMN_WIDTH_PX; - - public void setWidth(int px) { - definedWidth = px; - calculatedWidth = (px >= 0) ? px : DEFAULT_COLUMN_WIDTH_PX; - } - - public int getDefinedWidth() { - return definedWidth; - } - - public int getCalculatedWidth() { - return calculatedWidth; - } - } - - private final List<Column> columns = new ArrayList<Column>(); - private int frozenColumns = 0; - - /** - * A cached array of all the calculated column widths. - * - * @see #getCalculatedColumnWidths() - */ - private int[] widthsArray = null; - - /** - * {@inheritDoc} - * <p> - * <em>Implementation detail:</em> This method does no DOM modifications - * (i.e. is very cheap to call) if there are no rows in the DOM when - * this method is called. - * - * @see #hasSomethingInDom() - */ - @Override - public void removeColumns(final int index, final int numberOfColumns) { - assertArgumentsAreValidAndWithinRange(index, numberOfColumns); - - flyweightRow.removeCells(index, numberOfColumns); - - // Cope with removing frozen columns - if (index < frozenColumns) { - if (index + numberOfColumns < frozenColumns) { - /* - * Last removed column was frozen, meaning that all removed - * columns were frozen. Just decrement the number of frozen - * columns accordingly. - */ - frozenColumns -= numberOfColumns; - } else { - /* - * If last removed column was not frozen, we have removed - * columns beyond the frozen range, so all remaining frozen - * columns are to the left of the removed columns. - */ - frozenColumns = index; - } - } - - List<Column> removedColumns = new ArrayList<Column>(); - for (int i = 0; i < numberOfColumns; i++) { - removedColumns.add(columns.remove(index)); - } - - if (hasSomethingInDom()) { - for (final AbstractRowContainer rowContainer : rowContainers) { - rowContainer.paintRemoveColumns(index, numberOfColumns, - removedColumns); - } - } - } - - /** - * Calculate the width of a row, as the sum of columns' widths. - * - * @return the width of a row, in pixels - */ - public int calculateRowWidth() { - return getCalculatedColumnsWidth(Range.between(0, getColumnCount())); - } - - private void assertArgumentsAreValidAndWithinRange(final int index, - final int numberOfColumns) { - if (numberOfColumns < 1) { - throw new IllegalArgumentException( - "Number of columns can't be less than 1 (was " - + numberOfColumns + ")"); - } - - if (index < 0 || index + numberOfColumns > getColumnCount()) { - throw new IndexOutOfBoundsException("The given " - + "column range (" + index + ".." - + (index + numberOfColumns) - + ") was outside of the current " - + "number of columns (" + getColumnCount() + ")"); - } - } - - /** - * {@inheritDoc} - * <p> - * <em>Implementation detail:</em> This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for rows when this - * method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - public void insertColumns(final int index, final int numberOfColumns) { - if (index < 0 || index > getColumnCount()) { - throw new IndexOutOfBoundsException("The given index(" + index - + ") was outside of the current number of columns (0.." - + getColumnCount() + ")"); - } - - if (numberOfColumns < 1) { - throw new IllegalArgumentException( - "Number of columns must be 1 or greater (was " - + numberOfColumns); - } - - flyweightRow.addCells(index, numberOfColumns); - - for (int i = 0; i < numberOfColumns; i++) { - columns.add(index, new Column()); - } - - // Either all or none of the new columns are frozen - boolean frozen = index < frozenColumns; - if (frozen) { - frozenColumns += numberOfColumns; - } - - if (hasColumnAndRowData()) { - for (final AbstractRowContainer rowContainer : rowContainers) { - rowContainer.paintInsertColumns(index, numberOfColumns, - frozen); - } - } - } - - @Override - public int getColumnCount() { - return columns.size(); - } - - @Override - public void setFrozenColumnCount(int count) - throws IllegalArgumentException { - if (count < 0 || count > getColumnCount()) { - throw new IllegalArgumentException( - "count must be between 0 and the current number of columns (" - + columns + ")"); - } - int oldCount = frozenColumns; - if (count == oldCount) { - return; - } - - frozenColumns = count; - - if (hasSomethingInDom()) { - // Are we freezing or unfreezing? - boolean frozen = count > oldCount; - - int firstAffectedCol; - int firstUnaffectedCol; - - if (frozen) { - firstAffectedCol = oldCount; - firstUnaffectedCol = count; - } else { - firstAffectedCol = count; - firstUnaffectedCol = oldCount; - } - - for (int col = firstAffectedCol; col < firstUnaffectedCol; col++) { - header.setColumnFrozen(col, frozen); - body.setColumnFrozen(col, frozen); - footer.setColumnFrozen(col, frozen); - } - } - - scroller.recalculateScrollbarsForVirtualViewport(); - } - - @Override - public int getFrozenColumnCount() { - return frozenColumns; - } - - @Override - public void setColumnWidth(int index, int px) - throws IllegalArgumentException { - checkValidColumnIndex(index); - - columns.get(index).setWidth(px); - widthsArray = null; - - /* - * TODO [[optimize]]: only modify the elements that are actually - * modified. - */ - header.reapplyColumnWidths(); - body.reapplyColumnWidths(); - footer.reapplyColumnWidths(); - recalculateElementSizes(); - } - - private void checkValidColumnIndex(int index) - throws IllegalArgumentException { - if (!Range.withLength(0, getColumnCount()).contains(index)) { - throw new IllegalArgumentException("The given column index (" - + index + ") does not exist"); - } - } - - @Override - public int getColumnWidth(int index) throws IllegalArgumentException { - checkValidColumnIndex(index); - return columns.get(index).getDefinedWidth(); - } - - @Override - public int getColumnWidthActual(int index) { - return columns.get(index).getCalculatedWidth(); - } - - /** - * Calculates the width of the columns in a given range. - * - * @param columns - * the columns to calculate - * @return the total width of the columns in the given - * <code>columns</code> - */ - int getCalculatedColumnsWidth(@SuppressWarnings("hiding") - final Range columns) { - /* - * This is an assert instead of an exception, since this is an - * internal method. - */ - assert columns.isSubsetOf(Range.between(0, getColumnCount())) : "Range " - + "was outside of current column range (i.e.: " - + Range.between(0, getColumnCount()) - + ", but was given :" - + columns; - - int sum = 0; - for (int i = columns.getStart(); i < columns.getEnd(); i++) { - sum += getColumnWidthActual(i); - } - return sum; - } - - void setCalculatedColumnWidth(int index, int width) { - columns.get(index).calculatedWidth = width; - widthsArray = null; - } - - int[] getCalculatedColumnWidths() { - if (widthsArray == null || widthsArray.length != getColumnCount()) { - widthsArray = new int[getColumnCount()]; - for (int i = 0; i < columns.size(); i++) { - widthsArray[i] = columns.get(i).getCalculatedWidth(); - } - } - return widthsArray; - } - } - - // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y - /** - * The solution to - * <code>|tan<sup>-1</sup>(<i>x</i>)|×(180/π) = 30</code> - * . - * <p> - * This constant is placed in the Escalator class, instead of an inner - * class, since even mathematical expressions aren't allowed in non-static - * inner classes for constants. - */ - private static final double RATIO_OF_30_DEGREES = 1 / Math.sqrt(3); - /** - * The solution to - * <code>|tan<sup>-1</sup>(<i>x</i>)|×(180/π) = 40</code> - * . - * <p> - * This constant is placed in the Escalator class, instead of an inner - * class, since even mathematical expressions aren't allowed in non-static - * inner classes for constants. - */ - private static final double RATIO_OF_40_DEGREES = Math.tan(2 * Math.PI / 9); - - private static final String DEFAULT_WIDTH = "400.0px"; - private static final String DEFAULT_HEIGHT = "400.0px"; - - private FlyweightRow flyweightRow = new FlyweightRow(this); - - /** The {@code <thead/>} tag. */ - private final Element headElem = DOM.createTHead(); - /** The {@code <tbody/>} tag. */ - private final Element bodyElem = DOM.createTBody(); - /** The {@code <tfoot/>} tag. */ - private final Element footElem = DOM.createTFoot(); - - /** - * TODO: investigate whether this field is now unnecessary, as - * {@link ScrollbarBundle} now caches its values. - * - * @deprecated maybe... - */ - @Deprecated - private int tBodyScrollTop = 0; - - /** - * TODO: investigate whether this field is now unnecessary, as - * {@link ScrollbarBundle} now caches its values. - * - * @deprecated maybe... - */ - @Deprecated - private int tBodyScrollLeft = 0; - - private final VerticalScrollbarBundle verticalScrollbar = new VerticalScrollbarBundle(); - private final HorizontalScrollbarBundle horizontalScrollbar = new HorizontalScrollbarBundle(); - - private final HeaderRowContainer header = new HeaderRowContainer(headElem); - private final BodyRowContainer body = new BodyRowContainer(bodyElem); - private final FooterRowContainer footer = new FooterRowContainer(footElem); - - private final Scroller scroller = new Scroller(); - - private final AbstractRowContainer[] rowContainers = new AbstractRowContainer[] { - header, body, footer }; - - private final ColumnConfigurationImpl columnConfiguration = new ColumnConfigurationImpl(); - private final Element tableWrapper; - - private PositionFunction position; - - private int internalScrollEventCalls = 0; - - /** The cached width of the escalator, in pixels. */ - private double widthOfEscalator; - /** The cached height of the escalator, in pixels. */ - private double heightOfEscalator; - - private static native double getPreciseWidth(Element element) - /*-{ - if (element.getBoundingClientRect) { - var rect = element.getBoundingClientRect(); - return rect.right - rect.left; - } else { - return element.offsetWidth; - } - }-*/; - - private static native double getPreciseHeight(Element element) - /*-{ - if (element.getBoundingClientRect) { - var rect = element.getBoundingClientRect(); - return rect.bottom - rect.top; - } else { - return element.offsetHeight; - } - }-*/; - - /** - * Creates a new Escalator widget instance. - */ - public Escalator() { - - detectAndApplyPositionFunction(); - getLogger().info( - "Using " + position.getClass().getSimpleName() - + " for position"); - - final Element root = DOM.createDiv(); - setElement(root); - - root.appendChild(verticalScrollbar.getElement()); - root.appendChild(horizontalScrollbar.getElement()); - verticalScrollbar.setScrollbarThickness(Util.getNativeScrollbarSize()); - horizontalScrollbar - .setScrollbarThickness(Util.getNativeScrollbarSize()); - - tableWrapper = DOM.createDiv(); - - root.appendChild(tableWrapper); - - final Element table = DOM.createTable(); - tableWrapper.appendChild(table); - - table.appendChild(headElem); - table.appendChild(bodyElem); - table.appendChild(footElem); - - setStylePrimaryName("v-escalator"); - - // init default dimensions - setHeight(null); - setWidth(null); - } - - @Override - protected void onLoad() { - super.onLoad(); - - header.autodetectRowHeight(); - body.autodetectRowHeight(); - footer.autodetectRowHeight(); - - header.paintInsertRows(0, header.getRowCount()); - footer.paintInsertRows(0, footer.getRowCount()); - recalculateElementSizes(); - /* - * Note: There's no need to explicitly insert rows into the body. - * - * recalculateElementSizes will recalculate the height of the body. This - * has the side-effect that as the body's size grows bigger (i.e. from 0 - * to its actual height), more escalator rows are populated. Those - * escalator rows are then immediately rendered. This, in effect, is the - * same thing as inserting those rows. - * - * In fact, having an extra paintInsertRows here would lead to duplicate - * rows. - */ - - scroller.attachScrollListener(verticalScrollbar.getElement()); - scroller.attachScrollListener(horizontalScrollbar.getElement()); - scroller.attachMousewheelListener(getElement()); - scroller.attachTouchListeners(getElement()); - } - - @Override - protected void onUnload() { - - scroller.detachScrollListener(verticalScrollbar.getElement()); - scroller.detachScrollListener(horizontalScrollbar.getElement()); - scroller.detachMousewheelListener(getElement()); - scroller.detachTouchListeners(getElement()); - - header.paintRemoveRows(0, header.getRowCount()); - footer.paintRemoveRows(0, footer.getRowCount()); - body.paintRemoveRows(0, body.getRowCount()); - - super.onUnload(); - } - - private void detectAndApplyPositionFunction() { - /* - * firefox has a bug in its translate operation, showing white space - * when adjusting the scrollbar in BodyRowContainer.paintInsertRows - */ - if (Window.Navigator.getUserAgent().contains("Firefox")) { - position = new AbsolutePosition(); - return; - } - - final Style docStyle = Document.get().getBody().getStyle(); - if (hasProperty(docStyle, "transform")) { - if (hasProperty(docStyle, "transformStyle")) { - position = new Translate3DPosition(); - } else { - position = new TranslatePosition(); - } - } else if (hasProperty(docStyle, "webkitTransform")) { - position = new WebkitTranslate3DPosition(); - } else { - position = new AbsolutePosition(); - } - } - - private Logger getLogger() { - return Logger.getLogger(getClass().getName()); - } - - private static native boolean hasProperty(Style style, String name) - /*-{ - return style[name] !== undefined; - }-*/; - - /** - * Check whether there are both columns and any row data (for either - * headers, body or footer). - * - * @return <code>true</code> iff header, body or footer has rows && there - * are columns - */ - private boolean hasColumnAndRowData() { - return (header.getRowCount() > 0 || body.getRowCount() > 0 || footer - .getRowCount() > 0) && columnConfiguration.getColumnCount() > 0; - } - - /** - * Check whether there are any cells in the DOM. - * - * @return <code>true</code> iff header, body or footer has any child - * elements - */ - private boolean hasSomethingInDom() { - return headElem.hasChildNodes() || bodyElem.hasChildNodes() - || footElem.hasChildNodes(); - } - - /** - * Returns the representation of this Escalator header. - * - * @return the header. Never <code>null</code> - */ - public RowContainer getHeader() { - return header; - } - - /** - * Returns the representation of this Escalator body. - * - * @return the body. Never <code>null</code> - */ - public RowContainer getBody() { - return body; - } - - /** - * Returns the representation of this Escalator footer. - * - * @return the footer. Never <code>null</code> - */ - public RowContainer getFooter() { - return footer; - } - - /** - * Returns the configuration object for the columns in this Escalator. - * - * @return the configuration object for the columns in this Escalator. Never - * <code>null</code> - */ - public ColumnConfiguration getColumnConfiguration() { - return columnConfiguration; - } - - /* - * TODO remove method once RequiresResize and the Vaadin layoutmanager - * listening mechanisms are implemented (https://trello.com/c/r3Kh0Kfy) - */ - @Override - public void setWidth(final String width) { - super.setWidth(width != null && !width.isEmpty() ? width - : DEFAULT_WIDTH); - recalculateElementSizes(); - } - - /* - * TODO remove method once RequiresResize and the Vaadin layoutmanager - * listening mechanisms are implemented (https://trello.com/c/r3Kh0Kfy) - */ - @Override - public void setHeight(final String height) { - final int escalatorRowsBefore = body.visualRowOrder.size(); - - super.setHeight(height != null && !height.isEmpty() ? height - : DEFAULT_HEIGHT); - recalculateElementSizes(); - - if (escalatorRowsBefore != body.visualRowOrder.size()) { - fireRowVisibilityChangeEvent(); - } - } - - /** - * Returns the vertical scroll offset. Note that this is not necessarily the - * same as the scroll top in the DOM - * - * @return the logical vertical scroll offset - */ - public double getScrollTop() { - return verticalScrollbar.getScrollPos(); - } - - /** - * Sets the vertical scroll offset. Note that this is not necessarily the - * same as the scroll top in the DOM - * - * @param scrollTop - * the number of pixels to scroll vertically - */ - public void setScrollTop(final double scrollTop) { - verticalScrollbar.setScrollPos((int) scrollTop); - } - - /** - * Returns the logical horizontal scroll offset. Note that this is not - * necessarily the same as the scroll left in the DOM. - * - * @return the logical horizontal scroll offset - */ - public int getScrollLeft() { - return horizontalScrollbar.getScrollPos(); - } - - /** - * Sets the logical horizontal scroll offset. Note that this is not - * necessarily the same as the scroll left in the DOM. - * - * @param scrollLeft - * the number of pixels to scroll horizontally - */ - public void setScrollLeft(final int scrollLeft) { - horizontalScrollbar.setScrollPos(scrollLeft); - } - - /** - * Scrolls the body horizontally so that the column at the given index is - * visible and there is at least {@code padding} pixels to the given scroll - * destination. - * - * @param columnIndex - * the index of the column to scroll to - * @param destination - * where the column should be aligned visually after scrolling - * @param padding - * the number pixels to place between the scrolled-to column and - * the viewport edge. - * @throws IndexOutOfBoundsException - * if {@code columnIndex} is not a valid index for an existing - * column - * @throws IllegalArgumentException - * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and padding is nonzero, because having a padding on a - * centered column is undefined behavior, or if the column is - * frozen - */ - public void scrollToColumn(final int columnIndex, - final ScrollDestination destination, final int padding) - throws IndexOutOfBoundsException, IllegalArgumentException { - if (destination == ScrollDestination.MIDDLE && padding != 0) { - throw new IllegalArgumentException( - "You cannot have a padding with a MIDDLE destination"); - } - verifyValidColumnIndex(columnIndex); - - if (columnIndex < columnConfiguration.frozenColumns) { - throw new IllegalArgumentException("The given column index " - + columnIndex + " is frozen."); - } - - scroller.scrollToColumn(columnIndex, destination, padding); - } - - private void verifyValidColumnIndex(final int columnIndex) - throws IndexOutOfBoundsException { - if (columnIndex < 0 - || columnIndex >= columnConfiguration.getColumnCount()) { - throw new IndexOutOfBoundsException("The given column index " - + columnIndex + " does not exist."); - } - } - - /** - * Scrolls the body vertically so that the row at the given index is visible - * and there is at least {@literal padding} pixels to the given scroll - * destination. - * - * @param rowIndex - * the index of the logical row to scroll to - * @param destination - * where the row should be aligned visually after scrolling - * @param padding - * the number pixels to place between the scrolled-to row and the - * viewport edge. - * @throws IndexOutOfBoundsException - * if {@code rowIndex} is not a valid index for an existing row - * @throws IllegalArgumentException - * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and padding is nonzero, because having a padding on a - * centered row is undefined behavior - */ - public void scrollToRow(final int rowIndex, - final ScrollDestination destination, final int padding) - throws IndexOutOfBoundsException, IllegalArgumentException { - if (destination == ScrollDestination.MIDDLE && padding != 0) { - throw new IllegalArgumentException( - "You cannot have a padding with a MIDDLE destination"); - } - verifyValidRowIndex(rowIndex); - - scroller.scrollToRow(rowIndex, destination, padding); - } - - private void verifyValidRowIndex(final int rowIndex) { - if (rowIndex < 0 || rowIndex >= body.getRowCount()) { - throw new IndexOutOfBoundsException("The given row index " - + rowIndex + " does not exist."); - } - } - - /** - * Recalculates the dimensions for all elements that require manual - * calculations. Also updates the dimension caches. - * <p> - * <em>Note:</em> This method has the <strong>side-effect</strong> - * automatically makes sure that an appropriate amount of escalator rows are - * present. So, if the body area grows, more <strong>escalator rows might be - * inserted</strong>. Conversely, if the body area shrinks, - * <strong>escalator rows might be removed</strong>. - */ - private void recalculateElementSizes() { - if (!isAttached()) { - return; - } - - Profiler.enter("Escalator.recalculateElementSizes"); - widthOfEscalator = getPreciseWidth(getElement()); - heightOfEscalator = getPreciseHeight(getElement()); - for (final AbstractRowContainer rowContainer : rowContainers) { - rowContainer.recalculateSectionHeight(); - } - - scroller.recalculateScrollbarsForVirtualViewport(); - body.verifyEscalatorCount(); - Profiler.leave("Escalator.recalculateElementSizes"); - } - - /** - * A routing method for {@link Scroller#onScroll(double, double)}. - * <p> - * This is a workaround for GWT and JSNI unable to properly handle inner - * classes, so instead we call the outer class' method, which calls the - * inner class' respective method. - * <p> - * Ideally, this method would not exist, and - * {@link Scroller#onScroll(double, double)} would be called directly. - */ - private void onScroll() { - scroller.onScroll(); - } - - /** - * Snap deltas of x and y to the major four axes (up, down, left, right) - * with a threshold of a number of degrees from those axes. - * - * @param deltaX - * the delta in the x axis - * @param deltaY - * the delta in the y axis - * @param thresholdRatio - * the threshold in ratio (0..1) between x and y for when to snap - * @return a two-element array: <code>[snappedX, snappedY]</code> - */ - private static double[] snapDeltas(final double deltaX, - final double deltaY, final double thresholdRatio) { - - final double[] array = new double[2]; - if (deltaX != 0 && deltaY != 0) { - final double aDeltaX = Math.abs(deltaX); - final double aDeltaY = Math.abs(deltaY); - final double yRatio = aDeltaY / aDeltaX; - final double xRatio = aDeltaX / aDeltaY; - - array[0] = (xRatio < thresholdRatio) ? 0 : deltaX; - array[1] = (yRatio < thresholdRatio) ? 0 : deltaY; - } else { - array[0] = deltaX; - array[1] = deltaY; - } - - return array; - } - - /** - * Adds an event handler that gets notified when the range of visible rows - * changes e.g. because of scrolling. - * - * @param rowVisibilityChangeHandler - * the event handler - * @return a handler registration for the added handler - */ - public HandlerRegistration addRowVisibilityChangeHandler( - RowVisibilityChangeHandler rowVisibilityChangeHandler) { - return addHandler(rowVisibilityChangeHandler, - RowVisibilityChangeEvent.TYPE); - } - - private void fireRowVisibilityChangeEvent() { - if (!body.visualRowOrder.isEmpty()) { - int visibleRangeStart = body.getLogicalRowIndex(body.visualRowOrder - .getFirst()); - int visibleRangeEnd = body.getLogicalRowIndex(body.visualRowOrder - .getLast()) + 1; - - int visibleRowCount = visibleRangeEnd - visibleRangeStart; - - fireEvent(new RowVisibilityChangeEvent(visibleRangeStart, - visibleRowCount)); - } else { - fireEvent(new RowVisibilityChangeEvent(0, 0)); - } - } - - /** - * Accesses the package private method Widget#setParent() - * - * @param widget - * The widget to access - * @param parent - * The parent to set - */ - static native final void setParent(Widget widget, Widget parent) - /*-{ - widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent); - }-*/; - - /** - * Returns the widget from a cell node or <code>null</code> if there is no - * widget in the cell - * - * @param cellNode - * The cell node - */ - static Widget getWidgetFromCell(Node cellNode) { - Node possibleWidgetNode = cellNode.getFirstChild(); - if (possibleWidgetNode != null - && possibleWidgetNode.getNodeType() == Node.ELEMENT_NODE) { - @SuppressWarnings("deprecation") - com.google.gwt.user.client.Element castElement = (com.google.gwt.user.client.Element) possibleWidgetNode - .cast(); - return Util.findWidget(castElement, null); - } - return null; - } - - /** - * Forces the escalator to recalculate the widths of its columns. - * <p> - * All columns that haven't been assigned an explicit width will be resized - * to fit all currently visible contents. - * - * @see ColumnConfiguration#setColumnWidth(int, int) - */ - public void calculateColumnWidths() { - boolean widthsHaveChanged = false; - for (int colIndex = 0; colIndex < columnConfiguration.getColumnCount(); colIndex++) { - if (columnConfiguration.getColumnWidth(colIndex) >= 0) { - continue; - } - - final int oldColumnWidth = columnConfiguration - .getColumnWidthActual(colIndex); - - int maxColumnWidth = 0; - maxColumnWidth = Math.max(maxColumnWidth, - header.calculateMaxColWidth(colIndex)); - maxColumnWidth = Math.max(maxColumnWidth, - body.calculateMaxColWidth(colIndex)); - maxColumnWidth = Math.max(maxColumnWidth, - footer.calculateMaxColWidth(colIndex)); - - Logger.getLogger("Escalator.calculateColumnWidths").info( - "#" + colIndex + ": " + maxColumnWidth + "px"); - - if (oldColumnWidth != maxColumnWidth) { - columnConfiguration.setCalculatedColumnWidth(colIndex, - maxColumnWidth); - widthsHaveChanged = true; - } - } - - if (widthsHaveChanged) { - header.reapplyColumnWidths(); - body.reapplyColumnWidths(); - footer.reapplyColumnWidths(); - recalculateElementSizes(); - } - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - - verticalScrollbar.setStylePrimaryName(style); - horizontalScrollbar.setStylePrimaryName(style); - - UIObject.setStylePrimaryName(tableWrapper, style + "-tablewrapper"); - - header.setStylePrimaryName(style); - body.setStylePrimaryName(style); - footer.setStylePrimaryName(style); - } -} diff --git a/client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java b/client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java deleted file mode 100644 index 283517fcc4..0000000000 --- a/client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - - -/** - * A functional interface that allows client code to define how a certain row in - * Escalator will be displayed. The contents of an escalator's header, body and - * footer are rendered by their respective updaters. - * <p> - * The updater is responsible for internally handling all remote communication, - * should the displayed data need to be fetched remotely. - * - * @since 7.2 - * @author Vaadin Ltd - * @see RowContainer#setEscalatorUpdater(EscalatorUpdater) - * @see Escalator#getHeader() - * @see Escalator#getBody() - * @see Escalator#getFooter() - */ -public interface EscalatorUpdater { - /** An {@link EscalatorUpdater} that doesn't render anything. */ - public static final EscalatorUpdater NULL = new EscalatorUpdater() { - @Override - public void updateCells(final Row row, - final Iterable<Cell> cellsToUpdate) { - // NOOP - } - }; - - /** - * Renders a row contained in a row container. - * <p> - * <em>Note:</em> If rendering of cells is deferred (e.g. because - * asynchronous data retrieval), this method is responsible for explicitly - * displaying some placeholder data (empty content is valid). Because the - * cells (and rows) in an escalator are recycled, failing to reset a cell - * will lead to invalid data being displayed in the escalator. - * <p> - * For performance reasons, the escalator will never autonomously clear any - * data in a cell. - * - * @param row - * information about the row to update. <em>Note:</em> You should - * not store nor reuse this reference - * @param cellsToUpdate - * a collection of cells which need to be updated. <em>Note:</em> - * You should neither store nor reuse the reference to the list, - * nor to the individual cells - */ - public void updateCells(Row row, Iterable<Cell> cellsToUpdate); -} diff --git a/client/src/com/vaadin/client/ui/grid/FlyweightCell.java b/client/src/com/vaadin/client/ui/grid/FlyweightCell.java deleted file mode 100644 index 296a70934b..0000000000 --- a/client/src/com/vaadin/client/ui/grid/FlyweightCell.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -import java.util.List; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.ui.IsWidget; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ui.grid.FlyweightRow.CellIterator; - -/** - * An internal implementation of the {@link Cell} interface. - * <p> - * These instances are populated into a {@link FlyweightRow} instance, and - * intended to be reused when rendering cells in an escalator. - * - * @since 7.2 - * @author Vaadin Ltd - * @see FlyweightRow#getCells() - * @see FlyweightRow#addCells(int, int) - * @see FlyweightRow#removeCells(int, int) - */ -class FlyweightCell implements Cell { - static final String COLSPAN_ATTR = "colSpan"; - - private final int column; - private final FlyweightRow row; - - private CellIterator currentIterator = null; - - private final Escalator escalator; - - public FlyweightCell(final FlyweightRow row, final int column, - Escalator escalator) { - this.row = row; - this.column = column; - this.escalator = escalator; - } - - @Override - public int getRow() { - assertSetup(); - return row.getRow(); - } - - @Override - public int getColumn() { - assertSetup(); - return column; - } - - @Override - public Element getElement() { - return (Element) row.getElement().getChild(column); - } - - void setup(final CellIterator cellIterator) { - currentIterator = cellIterator; - - final Element e = getElement(); - e.setPropertyInt(COLSPAN_ATTR, 1); - e.getStyle().setWidth(row.getColumnWidth(column), Unit.PX); - e.getStyle().clearDisplay(); - } - - /** - * Tear down the state of the Cell. - * <p> - * This is an internal check method, to prevent retrieving uninitialized - * data by calling {@link #getRow()}, {@link #getColumn()} or - * {@link #getElement()} at an improper time. - * <p> - * This should only be used with asserts (" - * <code>assert flyweightCell.teardown()</code> ") so that the code is never - * run when asserts aren't enabled. - * - * @return always <code>true</code> - * @see FlyweightRow#teardown() - */ - boolean teardown() { - currentIterator = null; - return true; - } - - /** - * Asserts that the flyweight cell has properly been set up before trying to - * access any of its data. - */ - private void assertSetup() { - assert currentIterator != null : "FlyweightCell was not properly " - + "initialized. This is either a bug in Grid/Escalator " - + "or a Cell reference has been stored and reused " - + "inappropriately."; - } - - @Override - public void setColSpan(final int numberOfCells) { - /*- - * This will default to 1 if unset, as per DOM specifications: - * http://www.w3.org/TR/html5/tabular-data.html#attributes-common-to-td-and-th-elements - */ - final int prevColSpan = getElement().getPropertyInt(COLSPAN_ATTR); - if (numberOfCells == 1 && prevColSpan == 1) { - return; - } - - getElement().setPropertyInt(COLSPAN_ATTR, numberOfCells); - adjustCellWidthForSpan(numberOfCells); - hideOrRevealAdjacentCellElements(numberOfCells, prevColSpan); - currentIterator.setSkipNext(numberOfCells - 1); - } - - private void adjustCellWidthForSpan(final int numberOfCells) { - final int cellsToTheRight = currentIterator.rawPeekNext( - numberOfCells - 1).size(); - - final int selfWidth = row.getColumnWidth(column); - int widthsOfColumnsToTheRight = 0; - for (int i = 0; i < cellsToTheRight; i++) { - widthsOfColumnsToTheRight += row.getColumnWidth(column + i + 1); - } - getElement().getStyle().setWidth(selfWidth + widthsOfColumnsToTheRight, - Unit.PX); - } - - private void hideOrRevealAdjacentCellElements(final int numberOfCells, - final int prevColSpan) { - final int affectedCellsNumber = Math.max(prevColSpan, numberOfCells); - final List<FlyweightCell> affectedCells = currentIterator - .rawPeekNext(affectedCellsNumber - 1); - if (prevColSpan < numberOfCells) { - for (int i = 0; i < affectedCells.size(); i++) { - affectedCells.get(prevColSpan + i - 1).getElement().getStyle() - .setDisplay(Display.NONE); - } - } else if (prevColSpan > numberOfCells) { - for (int i = 0; i < affectedCells.size(); i++) { - affectedCells.get(numberOfCells + i - 1).getElement() - .getStyle().clearDisplay(); - } - } - } - - @Override - public Widget getWidget() { - return Escalator.getWidgetFromCell(getElement()); - } - - @Override - public void setWidget(Widget widget) { - - Widget oldWidget = getWidget(); - - // Validate - if (oldWidget == widget) { - return; - } - - // Detach old child. - if (oldWidget != null) { - // Orphan. - Escalator.setParent(oldWidget, null); - - // Physical detach. - getElement().removeChild(oldWidget.getElement()); - } - - // Remove any previous text nodes from previous - // setInnerText/setInnerHTML - getElement().removeAllChildren(); - - // Attach new child. - if (widget != null) { - // Detach new child from old parent. - widget.removeFromParent(); - - // Physical attach. - getElement().appendChild(widget.getElement()); - - Escalator.setParent(widget, escalator); - } - } - - @Override - public void setWidget(IsWidget w) { - setWidget(Widget.asWidgetOrNull(w)); - } - -} diff --git a/client/src/com/vaadin/client/ui/grid/FlyweightRow.java b/client/src/com/vaadin/client/ui/grid/FlyweightRow.java deleted file mode 100644 index 6bfd368c6b..0000000000 --- a/client/src/com/vaadin/client/ui/grid/FlyweightRow.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; - -/** - * An internal implementation of the {@link Row} interface. - * <p> - * There is only one instance per Escalator. This is designed to be re-used when - * rendering rows. - * - * @since 7.2 - * @author Vaadin Ltd - * @see Escalator.AbstractRowContainer#refreshRow(Node, int) - */ -class FlyweightRow implements Row { - - static class CellIterator implements Iterator<Cell> { - /** A defensive copy of the cells in the current row. */ - private final ArrayList<FlyweightCell> cells; - private int cursor = 0; - private int skipNext = 0; - - public CellIterator(final Collection<FlyweightCell> cells) { - this.cells = new ArrayList<FlyweightCell>(cells); - } - - @Override - public boolean hasNext() { - return cursor + skipNext < cells.size(); - } - - @Override - public FlyweightCell next() { - // if we needed to skip some cells since the last invocation. - for (int i = 0; i < skipNext; i++) { - cells.remove(cursor); - } - skipNext = 0; - - final FlyweightCell cell = cells.get(cursor++); - cell.setup(this); - return cell; - } - - @Override - public void remove() { - throw new UnsupportedOperationException( - "Cannot remove cells via iterator"); - } - - /** - * Sets the number of cells to skip when {@link #next()} is called the - * next time. Cell hiding is also handled eagerly in this method. - * - * @param colspan - * the number of cells to skip on next invocation of - * {@link #next()} - */ - public void setSkipNext(final int colspan) { - assert colspan > 0 : "Number of cells didn't make sense: " - + colspan; - skipNext = colspan; - } - - /** - * Gets the next <code>n</code> cells in the iterator, ignoring any - * possibly spanned cells. - * - * @param n - * the number of next cells to retrieve - * @return A list of next <code>n</code> cells, or less if there aren't - * enough cells to retrieve - */ - public List<FlyweightCell> rawPeekNext(final int n) { - final int from = Math.min(cursor, cells.size()); - final int to = Math.min(cursor + n, cells.size()); - return cells.subList(from, to); - } - } - - private static final int BLANK = Integer.MIN_VALUE; - - private int row; - private Element element; - private int[] columnWidths = null; - private final Escalator escalator; - private final List<FlyweightCell> cells = new ArrayList<FlyweightCell>(); - - public FlyweightRow(final Escalator escalator) { - this.escalator = escalator; - } - - @Override - public Escalator getEscalator() { - return escalator; - } - - void setup(final Element e, final int row, int[] columnWidths) { - element = e; - this.row = row; - this.columnWidths = columnWidths; - } - - /** - * Tear down the state of the Row. - * <p> - * This is an internal check method, to prevent retrieving uninitialized - * data by calling {@link #getRow()}, {@link #getElement()} or - * {@link #getCells()} at an improper time. - * <p> - * This should only be used with asserts (" - * <code>assert flyweightRow.teardown()</code> ") so that the code is never - * run when asserts aren't enabled. - * - * @return always <code>true</code> - */ - boolean teardown() { - element = null; - row = BLANK; - columnWidths = null; - for (final FlyweightCell cell : cells) { - assert cell.teardown(); - } - return true; - } - - @Override - public int getRow() { - assertSetup(); - return row; - } - - @Override - public Element getElement() { - assertSetup(); - return element; - } - - void addCells(final int index, final int numberOfColumns) { - for (int i = 0; i < numberOfColumns; i++) { - final int col = index + i; - cells.add(col, new FlyweightCell(this, col, escalator)); - } - updateRestOfCells(index + numberOfColumns); - } - - void removeCells(final int index, final int numberOfColumns) { - for (int i = 0; i < numberOfColumns; i++) { - cells.remove(index); - } - updateRestOfCells(index); - } - - private void updateRestOfCells(final int startPos) { - // update the column number for the cells to the right - for (int col = startPos; col < cells.size(); col++) { - cells.set(col, new FlyweightCell(this, col, escalator)); - } - } - - /** - * Get flyweight cells for the client code to render. - * - * @return a list of {@link FlyweightCell FlyweightCells}. They are - * generified into {@link Cell Cells}, because Java's generics - * system isn't expressive enough. - * @see #setup(Element, int) - * @see #teardown() - */ - Iterable<Cell> getCells() { - assertSetup(); - return new Iterable<Cell>() { - @Override - public Iterator<Cell> iterator() { - return new CellIterator(cells); - } - }; - } - - /** - * Asserts that the flyweight row has properly been set up before trying to - * access any of its data. - */ - private void assertSetup() { - assert element != null && row != BLANK && columnWidths != null : "Flyweight row was not " - + "properly initialized. Make sure the setup-method is " - + "called before retrieving data. This is either a bug " - + "in Escalator, or the instance of the flyweight row " - + "has been stored and accessed."; - } - - int getColumnWidth(int column) { - assertSetup(); - return columnWidths[column]; - } -} diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java deleted file mode 100644 index 02aa194655..0000000000 --- a/client/src/com/vaadin/client/ui/grid/Grid.java +++ /dev/null @@ -1,1318 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.logging.Logger; - -import com.google.gwt.core.shared.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.HasVisibility; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.data.DataChangeHandler; -import com.vaadin.client.data.DataSource; -import com.vaadin.client.ui.grid.renderers.TextRenderer; -import com.vaadin.shared.ui.grid.GridConstants; -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. - * - * <h3>Columns</h3> - * <p> - * The {@link GridColumn} class defines the renderer used to render a cell in - * the grid. Implement {@link GridColumn#getValue(Object)} to retrieve the cell - * value from the row object and return the cell renderer to render that cell. - * </p> - * <p> - * {@link GridColumn}s contain other properties like the width of the column and - * the visiblity of the column. If you want to change a column's properties - * after it has been added to the grid you can get a column object for a - * specific column index using {@link Grid#getColumn(int)}. - * </p> - * <p> - * - * TODO Explain about headers/footers once the multiple header/footer api has - * been implemented - * - * <h3>Data sources</h3> - * <p> - * TODO Explain about what a data source is and how it should be implemented. - * </p> - * - * @param <T> - * The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.2 - * @author Vaadin Ltd - */ -public class Grid<T> extends Composite { - - /** - * Escalator used internally by grid to render the rows - */ - private Escalator escalator = GWT.create(Escalator.class); - - /** - * List of columns in the grid. Order defines the visible order. - */ - private final List<GridColumn<?, T>> columns = new ArrayList<GridColumn<?, T>>(); - - /** - * The datasource currently in use. <em>Note:</em> it is <code>null</code> - * on initialization, but not after that. - */ - private DataSource<T> dataSource; - - /** - * The column groups rows added to the grid - */ - private final List<ColumnGroupRow<T>> columnGroupRows = new ArrayList<ColumnGroupRow<T>>(); - - /** - * Are the headers for the columns visible - */ - private boolean columnHeadersVisible = true; - - /** - * Are the footers for the columns visible - */ - private boolean columnFootersVisible = false; - - /** - * The last column frozen counter from the left - */ - private GridColumn<?, T> lastFrozenColumn; - - /** - * Base class for grid columns internally used by the Grid. The user should - * use {@link GridColumn} when creating new columns. - * - * @param <C> - * the column type - * - * @param <T> - * the row type - */ - static abstract class AbstractGridColumn<C, T> implements HasVisibility { - - /** - * The grid the column is associated with - */ - private Grid<T> grid; - - /** - * Should the column be visible in the grid - */ - private boolean visible = true; - - /** - * The text displayed in the header of the column - */ - private String header; - - /** - * Text displayed in the column footer - */ - private String footer; - - /** - * Width of column in pixels - */ - private int width = 100; - - /** - * Renderer for rendering a value into the cell - */ - private Renderer<C> bodyRenderer = new Renderer<C>() { - - @Override - public void renderCell(Cell cell, C value) { - if (value instanceof Widget) { - cell.setWidget((Widget) value); - } else if (value instanceof String) { - cell.getElement().setInnerText(value.toString()); - } else { - throw new IllegalArgumentException( - "Cell value cannot be converted into a String. Please use a custom renderer to convert the value."); - } - } - }; - - /** - * Renderer for rendering the header cell value into the cell - */ - private Renderer<String> headerRenderer = new TextRenderer(); - - /** - * Renderer for rendering the footer cell value into the cell - */ - private Renderer<String> footerRenderer = new TextRenderer(); - - /** - * Constructs a new column. - */ - public AbstractGridColumn() { - - } - - /** - * Constructs a new column with a custom renderer. - * - * @param renderer - * The renderer to use for rendering the cells - */ - public AbstractGridColumn(Renderer<C> renderer) { - if (renderer == null) { - throw new IllegalArgumentException("Renderer cannot be null."); - } - this.bodyRenderer = renderer; - } - - /** - * Constructs a new column with custom renderers for rows, header and - * footer cells. - * - * @param bodyRenderer - * The renderer to use for rendering body cells - * @param headerRenderer - * The renderer to use for rendering header cells - * @param footerRenderer - * The renderer to use for rendering footer cells - */ - public AbstractGridColumn(Renderer<C> bodyRenderer, - Renderer<String> headerRenderer, Renderer<String> footerRenderer) { - this(bodyRenderer); - if (headerRenderer == null || footerRenderer == null) { - throw new IllegalArgumentException("Renderer cannot be null."); - } - - this.headerRenderer = headerRenderer; - this.footerRenderer = footerRenderer; - } - - /** - * 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 grid. Remove the column first from the grid and then add it."); - } - - this.grid = grid; - - setVisible(this.visible); - setWidth(this.width); - setHeaderCaption(this.header); - setFooterCaption(this.footer); - } - - /** - * Gets text in the header of the column. By default the header caption - * is empty. - * - * @return the text displayed in the column caption - */ - public String getHeaderCaption() { - return header; - } - - /** - * Returns the renderer used for rendering the header cells - * - * @return a renderer that renders header cells - */ - public Renderer<String> getHeaderRenderer() { - return headerRenderer; - } - - /** - * Sets the renderer that renders header cells. Should not be null. - * - * @param renderer - * The renderer to use for rendering header cells. - */ - public void setHeaderRenderer(Renderer<String> renderer) { - if (renderer == null) { - throw new IllegalArgumentException("Renderer cannot be null."); - } - headerRenderer = renderer; - if (grid != null) { - grid.refreshHeader(); - } - } - - /** - * Returns the renderer used for rendering the footer cells - * - * @return a renderer that renders footer cells - */ - public Renderer<String> getFooterRenderer() { - return footerRenderer; - } - - /** - * Sets the renderer that renders footer cells. Should not be null. - * - * @param renderer - * The renderer to use for rendering footer cells. - */ - public void setFooterRenderer(Renderer<String> renderer) { - if (renderer == null) { - throw new IllegalArgumentException("Renderer cannot be null."); - } - footerRenderer = renderer; - if (grid != null) { - grid.refreshFooter(); - } - } - - /** - * Sets the text in the header of the column. - * - * @param caption - * the text displayed in the column header - */ - public void setHeaderCaption(String caption) { - if (SharedUtil.equals(caption, header)) { - return; - } - - header = caption; - - if (grid != null) { - grid.refreshHeader(); - } - } - - /** - * Gets text in the footer of the column. By default the footer caption - * is empty. - * - * @return The text displayed in the footer of the column - */ - public String getFooterCaption() { - return footer; - } - - /** - * Sets text in the footer of the column. - * - * @param caption - * the text displayed in the footer of the column - */ - public void setFooterCaption(String caption) { - if (SharedUtil.equals(caption, footer)) { - return; - } - - footer = caption; - - if (grid != null) { - grid.refreshFooter(); - } - } - - /** - * Is the column visible. By default all columns are visible. - * - * @return <code>true</code> if the column is visible - */ - @Override - public boolean isVisible() { - return visible; - } - - /** - * Sets a column as visible in the grid. - * - * @param visible - * <code>true</code> if the column should be displayed in the - * grid - */ - @Override - public void setVisible(boolean visible) { - if (this.visible == visible) { - return; - } - - this.visible = visible; - - // Remove column - if (grid != null) { - int index = findIndexOfColumn(); - ColumnConfiguration conf = grid.escalator - .getColumnConfiguration(); - - if (visible) { - conf.insertColumns(index, 1); - } else { - conf.removeColumns(index, 1); - } - } - - } - - /** - * 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 width. 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<C> getRenderer() { - return bodyRenderer; - } - - /** - * Finds the index of this column instance - * - */ - private int findIndexOfColumn() { - return grid.findVisibleColumnIndex((GridColumn<?, T>) this); - } - - /** - * Sets the pixel width of the column. Use a negative value for the grid - * to autosize column based on content and available space - * - * @param pixels - * the width in pixels or negative for auto sizing - */ - public void setWidth(int pixels) { - this.width = pixels; - - if (grid != null && isVisible()) { - int index = findIndexOfColumn(); - ColumnConfiguration conf = grid.escalator - .getColumnConfiguration(); - conf.setColumnWidth(index, pixels); - } - } - - /** - * Returns the pixel width of the column - * - * @return pixel width of the column - */ - public int getWidth() { - if (grid == null) { - return this.width; - } else { - int index = findIndexOfColumn(); - ColumnConfiguration conf = grid.escalator - .getColumnConfiguration(); - return conf.getColumnWidth(index); - } - } - } - - /** - * Base class for header / footer escalator updater - */ - protected abstract class HeaderFooterEscalatorUpdater implements - EscalatorUpdater { - - /** - * The row container which contains the header or footer rows - */ - private RowContainer rows; - - /** - * Should the index be counted from 0-> or 0<- - */ - private boolean inverted; - - /** - * Constructs an updater for updating a header / footer - * - * @param rows - * The row container - * @param inverted - * Should index counting be inverted - */ - public HeaderFooterEscalatorUpdater(RowContainer rows, boolean inverted) { - this.rows = rows; - this.inverted = inverted; - } - - /** - * Gets the header/footer caption value - * - * @param column - * The column to get the value for. - * - * @return The value that should be rendered for the column caption - */ - public abstract String getColumnValue(GridColumn<?, T> column); - - /** - * Gets the group caption value - * - * @param group - * The group for with the caption value should be returned - * @return The value that should be rendered for the column caption - */ - public abstract String getGroupValue(ColumnGroup<T> group); - - /** - * Is the row visible in the header/footer - * - * @param row - * the row to check - * - * @return <code>true</code> if the row should be visible - */ - public abstract boolean isRowVisible(ColumnGroupRow<T> row); - - /** - * Should the first row be visible - * - * @return <code>true</code> if the first row should be visible - */ - public abstract boolean firstRowIsVisible(); - - /** - * The renderer that renders the cell - * - * @param column - * The column for which the cell should be rendered - * - * @return renderer used for rendering - */ - public abstract Renderer<String> getRenderer(GridColumn<?, T> column); - - /** - * The renderer that renders the cell for column groups - * - * @param group - * The group that should be rendered - * @return renderer used for rendering - */ - public abstract Renderer<String> getGroupRenderer(ColumnGroup<T> group); - - @Override - public void updateCells(Row row, Iterable<Cell> cellsToUpdate) { - - int rowIndex; - if (inverted) { - rowIndex = rows.getRowCount() - row.getRow() - 1; - } else { - rowIndex = row.getRow(); - } - - if (firstRowIsVisible() && rowIndex == 0) { - // column headers - for (Cell cell : cellsToUpdate) { - GridColumn<?, T> column = getColumnFromVisibleIndex(cell - .getColumn()); - if (column != null) { - getRenderer(column).renderCell(cell, - getColumnValue(column)); - } - } - - } else if (columnGroupRows.size() > 0) { - // Adjust for headers - if (firstRowIsVisible()) { - rowIndex--; - } - - // Adjust for previous invisible header rows - ColumnGroupRow<T> groupRow = null; - for (int i = 0, realIndex = 0; i < columnGroupRows.size(); i++) { - groupRow = columnGroupRows.get(i); - if (isRowVisible(groupRow)) { - if (realIndex == rowIndex) { - rowIndex = realIndex; - break; - } - realIndex++; - } - } - - assert groupRow != null; - - for (Cell cell : cellsToUpdate) { - GridColumn<?, T> column = getColumnFromVisibleIndex(cell - .getColumn()); - ColumnGroup<T> group = getGroupForColumn(groupRow, column); - Element cellElement = cell.getElement(); - - if (group != null) { - getGroupRenderer(group).renderCell(cell, - getGroupValue(group)); - cell.setColSpan(group.getColumns().size()); - } else { - // Cells are reused - cellElement.setInnerHTML(null); - cell.setColSpan(1); - } - } - } - } - } - - /** - * Creates a new instance. - */ - public Grid() { - initWidget(escalator); - - setStylePrimaryName("v-grid"); - - escalator.getHeader().setEscalatorUpdater(createHeaderUpdater()); - escalator.getBody().setEscalatorUpdater(createBodyUpdater()); - escalator.getFooter().setEscalatorUpdater(createFooterUpdater()); - - refreshHeader(); - refreshFooter(); - - escalator - .addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() { - @Override - public void onRowVisibilityChange( - RowVisibilityChangeEvent event) { - if (dataSource != null) { - dataSource.ensureAvailability( - event.getFirstVisibleRow(), - event.getVisibleRowCount()); - } - } - }); - - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - escalator.setStylePrimaryName(style); - - } - - /** - * Creates the header updater that updates the escalator header rows from - * the column and column group rows. - * - * @return the updater that updates the data in the escalator. - */ - private EscalatorUpdater createHeaderUpdater() { - return new HeaderFooterEscalatorUpdater(escalator.getHeader(), true) { - - @Override - public boolean isRowVisible(ColumnGroupRow<T> row) { - return row.isHeaderVisible(); - } - - @Override - public String getGroupValue(ColumnGroup<T> group) { - return group.getHeaderCaption(); - } - - @Override - public String getColumnValue(GridColumn<?, T> column) { - return column.getHeaderCaption(); - } - - @Override - public boolean firstRowIsVisible() { - return isColumnHeadersVisible(); - } - - @Override - public Renderer<String> getRenderer(GridColumn<?, T> column) { - return column.getHeaderRenderer(); - } - - @Override - public Renderer<String> getGroupRenderer(ColumnGroup<T> group) { - return group.getHeaderRenderer(); - } - }; - } - - private EscalatorUpdater createBodyUpdater() { - return new EscalatorUpdater() { - - @Override - public void updateCells(Row row, Iterable<Cell> cellsToUpdate) { - int rowIndex = row.getRow(); - if (dataSource == null) { - setCellsLoading(cellsToUpdate); - return; - } - - T rowData = dataSource.getRow(rowIndex); - if (rowData == null) { - setCellsLoading(cellsToUpdate); - return; - } - - for (Cell cell : cellsToUpdate) { - GridColumn column = getColumnFromVisibleIndex(cell - .getColumn()); - if (column != null) { - Object value = column.getValue(rowData); - column.getRenderer().renderCell(cell, value); - } - } - } - - private void setCellsLoading(Iterable<Cell> cellsToUpdate) { - for (Cell cell : cellsToUpdate) { - cell.getElement().setInnerText("..."); - } - } - }; - } - - /** - * Creates the footer updater that updates the escalator footer rows from - * the column and column group rows. - * - * @return the updater that updates the data in the escalator. - */ - private EscalatorUpdater createFooterUpdater() { - return new HeaderFooterEscalatorUpdater(escalator.getFooter(), false) { - - @Override - public boolean isRowVisible(ColumnGroupRow<T> row) { - return row.isFooterVisible(); - } - - @Override - public String getGroupValue(ColumnGroup<T> group) { - return group.getFooterCaption(); - } - - @Override - public String getColumnValue(GridColumn<?, T> column) { - return column.getFooterCaption(); - } - - @Override - public boolean firstRowIsVisible() { - return isColumnFootersVisible(); - } - - @Override - public Renderer<String> getRenderer(GridColumn<?, T> column) { - return column.getFooterRenderer(); - } - - @Override - public Renderer<String> getGroupRenderer(ColumnGroup<T> group) { - return group.getFooterRenderer(); - } - }; - } - - /** - * 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, - boolean firstRowIsVisible, boolean isHeader) { - - // Count needed rows - int totalRows = firstRowIsVisible ? 1 : 0; - for (ColumnGroupRow<T> row : columnGroupRows) { - if (isHeader ? row.isHeaderVisible() : row.isFooterVisible()) { - totalRows++; - } - } - - // Add or Remove rows on demand - int rowDiff = totalRows - 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()); - } - } - - /** - * Refreshes all header rows - */ - void refreshHeader() { - refreshRowContainer(escalator.getHeader(), isColumnHeadersVisible(), - true); - } - - /** - * Refreshes all footer rows - */ - void refreshFooter() { - refreshRowContainer(escalator.getFooter(), isColumnFootersVisible(), - false); - } - - /** - * Adds a column as the last column in the grid. - * - * @param column - * the column to add - */ - public void addColumn(GridColumn<?, T> column) { - ColumnConfiguration conf = escalator.getColumnConfiguration(); - addColumn(column, conf.getColumnCount()); - } - - /** - * 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 - */ - public void addColumn(GridColumn<?, T> column, int index) { - - // Register column with grid - columns.add(index, column); - - // Insert column into escalator - if (column.isVisible()) { - int visibleIndex = findVisibleColumnIndex(column); - ColumnConfiguration conf = escalator.getColumnConfiguration(); - conf.insertColumns(visibleIndex, 1); - } - - // Register this grid instance with the column - ((AbstractGridColumn<?, T>) column).setGrid(this); - - if (lastFrozenColumn != null - && ((AbstractGridColumn<?, T>) lastFrozenColumn) - .findIndexOfColumn() < index) { - refreshFrozenColumns(); - } - } - - private int findVisibleColumnIndex(GridColumn<?, T> column) { - int idx = 0; - for (GridColumn<?, T> c : columns) { - if (c == column) { - return idx; - } else if (c.isVisible()) { - idx++; - } - } - return -1; - } - - private GridColumn<?, T> getColumnFromVisibleIndex(int index) { - int idx = -1; - for (GridColumn<?, T> c : columns) { - if (c.isVisible()) { - idx++; - } - if (index == idx) { - return c; - } - } - return null; - } - - /** - * Removes a column from the grid. - * - * @param column - * the column to remove - */ - public void removeColumn(GridColumn<?, T> column) { - - int columnIndex = columns.indexOf(column); - int visibleIndex = findVisibleColumnIndex(column); - columns.remove(columnIndex); - - // de-register column with grid - ((AbstractGridColumn<?, T>) column).setGrid(null); - - if (column.isVisible()) { - ColumnConfiguration conf = escalator.getColumnConfiguration(); - conf.removeColumns(visibleIndex, 1); - } - - if (column.equals(lastFrozenColumn)) { - setLastFrozenColumn(null); - } else { - refreshFrozenColumns(); - } - } - - /** - * Returns the amount of columns in the grid. - * - * @return The number of columns in the grid - */ - public int getColumnCount() { - return columns.size(); - } - - /** - * Returns a list of columns in the grid. - * - * @return A unmodifiable list of the columns in the grid - */ - public List<GridColumn<?, T>> getColumns() { - return Collections.unmodifiableList(new ArrayList<GridColumn<?, T>>( - columns)); - } - - /** - * Returns a column by its index in the grid. - * - * @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 GridColumn<?, T> getColumn(int index) - throws IllegalArgumentException { - if (index < 0 || index >= columns.size()) { - throw new IllegalStateException("Column not found."); - } - return columns.get(index); - } - - /** - * Set the column headers visible. - * - * <p> - * A column header is a single cell header on top of each column reserved - * for a specific header for that column. The column header can be set by - * {@link GridColumn#setHeaderCaption(String)} and column headers cannot be - * merged with other column headers. - * </p> - * - * <p> - * All column headers occupy the first header row of the grid. If you do not - * wish to show the column headers in the grid you should hide the row by - * setting visibility of the header row to <code>false</code>. - * </p> - * - * <p> - * If you want to merge the column headers into groups you can use - * {@link ColumnGroupRow}s to group columns together and give them a common - * header. See {@link #addColumnGroupRow()} for details. - * </p> - * - * <p> - * The header row is by default visible. - * </p> - * - * @param visible - * <code>true</code> if header rows should be visible - */ - public void setColumnHeadersVisible(boolean visible) { - if (visible == isColumnHeadersVisible()) { - return; - } - columnHeadersVisible = visible; - refreshHeader(); - } - - /** - * Are the column headers visible - * - * @return <code>true</code> if they are visible - */ - public boolean isColumnHeadersVisible() { - return columnHeadersVisible; - } - - /** - * Set the column footers visible. - * - * <p> - * A column footer is a single cell footer below of each column reserved for - * a specific footer for that column. The column footer can be set by - * {@link GridColumn#setFooterCaption(String)} and column footers cannot be - * merged with other column footers. - * </p> - * - * <p> - * All column footers occupy the first footer row of the grid. If you do not - * wish to show the column footers in the grid you should hide the row by - * setting visibility of the footer row to <code>false</code>. - * </p> - * - * <p> - * If you want to merge the column footers into groups you can use - * {@link ColumnGroupRow}s to group columns together and give them a common - * footer. See {@link #addColumnGroupRow()} for details. - * </p> - * - * <p> - * The footer row is by default hidden. - * </p> - * - * @param visible - * <code>true</code> if the footer row should be visible - */ - public void setColumnFootersVisible(boolean visible) { - if (visible == isColumnFootersVisible()) { - return; - } - this.columnFootersVisible = visible; - refreshFooter(); - } - - /** - * Are the column footers visible - * - * @return <code>true</code> if they are visible - * - */ - public boolean isColumnFootersVisible() { - return columnFootersVisible; - } - - /** - * Adds a new column group row to the grid. - * - * <p> - * Column group rows are rendered in the header and footer of the grid. - * Column group rows are made up of column groups which groups together - * columns for adding a common auxiliary header or footer for the columns. - * </p> - * - * Example usage: - * - * <pre> - * // Add a new column group row to the grid - * ColumnGroupRow row = grid.addColumnGroupRow(); - * - * // Group "Column1" and "Column2" together to form a header in the row - * ColumnGroup column12 = row.addGroup("Column1", "Column2"); - * - * // Set a common header for "Column1" and "Column2" - * column12.setHeader("Column 1&2"); - * - * // Set a common footer for "Column1" and "Column2" - * column12.setFooter("Column 1&2"); - * </pre> - * - * @return a column group row instance you can use to add column groups - */ - public ColumnGroupRow<T> addColumnGroupRow() { - ColumnGroupRow<T> row = new ColumnGroupRow<T>(this); - columnGroupRows.add(row); - refreshHeader(); - refreshFooter(); - return row; - } - - /** - * Adds a new column group row to the grid at a specific index. - * - * @see #addColumnGroupRow() {@link Grid#addColumnGroupRow()} for example - * usage - * - * @param rowIndex - * the index where the column group row should be added - * @return a column group row instance you can use to add column groups - */ - public ColumnGroupRow<T> addColumnGroupRow(int rowIndex) { - ColumnGroupRow<T> row = new ColumnGroupRow<T>(this); - columnGroupRows.add(rowIndex, row); - refreshHeader(); - refreshFooter(); - return row; - } - - /** - * Removes a column group row - * - * @param row - * The row to remove - */ - public void removeColumnGroupRow(ColumnGroupRow<T> row) { - columnGroupRows.remove(row); - refreshHeader(); - refreshFooter(); - } - - /** - * Get the column group rows - * - * @return a unmodifiable list of column group rows - * - */ - public List<ColumnGroupRow<T>> getColumnGroupRows() { - return Collections.unmodifiableList(new ArrayList<ColumnGroupRow<T>>( - columnGroupRows)); - } - - /** - * Returns the column group for a row and column - * - * @param row - * The row of the column - * @param column - * the column to get the group for - * @return A column group for the row and column or <code>null</code> if not - * found. - */ - private ColumnGroup<T> getGroupForColumn(ColumnGroupRow<T> row, - GridColumn<?, T> column) { - for (ColumnGroup<T> group : row.getGroups()) { - List<GridColumn<?, T>> columns = group.getColumns(); - if (columns.contains(column)) { - return group; - } - } - return null; - } - - @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(DataSource<T> dataSource) - throws IllegalArgumentException { - if (dataSource == null) { - throw new IllegalArgumentException("dataSource can't be null."); - } - - if (this.dataSource != null) { - this.dataSource.setDataChangeHandler(null); - } - - this.dataSource = dataSource; - dataSource.setDataChangeHandler(new DataChangeHandler() { - @Override - public void dataUpdated(int firstIndex, int numberOfItems) { - escalator.getBody().refreshRows(firstIndex, numberOfItems); - } - - @Override - public void dataRemoved(int firstIndex, int numberOfItems) { - escalator.getBody().removeRows(firstIndex, numberOfItems); - } - - @Override - public void dataAdded(int firstIndex, int numberOfItems) { - escalator.getBody().insertRows(firstIndex, numberOfItems); - } - }); - - int previousRowCount = escalator.getBody().getRowCount(); - if (previousRowCount != 0) { - escalator.getBody().removeRows(0, previousRowCount); - } - - int estimatedSize = dataSource.getEstimatedSize(); - if (estimatedSize > 0) { - escalator.getBody().insertRows(0, estimatedSize); - } - } - - /** - * Sets the rightmost frozen column in the grid. - * <p> - * All columns up to and including the given column will be frozen in place - * when the grid is scrolled sideways. - * - * @param lastFrozenColumn - * the rightmost column to freeze, or <code>null</code> to not - * have any columns frozen - * @throws IllegalArgumentException - * if {@code lastFrozenColumn} is not a column from this grid - */ - public void setLastFrozenColumn(GridColumn<?, T> lastFrozenColumn) { - this.lastFrozenColumn = lastFrozenColumn; - refreshFrozenColumns(); - } - - private void refreshFrozenColumns() { - final int frozenCount; - if (lastFrozenColumn != null) { - frozenCount = columns.indexOf(lastFrozenColumn) + 1; - if (frozenCount == 0) { - throw new IllegalArgumentException( - "The given column isn't attached to this grid"); - } - } else { - frozenCount = 0; - } - - escalator.getColumnConfiguration().setFrozenColumnCount(frozenCount); - } - - /** - * Gets the rightmost frozen column in the grid. - * <p> - * <em>Note:</em> Most usually, this method returns the very value set with - * {@link #setLastFrozenColumn(GridColumn)}. This value, however, can be - * reset to <code>null</code> if the column is removed from this grid. - * - * @return the rightmost frozen column in the grid, or <code>null</code> if - * no columns are frozen. - */ - public GridColumn<?, T> getLastFrozenColumn() { - return lastFrozenColumn; - } - - 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}. - * - * @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. - * - * @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); - } - - /** - * Scrolls to a certain row using only user-specified parameters. - * - * @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.scrollToRow(rowIndex, destination, paddingPx); - } - - /** - * Scrolls to the beginning of the very first row. - */ - public void scrollToStart() { - scrollToRow(0, ScrollDestination.START); - } - - /** - * Scrolls to the end of the very last row. - */ - public void scrollToEnd() { - scrollToRow(escalator.getBody().getRowCount() - 1, - ScrollDestination.END); - } - - private static final Logger getLogger() { - return Logger.getLogger(Grid.class.getName()); - } - -} diff --git a/client/src/com/vaadin/client/ui/grid/GridColumn.java b/client/src/com/vaadin/client/ui/grid/GridColumn.java deleted file mode 100644 index afd80a5f5a..0000000000 --- a/client/src/com/vaadin/client/ui/grid/GridColumn.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -/** - * Represents a column in the {@link Grid}. - * - * @param <C> - * The column type - * - * @param <T> - * The row type - * - * @since 7.2 - * @author Vaadin Ltd - */ -public abstract class GridColumn<C, T> extends Grid.AbstractGridColumn<C, T> { - - /* - * This class is a convenience class so you do not have to reference - * Grid.AbstractGridColumn in your production code. The real implementation - * should be in the abstract class. - */ - - /** - * Constructs a new column. - */ - public GridColumn() { - super(); - } - - /** - * Constructs a new column with a custom renderer. - * - * @param renderer - * The renderer to use for rendering the cells - */ - public GridColumn(Renderer<C> renderer) { - super(renderer); - } -} diff --git a/client/src/com/vaadin/client/ui/grid/GridConnector.java b/client/src/com/vaadin/client/ui/grid/GridConnector.java deleted file mode 100644 index 5e0664667d..0000000000 --- a/client/src/com/vaadin/client/ui/grid/GridConnector.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.grid.ColumnGroupRowState; -import com.vaadin.shared.ui.grid.ColumnGroupState; -import com.vaadin.shared.ui.grid.GridClientRpc; -import com.vaadin.shared.ui.grid.GridColumnState; -import com.vaadin.shared.ui.grid.GridServerRpc; -import com.vaadin.shared.ui.grid.GridState; -import com.vaadin.shared.ui.grid.ScrollDestination; - -/** - * Connects the client side {@link Grid} widget with the server side - * {@link com.vaadin.ui.components.grid.Grid} component. - * - * @since 7.2 - * @author Vaadin Ltd - */ -@Connect(com.vaadin.ui.components.grid.Grid.class) -public class GridConnector extends AbstractComponentConnector { - - /** - * Custom implementation of the custom grid column using a String[]Â to - * represent the cell value and String as a column type. - */ - private class CustomGridColumn extends GridColumn<String, String[]> { - - private final int columnIndex; - - public CustomGridColumn(int columnIndex) { - this.columnIndex = columnIndex; - } - - @Override - public String getValue(String[] obj) { - return obj[columnIndex]; - } - } - - /** - * Maps a generated column id to a grid column instance - */ - private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>(); - - @Override - protected Grid<String[]> createWidget() { - // FIXME Shouldn't be needed after #12873 has been fixed. - return new Grid<String[]>(); - } - - @Override - @SuppressWarnings("unchecked") - public Grid<String[]> getWidget() { - return (Grid<String[]>) super.getWidget(); - } - - @Override - public GridState getState() { - return (GridState) super.getState(); - } - - @Override - protected void init() { - super.init(); - getWidget().addRowVisibilityChangeHandler( - new RowVisibilityChangeHandler() { - @Override - public void onRowVisibilityChange( - RowVisibilityChangeEvent event) { - getRpcProxy(GridServerRpc.class).setVisibleRows( - event.getFirstVisibleRow(), - event.getVisibleRowCount()); - } - }); - - registerRpc(GridClientRpc.class, new GridClientRpc() { - @Override - public void scrollToStart() { - getWidget().scrollToStart(); - } - - @Override - public void scrollToEnd() { - getWidget().scrollToEnd(); - } - - @Override - public void scrollToRow(int row, ScrollDestination destination) { - getWidget().scrollToRow(row, destination); - } - }); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - // Column updates - if (stateChangeEvent.hasPropertyChanged("columns")) { - - int totalColumns = getState().columns.size(); - - // Remove old columns - purgeRemovedColumns(); - - int currentColumns = getWidget().getColumnCount(); - - // Add new columns - for (int columnIndex = currentColumns; columnIndex < totalColumns; columnIndex++) { - addColumnFromStateChangeEvent(columnIndex); - } - - // Update old columns - for (int columnIndex = 0; columnIndex < currentColumns; columnIndex++) { - // FIXME Currently updating all column header / footers when a - // change in made in one column. When the framework supports - // quering a specific item in a list then it should do so here. - updateColumnFromStateChangeEvent(columnIndex); - } - } - - // Header - if (stateChangeEvent.hasPropertyChanged("columnHeadersVisible")) { - getWidget() - .setColumnHeadersVisible(getState().columnHeadersVisible); - } - - // Footer - if (stateChangeEvent.hasPropertyChanged("columnFootersVisible")) { - getWidget() - .setColumnFootersVisible(getState().columnFootersVisible); - } - - // Column row groups - if (stateChangeEvent.hasPropertyChanged("columnGroupRows")) { - updateColumnGroupsFromStateChangeEvent(); - } - - if (stateChangeEvent.hasPropertyChanged("lastFrozenColumnId")) { - String frozenColId = getState().lastFrozenColumnId; - if (frozenColId != null) { - CustomGridColumn column = columnIdToColumn.get(frozenColId); - assert column != null : "Column to be frozen could not be found (id:" - + frozenColId + ")"; - getWidget().setLastFrozenColumn(column); - } else { - getWidget().setLastFrozenColumn(null); - } - } - } - - /** - * Updates a column from a state change event. - * - * @param columnIndex - * The index of the column to update - */ - private void updateColumnFromStateChangeEvent(int columnIndex) { - GridColumn<?, String[]> column = getWidget().getColumn(columnIndex); - GridColumnState columnState = getState().columns.get(columnIndex); - updateColumnFromState(column, columnState); - } - - /** - * Adds a new column to the grid widget from a state change event - * - * @param columnIndex - * The index of the column, according to how it - */ - private void addColumnFromStateChangeEvent(int columnIndex) { - GridColumnState state = getState().columns.get(columnIndex); - CustomGridColumn column = new CustomGridColumn(columnIndex); - updateColumnFromState(column, state); - - columnIdToColumn.put(state.id, column); - - getWidget().addColumn(column, columnIndex); - } - - /** - * Updates the column values from a state - * - * @param column - * The column to update - * @param state - * The state to get the data from - */ - private static void updateColumnFromState(GridColumn<?, String[]> column, - GridColumnState state) { - column.setVisible(state.visible); - column.setHeaderCaption(state.header); - column.setFooterCaption(state.footer); - column.setWidth(state.width); - } - - /** - * Removes any orphan columns that has been removed from the state from the - * grid - */ - private void purgeRemovedColumns() { - - // Get columns still registered in the state - Set<String> columnsInState = new HashSet<String>(); - for (GridColumnState columnState : getState().columns) { - columnsInState.add(columnState.id); - } - - // Remove column no longer in state - Iterator<String> columnIdIterator = columnIdToColumn.keySet() - .iterator(); - while (columnIdIterator.hasNext()) { - String id = columnIdIterator.next(); - if (!columnsInState.contains(id)) { - CustomGridColumn column = columnIdToColumn.get(id); - columnIdIterator.remove(); - getWidget().removeColumn(column); - } - } - } - - /** - * Updates the column groups from a state change - */ - private void updateColumnGroupsFromStateChangeEvent() { - - // FIXME When something changes the header/footer rows will be - // re-created. At some point we should optimize this so partial updates - // can be made on the header/footer. - for (ColumnGroupRow<String[]> row : getWidget().getColumnGroupRows()) { - getWidget().removeColumnGroupRow(row); - } - - for (ColumnGroupRowState rowState : getState().columnGroupRows) { - ColumnGroupRow<String[]> row = getWidget().addColumnGroupRow(); - row.setFooterVisible(rowState.footerVisible); - row.setHeaderVisible(rowState.headerVisible); - - for (ColumnGroupState groupState : rowState.groups) { - List<GridColumn<String, String[]>> columns = new ArrayList<GridColumn<String, String[]>>(); - for (String columnId : groupState.columns) { - CustomGridColumn column = columnIdToColumn.get(columnId); - columns.add(column); - } - ColumnGroup<String[]> group = row.addGroup(columns - .toArray(new GridColumn[columns.size()])); - group.setFooterCaption(groupState.footer); - group.setHeaderCaption(groupState.header); - } - } - } -} diff --git a/client/src/com/vaadin/client/ui/grid/PositionFunction.java b/client/src/com/vaadin/client/ui/grid/PositionFunction.java deleted file mode 100644 index e41e533996..0000000000 --- a/client/src/com/vaadin/client/ui/grid/PositionFunction.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Unit; - -/** - * A functional interface that can be used for positioning elements in the DOM. - * - * @since 7.2 - * @author Vaadin Ltd - */ -interface PositionFunction { - /** - * A position function using "transform: translate3d(x,y,z)" to position - * elements in the DOM. - */ - public static class Translate3DPosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setProperty("transform", - "translate3d(" + x + "px, " + y + "px, 0)"); - } - - @Override - public void reset(Element e) { - e.getStyle().clearProperty("transform"); - } - } - - /** - * A position function using "transform: translate(x,y)" to position - * elements in the DOM. - */ - public static class TranslatePosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setProperty("transform", - "translate(" + x + "px," + y + "px)"); - } - - @Override - public void reset(Element e) { - e.getStyle().clearProperty("transform"); - } - } - - /** - * A position function using "-webkit-transform: translate3d(x,y,z)" to - * position elements in the DOM. - */ - public static class WebkitTranslate3DPosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setProperty("webkitTransform", - "translate3d(" + x + "px," + y + "px,0)"); - } - - @Override - public void reset(Element e) { - e.getStyle().clearProperty("webkitTransform"); - } - } - - /** - * A position function using "left: x" and "top: y" to position elements in - * the DOM. - */ - public static class AbsolutePosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setLeft(x, Unit.PX); - e.getStyle().setTop(y, Unit.PX); - } - - @Override - public void reset(Element e) { - e.getStyle().clearLeft(); - e.getStyle().clearTop(); - } - } - - /** - * Position an element in an (x,y) coordinate system in the DOM. - * - * @param e - * the element to position. Never <code>null</code>. - * @param x - * the x coordinate, in pixels - * @param y - * the y coordinate, in pixels - */ - void set(Element e, double x, double y); - - /** - * Resets any previously applied positioning, clearing the used style - * attributes. - * - * @param e - * the element for which to reset the positioning - */ - void reset(Element e); -}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/grid/Renderer.java b/client/src/com/vaadin/client/ui/grid/Renderer.java deleted file mode 100644 index 3312ec87e4..0000000000 --- a/client/src/com/vaadin/client/ui/grid/Renderer.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -/** - * Renderer for rending a value <T> into cell. - * <p> - * You can add a renderer to any column by overring the - * {@link GridColumn#getRenderer()} method and returning your own renderer. You - * can retrieve the cell element using {@link Cell#getElement()}. - * - * @param <T> - * The column type - * - * @since 7.2 - * @author Vaadin Ltd - */ -public interface Renderer<T> { - - /** - * Called whenever the {@link Grid} updates a cell - * - * @param cell - * The cell that gets updated - * - * @param data - * The column data object - */ - public void renderCell(Cell cell, T data); -} diff --git a/client/src/com/vaadin/client/ui/grid/Row.java b/client/src/com/vaadin/client/ui/grid/Row.java deleted file mode 100644 index 209da58fec..0000000000 --- a/client/src/com/vaadin/client/ui/grid/Row.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import com.google.gwt.dom.client.Element; - -/** - * A representation of a row in an {@link Escalator}. - * - * @since 7.2 - * @author Vaadin Ltd - */ -public interface Row { - /** - * Gets the escalator containing the row. - * - * @return the escalator containing the row - */ - public Escalator getEscalator(); - - /** - * Gets the row index. - * - * @return the row index - */ - public int getRow(); - - /** - * Gets the root element for this row. - * <p> - * The {@link EscalatorUpdater} may update the class names of the element - * and add inline styles, but may not modify the contained DOM structure. - * <p> - * If you wish to modify the cells within this row element, access them via - * the <code>List<{@link Cell}></code> objects passed in to - * {@code EscalatorUpdater.updateCells(Row, List)} - * - * @return the root element of the row - */ - public Element getElement(); -}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/grid/RowContainer.java b/client/src/com/vaadin/client/ui/grid/RowContainer.java deleted file mode 100644 index 0312164569..0000000000 --- a/client/src/com/vaadin/client/ui/grid/RowContainer.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -/** - * A representation of the rows in each of the sections (header, body and - * footer) in an {@link Escalator}. - * - * @since 7.2 - * @author Vaadin Ltd - * @see Escalator#getHeader() - * @see Escalator#getBody() - * @see Escalator#getFooter() - */ -public interface RowContainer { - - /** - * An arbitrary pixel height of a row, before any autodetection for the row - * height has been made. - * */ - public static final int INITIAL_DEFAULT_ROW_HEIGHT = 20; - - /** - * Returns the current {@link EscalatorUpdater} used to render cells. - * - * @return the current escalator updater - */ - public EscalatorUpdater getEscalatorUpdater(); - - /** - * Sets the {@link EscalatorUpdater} to use when displaying data in the - * escalator. - * - * @param escalatorUpdater - * the escalator updater to use to render cells. May not be - * <code>null</code> - * @throws IllegalArgumentException - * if {@code cellRenderer} is <code>null</code> - * @see EscalatorUpdater#NULL - */ - public void setEscalatorUpdater(EscalatorUpdater escalatorUpdater) - throws IllegalArgumentException; - - /** - * Removes rows at a certain index in the current row container. - * - * @param index - * the index of the first row to be removed - * @param numberOfRows - * the number of rows to remove, starting from the index - * @throws IndexOutOfBoundsException - * if any integer number in the range - * <code>[index..(index+numberOfRows)]</code> is not an existing - * row index - * @throws IllegalArgumentException - * if {@code numberOfRows} is less than 1. - */ - public void removeRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Adds rows at a certain index in this row container. - * <p> - * The new rows will be inserted between the row at the index, and the row - * before (an index of 0 means that the rows are inserted at the beginning). - * Therefore, the rows currently at the index and afterwards will be moved - * downwards. - * <p> - * The contents of the inserted rows will subsequently be queried from the - * escalator updater. - * <p> - * <em>Note:</em> Only the contents of the inserted rows will be rendered. - * If inserting new rows affects the contents of existing rows, - * {@link #refreshRows(int, int)} needs to be called for those rows - * separately. - * - * @param index - * the index of the row before which new rows are inserted, or - * {@link #getRowCount()} to add rows at the end - * @param numberOfRows - * the number of rows to insert after the <code>index</code> - * @see #setEscalatorUpdater(EscalatorUpdater) - * @throws IndexOutOfBoundsException - * if <code>index</code> is not an integer in the range - * <code>[0..{@link #getRowCount()}]</code> - * @throws IllegalArgumentException - * if {@code numberOfRows} is less than 1. - */ - public void insertRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Refreshes a range of rows in the current row container. - * <p> - * The data for the refreshed rows are queried from the current cell - * renderer. - * - * @param index - * the index of the first row that will be updated - * @param numberOfRows - * the number of rows to update, starting from the index - * @see #setEscalatorUpdater(EscalatorUpdater) - * @throws IndexOutOfBoundsException - * if any integer number in the range - * <code>[index..(index+numberOfColumns)]</code> is not an - * existing column index. - * @throws IllegalArgumentException - * if {@code numberOfRows} is less than 1. - */ - public void refreshRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Gets the number of rows in the current row container. - * - * @return the number of rows in the current row container - */ - public int getRowCount(); - - /** - * The default height of the rows in this RowContainer. - * - * @param px - * the default height in pixels of the rows in this RowContainer - * @throws IllegalArgumentException - * if <code>px < 1</code> - * @see #getDefaultRowHeight() - */ - public void setDefaultRowHeight(int px) throws IllegalArgumentException; - - /** - * Returns the default height of the rows in this RowContainer. - * <p> - * This value will be equal to {@link #INITIAL_DEFAULT_ROW_HEIGHT} if the - * {@link Escalator} has not yet had a chance to autodetect the row height, - * or no explicit value has yet given via {@link #setDefaultRowHeight(int)} - * - * @return the default height of the rows in this RowContainer, in pixels - * @see #setDefaultRowHeight(int) - */ - public int getDefaultRowHeight(); -} diff --git a/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java b/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java deleted file mode 100644 index 0e9652e215..0000000000 --- a/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeEvent.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import com.google.gwt.event.shared.GwtEvent; - -/** - * Event fired when the range of visible rows changes e.g. because of scrolling. - * - * @since 7.2 - * @author Vaadin Ltd - */ -public class RowVisibilityChangeEvent extends - GwtEvent<RowVisibilityChangeHandler> { - /** - * The type of this event. - */ - public static final Type<RowVisibilityChangeHandler> TYPE = new Type<RowVisibilityChangeHandler>(); - - private final int firstVisibleRow; - private final int visibleRowCount; - - /** - * Creates a new row visibility change event - * - * @param firstVisibleRow - * the index of the first visible row - * @param visibleRowCount - * the number of visible rows - */ - public RowVisibilityChangeEvent(int firstVisibleRow, int visibleRowCount) { - this.firstVisibleRow = firstVisibleRow; - this.visibleRowCount = visibleRowCount; - } - - /** - * Gets the index of the first row that is at least partially visible. - * - * @return the index of the first visible row - */ - public int getFirstVisibleRow() { - return firstVisibleRow; - } - - /** - * Gets the number of at least partially visible rows. - * - * @return the number of visible rows - */ - public int getVisibleRowCount() { - return visibleRowCount; - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.event.shared.GwtEvent#getAssociatedType() - */ - @Override - public Type<RowVisibilityChangeHandler> getAssociatedType() { - return TYPE; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.shared.GwtEvent#dispatch(com.google.gwt.event.shared - * .EventHandler) - */ - @Override - protected void dispatch(RowVisibilityChangeHandler handler) { - handler.onRowVisibilityChange(this); - } - -} diff --git a/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java b/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java deleted file mode 100644 index dd24521499..0000000000 --- a/client/src/com/vaadin/client/ui/grid/RowVisibilityChangeHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Event handler that gets notified when the range of visible rows changes e.g. - * because of scrolling. - * - * @since 7.2 - * @author Vaadin Ltd - */ -public interface RowVisibilityChangeHandler extends EventHandler { - - /** - * Called when the range of visible rows changes e.g. because of scrolling. - * - * @param event - * the row visibility change event describing the change - */ - void onRowVisibilityChange(RowVisibilityChangeEvent event); - -} diff --git a/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java b/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java deleted file mode 100644 index b9267178c1..0000000000 --- a/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.DOM; - -/** - * An element-like bundle representing a configurable and visual scrollbar in - * one axis. - * - * @since 7.2 - * @author Vaadin Ltd - * @see VerticalScrollbarBundle - * @see HorizontalScrollbarBundle - */ -abstract class ScrollbarBundle { - - /** - * The pixel size for OSX's invisible scrollbars. - * <p> - * Touch devices don't show a scrollbar at all, so the scrollbar size is - * irrelevant in their case. There doesn't seem to be any other popular - * platforms that has scrollbars similar to OSX. Thus, this behavior is - * tailored for OSX only, until additional platforms start behaving this - * way. - */ - private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13; - - /** - * A representation of a single vertical scrollbar. - * - * @see VerticalScrollbarBundle#getElement() - */ - final static class VerticalScrollbarBundle extends ScrollbarBundle { - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - root.addClassName(primaryStyleName + "-scroller-vertical"); - } - - @Override - protected void internalSetScrollPos(int px) { - root.setScrollTop(px); - } - - @Override - protected int internalGetScrollPos() { - return root.getScrollTop(); - } - - @Override - protected void internalSetScrollSize(int px) { - scrollSizeElement.getStyle().setHeight(px, Unit.PX); - } - - @Override - public int getScrollSize() { - return scrollSizeElement.getOffsetHeight(); - } - - @Override - protected void internalSetOffsetSize(int px) { - root.getStyle().setHeight(px, Unit.PX); - } - - @Override - public int getOffsetSize() { - return root.getOffsetHeight(); - } - - @Override - protected void internalSetScrollbarThickness(int px) { - root.getStyle().setWidth(px, Unit.PX); - scrollSizeElement.getStyle().setWidth(px, Unit.PX); - } - - @Override - protected int internalGetScrollbarThickness() { - return root.getOffsetWidth(); - } - - @Override - protected void forceScrollbar(boolean enable) { - if (enable) { - root.getStyle().setOverflowY(Overflow.SCROLL); - } else { - root.getStyle().clearOverflowY(); - } - } - } - - /** - * A representation of a single horizontal scrollbar. - * - * @see HorizontalScrollbarBundle#getElement() - */ - final static class HorizontalScrollbarBundle extends ScrollbarBundle { - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - root.addClassName(primaryStyleName + "-scroller-horizontal"); - } - - @Override - protected void internalSetScrollPos(int px) { - root.setScrollLeft(px); - } - - @Override - protected int internalGetScrollPos() { - return root.getScrollLeft(); - } - - @Override - protected void internalSetScrollSize(int px) { - scrollSizeElement.getStyle().setWidth(px, Unit.PX); - } - - @Override - public int getScrollSize() { - return scrollSizeElement.getOffsetWidth(); - } - - @Override - protected void internalSetOffsetSize(int px) { - root.getStyle().setWidth(px, Unit.PX); - } - - @Override - public int getOffsetSize() { - return root.getOffsetWidth(); - } - - @Override - protected void internalSetScrollbarThickness(int px) { - root.getStyle().setHeight(px, Unit.PX); - scrollSizeElement.getStyle().setHeight(px, Unit.PX); - } - - @Override - protected int internalGetScrollbarThickness() { - return root.getOffsetHeight(); - } - - @Override - protected void forceScrollbar(boolean enable) { - if (enable) { - root.getStyle().setOverflowX(Overflow.SCROLL); - } else { - root.getStyle().clearOverflowX(); - } - } - } - - protected final Element root = DOM.createDiv(); - protected final Element scrollSizeElement = DOM.createDiv(); - protected boolean isInvisibleScrollbar = false; - - private int scrollPos = 0; - private int maxScrollPos = 0; - - private ScrollbarBundle() { - root.appendChild(scrollSizeElement); - } - - /** - * Sets the primary style name - * - * @param primaryStyleName - * The primary style name to use - */ - public void setStylePrimaryName(String primaryStyleName) { - root.setClassName(primaryStyleName + "-scroller"); - } - - /** - * Gets the root element of this scrollbar-composition. - * - * @return the root element - */ - public final Element getElement() { - return root; - } - - /** - * Modifies the scroll position of this scrollbar by a number of pixels - * - * @param delta - * the delta in pixels to change the scroll position by - */ - public final void setScrollPosByDelta(int delta) { - if (delta != 0) { - setScrollPos(getScrollPos() + delta); - } - } - - /** - * Modifies {@link #root root's} dimensions in the axis the scrollbar is - * representing. - * - * @param px - * the new size of {@link #root} in the dimension this scrollbar - * is representing - */ - protected abstract void internalSetOffsetSize(int px); - - /** - * Sets the length of the scrollbar. - * - * @param px - * the length of the scrollbar in pixels - */ - public final void setOffsetSize(int px) { - internalSetOffsetSize(px); - forceScrollbar(showsScrollHandle()); - recalculateMaxScrollPos(); - } - - /** - * Force the scrollbar to be visible with CSS. In practice, this means to - * set either <code>overflow-x</code> or <code>overflow-y</code> to " - * <code>scroll</code>" in the scrollbar's direction. - * <p> - * This is an IE8 workaround, since it doesn't always show scrollbars with - * <code>overflow: auto</code> enabled. - */ - protected abstract void forceScrollbar(boolean enable); - - /** - * Gets the length of the scrollbar - * - * @return the length of the scrollbar in pixels - */ - public abstract int getOffsetSize(); - - /** - * Sets the scroll position of the scrollbar in the axis the scrollbar is - * representing. - * - * @param px - * the new scroll position in pixels - */ - public final void setScrollPos(int px) { - int oldScrollPos = scrollPos; - scrollPos = Math.max(0, Math.min(maxScrollPos, px)); - - if (oldScrollPos != scrollPos) { - internalSetScrollPos(px); - } - } - - protected abstract void internalSetScrollPos(int px); - - /** - * Gets the scroll position of the scrollbar in the axis the scrollbar is - * representing. - * - * @return the new scroll position in pixels - */ - public final int getScrollPos() { - assert internalGetScrollPos() == scrollPos : "calculated scroll position (" - + scrollPos - + ") did not match the DOM element scroll position (" - + internalGetScrollPos() + ")"; - return scrollPos; - } - - protected abstract int internalGetScrollPos(); - - /** - * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in - * such a way that the scrollbar is able to scroll a certain number of - * pixels in the axis it is representing. - * - * @param px - * the new size of {@link #scrollSizeElement} in the dimension - * this scrollbar is representing - */ - protected abstract void internalSetScrollSize(int px); - - /** - * Sets the amount of pixels the scrollbar needs to be able to scroll - * through. - * - * @param px - * the number of pixels the scrollbar should be able to scroll - * through - */ - public final void setScrollSize(int px) { - internalSetScrollSize(px); - forceScrollbar(showsScrollHandle()); - recalculateMaxScrollPos(); - } - - /** - * Gets the amount of pixels the scrollbar needs to be able to scroll - * through. - * - * @return the number of pixels the scrollbar should be able to scroll - * through - */ - public abstract int getScrollSize(); - - /** - * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the - * opposite axis to what the scrollbar is representing. - * - * @param px - * the dimension that {@link #scrollSizeElement} should take in - * the opposite axis to what the scrollbar is representing - */ - protected abstract void internalSetScrollbarThickness(int px); - - /** - * Sets the scrollbar's thickness. - * <p> - * If the thickness is set to 0, the scrollbar will be treated as an - * "invisible" scrollbar. This means, the DOM structure will be given a - * non-zero size, but {@link #getScrollbarThickness()} will still return the - * value 0. - * - * @param px - * the scrollbar's thickness in pixels - */ - public final void setScrollbarThickness(int px) { - isInvisibleScrollbar = (px == 0); - internalSetScrollbarThickness(px != 0 ? px - : OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX); - } - - /** - * Gets the scrollbar's thickness as defined in the DOM. - * - * @return the scrollbar's thickness as defined in the DOM, in pixels - */ - protected abstract int internalGetScrollbarThickness(); - - /** - * Gets the scrollbar's thickness. - * <p> - * This value will differ from the value in the DOM, if the thickness was - * set to 0 with {@link #setScrollbarThickness(int)}, as the scrollbar is - * then treated as "invisible." - * - * @return the scrollbar's thickness in pixels - */ - public final int getScrollbarThickness() { - if (!isInvisibleScrollbar) { - return internalGetScrollbarThickness(); - } else { - return 0; - } - } - - /** - * Checks whether the scrollbar's handle is visible. - * <p> - * In other words, this method checks whether the contents is larger than - * can visually fit in the element. - * - * @return <code>true</code> iff the scrollbar's handle is visible - */ - public boolean showsScrollHandle() { - return getOffsetSize() < getScrollSize(); - } - - public void recalculateMaxScrollPos() { - int scrollSize = getScrollSize(); - int offsetSize = getOffsetSize(); - maxScrollPos = Math.max(0, scrollSize - offsetSize); - - // make sure that the correct max scroll position is maintained. - setScrollPos(scrollPos); - } - - /** - * This is a method that JSNI can call to synchronize the object state from - * the DOM. - */ - @SuppressWarnings("unused") - private final void updateScrollPosFromDom() { - scrollPos = internalGetScrollPos(); - } -} diff --git a/client/src/com/vaadin/client/ui/grid/datasources/ListDataSource.java b/client/src/com/vaadin/client/ui/grid/datasources/ListDataSource.java deleted file mode 100644 index 0a3edbd349..0000000000 --- a/client/src/com/vaadin/client/ui/grid/datasources/ListDataSource.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid.datasources; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -import com.vaadin.client.data.DataChangeHandler; -import com.vaadin.client.data.DataSource; - -/** - * A simple list based on an in-memory data source for simply adding a list of - * row pojos to the grid. Based on a wrapped list instance which supports adding - * and removing of items. - * - * <p> - * Usage: - * - * <pre> - * ListDataSource<Integer> ds = new ListDataSource<Integer>(1, 2, 3, 4); - * - * // Add item to the data source - * ds.asList().add(5); - * - * // Remove item from the data source - * ds.asList().remove(3); - * - * // Add multiple items - * ds.asList().addAll(Arrays.asList(5, 6, 7)); - * </pre> - * - * @since 7.2 - * @author Vaadin Ltd - */ -public class ListDataSource<T> implements DataSource<T> { - - /** - * Wraps the datasource list and notifies the change handler of changing to - * the list - */ - private class ListWrapper implements List<T> { - - @Override - public int size() { - return ds.size(); - } - - @Override - public boolean isEmpty() { - return ds.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return contains(o); - } - - @Override - public Iterator<T> iterator() { - return new ListWrapperIterator(ds.iterator()); - } - - @Override - public Object[] toArray() { - return ds.toArray(); - } - - @Override - public <T> T[] toArray(T[] a) { - return toArray(a); - } - - @Override - public boolean add(T e) { - if (ds.add(e)) { - if (changeHandler != null) { - changeHandler.dataAdded(ds.size() - 1, 1); - } - return true; - } - return false; - } - - @Override - public boolean remove(Object o) { - int index = ds.indexOf(o); - if (ds.remove(o)) { - if (changeHandler != null) { - changeHandler.dataRemoved(index, 1); - } - return true; - } - return false; - } - - @Override - public boolean containsAll(Collection<?> c) { - return ds.containsAll(c); - } - - @Override - public boolean addAll(Collection<? extends T> c) { - int idx = ds.size(); - if (ds.addAll(c)) { - if (changeHandler != null) { - changeHandler.dataAdded(idx, c.size()); - } - return true; - } - return false; - } - - @Override - public boolean addAll(int index, Collection<? extends T> c) { - if (ds.addAll(index, c)) { - if (changeHandler != null) { - changeHandler.dataAdded(index, c.size()); - } - return true; - } - return false; - } - - @Override - public boolean removeAll(Collection<?> c) { - if (ds.removeAll(c)) { - if (changeHandler != null) { - // Have to update the whole list as the removal does not - // have to be a continuous range - changeHandler.dataUpdated(0, ds.size()); - } - return true; - } - return false; - } - - @Override - public boolean retainAll(Collection<?> c) { - if (ds.retainAll(c)) { - if (changeHandler != null) { - // Have to update the whole list as the retain does not - // have to be a continuous range - changeHandler.dataUpdated(0, ds.size()); - } - return true; - } - return false; - } - - @Override - public void clear() { - int size = ds.size(); - ds.clear(); - if (changeHandler != null) { - changeHandler.dataRemoved(0, size); - } - } - - @Override - public T get(int index) { - return ds.get(index); - } - - @Override - public T set(int index, T element) { - T prev = ds.set(index, element); - if (changeHandler != null) { - changeHandler.dataUpdated(index, 1); - } - return prev; - } - - @Override - public void add(int index, T element) { - ds.add(index, element); - if (changeHandler != null) { - changeHandler.dataAdded(index, 1); - } - } - - @Override - public T remove(int index) { - T removed = ds.remove(index); - if (changeHandler != null) { - changeHandler.dataRemoved(index, 1); - } - return removed; - } - - @Override - public int indexOf(Object o) { - return ds.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return ds.lastIndexOf(o); - } - - @Override - public ListIterator<T> listIterator() { - // TODO could be implemented by a custom iterator. - throw new UnsupportedOperationException( - "List iterators not supported at this time."); - } - - @Override - public ListIterator<T> listIterator(int index) { - // TODO could be implemented by a custom iterator. - throw new UnsupportedOperationException( - "List iterators not supported at this time."); - } - - @Override - public List<T> subList(int fromIndex, int toIndex) { - throw new UnsupportedOperationException("Sub lists not supported."); - } - } - - /** - * Iterator returned by {@link ListWrapper} - */ - private class ListWrapperIterator implements Iterator<T> { - - private final Iterator<T> iterator; - - /** - * Constructs a new iterator - */ - public ListWrapperIterator(Iterator<T> iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public T next() { - return iterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException( - "Iterator.remove() is not supported by this iterator."); - } - } - - /** - * Datasource for providing row pojo's - */ - private final List<T> ds; - - /** - * Wrapper that wraps the data source - */ - private final ListWrapper wrapper; - - /** - * Handler for listening to changes in the underlying list. - */ - private DataChangeHandler changeHandler; - - /** - * Constructs a new list data source. - * <p> - * Note: Modifications to the original list will not be reflected in the - * data source after the data source has been constructed. To add or remove - * items to the data source after it has been constructed use - * {@link ListDataSource#asList()}. - * - * - * @param datasource - * The list to use for providing the data to the grid - */ - public ListDataSource(List<T> datasource) { - if (datasource == null) { - throw new IllegalArgumentException("datasource cannot be null"); - } - ds = new ArrayList<T>(datasource); - wrapper = new ListWrapper(); - } - - /** - * Constructs a data source with a set of rows. You can dynamically add and - * remove rows from the data source via the list you get from - * {@link ListDataSource#asList()} - * - * @param rows - * The rows to initially add to the data source - */ - public ListDataSource(T... rows) { - if (rows == null) { - ds = new ArrayList<T>(); - } else { - ds = new ArrayList<T>(Arrays.asList(rows)); - } - wrapper = new ListWrapper(); - } - - @Override - public void ensureAvailability(int firstRowIndex, int numberOfRows) { - if (firstRowIndex >= ds.size()) { - throw new IllegalStateException( - "Trying to fetch rows outside of array"); - } - } - - @Override - public T getRow(int rowIndex) { - return ds.get(rowIndex); - } - - @Override - public int getEstimatedSize() { - return ds.size(); - } - - @Override - public void setDataChangeHandler(DataChangeHandler dataChangeHandler) { - this.changeHandler = dataChangeHandler; - } - - /** - * Gets the list that backs this datasource. Any changes made to this list - * will be reflected in the datasource. - * <p> - * Note: The list is not the same list as passed into the data source via - * the constructor. - * - * @return Returns a list implementation that wraps the real list that backs - * the data source and provides events for the data source - * listeners. - */ - public List<T> asList() { - return wrapper; - } -} diff --git a/client/src/com/vaadin/client/ui/grid/renderers/DateRenderer.java b/client/src/com/vaadin/client/ui/grid/renderers/DateRenderer.java deleted file mode 100644 index d1f770f414..0000000000 --- a/client/src/com/vaadin/client/ui/grid/renderers/DateRenderer.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid.renderers; - -import java.util.Date; - -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.i18n.client.TimeZone; -import com.vaadin.client.ui.grid.Cell; -import com.vaadin.client.ui.grid.Renderer; - -/** - * A renderer for rendering dates into cells - * - * @since 7.2 - * @author Vaadin Ltd - */ -public class DateRenderer implements Renderer<Date> { - - private DateTimeFormat format = DateTimeFormat.getShortDateTimeFormat(); - - private TimeZone timeZone = TimeZone.createTimeZone(new Date() - .getTimezoneOffset()); - - @Override - public void renderCell(Cell cell, Date date) { - String dateStr = format.format(date, timeZone); - cell.getElement().setInnerText(dateStr); - } - - /** - * Gets the format of how the date is formatted. - * - * @return the format - * @see <a - * href="http://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/DateTimeFormat.html">GWT - * documentation on DateTimeFormat</a> - */ - public DateTimeFormat getFormat() { - return format; - } - - /** - * Sets the format used for formatting the dates. - * - * @param format - * the format to set - * @see <a - * href="http://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/DateTimeFormat.html">GWT - * documentation on DateTimeFormat</a> - */ - public void setFormat(DateTimeFormat format) { - if (format == null) { - throw new IllegalArgumentException("Format should not be null"); - } - this.format = format; - } - - /** - * Returns the time zone of the date. - * - * @return the time zone - */ - public TimeZone getTimeZone() { - return timeZone; - } - - /** - * Sets the time zone of the the date. By default uses the time zone of the - * browser. - * - * @param timeZone - * the timeZone to set - */ - public void setTimeZone(TimeZone timeZone) { - if (timeZone == null) { - throw new IllegalArgumentException("Timezone should not be null"); - } - this.timeZone = timeZone; - } -} diff --git a/client/src/com/vaadin/client/ui/grid/renderers/HtmlRenderer.java b/client/src/com/vaadin/client/ui/grid/renderers/HtmlRenderer.java deleted file mode 100644 index 0787dc2332..0000000000 --- a/client/src/com/vaadin/client/ui/grid/renderers/HtmlRenderer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid.renderers; - -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; -import com.vaadin.client.ui.grid.Cell; -import com.vaadin.client.ui.grid.Renderer; - -/** - * Renders a string as HTML into a cell. - * <p> - * The html string is rendered as is without any escaping. It is up to the - * developer to ensure that the html string honors the {@link SafeHtml} - * contract. For more information see - * {@link SafeHtmlUtils#fromSafeConstant(String)}. - * - * @since 7.2 - * @author Vaadin Ltd - * @see SafeHtmlUtils#fromSafeConstant(String) - */ -public class HtmlRenderer implements Renderer<String> { - - @Override - public void renderCell(Cell cell, String htmlString) { - cell.getElement().setInnerSafeHtml( - SafeHtmlUtils.fromSafeConstant(htmlString)); - } -} diff --git a/client/src/com/vaadin/client/ui/grid/renderers/NumberRenderer.java b/client/src/com/vaadin/client/ui/grid/renderers/NumberRenderer.java deleted file mode 100644 index f4efea33a5..0000000000 --- a/client/src/com/vaadin/client/ui/grid/renderers/NumberRenderer.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid.renderers; - -import com.google.gwt.i18n.client.NumberFormat; -import com.vaadin.client.ui.grid.Cell; -import com.vaadin.client.ui.grid.Renderer; - -/** - * Renders a number into a cell using a specific {@link NumberFormat}. By - * default uses the default number format returned by - * {@link NumberFormat#getDecimalFormat()}. - * - * @since 7.2 - * @author Vaadin Ltd - * @param <T> - * The number type to render. - */ -public class NumberRenderer<T extends Number> implements Renderer<T> { - - private NumberFormat format = NumberFormat.getDecimalFormat(); - - /** - * Gets the number format that the number should be formatted in. - * - * @return the number format used to render the number - */ - public NumberFormat getFormat() { - return format; - } - - /** - * Sets the number format to use for formatting the number. - * - * @param format - * the format to use - * @throws IllegalArgumentException - * when the format is null - */ - public void setFormat(NumberFormat format) throws IllegalArgumentException { - if (format == null) { - throw new IllegalArgumentException("Format cannot be null"); - } - this.format = format; - } - - @Override - public void renderCell(Cell cell, Number number) { - cell.getElement().setInnerText(format.format(number)); - } -} diff --git a/client/src/com/vaadin/client/ui/grid/renderers/TextRenderer.java b/client/src/com/vaadin/client/ui/grid/renderers/TextRenderer.java deleted file mode 100644 index 1f06a555c3..0000000000 --- a/client/src/com/vaadin/client/ui/grid/renderers/TextRenderer.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid.renderers; - -import com.vaadin.client.ui.grid.Cell; -import com.vaadin.client.ui.grid.Renderer; - -/** - * Renderer that renders text into a cell. - * - * @since 7.2 - * @author Vaadin Ltd - */ -public class TextRenderer implements Renderer<String> { - - @Override - public void renderCell(Cell cell, String text) { - cell.getElement().setInnerText(text); - } -} diff --git a/client/tests/src/com/vaadin/client/ui/grid/ListDataSourceTest.java b/client/tests/src/com/vaadin/client/ui/grid/ListDataSourceTest.java deleted file mode 100644 index 5c5e88bf69..0000000000 --- a/client/tests/src/com/vaadin/client/ui/grid/ListDataSourceTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; - -import org.easymock.EasyMock; -import org.junit.Test; - -import com.vaadin.client.data.DataChangeHandler; -import com.vaadin.client.ui.grid.datasources.ListDataSource; - -/** - * - * @since 7.2 - * @author Vaadin Ltd - */ -public class ListDataSourceTest { - - @Test - public void testDataSourceConstruction() throws Exception { - - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - - assertEquals(4, ds.getEstimatedSize()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - - ds = new ListDataSource<Integer>(Arrays.asList(0, 1, 2, 3)); - - assertEquals(4, ds.getEstimatedSize()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - } - - @Test - public void testListAddOperation() throws Exception { - - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataAdded(4, 1); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().add(4); - - assertEquals(5, ds.getEstimatedSize()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - assertEquals(4, (int) ds.getRow(4)); - } - - @Test - public void testListAddAllOperation() throws Exception { - - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataAdded(4, 3); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().addAll(Arrays.asList(4, 5, 6)); - - assertEquals(7, ds.getEstimatedSize()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - assertEquals(4, (int) ds.getRow(4)); - assertEquals(5, (int) ds.getRow(5)); - assertEquals(6, (int) ds.getRow(6)); - } - - @Test - public void testListRemoveOperation() throws Exception { - - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataRemoved(3, 1); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().remove(2); - - assertEquals(3, ds.getEstimatedSize()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(3, (int) ds.getRow(2)); - } - - @Test - public void testListRemoveAllOperation() throws Exception { - - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataRemoved(0, 3); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().removeAll(Arrays.asList(0, 2, 3)); - - assertEquals(1, ds.getEstimatedSize()); - assertEquals(1, (int) ds.getRow(0)); - } - - @Test - public void testListClearOperation() throws Exception { - - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataRemoved(0, 4); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().clear(); - - assertEquals(0, ds.getEstimatedSize()); - } - - @Test(expected = IllegalStateException.class) - public void testFetchingNonExistantItem() { - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - ds.ensureAvailability(5, 1); - } - - @Test(expected = UnsupportedOperationException.class) - public void testUnsupportedIteratorRemove() { - ListDataSource<Integer> ds = new ListDataSource<Integer>(0, 1, 2, 3); - ds.asList().iterator().remove(); - } - -} diff --git a/client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java b/client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java deleted file mode 100644 index e97bb339e4..0000000000 --- a/client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -import com.vaadin.shared.ui.grid.Range; - -@SuppressWarnings("static-method") -public class PartitioningTest { - - @Test - public void selfRangeTest() { - final Range range = Range.between(0, 10); - final Range[] partitioning = range.partitionWith(range); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertTrue("inside is self", partitioning[1].equals(range)); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void beforeRangeTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(10, 20); - final Range[] partitioning = beforeRange.partitionWith(afterRange); - - assertTrue("before is self", partitioning[0].equals(beforeRange)); - assertTrue("inside is empty", partitioning[1].isEmpty()); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void afterRangeTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(10, 20); - final Range[] partitioning = afterRange.partitionWith(beforeRange); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertTrue("inside is empty", partitioning[1].isEmpty()); - assertTrue("after is self", partitioning[2].equals(afterRange)); - } - - @Test - public void beforeAndInsideRangeTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(5, 15); - final Range[] partitioning = beforeRange.partitionWith(afterRange); - - assertEquals("before", Range.between(0, 5), partitioning[0]); - assertEquals("inside", Range.between(5, 10), partitioning[1]); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void insideRangeTest() { - final Range fullRange = Range.between(0, 20); - final Range insideRange = Range.between(5, 15); - final Range[] partitioning = insideRange.partitionWith(fullRange); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertEquals("inside", Range.between(5, 15), partitioning[1]); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void insideAndBelowTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(5, 15); - final Range[] partitioning = afterRange.partitionWith(beforeRange); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertEquals("inside", Range.between(5, 10), partitioning[1]); - assertEquals("after", Range.between(10, 15), partitioning[2]); - } - - @Test - public void aboveAndBelowTest() { - final Range fullRange = Range.between(0, 20); - final Range insideRange = Range.between(5, 15); - final Range[] partitioning = fullRange.partitionWith(insideRange); - - assertEquals("before", Range.between(0, 5), partitioning[0]); - assertEquals("inside", Range.between(5, 15), partitioning[1]); - assertEquals("after", Range.between(15, 20), partitioning[2]); - } -} |