blob: 55c23986a418df6738f3fda4fe08d1e43e61ad40 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.google.gwt.user.cellview.client;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.HasKeyProvider;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.RangeChangeEvent;
import com.google.gwt.view.client.RowCountChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionModel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* <p>
* Presenter implementation of {@link HasData} that presents data for various
* cell based widgets. This class contains most of the shared logic used by
* these widgets, making it easier to test the common code.
* <p>
* <p>
* In proper MVP design, user code would interact with the presenter. However,
* that would complicate the widget code. Instead, each widget owns its own
* presenter and contains its own View. The widget forwards commands through to
* the presenter, which then updates the widget via the view. This keeps the
* user facing API simpler.
* <p>
*
* @param <T> the data type of items in the list
*/
class HasDataPresenter<T> implements HasData<T>, HasKeyProvider<T>,
HasKeyboardPagingPolicy {
/**
* Default iterator over DOM elements.
*/
static class DefaultElementIterator implements ElementIterator {
private Element current;
private Element next;
private final View<?> view;
public DefaultElementIterator(View<?> view, Element first) {
this.view = view;
next = first;
}
public boolean hasNext() {
return next != null;
}
public Element next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
current = next;
next = next.getNextSiblingElement();
return current;
}
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Set the selection state of the current element.
*
* @param selected the selection state
* @throws IllegalStateException if {@link #next()} has not been called
*/
public void setSelected(boolean selected) throws IllegalStateException {
if (current == null) {
throw new IllegalStateException();
}
view.setSelected(current, selected);
}
}
/**
* An iterator over DOM elements.
*/
static interface ElementIterator extends Iterator<Element> {
/**
* Set the selection state of the current element.
*
* @param selected the selection state
* @throws IllegalStateException if {@link #next()} has not been called
*/
void setSelected(boolean selected) throws IllegalStateException;
}
/**
* The loading state of the data.
*/
static enum LoadingState {
LOADING, // Waiting for data to load.
PARTIALLY_LOADED, // Partial page data loaded.
LOADED, // All page data loaded.
EMPTY; // The data size is 0.
}
/**
* The view that this presenter presents.
*
* @param <T> the data type
*/
static interface View<T> {
/**
* Add a handler to the view.
*
* @param <H> the handler type
* @param handler the handler to add
* @param type the event type
*/
<H extends EventHandler> HandlerRegistration addHandler(final H handler,
GwtEvent.Type<H> type);
/**
* Check whether or not the cells in the view depend on the selection state.
*
* @return true if cells depend on selection, false if not
*/
boolean dependsOnSelection();
/**
* Get the physical child count.
*
* @return the child count
*/
int getChildCount();
/**
* Get an iterator over the children of the view.
*
* @return the iterator
*/
ElementIterator getChildIterator();
/**
* Called when selection changes.
*/
void onUpdateSelection();
/**
* Construct the HTML that represents the list of values, taking the
* selection state into account.
*
* @param sb the {@link SafeHtmlBuilder} to build into
* @param values the values to render
* @param start the start index that is being rendered
* @param selectionModel the {@link SelectionModel}
*/
void render(SafeHtmlBuilder sb, List<T> values, int start,
SelectionModel<? super T> selectionModel);
/**
* Replace all children with the specified html.
*
* @param values the values of the new children
* @param html the html to render in the child
*/
void replaceAllChildren(List<T> values, SafeHtml html);
/**
* Convert the specified HTML into DOM elements and replace the existing
* elements starting at the specified index. If the number of children
* specified exceeds the existing number of children, the remaining children
* should be appended.
*
* @param values the values of the new children
* @param start the start index to be replaced
* @param html the HTML to convert
*/
void replaceChildren(List<T> values, int start, SafeHtml html);
/**
* Re-establish focus on an element within the view if the view already had
* focus.
*/
void resetFocus();
/**
* Update an element to reflect its keyboard selected state.
*
* @param index the index of the element relative to page start
* @param selected true if selected, false if not
* @param stealFocus true if the row should steal focus, false if not
*/
void setKeyboardSelected(int index, boolean selected, boolean stealFocus);
/**
* Set the current loading state of the data.
*
* @param state the loading state
*/
void setLoadingState(LoadingState state);
/**
* Update an element to reflect its selected state.
*
* @param elem the element to update
* @param selected true if selected, false if not
*/
void setSelected(Element elem, boolean selected);
}
/**
* The number of rows to jump when PAGE_UP or PAGE_DOWN is pressed and the
* {@link KeyboardSelectionPolicy} is
* {@link KeyboardSelectionPolicy.INCREMENT_PAGE}.
*/
static final int PAGE_INCREMENT = 30;
private final HasData<T> display;
/**
* The current keyboard selected row relative to page start. This value should
* never be negative.
*/
private int keyboardSelectedRow = 0;
/**
* The last row value that was selected with the keyboard.
*/
private T keyboardSelectedRowValue;
private KeyboardPagingPolicy keyboardPagingPolicy = KeyboardPagingPolicy.CHANGE_PAGE;
private KeyboardSelectionPolicy keyboardSelectionPolicy = KeyboardSelectionPolicy.ENABLED;
private final ProvidesKey<T> keyProvider;
/**
* As an optimization, keep track of the last HTML string that we rendered. If
* the contents do not change the next time we render, then we don't have to
* set inner html.
*/
private SafeHtml lastContents = null;
private int pageSize;
private int pageStart = 0;
/**
* Set to true when the page start changes, and we need to do a full refresh.
*/
private boolean pageStartChangedSinceRender;
private int rowCount = 0;
private boolean rowCountIsExact;
/**
* The local cache of data in the view. The 0th index in the list corresponds
* to the value at pageStart.
*/
private final List<T> rowData = new ArrayList<T>();
/**
* A local cache of the currently selected rows. We cannot track selected keys
* instead because we might end up in an inconsistent state where we render a
* subset of a list with duplicate values, styling a value in the subset but
* not styling the duplicate value outside of the subset.
*/
private final Set<Integer> selectedRows = new HashSet<Integer>();
private HandlerRegistration selectionHandler;
private SelectionModel<? super T> selectionModel;
private final View<T> view;
/**
* Construct a new {@link HasDataPresenter}.
*
* @param display the display that is being presented
* @param view the view implementation
* @param pageSize the default page size
*/
public HasDataPresenter(HasData<T> display, View<T> view, int pageSize,
ProvidesKey<T> keyProvider) {
this.display = display;
this.view = view;
this.pageSize = pageSize;
this.keyProvider = keyProvider;
}
public HandlerRegistration addRangeChangeHandler(
RangeChangeEvent.Handler handler) {
return view.addHandler(handler, RangeChangeEvent.getType());
}
public HandlerRegistration addRowCountChangeHandler(
RowCountChangeEvent.Handler handler) {
return view.addHandler(handler, RowCountChangeEvent.getType());
}
/**
* Clear the {@link SelectionModel} without updating the view.
*/
public void clearSelectionModel() {
if (selectionHandler != null) {
selectionHandler.removeHandler();
selectionHandler = null;
}
selectionModel = null;
}
/**
* @throws UnsupportedOperationException
*/
public void fireEvent(GwtEvent<?> event) {
// HasData should fire their own events.
throw new UnsupportedOperationException();
}
/**
* Get the current page size. This is usually the page size, but can be less
* if the data size cannot fill the current page.
*
* @return the size of the current page
*/
public int getCurrentPageSize() {
return Math.min(pageSize, rowCount - pageStart);
}
public KeyboardPagingPolicy getKeyboardPagingPolicy() {
return keyboardPagingPolicy;
}
/**
* Get the index of the keyboard selected row relative to the page start.
*
* @return the row index, or -1 if disabled
*/
public int getKeyboardSelectedRow() {
return KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy ? -1
: keyboardSelectedRow;
}
public KeyboardSelectionPolicy getKeyboardSelectionPolicy() {
return keyboardSelectionPolicy;
}
public ProvidesKey<T> getKeyProvider() {
return keyProvider;
}
/**
* Get the overall data size.
*
* @return the data size
*/
public int getRowCount() {
return rowCount;
}
/**
* Get the list of data within the current range. The 0th index corresponds to
* the first value on the page. The data may not be complete or may contain
* null values.
*
* @return the list of data for the current page
*/
public List<T> getRowData() {
return rowData;
}
public SelectionModel<? super T> getSelectionModel() {
return selectionModel;
}
/**
* Return the range of data being displayed.
*/
public Range getVisibleRange() {
return new Range(pageStart, pageSize);
}
/**
* Check if the next call to {@link #keyboardNext()} would succeed.
*
* @return true if there is another row accessible by the keyboard
*/
public boolean hasKeyboardNext() {
if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
return false;
} else if (keyboardSelectedRow < rowData.size() - 1) {
return true;
} else if (!keyboardPagingPolicy.isLimitedToRange()
&& (keyboardSelectedRow + pageStart < rowCount - 1 || !rowCountIsExact)) {
return true;
}
return false;
}
/**
* Check if the next call to {@link #keyboardPrevious()} would succeed.
*
* @return true if there is a previous row accessible by the keyboard
*/
public boolean hasKeyboardPrev() {
if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
return false;
} else if (keyboardSelectedRow > 0) {
return true;
} else if (!keyboardPagingPolicy.isLimitedToRange() && pageStart > 0) {
return true;
}
return false;
}
public boolean isRowCountExact() {
return rowCountIsExact;
}
/**
* Move keyboard selection to the last row.
*/
public void keyboardEnd() {
if (!keyboardPagingPolicy.isLimitedToRange()) {
setKeyboardSelectedRow(rowCount - 1, true);
}
}
/**
* Move keyboard selection to the absolute 0th row.
*/
public void keyboardHome() {
if (!keyboardPagingPolicy.isLimitedToRange()) {
setKeyboardSelectedRow(-pageStart, true);
}
}
/**
* Move keyboard selection to the next row.
*/
public void keyboardNext() {
if (hasKeyboardNext()) {
setKeyboardSelectedRow(keyboardSelectedRow + 1, true);
}
}
/**
* Move keyboard selection to the next page.
*/
public void keyboardNextPage() {
if (KeyboardPagingPolicy.CHANGE_PAGE == keyboardPagingPolicy) {
// 0th index of next page.
setKeyboardSelectedRow(pageSize, true);
} else if (KeyboardPagingPolicy.INCREASE_RANGE == keyboardPagingPolicy) {
setKeyboardSelectedRow(keyboardSelectedRow + PAGE_INCREMENT, true);
}
}
/**
* Move keyboard selection to the previous row.
*/
public void keyboardPrev() {
if (hasKeyboardPrev()) {
setKeyboardSelectedRow(keyboardSelectedRow - 1, true);
}
}
/**
* Move keyboard selection to the previous page.
*/
public void keyboardPrevPage() {
if (KeyboardPagingPolicy.CHANGE_PAGE == keyboardPagingPolicy) {
// 0th index of previous page.
setKeyboardSelectedRow(-pageSize, true);
} else if (KeyboardPagingPolicy.INCREASE_RANGE == keyboardPagingPolicy) {
setKeyboardSelectedRow(keyboardSelectedRow - PAGE_INCREMENT, true);
}
}
/**
* Toggle selection of the current keyboard row in the {@link SelectionModel}.
*/
public void keyboardToggleSelect() {
if (KeyboardSelectionPolicy.ENABLED == keyboardSelectionPolicy
&& selectionModel != null && keyboardSelectedRow >= 0
&& keyboardSelectedRow < rowData.size()) {
T value = rowData.get(keyboardSelectedRow);
if (value != null) {
selectionModel.setSelected(value, !selectionModel.isSelected(value));
}
}
}
/**
* Redraw the list with the current data.
*/
public void redraw() {
lastContents = null;
setRowData(pageStart, rowData);
}
public void setKeyboardPagingPolicy(KeyboardPagingPolicy policy) {
if (policy == null) {
throw new NullPointerException("KeyboardPagingPolicy cannot be null");
}
this.keyboardPagingPolicy = policy;
}
/**
* Set the row index of the keyboard selected element.
*
* @param index the row index
* @param stealFocus true to steal focus
*/
public void setKeyboardSelectedRow(int index, boolean stealFocus) {
// Early exit if disabled.
if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
return;
}
boolean isBound = KeyboardSelectionPolicy.BOUND_TO_SELECTION == keyboardSelectionPolicy;
// Deselect the old index.
if (keyboardSelectedRow >= 0 && keyboardSelectedRow < view.getChildCount()) {
view.setKeyboardSelected(keyboardSelectedRow, false, false);
if (isBound) {
deselectKeyboardValue();
}
}
// Trim to within bounds.
int absIndex = pageStart + index;
if (absIndex < 0) {
absIndex = 0;
} else if (absIndex >= rowCount && rowCountIsExact) {
absIndex = rowCount - 1;
}
index = absIndex - pageStart;
if (keyboardPagingPolicy.isLimitedToRange()) {
index = Math.max(0, Math.min(index, pageSize - 1));
}
// Select the new index.
int newPageStart = pageStart;
int newPageSize = pageSize;
keyboardSelectedRow = 0;
if (index >= 0 && index < pageSize) {
keyboardSelectedRow = index;
if (isBound) {
selectKeyboardValue(index);
}
view.setKeyboardSelected(index, true, stealFocus);
return;
} else if (KeyboardPagingPolicy.CHANGE_PAGE == keyboardPagingPolicy) {
// Go to previous page.
while (index < 0) {
newPageStart -= pageSize;
index += pageSize;
}
// Go to next page.
while (index >= pageSize) {
newPageStart += pageSize;
index -= pageSize;
}
} else if (KeyboardPagingPolicy.INCREASE_RANGE == keyboardPagingPolicy) {
// Increase range at the beginning.
while (index < 0) {
newPageSize += PAGE_INCREMENT;
newPageStart -= PAGE_INCREMENT;
index += PAGE_INCREMENT;
}
if (newPageStart < 0) {
index += newPageStart;
newPageSize += newPageStart;
newPageStart = 0;
}
// Increase range at the end.
while (index >= newPageSize) {
newPageSize += PAGE_INCREMENT;
}
if (isRowCountExact()) {
newPageSize = Math.min(newPageSize, rowCount - newPageStart);
if (index >= rowCount) {
index = rowCount - 1;
}
}
}
// Update the range if it changed.
if (newPageStart != pageStart || newPageSize != pageSize) {
deselectKeyboardValue();
keyboardSelectedRow = index;
setVisibleRange(new Range(newPageStart, newPageSize), false, false);
}
}
public void setKeyboardSelectionPolicy(KeyboardSelectionPolicy policy) {
if (policy == null) {
throw new NullPointerException("KeyboardSelectionPolicy cannot be null");
}
this.keyboardSelectionPolicy = policy;
}
/**
* @throws UnsupportedOperationException
*/
public final void setRowCount(int count) {
// Views should defer to their own implementation of
// setRowCount(int, boolean)) per HasRows spec.
throw new UnsupportedOperationException();
}
public void setRowCount(int count, boolean isExact) {
if (count == this.rowCount && isExact == this.rowCountIsExact) {
return;
}
this.rowCount = count;
this.rowCountIsExact = isExact;
updateLoadingState();
// Update the keyboardSelectedRow.
if (keyboardSelectedRow >= count) {
keyboardSelectedRow = Math.max(0, count - 1);
}
// Redraw the current page if it is affected by the new data size.
if (updateCachedData()) {
redraw();
}
// Update the pager.
RowCountChangeEvent.fire(display, count, rowCountIsExact);
}
public void setRowData(int start, List<T> values) {
int valuesLength = values.size();
int valuesEnd = start + valuesLength;
// Calculate the bounded start (inclusive) and end index (exclusive).
int pageEnd = pageStart + pageSize;
int boundedStart = Math.max(start, pageStart);
int boundedEnd = Math.min(valuesEnd, pageEnd);
if (start != pageStart && boundedStart >= boundedEnd) {
// The data is out of range for the current page.
// Intentionally allow empty lists that start on the page start.
return;
}
// The data size must be at least as large as the data.
if (valuesEnd > rowCount) {
rowCount = valuesEnd;
RowCountChangeEvent.fire(display, rowCount, rowCountIsExact);
}
// Create placeholders up to the specified index.
int cacheOffset = Math.max(0, boundedStart - pageStart - rowData.size());
for (int i = 0; i < cacheOffset; i++) {
rowData.add(null);
}
// If the keyboard selected row is within the data set, clear it out. If the
// key still exists, it will be reset below at its new index.
Object keyboardSelectedKey = null;
int keyboardSelectedAbsoluteRow = pageStart + keyboardSelectedRow;
boolean keyboardSelectedInRange = false;
boolean keyboardSelectedStillExists = false;
if (keyboardSelectedAbsoluteRow >= boundedStart
&& keyboardSelectedAbsoluteRow < boundedEnd) {
keyboardSelectedInRange = true;
// If the value is null, then we will select whatever value is at the
// selected row.
if (keyboardSelectedRowValue != null) {
keyboardSelectedKey = getRowValueKey(keyboardSelectedRowValue);
keyboardSelectedRow = 0; // Will be set to a non-negative number later.
}
}
// Insert the new values into the data array.
for (int i = boundedStart; i < boundedEnd; i++) {
T value = values.get(i - start);
int dataIndex = i - pageStart;
if (dataIndex < rowData.size()) {
rowData.set(dataIndex, value);
} else {
rowData.add(value);
}
// Update our local cache of selected rows.
if (selectionModel != null) {
if (value != null && selectionModel.isSelected(value)) {
selectedRows.add(i);
} else {
selectedRows.remove(i);
}
}
// Update the keyboard selected index.
if (keyboardSelectedKey != null && value != null
&& keyboardSelectedKey.equals(getRowValueKey(value))) {
keyboardSelectedRow = i - pageStart;
keyboardSelectedStillExists = true;
}
}
// Construct a run of elements within the range of the data and the page.
// boundedStart = start index of the data to replace.
// boundedSize = the number of items to replace.
boundedStart = pageStartChangedSinceRender ? pageStart : boundedStart;
boundedStart -= cacheOffset;
List<T> boundedValues = rowData.subList(boundedStart - pageStart,
boundedEnd - pageStart);
int boundedSize = boundedValues.size();
SafeHtmlBuilder sb = new SafeHtmlBuilder();
view.render(sb, boundedValues, boundedStart, selectionModel);
// Update the loading state.
updateLoadingState();
// Replace the DOM elements with the new rendered cells.
int childCount = view.getChildCount();
if (boundedStart == pageStart
&& (boundedSize >= childCount || boundedSize >= getCurrentPageSize() || rowData.size() < childCount)) {
// If the contents have not changed, we're done.
SafeHtml newContents = sb.toSafeHtml();
if (!newContents.equals(lastContents)) {
lastContents = newContents;
view.replaceAllChildren(boundedValues, newContents);
}
// Allow the view to reestablish focus after being re-rendered.
view.resetFocus();
} else {
lastContents = null;
view.replaceChildren(boundedValues, boundedStart - pageStart,
sb.toSafeHtml());
// Only reset focus if needed.
if (keyboardSelectedStillExists) {
view.resetFocus();
}
}
// Reset the pageStartChanged boolean.
pageStartChangedSinceRender = false;
// Update the keyboard selected value.
if (keyboardSelectedInRange && !keyboardSelectedStillExists) {
if (keyboardSelectedKey != null) {
// We had a value, but its lost.
deselectKeyboardValue();
}
// Select the selected row based off the row index.
if (KeyboardSelectionPolicy.BOUND_TO_SELECTION == keyboardSelectionPolicy) {
selectKeyboardValue(keyboardSelectedRow);
}
}
}
/**
* @throws UnsupportedOperationException
*/
public final void setVisibleRange(int start, int length) {
// Views should defer to their own implementation of setVisibleRange(Range)
// per HasRows spec.
throw new UnsupportedOperationException();
}
public void setVisibleRange(Range range) {
setVisibleRange(range, false, false);
}
public void setVisibleRangeAndClearData(Range range,
boolean forceRangeChangeEvent) {
setVisibleRange(range, true, forceRangeChangeEvent);
}
public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
clearSelectionModel();
// Set the new selection model.
this.selectionModel = selectionModel;
if (selectionModel != null) {
selectionHandler = selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
public void onSelectionChange(SelectionChangeEvent event) {
updateSelection();
}
});
}
// Update the current selection state based on the new model.
updateSelection();
}
/**
* Deselect the keyboard selected value.
*/
private void deselectKeyboardValue() {
if (selectionModel != null && keyboardSelectedRowValue != null) {
T curValue = keyboardSelectedRowValue;
keyboardSelectedRow = 0;
keyboardSelectedRowValue = null;
selectionModel.setSelected(curValue, false);
}
}
/**
* Get the key for the specified row value.
*
* @param rowValue the row value
* @return the key
*/
private Object getRowValueKey(T rowValue) {
return keyProvider == null ? rowValue : keyProvider.getKey(rowValue);
}
/**
* Select the value at the keyboard selected row.
*
* @param row the row index
*/
private void selectKeyboardValue(int row) {
if (selectionModel != null && row >= 0 && row < rowData.size()) {
keyboardSelectedRowValue = rowData.get(row);
if (keyboardSelectedRowValue != null) {
selectionModel.setSelected(keyboardSelectedRowValue, true);
}
}
}
/**
* Set the visible {@link Range}, optionally clearing data and/or firing a
* {@link RangeChangeEvent}.
*
* @param range the new {@link Range}
* @param clearData true to clear all data
* @param forceRangeChangeEvent true to force a {@link RangeChangeEvent}
*/
private void setVisibleRange(Range range, boolean clearData,
boolean forceRangeChangeEvent) {
final int start = range.getStart();
final int length = range.getLength();
if (length < 0) {
throw new IllegalArgumentException("Range length cannot be less than 1");
}
// Update the page start.
final boolean pageStartChanged = (pageStart != start);
if (pageStartChanged) {
// Trim the data if we aren't clearing it.
if (!clearData) {
if (start > pageStart) {
int increase = start - pageStart;
if (rowData.size() > increase) {
// Remove the data we no longer need.
for (int i = 0; i < increase; i++) {
rowData.remove(0);
}
} else {
// We have no overlapping data, so just clear it.
rowData.clear();
}
} else {
int decrease = pageStart - start;
if ((rowData.size() > 0) && (decrease < pageSize)) {
// Insert null data at the beginning.
for (int i = 0; i < decrease; i++) {
rowData.add(0, null);
}
} else {
// We have no overlapping data, so just clear it.
rowData.clear();
}
}
}
// Update the page start.
pageStart = start;
pageStartChangedSinceRender = true;
}
// Update the page size.
final boolean pageSizeChanged = (pageSize != length);
if (pageSizeChanged) {
pageSize = length;
}
// Clear the data.
if (clearData) {
rowData.clear();
selectedRows.clear();
}
// Update the loading state.
updateLoadingState();
// Redraw with the existing data.
if (pageStartChanged || clearData || updateCachedData()) {
redraw();
}
// Update the pager and data source if the range changed.
if (pageStartChanged || pageSizeChanged || forceRangeChangeEvent) {
RangeChangeEvent.fire(display, getVisibleRange());
}
}
/**
* Ensure that the cached data is consistent with the data size.
*
* @return true if the data was updated, false if not
*/
private boolean updateCachedData() {
boolean updated = false;
int expectedLastIndex = Math.max(0,
Math.min(pageSize, rowCount - pageStart));
int lastIndex = rowData.size() - 1;
while (lastIndex >= expectedLastIndex) {
rowData.remove(lastIndex);
selectedRows.remove(lastIndex + pageStart);
lastIndex--;
updated = true;
}
return updated;
}
/**
* Update the loading state of the view based on the data size and page size.
*/
private void updateLoadingState() {
int cacheSize = rowData.size();
int curPageSize = isRowCountExact() ? getCurrentPageSize() : pageSize;
if (rowCount == 0 && rowCountIsExact) {
view.setLoadingState(LoadingState.EMPTY);
} else if (cacheSize >= curPageSize) {
view.setLoadingState(LoadingState.LOADED);
} else if (cacheSize == 0) {
view.setLoadingState(LoadingState.LOADING);
} else {
view.setLoadingState(LoadingState.PARTIALLY_LOADED);
}
}
/**
* Update the table based on the current selection.
*/
private void updateSelection() {
view.onUpdateSelection();
// Determine if our selection states are stale.
boolean dependsOnSelection = view.dependsOnSelection();
boolean refreshRequired = false;
ElementIterator children = view.getChildIterator();
int row = pageStart;
for (T value : rowData) {
// Increment the child.
if (!children.hasNext()) {
break;
}
children.next();
// Update the selection state.
boolean selected = selectionModel == null ? false
: selectionModel.isSelected(value);
if (selected != selectedRows.contains(row)) {
refreshRequired = true;
if (selected) {
selectedRows.add(row);
} else {
selectedRows.remove(row);
}
if (!dependsOnSelection) {
// The cell doesn't depend on selection, so we only need to update
// the style.
children.setSelected(selected);
}
}
row++;
}
// Redraw the entire list if needed.
if (refreshRequired && dependsOnSelection) {
redraw();
}
}
}