aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/vaadin/data/util/ContainerOrderedWrapper.java17
-rw-r--r--src/com/vaadin/data/util/FilesystemContainer.java5
-rw-r--r--src/com/vaadin/terminal/Scrollable.java50
-rw-r--r--[-rwxr-xr-x]src/com/vaadin/terminal/gwt/client/ApplicationConnection.java0
-rw-r--r--[-rwxr-xr-x]src/com/vaadin/terminal/gwt/client/VDebugConsole.java0
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java92
-rw-r--r--src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java2
-rw-r--r--src/com/vaadin/ui/Panel.java40
-rw-r--r--src/com/vaadin/ui/Table.java415
-rw-r--r--src/com/vaadin/ui/TreeTable.java22
10 files changed, 517 insertions, 126 deletions
diff --git a/src/com/vaadin/data/util/ContainerOrderedWrapper.java b/src/com/vaadin/data/util/ContainerOrderedWrapper.java
index 958476240e..1bf9a49a3d 100644
--- a/src/com/vaadin/data/util/ContainerOrderedWrapper.java
+++ b/src/com/vaadin/data/util/ContainerOrderedWrapper.java
@@ -68,6 +68,13 @@ public class ContainerOrderedWrapper implements Container.Ordered,
private boolean ordered = false;
/**
+ * The last known size of the wrapped container. Used to check whether items
+ * have been added or removed to the wrapped container, when the wrapped
+ * container does not send ItemSetChangeEvents.
+ */
+ private int lastKnownSize = -1;
+
+ /**
* Constructs a new ordered wrapper for an existing Container. Works even if
* the to-be-wrapped container already implements the Container.Ordered
* interface.
@@ -457,7 +464,15 @@ public class ContainerOrderedWrapper implements Container.Ordered,
* here, we use the default documentation from implemented interface.
*/
public int size() {
- return container.size();
+ int newSize = container.size();
+ if (lastKnownSize != -1 && newSize != lastKnownSize
+ && !(container instanceof Container.ItemSetChangeNotifier)) {
+ // Update the internal cache when the size of the container changes
+ // and the container is incapable of sending ItemSetChangeEvents
+ updateOrderWrapper();
+ }
+ lastKnownSize = newSize;
+ return newSize;
}
/*
diff --git a/src/com/vaadin/data/util/FilesystemContainer.java b/src/com/vaadin/data/util/FilesystemContainer.java
index a26323f487..76a7b90376 100644
--- a/src/com/vaadin/data/util/FilesystemContainer.java
+++ b/src/com/vaadin/data/util/FilesystemContainer.java
@@ -395,6 +395,11 @@ public class FilesystemContainer implements Container.Hierarchical {
} else {
l = f.listFiles();
}
+ if (l == null) {
+ // File.listFiles returns null if File does not exist or if there
+ // was an IO error (permission denied)
+ return;
+ }
final List<File> ll = Arrays.asList(l);
Collections.sort(ll);
diff --git a/src/com/vaadin/terminal/Scrollable.java b/src/com/vaadin/terminal/Scrollable.java
index 5f57a77e76..7f3a0cbd33 100644
--- a/src/com/vaadin/terminal/Scrollable.java
+++ b/src/com/vaadin/terminal/Scrollable.java
@@ -8,8 +8,9 @@ import java.io.Serializable;
/**
* <p>
- * This interface is implemented by all visual objects that can be scrolled. The
- * unit of scrolling is pixel.
+ * This interface is implemented by all visual objects that can be scrolled
+ * programmatically from the server-side, or for which it is possible to know
+ * the scroll position on the server-side. The unit of scrolling is pixel.
* </p>
*
* @author IT Mill Ltd.
@@ -39,6 +40,13 @@ public interface Scrollable extends Serializable {
* scrolled right.
* </p>
*
+ * <p>
+ * The method only has effect if programmatic scrolling is enabled for the
+ * scrollable. Some implementations may require enabling programmatic before
+ * this method can be used. See {@link #setScrollable(boolean)} for more
+ * information.
+ * </p>
+ *
* @param pixelsScrolled
* the xOffset.
*/
@@ -64,30 +72,54 @@ public interface Scrollable extends Serializable {
* scrolled down.
* </p>
*
+ * <p>
+ * The method only has effect if programmatic scrolling is enabled for the
+ * scrollable. Some implementations may require enabling programmatic before
+ * this method can be used. See {@link #setScrollable(boolean)} for more
+ * information.
+ * </p>
+ *
+ * <p>
+ * The scrolling position is limited by the current height of the content
+ * area. If the position is below the height, it is scrolled to the bottom.
+ * However, if the same response also adds height to the content area,
+ * scrolling to bottom only scrolls to the bottom of the previous content
+ * area.
+ * </p>
+ *
* @param pixelsScrolled
* the yOffset.
*/
public void setScrollTop(int pixelsScrolled);
/**
- * Is the scrolling enabled.
+ * Is programmatic scrolling enabled.
*
* <p>
- * Enabling scrolling allows the user to scroll the scrollable view
- * interactively
+ * Whether programmatic scrolling with {@link #setScrollLeft(int)} and
+ * {@link #setScrollTop(int)} is enabled.
* </p>
*
- * @return <code>true</code> if the scrolling is allowed, otherwise
+ * @return <code>true</code> if the scrolling is enabled, otherwise
* <code>false</code>.
*/
public boolean isScrollable();
/**
- * Enables or disables scrolling..
+ * Enables or disables programmatic scrolling.
+ *
+ * <p>
+ * Enables setting the scroll position with {@link #setScrollLeft(int)} and
+ * {@link #setScrollTop(int)}. Implementations of the interface may have
+ * programmatic scrolling disabled by default, in which case you need to
+ * enable it to use the mentioned methods.
+ * </p>
*
* <p>
- * Enabling scrolling allows the user to scroll the scrollable view
- * interactively
+ * Notice that this does <i>not</i> control whether scroll bars are shown
+ * for a scrollable component. That normally happens automatically when the
+ * content grows too big for the component, relying on the "overflow: auto"
+ * property in CSS.
* </p>
*
* @param isScrollingEnabled
diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
index 18ccd363a8..18ccd363a8 100755..100644
--- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
+++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
diff --git a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java
index 2eddd51008..2eddd51008 100755..100644
--- a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java
+++ b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
index 933c5de1c9..959b92cffa 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
@@ -104,6 +104,9 @@ import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
VHasDropHandler, FocusHandler, BlurHandler, Focusable, ActionOwner {
+ public static final String ATTRIBUTE_PAGEBUFFER_FIRST = "pb-ft";
+ public static final String ATTRIBUTE_PAGEBUFFER_LAST = "pb-l";
+
private static final String ROW_HEADER_COLUMN_KEY = "0";
public static final String CLASSNAME = "v-table";
@@ -203,6 +206,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private String[] bodyActionKeys;
+ private boolean enableDebug = false;
+
/**
* Represents a select range of rows
*/
@@ -423,6 +428,21 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private int lastRenderedHeight;
+ /**
+ * Values (serverCacheFirst+serverCacheLast) sent by server that tells which
+ * rows (indexes) are in the server side cache (page buffer). -1 means
+ * unknown. The server side cache row MUST MATCH the client side cache rows.
+ *
+ * If the client side cache contains additional rows with e.g. buttons, it
+ * will cause out of sync when such a button is pressed.
+ *
+ * If the server side cache contains additional rows with e.g. buttons,
+ * scrolling in the client will cause empty buttons to be rendered
+ * (cached=true request for non-existing components)
+ */
+ private int serverCacheFirst = -1;
+ private int serverCacheLast = -1;
+
public VScrollTable() {
setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
@@ -798,6 +818,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
rendering = true;
+ if (uidl.hasAttribute(ATTRIBUTE_PAGEBUFFER_FIRST)) {
+ serverCacheFirst = uidl.getIntAttribute(ATTRIBUTE_PAGEBUFFER_FIRST);
+ serverCacheLast = uidl.getIntAttribute(ATTRIBUTE_PAGEBUFFER_LAST);
+ } else {
+ serverCacheFirst = -1;
+ serverCacheLast = -1;
+ }
/*
* We need to do this before updateComponent since updateComponent calls
* this.setHeight() which will calculate a new body height depending on
@@ -1404,8 +1431,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
int count = partialRowUpdates.getIntAttribute("numurows");
scrollBody.unlinkRows(firstRowIx, count);
scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
-
- discardRowsOutsideCacheWindow();
}
/**
@@ -1413,24 +1438,71 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
* caching window.
*/
protected void discardRowsOutsideCacheWindow() {
- final int optimalFirstRow = (int) (firstRowInViewPort - pageLength
+ int firstRowToKeep = (int) (firstRowInViewPort - pageLength
+ * cache_rate);
+ int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength
* cache_rate);
+ debug("Client side calculated cache rows to keep: " + firstRowToKeep
+ + "-" + lastRowToKeep);
+
+ if (serverCacheFirst != -1) {
+ firstRowToKeep = serverCacheFirst;
+ lastRowToKeep = serverCacheLast;
+ debug("Server cache rows that override: " + serverCacheFirst + "-"
+ + serverCacheLast);
+ if (firstRowToKeep < scrollBody.getFirstRendered()
+ || lastRowToKeep > scrollBody.getLastRendered()) {
+ debug("*** Server wants us to keep " + serverCacheFirst + "-"
+ + serverCacheLast + " but we only have rows "
+ + scrollBody.getFirstRendered() + "-"
+ + scrollBody.getLastRendered() + " rendered!");
+ }
+ }
+ discardRowsOutsideOf(firstRowToKeep, lastRowToKeep);
+
+ scrollBody.fixSpacers();
+
+ scrollBody.restoreRowVisibility();
+ }
+
+ private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) {
+ /*
+ * firstDiscarded and lastDiscarded are only calculated for debug
+ * purposes
+ */
+ int firstDiscarded = -1, lastDiscarded = -1;
boolean cont = true;
while (cont && scrollBody.getLastRendered() > optimalFirstRow
&& scrollBody.getFirstRendered() < optimalFirstRow) {
+ if (firstDiscarded == -1) {
+ firstDiscarded = scrollBody.getFirstRendered();
+ }
+
// removing row from start
cont = scrollBody.unlinkRow(true);
}
- final int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength
- * cache_rate);
+ if (firstDiscarded != -1) {
+ lastDiscarded = scrollBody.getFirstRendered() - 1;
+ debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
+ }
+ firstDiscarded = lastDiscarded = -1;
+
cont = true;
while (cont && scrollBody.getLastRendered() > optimalLastRow) {
+ if (lastDiscarded == -1) {
+ lastDiscarded = scrollBody.getLastRendered();
+ }
+
// removing row from the end
cont = scrollBody.unlinkRow(false);
}
- scrollBody.fixSpacers();
+ if (lastDiscarded != -1) {
+ firstDiscarded = scrollBody.getLastRendered() + 1;
+ debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
+ }
- scrollBody.restoreRowVisibility();
+ debug("Now in cache: " + scrollBody.getFirstRendered() + "-"
+ + scrollBody.getLastRendered());
}
/**
@@ -6752,4 +6824,10 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
}
}
+
+ private void debug(String msg) {
+ if (enableDebug) {
+ VConsole.error(msg);
+ }
+ }
}
diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java
index 9b9639d307..2fa90f3e5f 100644
--- a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java
+++ b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java
@@ -66,7 +66,7 @@ public class WidgetSetBuilder {
if (!parent.mkdirs()) {
throw new IOException(
"Could not create directory for the widgetset: "
- + parent.getCanonicalPath());
+ + parent.getPath());
}
}
widgetsetFile.createNewFile();
diff --git a/src/com/vaadin/ui/Panel.java b/src/com/vaadin/ui/Panel.java
index 877171232e..999b45cfa3 100644
--- a/src/com/vaadin/ui/Panel.java
+++ b/src/com/vaadin/ui/Panel.java
@@ -405,8 +405,16 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
return scrollable;
}
- /*
- * (non-Javadoc)
+ /**
+ * Sets the panel as programmatically scrollable.
+ *
+ * <p>
+ * Panel is by default not scrollable programmatically with
+ * {@link #setScrollLeft(int)} and {@link #setScrollTop(int)}, so if you use
+ * those methods, you need to enable scrolling with this method. Components
+ * that extend Panel may have a different default for the programmatic
+ * scrollability.
+ * </p>
*
* @see com.vaadin.terminal.Scrollable#setScrollable(boolean)
*/
@@ -417,10 +425,19 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
}
}
- /*
- * (non-Javadoc)
+ /**
+ * Sets the horizontal scroll position.
+ *
+ * <p>
+ * Setting the horizontal scroll position with this method requires that
+ * programmatic scrolling of the component has been enabled. For Panel it is
+ * disabled by default, so you have to call {@link #setScrollable(boolean)}.
+ * Components extending Panel may have a different default for programmatic
+ * scrollability.
+ * </p>
*
* @see com.vaadin.terminal.Scrollable#setScrollLeft(int)
+ * @see #setScrollable(boolean)
*/
public void setScrollLeft(int pixelsScrolled) {
if (pixelsScrolled < 0) {
@@ -441,7 +458,20 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
setScrollLeft(pixels);
}
- /* Documented in interface */
+ /**
+ * Sets the vertical scroll position.
+ *
+ * <p>
+ * Setting the vertical scroll position with this method requires that
+ * programmatic scrolling of the component has been enabled. For Panel it is
+ * disabled by default, so you have to call {@link #setScrollable(boolean)}.
+ * Components extending Panel may have a different default for programmatic
+ * scrollability.
+ * </p>
+ *
+ * @see com.vaadin.terminal.Scrollable#setScrollTop(int)
+ * @see #setScrollable(boolean)
+ */
public void setScrollTop(int pixelsScrolledDown) {
if (pixelsScrolledDown < 0) {
throw new IllegalArgumentException(
diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java
index 0ededf3c6b..5efb2545e0 100644
--- a/src/com/vaadin/ui/Table.java
+++ b/src/com/vaadin/ui/Table.java
@@ -522,8 +522,7 @@ public class Table extends AbstractSelect implements Action.Container,
this.visibleColumns = newVC;
// Assures visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
/**
@@ -721,8 +720,8 @@ public class Table extends AbstractSelect implements Action.Container,
}
this.columnAlignments = newCA;
- // Assures the visual refresh
- resetPageBuffer();
+ // Assures the visual refresh. No need to reset the page buffer before
+ // as the content has not changed, only the alignments.
refreshRenderedCells();
}
@@ -860,8 +859,7 @@ public class Table extends AbstractSelect implements Action.Container,
if (pageLength >= 0 && this.pageLength != pageLength) {
this.pageLength = pageLength;
// Assures the visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
}
@@ -982,8 +980,7 @@ public class Table extends AbstractSelect implements Action.Container,
}
// Assures the visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
@@ -1107,7 +1104,8 @@ public class Table extends AbstractSelect implements Action.Container,
columnAlignments.put(propertyId, alignment);
}
- // Assures the visual refresh
+ // Assures the visual refresh. No need to reset the page buffer before
+ // as the content has not changed, only the alignments.
refreshRenderedCells();
}
@@ -1147,8 +1145,7 @@ public class Table extends AbstractSelect implements Action.Container,
}
// Assures the visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
/**
@@ -1173,7 +1170,8 @@ public class Table extends AbstractSelect implements Action.Container,
collapsedColumns.clear();
}
- // Assures the visual refresh
+ // Assures the visual refresh. No need to reset the page buffer before
+ // as the content has not changed, only the alignments.
refreshRenderedCells();
}
@@ -1227,8 +1225,7 @@ public class Table extends AbstractSelect implements Action.Container,
visibleColumns = newOrder;
// Assure visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
/**
@@ -1322,8 +1319,7 @@ public class Table extends AbstractSelect implements Action.Container,
}
if (needsPageBufferReset) {
// Assures the visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
}
@@ -1418,7 +1414,9 @@ public class Table extends AbstractSelect implements Action.Container,
}
/**
- * Refreshes rendered rows
+ * Refreshes the rows in the internal cache. Only if
+ * {@link #resetPageBuffer()} is called before this then all values are
+ * guaranteed to be recreated.
*/
protected void refreshRenderedCells() {
if (getParent() == null) {
@@ -1474,39 +1472,93 @@ public class Table extends AbstractSelect implements Action.Container,
requestRepaint();
}
+ /**
+ * Requests that the Table should be repainted as soon as possible.
+ *
+ * Note that a {@code Table} does not necessarily repaint its contents when
+ * this method has been called. See {@link #refreshRowCache()} for forcing
+ * an update of the contents.
+ */
+ @Override
+ public void requestRepaint() {
+ // Overridden only for javadoc
+ super.requestRepaint();
+ }
+
private void removeRowsFromCacheAndFillBottom(int firstIndex, int rows) {
int totalCachedRows = pageBuffer[CELL_ITEMID].length;
int totalRows = size();
- int cacheIx = firstIndex - pageBufferFirstIndex;
+ int firstIndexInPageBuffer = firstIndex - pageBufferFirstIndex;
- // Make sure that no components leak.
+ /*
+ * firstIndexInPageBuffer is the first row to be removed. "rows" rows
+ * after that should be removed. If the page buffer does not contain
+ * that many rows, we only remove the rows that actually are in the page
+ * buffer.
+ */
+ if (firstIndexInPageBuffer + rows > totalCachedRows) {
+ rows = totalCachedRows - firstIndexInPageBuffer;
+ }
+
+ /*
+ * Unregister components that will no longer be in the page buffer to
+ * make sure that no components leak.
+ */
unregisterComponentsAndPropertiesInRows(firstIndex, rows);
- int newCachedRowCount = totalRows < totalCachedRows ? totalRows
- : totalCachedRows;
- int firstAppendedRow = newCachedRowCount > rows ? newCachedRowCount
- - rows : firstIndex;
- int rowsToAdd = Math.min(rows, totalCachedRows - firstAppendedRow);
- rowsToAdd = Math.min(rowsToAdd, totalRows
- - (firstAppendedRow + pageBufferFirstIndex));
- if (rowsToAdd == 0) {
- return;
+ /*
+ * The number of rows that should be in the cache after this operation
+ * is done (pageBuffer currently contains the expanded items).
+ */
+ int newCachedRowCount = totalCachedRows;
+ if (newCachedRowCount + pageBufferFirstIndex > totalRows) {
+ newCachedRowCount = totalRows - pageBufferFirstIndex;
+ }
+
+ /*
+ * The index at which we should render the first row that does not come
+ * from the previous page buffer.
+ */
+ int firstAppendedRowInPageBuffer = totalCachedRows - rows;
+ int firstAppendedRow = firstAppendedRowInPageBuffer
+ + pageBufferFirstIndex;
+
+ /*
+ * Calculate the maximum number of new rows that we can add to the page
+ * buffer. Less than the rows we removed if the container does not
+ * contain that many items afterwards.
+ */
+ int maxRowsToRender = (totalRows - firstAppendedRow);
+ int rowsToAdd = rows;
+ if (rowsToAdd > maxRowsToRender) {
+ rowsToAdd = maxRowsToRender;
}
- Object[][] cells = getVisibleCellsNoCache(firstAppendedRow, rowsToAdd,
- false);
- // Create the new cache buffer by copying data from the old one and
- // appending more rows if applicable.
+ Object[][] cells = null;
+ if (rowsToAdd > 0) {
+ cells = getVisibleCellsNoCache(firstAppendedRow, rowsToAdd, false);
+ }
+ /*
+ * Create the new cache buffer by copying the first rows from the old
+ * buffer, moving the following rows upwards and appending more rows if
+ * applicable.
+ */
Object[][] newPageBuffer = new Object[pageBuffer.length][newCachedRowCount];
- for (int ix = 0; ix < newCachedRowCount; ix++) {
- for (int i = 0; i < pageBuffer.length; i++) {
- if (ix >= firstAppendedRow) {
- newPageBuffer[i][ix] = cells[i][ix - firstAppendedRow];
- } else if (ix >= cacheIx && ix + rows < totalCachedRows) {
- newPageBuffer[i][ix] = pageBuffer[i][ix + rows];
- } else {
- newPageBuffer[i][ix] = pageBuffer[i][ix];
- }
+
+ for (int i = 0; i < pageBuffer.length; i++) {
+ for (int row = 0; row < firstIndexInPageBuffer; row++) {
+ // Copy the first rows
+ newPageBuffer[i][row] = pageBuffer[i][row];
+ }
+ for (int row = firstIndexInPageBuffer; row < firstAppendedRowInPageBuffer; row++) {
+ // Move the rows that were after the expanded rows
+ newPageBuffer[i][row] = pageBuffer[i][row + rows];
+ }
+ for (int row = firstAppendedRowInPageBuffer; row < newCachedRowCount; row++) {
+ // Add the newly rendered rows. Only used if rowsToAdd > 0
+ // (cells != null)
+ newPageBuffer[i][row] = cells[i][row
+ - firstAppendedRowInPageBuffer];
}
}
pageBuffer = newPageBuffer;
@@ -1526,46 +1578,143 @@ public class Table extends AbstractSelect implements Action.Container,
return cells;
}
+ /**
+ * @param firstIndex
+ * The position where new rows should be inserted
+ * @param rows
+ * The maximum number of rows that should be inserted at position
+ * firstIndex. Less rows will be inserted if the page buffer is
+ * too small.
+ * @return
+ */
private Object[][] getVisibleCellsInsertIntoCache(int firstIndex, int rows) {
- Object[][] cells = getVisibleCellsNoCache(firstIndex, rows, false);
+ logger.finest("Insert " + rows + " rows at index " + firstIndex
+ + " to existing page buffer requested");
+
+ // Page buffer must not become larger than pageLength*cacheRate before
+ // or after the current page
+ int minPageBufferIndex = getCurrentPageFirstItemIndex()
+ - (int) (getPageLength() * getCacheRate());
+ if (minPageBufferIndex < 0) {
+ minPageBufferIndex = 0;
+ }
+
+ int maxPageBufferIndex = getCurrentPageFirstItemIndex()
+ + (int) (getPageLength() * (1 + getCacheRate()));
+ int maxBufferSize = maxPageBufferIndex - minPageBufferIndex;
+
+ if (getPageLength() == 0) {
+ // If pageLength == 0 then all rows should be rendered
+ maxBufferSize = pageBuffer[0].length + rows;
+ }
+ /*
+ * Number of rows that were previously cached. This is not necessarily
+ * the same as pageLength if we do not have enough rows in the
+ * container.
+ */
int currentlyCachedRowCount = pageBuffer[CELL_ITEMID].length;
- int lastCachedRow = currentlyCachedRowCount - rows;
- int cacheIx = firstIndex - pageBufferFirstIndex;
- // Unregister all components that fall beyond the cache limits after
- // inserting the new rows.
- unregisterComponentsAndPropertiesInRows(lastCachedRow + 1,
- currentlyCachedRowCount - lastCachedRow);
+ /*
+ * firstIndexInPageBuffer is the offset in pageBuffer where the new rows
+ * will be inserted (firstIndex is the index in the whole table).
+ *
+ * E.g. scrolled down to row 1000: firstIndex==1010,
+ * pageBufferFirstIndex==1000 -> cacheIx==10
+ */
+ int firstIndexInPageBuffer = firstIndex - pageBufferFirstIndex;
+
+ /* If rows > size available in page buffer */
+ if (firstIndexInPageBuffer + rows > maxBufferSize) {
+ rows = maxBufferSize - firstIndexInPageBuffer;
+ }
+
+ /*
+ * "rows" rows will be inserted at firstIndex. Find out how many old
+ * rows fall outside the new buffer so we can unregister components in
+ * the cache.
+ */
+
+ /* All rows until the insertion point remain, always. */
+ int firstCacheRowToRemoveInPageBuffer = firstIndexInPageBuffer;
+
+ /*
+ * IF there is space remaining in the buffer after the rows have been
+ * inserted, we can keep more rows.
+ */
+ int numberOfOldRowsAfterInsertedRows = maxBufferSize
+ - firstIndexInPageBuffer - rows;
+ if (numberOfOldRowsAfterInsertedRows > 0) {
+ firstCacheRowToRemoveInPageBuffer += numberOfOldRowsAfterInsertedRows;
+ }
+
+ if (firstCacheRowToRemoveInPageBuffer <= currentlyCachedRowCount) {
+ /*
+ * Unregister all components that fall beyond the cache limits after
+ * inserting the new rows.
+ */
+ unregisterComponentsAndPropertiesInRows(
+ firstCacheRowToRemoveInPageBuffer + pageBufferFirstIndex,
+ currentlyCachedRowCount - firstCacheRowToRemoveInPageBuffer
+ + pageBufferFirstIndex);
+ }
// Calculate the new cache size
int newCachedRowCount = currentlyCachedRowCount;
- if (pageLength == 0 || currentlyCachedRowCount < pageLength) {
+ if (maxBufferSize == 0 || currentlyCachedRowCount < maxBufferSize) {
newCachedRowCount = currentlyCachedRowCount + rows;
- if (pageLength > 0 && newCachedRowCount > pageLength) {
- newCachedRowCount = pageLength;
+ if (maxBufferSize > 0 && newCachedRowCount > maxBufferSize) {
+ newCachedRowCount = maxBufferSize;
}
}
- // Create the new cache buffer and fill it with the data from the old
- // buffer as well as the inserted rows.
+ /* Paint the new rows into a separate buffer */
+ Object[][] cells = getVisibleCellsNoCache(firstIndex, rows, false);
+
+ /*
+ * Create the new cache buffer and fill it with the data from the old
+ * buffer as well as the inserted rows.
+ */
Object[][] newPageBuffer = new Object[pageBuffer.length][newCachedRowCount];
- for (int ix = 0; ix < newCachedRowCount; ix++) {
- for (int i = 0; i < pageBuffer.length; i++) {
- if (ix >= cacheIx && ix < cacheIx + rows) {
- newPageBuffer[i][ix] = cells[i][ix - cacheIx];
- } else if (ix >= cacheIx + rows) {
- newPageBuffer[i][ix] = pageBuffer[i][ix - rows];
- } else {
- newPageBuffer[i][ix] = pageBuffer[i][ix];
- }
+
+ for (int i = 0; i < pageBuffer.length; i++) {
+ for (int row = 0; row < firstIndexInPageBuffer; row++) {
+ // Copy the first rows
+ newPageBuffer[i][row] = pageBuffer[i][row];
+ }
+ for (int row = firstIndexInPageBuffer; row < firstIndexInPageBuffer
+ + rows; row++) {
+ // Copy the newly created rows
+ newPageBuffer[i][row] = cells[i][row - firstIndexInPageBuffer];
+ }
+ for (int row = firstIndexInPageBuffer + rows; row < newCachedRowCount; row++) {
+ // Move the old rows down below the newly inserted rows
+ newPageBuffer[i][row] = pageBuffer[i][row - rows];
}
}
pageBuffer = newPageBuffer;
+ logger.finest("Page Buffer now contains "
+ + pageBuffer[CELL_ITEMID].length + " rows ("
+ + pageBufferFirstIndex + "-"
+ + (pageBufferFirstIndex + pageBuffer[CELL_ITEMID].length - 1)
+ + ")");
return cells;
}
+ /**
+ * Render rows with index "firstIndex" to "firstIndex+rows-1" to a new
+ * buffer.
+ *
+ * Reuses values from the current page buffer if the rows are found there.
+ *
+ * @param firstIndex
+ * @param rows
+ * @param replaceListeners
+ * @return
+ */
private Object[][] getVisibleCellsNoCache(int firstIndex, int rows,
boolean replaceListeners) {
+ logger.finest("Render visible cells for rows " + firstIndex + "-"
+ + (firstIndex + rows - 1));
final Object[] colids = getVisibleColumns();
final int cols = colids.length;
@@ -1747,6 +1896,8 @@ public class Table extends AbstractSelect implements Action.Container,
}
protected void registerComponent(Component component) {
+ logger.finest("Registered " + component.getClass().getSimpleName()
+ + ": " + component.getCaption());
if (component.getParent() != this) {
component.setParent(this);
}
@@ -1772,9 +1923,13 @@ public class Table extends AbstractSelect implements Action.Container,
/**
* @param firstIx
+ * Index of the first row to process. Global index, not relative
+ * to page buffer.
* @param count
*/
private void unregisterComponentsAndPropertiesInRows(int firstIx, int count) {
+ logger.finest("Unregistering components in rows " + firstIx + "-"
+ + (firstIx + count - 1));
Object[] colids = getVisibleColumns();
if (pageBuffer != null && pageBuffer[CELL_ITEMID].length > 0) {
int bufSize = pageBuffer[CELL_ITEMID].length;
@@ -1854,6 +2009,8 @@ public class Table extends AbstractSelect implements Action.Container,
* a set of components that should be unregistered.
*/
protected void unregisterComponent(Component component) {
+ logger.finest("Unregistered " + component.getClass().getSimpleName()
+ + ": " + component.getCaption());
component.setParent(null);
/*
* Also remove property data sources to unregister listeners keeping the
@@ -1917,7 +2074,8 @@ public class Table extends AbstractSelect implements Action.Container,
setItemCaptionMode(mode);
}
- // Assure visual refresh
+ // Assures the visual refresh. No need to reset the page buffer before
+ // as the content has not changed, only the alignments.
refreshRenderedCells();
}
@@ -1983,13 +2141,35 @@ public class Table extends AbstractSelect implements Action.Container,
}
if (!(items instanceof Container.ItemSetChangeNotifier)) {
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
return itemId;
}
+ /**
+ * Discards and recreates the internal row cache. Call this if you make
+ * changes that affect the rows but the information about the changes are
+ * not automatically propagated to the Table.
+ * <p>
+ * Do not call this e.g. if you have updated the data model through a
+ * Property. These types of changes are automatically propagated to the
+ * Table.
+ * <p>
+ * A typical case when this is needed is if you update a generator (e.g.
+ * CellStyleGenerator) and want to ensure that the rows are redrawn with new
+ * styles.
+ * <p>
+ * <i>Note that calling this method is not cheap so avoid calling it
+ * unnecessarily.</i>
+ *
+ * @since 6.7.2
+ */
+ public void refreshRowCache() {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
@Override
public void setContainerDataSource(Container newDataSource) {
@@ -2220,6 +2400,8 @@ public class Table extends AbstractSelect implements Action.Container,
}
}
}
+ logger.finest("Client wants rows " + reqFirstRowToPaint + "-"
+ + (reqFirstRowToPaint + reqRowsToPaint - 1));
clientNeedsContentRefresh = true;
}
@@ -2485,6 +2667,21 @@ public class Table extends AbstractSelect implements Action.Container,
// Rows
if (isPartialRowUpdate() && painted && !target.isFullRepaint()) {
paintPartialRowUpdate(target, actionSet);
+ /*
+ * Send the page buffer indexes to ensure that the client side stays
+ * in sync. Otherwise we _might_ have the situation where the client
+ * side discards too few or too many rows, causing out of sync
+ * issues.
+ *
+ * This could probably be done for full repaints also to simplify
+ * the client side.
+ */
+ int pageBufferLastIndex = pageBufferFirstIndex
+ + pageBuffer[CELL_ITEMID].length - 1;
+ target.addAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST,
+ pageBufferFirstIndex);
+ target.addAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_LAST,
+ pageBufferLastIndex);
} else if (target.isFullRepaint() || isRowCacheInvalidated()) {
paintRows(target, cells, actionSet);
setRowCacheInvalidated(false);
@@ -2561,20 +2758,19 @@ public class Table extends AbstractSelect implements Action.Container,
target.startTag("prows");
- int maxRows = (int) (getPageLength() * getCacheRate());
- if (!shouldHideAddedRows() && count > maxRows) {
- count = maxRows + 1;
- // delete the rows below, since they will fall beyond the cache
- // page.
- target.addAttribute("delbelow", true);
- }
-
- target.addAttribute("firstprowix", firstIx);
- target.addAttribute("numprows", count);
-
if (!shouldHideAddedRows()) {
+ logger.finest("Paint rows for add. Index: " + firstIx + ", count: "
+ + count + ".");
+
// Partial row additions bypass the normal caching mechanism.
Object[][] cells = getVisibleCellsInsertIntoCache(firstIx, count);
+ if (cells[0].length < count) {
+ // delete the rows below, since they will fall beyond the cache
+ // page.
+ target.addAttribute("delbelow", true);
+ count = cells[0].length;
+ }
+
for (int indexInRowbuffer = 0; indexInRowbuffer < count; indexInRowbuffer++) {
final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
if (shouldHideNullSelectionItem()) {
@@ -2587,9 +2783,14 @@ public class Table extends AbstractSelect implements Action.Container,
indexInRowbuffer, itemId);
}
} else {
+ logger.finest("Paint rows for remove. Index: " + firstIx
+ + ", count: " + count + ".");
removeRowsFromCacheAndFillBottom(firstIx, count);
target.addAttribute("hide", true);
}
+
+ target.addAttribute("firstprowix", firstIx);
+ target.addAttribute("numprows", count);
target.endTag("prows");
}
@@ -3251,6 +3452,9 @@ public class Table extends AbstractSelect implements Action.Container,
if (!actionHandlers.contains(actionHandler)) {
actionHandlers.add(actionHandler);
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the action
+ // handlers.
refreshRenderedCells();
}
@@ -3274,6 +3478,9 @@ public class Table extends AbstractSelect implements Action.Container,
actionMapper = null;
}
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the action
+ // handlers.
refreshRenderedCells();
}
}
@@ -3284,6 +3491,9 @@ public class Table extends AbstractSelect implements Action.Container,
public void removeAllActionHandlers() {
actionHandlers = null;
actionMapper = null;
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the action
+ // handlers.
refreshRenderedCells();
}
@@ -3302,13 +3512,17 @@ public class Table extends AbstractSelect implements Action.Container,
|| event.getProperty() == getPropertyDataSource()) {
super.valueChange(event);
} else {
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
containerChangeToBeRendered = true;
}
requestRepaint();
}
+ /**
+ * Clears the current page buffer. Call this before
+ * {@link #refreshRenderedCells()} to ensure that all content is updated
+ * from the properties.
+ */
protected void resetPageBuffer() {
firstToBeRenderedInClient = -1;
lastToBeRenderedInClient = -1;
@@ -3378,8 +3592,7 @@ public class Table extends AbstractSelect implements Action.Container,
currentPageFirstItemId = nextItemId;
}
if (!(items instanceof Container.ItemSetChangeNotifier)) {
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
return ret;
}
@@ -3432,8 +3645,7 @@ public class Table extends AbstractSelect implements Action.Container,
return false;
}
if (!(items instanceof Container.PropertySetChangeNotifier)) {
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
return true;
}
@@ -3519,8 +3731,7 @@ public class Table extends AbstractSelect implements Action.Container,
if (!visibleColumns.contains(id)) {
visibleColumns.add(id);
}
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
}
@@ -3551,8 +3762,7 @@ public class Table extends AbstractSelect implements Action.Container,
if (!items.getContainerPropertyIds().contains(columnId)) {
visibleColumns.remove(columnId);
}
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
return true;
} else {
return false;
@@ -3611,8 +3821,7 @@ public class Table extends AbstractSelect implements Action.Container,
// (forced in this method)
setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex(), false);
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
/**
@@ -3739,8 +3948,7 @@ public class Table extends AbstractSelect implements Action.Container,
Object itemId = ((Container.Ordered) items)
.addItemAfter(previousItemId);
if (!(items instanceof Container.ItemSetChangeNotifier)) {
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
return itemId;
}
@@ -3756,8 +3964,7 @@ public class Table extends AbstractSelect implements Action.Container,
Item item = ((Container.Ordered) items).addItemAfter(previousItemId,
newItemId);
if (!(items instanceof Container.ItemSetChangeNotifier)) {
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
return item;
}
@@ -3824,8 +4031,7 @@ public class Table extends AbstractSelect implements Action.Container,
this.fieldFactory = fieldFactory;
// Assure visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
/**
@@ -3867,8 +4073,7 @@ public class Table extends AbstractSelect implements Action.Container,
this.editable = editable;
// Assure visual refresh
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
}
/**
@@ -3888,8 +4093,7 @@ public class Table extends AbstractSelect implements Action.Container,
final int pageIndex = getCurrentPageFirstItemIndex();
((Container.Sortable) c).sort(propertyId, ascending);
setCurrentPageFirstItemIndex(pageIndex);
- resetPageBuffer();
- refreshRenderedCells();
+ refreshRowCache();
} else if (c != null) {
throw new UnsupportedOperationException(
@@ -3960,7 +4164,8 @@ public class Table extends AbstractSelect implements Action.Container,
if (doSort) {
sort();
- // Assures the visual refresh
+ // Assures the visual refresh. This should not be necessary as
+ // sort() calls refreshRowCache
refreshRenderedCells();
}
}
@@ -3998,10 +4203,11 @@ public class Table extends AbstractSelect implements Action.Container,
sortAscending = ascending;
if (doSort) {
sort();
+ // Assures the visual refresh. This should not be necessary as
+ // sort() calls refreshRowCache
+ refreshRenderedCells();
}
}
- // Assures the visual refresh
- refreshRenderedCells();
}
/**
@@ -4080,7 +4286,10 @@ public class Table extends AbstractSelect implements Action.Container,
*/
public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
this.cellStyleGenerator = cellStyleGenerator;
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the style generators
refreshRenderedCells();
+
}
/**
@@ -4772,6 +4981,8 @@ public class Table extends AbstractSelect implements Action.Container,
public void setItemDescriptionGenerator(ItemDescriptionGenerator generator) {
if (generator != itemDescriptionGenerator) {
itemDescriptionGenerator = generator;
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the descriptions
refreshRenderedCells();
}
}
@@ -4899,7 +5110,7 @@ public class Table extends AbstractSelect implements Action.Container,
*/
public void setRowGenerator(RowGenerator generator) {
rowGenerator = generator;
- refreshRenderedCells();
+ refreshRowCache();
}
/**
diff --git a/src/com/vaadin/ui/TreeTable.java b/src/com/vaadin/ui/TreeTable.java
index aa72793636..9cba7362ac 100644
--- a/src/com/vaadin/ui/TreeTable.java
+++ b/src/com/vaadin/ui/TreeTable.java
@@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.logging.Logger;
import com.google.gwt.user.client.ui.Tree;
import com.vaadin.data.Container;
@@ -50,6 +51,9 @@ import com.vaadin.ui.treetable.HierarchicalContainerOrderedWrapper;
@ClientWidget(VTreeTable.class)
public class TreeTable extends Table implements Hierarchical {
+ private static final Logger logger = Logger.getLogger(TreeTable.class
+ .getName());
+
private interface ContainerStrategy extends Serializable {
public int size();
@@ -220,6 +224,9 @@ public class TreeTable extends Table implements Hierarchical {
boolean removed = openItems.remove(itemId);
if (!removed) {
openItems.add(itemId);
+ logger.finest("Item " + itemId + " is now expanded");
+ } else {
+ logger.finest("Item " + itemId + " is now collapsed");
}
clearPreorderCache();
}
@@ -312,6 +319,16 @@ public class TreeTable extends Table implements Hierarchical {
private ContainerStrategy cStrategy;
private Object focusedRowId = null;
private Object hierarchyColumnId;
+
+ /**
+ * The item id that was expanded or collapsed during this request. Reset at
+ * the end of paint and only used for determining if a partial or full paint
+ * should be done.
+ *
+ * Can safely be reset to null whenever a change occurs that would prevent a
+ * partial update from rendering the correct result, e.g. rows added or
+ * removed during an expand operation.
+ */
private Object toggledItemId;
private boolean animationsEnabled;
private boolean clearFocusedRowPending;
@@ -361,6 +378,7 @@ public class TreeTable extends Table implements Hierarchical {
if (variables.containsKey("toggleCollapsed")) {
String object = (String) variables.get("toggleCollapsed");
Object itemId = itemIdMapper.get(object);
+ toggledItemId = itemId;
toggleChildVisibility(itemId);
if (variables.containsKey("selectCollapsed")) {
// ensure collapsed is selected unless opened with selection
@@ -497,7 +515,6 @@ public class TreeTable extends Table implements Hierarchical {
// ensure that page still has first item in page, DON'T clear the
// caches.
setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex(), false);
- toggledItemId = itemId;
requestRepaint();
if (isCollapsed(itemId)) {
@@ -535,6 +552,9 @@ public class TreeTable extends Table implements Hierarchical {
@Override
public void containerItemSetChange(
com.vaadin.data.Container.ItemSetChangeEvent event) {
+ // Can't do partial repaints if items are added or removed during the
+ // expand/collapse request
+ toggledItemId = null;
getContainerStrategy().containerItemSetChange(event);
super.containerItemSetChange(event);
}