diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/com/vaadin/data/util/ContainerOrderedWrapper.java | 17 | ||||
-rw-r--r-- | src/com/vaadin/data/util/FilesystemContainer.java | 5 | ||||
-rw-r--r-- | src/com/vaadin/terminal/Scrollable.java | 50 | ||||
-rw-r--r--[-rwxr-xr-x] | src/com/vaadin/terminal/gwt/client/ApplicationConnection.java | 0 | ||||
-rw-r--r--[-rwxr-xr-x] | src/com/vaadin/terminal/gwt/client/VDebugConsole.java | 0 | ||||
-rw-r--r-- | src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java | 92 | ||||
-rw-r--r-- | src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java | 2 | ||||
-rw-r--r-- | src/com/vaadin/ui/Panel.java | 40 | ||||
-rw-r--r-- | src/com/vaadin/ui/Table.java | 415 | ||||
-rw-r--r-- | src/com/vaadin/ui/TreeTable.java | 22 |
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); } |