From e85d933b25cc3c5cc85eb7eb4b13b950fd8e1569 Mon Sep 17 00:00:00 2001
From: Artur Signell
+ *
+ * Scalability of the Table is largely dictated by the container. A table does
+ * not have a limit for the number of items and is just as fast with hundreds of
+ * thousands of items as with just a few. The current GWT implementation with
+ * scrolling however limits the number of rows to around 500000, depending on
+ * the browser and the pixel height of rows.
+ *
+ * Components in a Table will not have their caption nor icon rendered.
+ *
+ * This is the default behavior.
+ */
+ EXPLICIT_DEFAULTS_ID
+ }
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#HIDDEN} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_HIDDEN = ColumnHeaderMode.HIDDEN;
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#ID} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_ID = ColumnHeaderMode.ID;
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#EXPLICIT} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT = ColumnHeaderMode.EXPLICIT;
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#EXPLICIT_DEFAULTS_ID}
+ * instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = ColumnHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ public enum RowHeaderMode {
+ /**
+ * Row caption mode: The row headers are hidden. This is the default
+ * mode.
+ */
+ HIDDEN(null),
+ /**
+ * Row caption mode: Items Id-objects toString is used as row caption.
+ */
+ ID(ItemCaptionMode.ID),
+ /**
+ * Row caption mode: Item-objects toString is used as row caption.
+ */
+ ITEM(ItemCaptionMode.ITEM),
+ /**
+ * Row caption mode: Index of the item is used as item caption. The
+ * index mode can only be used with the containers implementing the
+ * {@link com.vaadin.data.Container.Indexed} interface.
+ */
+ INDEX(ItemCaptionMode.INDEX),
+ /**
+ * Row caption mode: Item captions are explicitly specified, but if the
+ * caption is missing, the item id objects
+ * The amount of items in the array must match the amount of properties
+ * identified by {@link #getVisibleColumns()}. The possible values for the
+ * alignments include:
+ * Table
is used for representing data or components in a pageable
+ * and selectable table.
+ * toString()
is
+ * used instead.
+ */
+ EXPLICIT_DEFAULTS_ID(ItemCaptionMode.EXPLICIT_DEFAULTS_ID),
+ /**
+ * Row caption mode: Item captions are explicitly specified.
+ */
+ EXPLICIT(ItemCaptionMode.EXPLICIT),
+ /**
+ * Row caption mode: Only icons are shown, the captions are hidden.
+ */
+ ICON_ONLY(ItemCaptionMode.ICON_ONLY),
+ /**
+ * Row caption mode: Item captions are read from property specified with
+ * {@link #setItemCaptionPropertyId(Object)}.
+ */
+ PROPERTY(ItemCaptionMode.PROPERTY);
+
+ ItemCaptionMode mode;
+
+ private RowHeaderMode(ItemCaptionMode mode) {
+ this.mode = mode;
+ }
+
+ public ItemCaptionMode getItemCaptionMode() {
+ return mode;
+ }
+ }
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#HIDDEN} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_HIDDEN = RowHeaderMode.HIDDEN;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#ID} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ID = RowHeaderMode.ID;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#ITEM} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ITEM = RowHeaderMode.ITEM;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#INDEX} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_INDEX = RowHeaderMode.INDEX;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#EXPLICIT_DEFAULTS_ID}
+ * instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = RowHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#EXPLICIT} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT = RowHeaderMode.EXPLICIT;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#ICON_ONLY} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ICON_ONLY = RowHeaderMode.ICON_ONLY;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#PROPERTY} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_PROPERTY = RowHeaderMode.PROPERTY;
+
+ /**
+ * The default rate that table caches rows for smooth scrolling.
+ */
+ private static final double CACHE_RATE_DEFAULT = 2;
+
+ private static final String ROW_HEADER_COLUMN_KEY = "0";
+ private static final Object ROW_HEADER_FAKE_PROPERTY_ID = new UniqueSerializable() {
+ };
+
+ /* Private table extensions to Select */
+
+ /**
+ * True if column collapsing is allowed.
+ */
+ private boolean columnCollapsingAllowed = false;
+
+ /**
+ * True if reordering of columns is allowed on the client side.
+ */
+ private boolean columnReorderingAllowed = false;
+
+ /**
+ * Keymapper for column ids.
+ */
+ private final KeyMapper
+ *
+ * The alignments default to {@link Align#LEFT}
+ *
+ * Column can either have a fixed width or expand ratio. The latter one set + * is used. See @link {@link #setColumnExpandRatio(Object, float)}. + * + * @param propertyId + * colunmns property id + * @param width + * width to be reserved for colunmns content + * @since 4.0.3 + */ + public void setColumnWidth(Object propertyId, int width) { + if (propertyId == null) { + // Since propertyId is null, this is the row header. Use the magic + // id to store the width of the row header. + propertyId = ROW_HEADER_FAKE_PROPERTY_ID; + } + if (width < 0) { + columnWidths.remove(propertyId); + } else { + columnWidths.put(propertyId, Integer.valueOf(width)); + } + requestRepaint(); + } + + /** + * Sets the column expand ratio for given column. + *
+ * Expand ratios can be defined to customize the way how excess space is + * divided among columns. Table can have excess space if it has its width + * defined and there is horizontally more space than columns consume + * naturally. Excess space is the space that is not used by columns with + * explicit width (see {@link #setColumnWidth(Object, int)}) or with natural + * width (no width nor expand ratio). + * + *
+ * By default (without expand ratios) the excess space is divided + * proportionally to columns natural widths. + * + *
+ * Only expand ratios of visible columns are used in final calculations. + * + *
+ * Column can either have a fixed width or expand ratio. The latter one set + * is used. + * + *
+ * A column with expand ratio is considered to be minimum width by default + * (if no excess space exists). The minimum width is defined by terminal + * implementation. + * + *
+ * If terminal implementation supports re-sizable columns the column becomes + * fixed width column if users resizes the column. + * + * @param propertyId + * columns property id + * @param expandRatio + * the expandRatio used to divide excess space for this column + */ + public void setColumnExpandRatio(Object propertyId, float expandRatio) { + if (expandRatio < 0) { + columnWidths.remove(propertyId); + } else { + columnWidths.put(propertyId, new Float(expandRatio)); + } + } + + public float getColumnExpandRatio(Object propertyId) { + final Object width = columnWidths.get(propertyId); + if (width == null || !(width instanceof Float)) { + return -1; + } + final Float value = (Float) width; + return value.floatValue(); + + } + + /** + * Gets the pixel width of column + * + * @param propertyId + * @return width of column or -1 when value not set + */ + public int getColumnWidth(Object propertyId) { + if (propertyId == null) { + // Since propertyId is null, this is the row header. Use the magic + // id to retrieve the width of the row header. + propertyId = ROW_HEADER_FAKE_PROPERTY_ID; + } + final Object width = columnWidths.get(propertyId); + if (width == null || !(width instanceof Integer)) { + return -1; + } + final Integer value = (Integer) width; + return value.intValue(); + } + + /** + * Gets the page length. + * + *
+ * Setting page length 0 disables paging. + *
+ * + * @return the Length of one page. + */ + public int getPageLength() { + return pageLength; + } + + /** + * Sets the page length. + * + *+ * Setting page length 0 disables paging. The page length defaults to 15. + *
+ * + *+ * If Table has width set ({@link #setWidth(float, int)} ) the client side + * may update the page length automatically the correct value. + *
+ * + * @param pageLength + * the length of one page. + */ + public void setPageLength(int pageLength) { + if (pageLength >= 0 && this.pageLength != pageLength) { + this.pageLength = pageLength; + // Assures the visual refresh + refreshRowCache(); + } + } + + /** + * This method adjusts a possible caching mechanism of table implementation. + * + *+ * Table component may fetch and render some rows outside visible area. With + * complex tables (for example containing layouts and components), the + * client side may become unresponsive. Setting the value lower, UI will + * become more responsive. With higher values scrolling in client will hit + * server less frequently. + * + *
+ * The amount of cached rows will be cacheRate multiplied with pageLength ( + * {@link #setPageLength(int)} both below and above visible area.. + * + * @param cacheRate + * a value over 0 (fastest rendering time). Higher value will + * cache more rows on server (smoother scrolling). Default value + * is 2. + */ + public void setCacheRate(double cacheRate) { + if (cacheRate < 0) { + throw new IllegalArgumentException( + "cacheRate cannot be less than zero"); + } + if (this.cacheRate != cacheRate) { + this.cacheRate = cacheRate; + requestRepaint(); + } + } + + /** + * @see #setCacheRate(double) + * + * @return the current cache rate value + */ + public double getCacheRate() { + return cacheRate; + } + + /** + * Getter for property currentPageFirstItem. + * + * @return the Value of property currentPageFirstItem. + */ + public Object getCurrentPageFirstItemId() { + + // Priorise index over id if indexes are supported + if (items instanceof Container.Indexed) { + final int index = getCurrentPageFirstItemIndex(); + Object id = null; + if (index >= 0 && index < size()) { + id = getIdByIndex(index); + } + if (id != null && !id.equals(currentPageFirstItemId)) { + currentPageFirstItemId = id; + } + } + + // If there is no item id at all, use the first one + if (currentPageFirstItemId == null) { + currentPageFirstItemId = firstItemId(); + } + + return currentPageFirstItemId; + } + + protected Object getIdByIndex(int index) { + return ((Container.Indexed) items).getIdByIndex(index); + } + + /** + * Setter for property currentPageFirstItemId. + * + * @param currentPageFirstItemId + * the New value of property currentPageFirstItemId. + */ + public void setCurrentPageFirstItemId(Object currentPageFirstItemId) { + + // Gets the corresponding index + int index = -1; + if (items instanceof Container.Indexed) { + index = indexOfId(currentPageFirstItemId); + } else { + // If the table item container does not have index, we have to + // calculates the index by hand + Object id = firstItemId(); + while (id != null && !id.equals(currentPageFirstItemId)) { + index++; + id = nextItemId(id); + } + if (id == null) { + index = -1; + } + } + + // If the search for item index was successful + if (index >= 0) { + /* + * The table is not capable of displaying an item in the container + * as the first if there are not enough items following the selected + * item so the whole table (pagelength) is filled. + */ + int maxIndex = size() - pageLength; + if (maxIndex < 0) { + maxIndex = 0; + } + + if (index > maxIndex) { + // Note that we pass index, not maxIndex, letting + // setCurrentPageFirstItemIndex handle the situation. + setCurrentPageFirstItemIndex(index); + return; + } + + this.currentPageFirstItemId = currentPageFirstItemId; + currentPageFirstItemIndex = index; + } + + // Assures the visual refresh + refreshRowCache(); + + } + + protected int indexOfId(Object itemId) { + return ((Container.Indexed) items).indexOfId(itemId); + } + + /** + * Gets the icon Resource for the specified column. + * + * @param propertyId + * the propertyId indentifying the column. + * @return the icon for the specified column; null if the column has no icon + * set, or if the column is not visible. + */ + public Resource getColumnIcon(Object propertyId) { + return columnIcons.get(propertyId); + } + + /** + * Sets the icon Resource for the specified column. + *
+ * Throws IllegalArgumentException if the specified column is not visible. + *
+ * + * @param propertyId + * the propertyId identifying the column. + * @param icon + * the icon Resource to set. + */ + public void setColumnIcon(Object propertyId, Resource icon) { + + if (icon == null) { + columnIcons.remove(propertyId); + } else { + columnIcons.put(propertyId, icon); + } + + requestRepaint(); + } + + /** + * Gets the header for the specified column. + * + * @param propertyId + * the propertyId identifying the column. + * @return the header for the specified column if it has one. + */ + public String getColumnHeader(Object propertyId) { + if (getColumnHeaderMode() == ColumnHeaderMode.HIDDEN) { + return null; + } + + String header = columnHeaders.get(propertyId); + if ((header == null && getColumnHeaderMode() == ColumnHeaderMode.EXPLICIT_DEFAULTS_ID) + || getColumnHeaderMode() == ColumnHeaderMode.ID) { + header = propertyId.toString(); + } + + return header; + } + + /** + * Sets the column header for the specified column; + * + * @param propertyId + * the propertyId identifying the column. + * @param header + * the header to set. + */ + public void setColumnHeader(Object propertyId, String header) { + + if (header == null) { + columnHeaders.remove(propertyId); + } else { + columnHeaders.put(propertyId, header); + } + + requestRepaint(); + } + + /** + * Gets the specified column's alignment. + * + * @param propertyId + * the propertyID identifying the column. + * @return the specified column's alignment if it as one; null otherwise. + */ + public Align getColumnAlignment(Object propertyId) { + final Align a = columnAlignments.get(propertyId); + return a == null ? Align.LEFT : a; + } + + /** + * Sets the specified column's alignment. + * + *+ * Throws IllegalArgumentException if the alignment is not one of the + * following: {@link Align#LEFT}, {@link Align#CENTER} or + * {@link Align#RIGHT} + *
+ * + * @param propertyId + * the propertyID identifying the column. + * @param alignment + * the desired alignment. + */ + public void setColumnAlignment(Object propertyId, Align alignment) { + if (alignment == null || alignment == Align.LEFT) { + columnAlignments.remove(propertyId); + } else { + columnAlignments.put(propertyId, alignment); + } + + // Assures the visual refresh. No need to reset the page buffer before + // as the content has not changed, only the alignments. + refreshRenderedCells(); + } + + /** + * Checks if the specified column is collapsed. + * + * @param propertyId + * the propertyID identifying the column. + * @return true if the column is collapsed; false otherwise; + */ + public boolean isColumnCollapsed(Object propertyId) { + return collapsedColumns != null + && collapsedColumns.contains(propertyId); + } + + /** + * Sets whether the specified column is collapsed or not. + * + * + * @param propertyId + * the propertyID identifying the column. + * @param collapsed + * the desired collapsedness. + * @throws IllegalStateException + * if column collapsing is not allowed + */ + public void setColumnCollapsed(Object propertyId, boolean collapsed) + throws IllegalStateException { + if (!isColumnCollapsingAllowed()) { + throw new IllegalStateException("Column collapsing not allowed!"); + } + if (collapsed && noncollapsibleColumns.contains(propertyId)) { + throw new IllegalStateException("The column is noncollapsible!"); + } + + if (collapsed) { + collapsedColumns.add(propertyId); + } else { + collapsedColumns.remove(propertyId); + } + + // Assures the visual refresh + refreshRowCache(); + } + + /** + * Checks if column collapsing is allowed. + * + * @return true if columns can be collapsed; false otherwise. + */ + public boolean isColumnCollapsingAllowed() { + return columnCollapsingAllowed; + } + + /** + * Sets whether column collapsing is allowed or not. + * + * @param collapsingAllowed + * specifies whether column collapsing is allowed. + */ + public void setColumnCollapsingAllowed(boolean collapsingAllowed) { + columnCollapsingAllowed = collapsingAllowed; + + if (!collapsingAllowed) { + collapsedColumns.clear(); + } + + // Assures the visual refresh. No need to reset the page buffer before + // as the content has not changed, only the alignments. + refreshRenderedCells(); + } + + /** + * Sets whether the given column is collapsible. Note that collapsible + * columns can only be actually collapsed (via UI or with + * {@link #setColumnCollapsed(Object, boolean) setColumnCollapsed()}) if + * {@link #isColumnCollapsingAllowed()} is true. By default all columns are + * collapsible. + * + * @param propertyId + * the propertyID identifying the column. + * @param collapsible + * true if the column should be collapsible, false otherwise. + */ + public void setColumnCollapsible(Object propertyId, boolean collapsible) { + if (collapsible) { + noncollapsibleColumns.remove(propertyId); + } else { + noncollapsibleColumns.add(propertyId); + collapsedColumns.remove(propertyId); + } + refreshRowCache(); + } + + /** + * Checks if the given column is collapsible. Note that even if this method + * returnstrue
, the column can only be actually collapsed (via
+ * UI or with {@link #setColumnCollapsed(Object, boolean)
+ * setColumnCollapsed()}) if {@link #isColumnCollapsingAllowed()} is also
+ * true.
+ *
+ * @return true if the column can be collapsed; false otherwise.
+ */
+ public boolean isColumnCollapsible(Object propertyId) {
+ return !noncollapsibleColumns.contains(propertyId);
+ }
+
+ /**
+ * Checks if column reordering is allowed.
+ *
+ * @return true if columns can be reordered; false otherwise.
+ */
+ public boolean isColumnReorderingAllowed() {
+ return columnReorderingAllowed;
+ }
+
+ /**
+ * Sets whether column reordering is allowed or not.
+ *
+ * @param columnReorderingAllowed
+ * specifies whether column reordering is allowed.
+ */
+ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
+ if (columnReorderingAllowed != this.columnReorderingAllowed) {
+ this.columnReorderingAllowed = columnReorderingAllowed;
+ requestRepaint();
+ }
+ }
+
+ /*
+ * Arranges visible columns according to given columnOrder. Silently ignores
+ * colimnId:s that are not visible columns, and keeps the internal order of
+ * visible columns left out of the ordering (trailing). Silently does
+ * nothing if columnReordering is not allowed.
+ */
+ private void setColumnOrder(Object[] columnOrder) {
+ if (columnOrder == null || !isColumnReorderingAllowed()) {
+ return;
+ }
+ final LinkedList+ * The table is not selectable by default. + *
+ * + * @return the Value of property selectable. + */ + public boolean isSelectable() { + return selectable; + } + + /** + * Setter for property selectable. + * + *+ * The table is not selectable by default. + *
+ * + * @param selectable + * the New value of property selectable. + */ + public void setSelectable(boolean selectable) { + if (this.selectable != selectable) { + this.selectable = selectable; + requestRepaint(); + } + } + + /** + * Getter for property columnHeaderMode. + * + * @return the Value of property columnHeaderMode. + */ + public ColumnHeaderMode getColumnHeaderMode() { + return columnHeaderMode; + } + + /** + * Setter for property columnHeaderMode. + * + * @param columnHeaderMode + * the New value of property columnHeaderMode. + */ + public void setColumnHeaderMode(ColumnHeaderMode columnHeaderMode) { + if (columnHeaderMode == null) { + throw new IllegalArgumentException( + "Column header mode can not be null"); + } + if (columnHeaderMode != this.columnHeaderMode) { + this.columnHeaderMode = columnHeaderMode; + requestRepaint(); + } + + } + + /** + * 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) { + return; + } + + if (!isContentRefreshesEnabled) { + return; + } + + // Collects the basic facts about the table page + final int pagelen = getPageLength(); + int rows, totalRows; + rows = totalRows = size(); + int firstIndex = Math + .min(getCurrentPageFirstItemIndex(), totalRows - 1); + if (rows > 0 && firstIndex >= 0) { + rows -= firstIndex; + } + if (pagelen > 0 && pagelen < rows) { + rows = pagelen; + } + + // If "to be painted next" variables are set, use them + if (lastToBeRenderedInClient - firstToBeRenderedInClient > 0) { + rows = lastToBeRenderedInClient - firstToBeRenderedInClient + 1; + } + if (firstToBeRenderedInClient >= 0) { + if (firstToBeRenderedInClient < totalRows) { + firstIndex = firstToBeRenderedInClient; + } else { + firstIndex = totalRows - 1; + } + } else { + // initial load + + // #8805 send one extra row in the beginning in case a partial + // row is shown on the UI + if (firstIndex > 0) { + firstIndex = firstIndex - 1; + rows = rows + 1; + } + firstToBeRenderedInClient = firstIndex; + } + if (totalRows > 0) { + if (rows + firstIndex > totalRows) { + rows = totalRows - firstIndex; + } + } else { + rows = 0; + } + + // Saves the results to internal buffer + pageBuffer = getVisibleCellsNoCache(firstIndex, rows, true); + + if (rows > 0) { + pageBufferFirstIndex = firstIndex; + } + + setRowCacheInvalidated(true); + 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(); + } + + @Override + public void requestRepaintAll() { + super.requestRepaintAll(); + + // Avoid sending a partial repaint (#8714) + refreshRowCache(); + } + + private void removeRowsFromCacheAndFillBottom(int firstIndex, int rows) { + int totalCachedRows = pageBuffer[CELL_ITEMID].length; + int totalRows = size(); + int firstIndexInPageBuffer = firstIndex - pageBufferFirstIndex; + + /* + * 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); + + /* + * 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 = 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 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; + } + + private Object[][] getVisibleCellsUpdateCacheRows(int firstIndex, int rows) { + Object[][] cells = getVisibleCellsNoCache(firstIndex, rows, false); + int cacheIx = firstIndex - pageBufferFirstIndex; + // update the new rows in the cache. + int totalCachedRows = pageBuffer[CELL_ITEMID].length; + int end = Math.min(cacheIx + rows, totalCachedRows); + for (int ix = cacheIx; ix < end; ix++) { + for (int i = 0; i < pageBuffer.length; i++) { + pageBuffer[i][ix] = cells[i][ix - cacheIx]; + } + } + 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) { + getLogger().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; + + /* + * 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 (maxBufferSize == 0 || currentlyCachedRowCount < maxBufferSize) { + newCachedRowCount = currentlyCachedRowCount + rows; + if (maxBufferSize > 0 && newCachedRowCount > maxBufferSize) { + newCachedRowCount = maxBufferSize; + } + } + + /* 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 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; + getLogger().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) { + getLogger().finest( + "Render visible cells for rows " + firstIndex + "-" + + (firstIndex + rows - 1)); + final Object[] colids = getVisibleColumns(); + final int cols = colids.length; + + HashSet+ * The mode can be one of the following ones: + *
toString()
+ * is used as row caption.
+ * toString()
+ * is used as row caption.
+ * toString()
is used as row header. If caption is explicitly
+ * specified, it overrides the id-caption.
+ * Container.Indexed
interface.+ * 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. + *
+ * 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. + *
+ * Note that calling this method is not cheap so avoid calling it
+ * unnecessarily.
+ *
+ * @since 6.7.2
+ */
+ public void refreshRowCache() {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ @Override
+ public void setContainerDataSource(Container newDataSource) {
+
+ disableContentRefreshing();
+
+ if (newDataSource == null) {
+ newDataSource = new IndexedContainer();
+ }
+
+ // Assures that the data source is ordered by making unordered
+ // containers ordered by wrapping them
+ if (newDataSource instanceof Container.Ordered) {
+ super.setContainerDataSource(newDataSource);
+ } else {
+ super.setContainerDataSource(new ContainerOrderedWrapper(
+ newDataSource));
+ }
+
+ // Resets page position
+ currentPageFirstItemId = null;
+ currentPageFirstItemIndex = 0;
+
+ // Resets column properties
+ if (collapsedColumns != null) {
+ collapsedColumns.clear();
+ }
+
+ // columnGenerators 'override' properties, don't add the same id twice
+ Collection
+ * A generated column is a column that exists only in the Table, not as a
+ * property in the underlying Container. It shows up just as a regular
+ * column.
+ *
+ * A generated column will override a property with the same id, so that the
+ * generated column is shown instead of the column representing the
+ * property. Note that getContainerProperty() will still get the real
+ * property.
+ *
+ * Table will not listen to value change events from properties overridden
+ * by generated columns. If the content of your generated column depends on
+ * properties that are not directly visible in the table, attach value
+ * change listener to update the content on all depended properties.
+ * Otherwise your UI might not get updated as expected.
+ *
+ * Also note that getVisibleColumns() will return the generated columns,
+ * while getContainerPropertyIds() will not.
+ *
+ * Note, that some due to historical reasons the name of the method is bit
+ * misleading. Some items may be partly or totally out of the viewport of
+ * the table's scrollable area. Actually detecting rows which can be
+ * actually seen by the end user may be problematic due to the client server
+ * architecture. Using {@link #getCurrentPageFirstItemId()} combined with
+ * {@link #getPageLength()} may produce good enough estimates in some
+ * situations.
+ *
+ * @see com.vaadin.ui.Select#getVisibleItemIds()
+ */
+
+ @Override
+ public Collection> getVisibleItemIds() {
+
+ final LinkedList
+ * Note that the {@link #isSortEnabled()} state affects what this method
+ * returns. Disabling sorting causes this method to always return an empty
+ * collection.
+ *
+ * Setting this to false disallows sorting by the user. It is still possible
+ * to call {@link #sort()}.
+ *
+ * Note, that on some clients the mode may not be respected. E.g. on touch
+ * based devices CTRL/SHIFT base selection method is invalid, so touch based
+ * browsers always use the {@link MultiSelectMode#SIMPLE}.
+ *
+ * @param mode
+ * The select mode of the table
+ */
+ public void setMultiSelectMode(MultiSelectMode mode) {
+ multiSelectMode = mode;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the select mode in which multi-select is used.
+ *
+ * @return The multi select mode
+ */
+ public MultiSelectMode getMultiSelectMode() {
+ return multiSelectMode;
+ }
+
+ /**
+ * Lazy loading accept criterion for Table. Accepted target rows are loaded
+ * from server once per drag and drop operation. Developer must override one
+ * method that decides on which rows the currently dragged data can be
+ * dropped.
+ *
+ *
+ * Initially pretty much no data is sent to client. On first required
+ * criterion check (per drag request) the client side data structure is
+ * initialized from server and no subsequent requests requests are needed
+ * during that drag and drop operation.
+ */
+ public static abstract class TableDropCriterion extends ServerSideCriterion {
+
+ private Table table;
+
+ private Set
+ * The listener will receive events which contain information about which
+ * column was clicked and some details about the mouse event.
+ *
+ * The listener will receive events which contain information about which
+ * column was clicked and some details about the mouse event.
+ *
+ * The footer can be used to add column related data like sums to the bottom
+ * of the Table using setColumnFooter(Object propertyId, String footer).
+ *
+ * The GeneratedRow data object contains the text that should be
+ * rendered in the row. The itemId in the container thus works only as a
+ * placeholder.
+ *
+ * If GeneratedRow.setSpanColumns(true) is used, there will be one
+ * String spanning all columns (use setText("Spanning text")). Otherwise
+ * you can define one String per visible column.
+ *
+ * If GeneratedRow.setRenderAsHtml(true) is used, the strings can
+ * contain HTML markup, otherwise all strings will be rendered as text
+ * (the default).
+ *
+ * A "v-table-generated-row" CSS class is added to all generated rows.
+ * For custom styling of a generated row you can combine a RowGenerator
+ * with a CellStyleGenerator.
+ *
+ *
+ * @param table
+ * The Table that is being painted
+ * @param itemId
+ * The itemId for the row
+ * @return A GeneratedRow describing how the row should be painted or
+ * null to paint the row with the contents from the container
+ */
+ public GeneratedRow generateRow(Table table, Object itemId);
+ }
+
+ public static class GeneratedRow implements Serializable {
+ private boolean htmlContentAllowed = false;
+ private boolean spanColumns = false;
+ private String[] text = null;
+
+ /**
+ * Creates a new generated row. If only one string is passed in, columns
+ * are automatically spanned.
+ *
+ * @param text
+ */
+ public GeneratedRow(String... text) {
+ setHtmlContentAllowed(false);
+ setSpanColumns(text == null || text.length == 1);
+ setText(text);
+ }
+
+ /**
+ * Pass one String if spanColumns is used, one String for each visible
+ * column otherwise
+ */
+ public void setText(String... text) {
+ if (text == null || (text.length == 1 && text[0] == null)) {
+ text = new String[] { "" };
+ }
+ this.text = text;
+ }
+
+ protected String[] getText() {
+ return text;
+ }
+
+ protected Object getValue() {
+ return getText();
+ }
+
+ protected boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+
+ /**
+ * If set to true, all strings passed to {@link #setText(String...)}
+ * will be rendered as HTML.
+ *
+ * @param htmlContentAllowed
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ }
+
+ protected boolean isSpanColumns() {
+ return spanColumns;
+ }
+
+ /**
+ * If set to true, only one string will be rendered, spanning the entire
+ * row.
+ *
+ * @param spanColumns
+ */
+ public void setSpanColumns(boolean spanColumns) {
+ this.spanColumns = spanColumns;
+ }
+ }
+
+ /**
+ * Assigns a row generator to the table. The row generator will be able to
+ * replace rows in the table when it is rendered.
+ *
+ * @param generator
+ * the new row generator
+ */
+ public void setRowGenerator(RowGenerator generator) {
+ rowGenerator = generator;
+ refreshRowCache();
+ }
+
+ /**
+ * @return the current row generator
+ */
+ public RowGenerator getRowGenerator() {
+ return rowGenerator;
+ }
+
+ /**
+ * Sets a converter for a property id.
+ *
+ * The converter is used to format the the data for the given property id
+ * before displaying it in the table.
+ * ItemId
from the Container.
+ *
+ * @see com.vaadin.data.Container#removeItem(Object)
+ */
+
+ @Override
+ public boolean removeItem(Object itemId) {
+ final Object nextItemId = nextItemId(itemId);
+ final boolean ret = super.removeItem(itemId);
+ if (ret && (itemId != null) && (itemId.equals(currentPageFirstItemId))) {
+ currentPageFirstItemId = nextItemId;
+ }
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return ret;
+ }
+
+ /**
+ * Removes a Property specified by the given Property ID from the Container.
+ *
+ * @see com.vaadin.data.Container#removeContainerProperty(Object)
+ */
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+
+ // If a visible property is removed, remove the corresponding column
+ visibleColumns.remove(propertyId);
+ columnAlignments.remove(propertyId);
+ columnIcons.remove(propertyId);
+ columnHeaders.remove(propertyId);
+ columnFooters.remove(propertyId);
+
+ return super.removeContainerProperty(propertyId);
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @param propertyId
+ * the Id of the proprty.
+ * @param type
+ * the class of the property.
+ * @param defaultValue
+ * the default value given for all existing items.
+ * @see com.vaadin.data.Container#addContainerProperty(Object, Class,
+ * Object)
+ */
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class> type,
+ Object defaultValue) throws UnsupportedOperationException {
+
+ boolean visibleColAdded = false;
+ if (!visibleColumns.contains(propertyId)) {
+ visibleColumns.add(propertyId);
+ visibleColAdded = true;
+ }
+
+ if (!super.addContainerProperty(propertyId, type, defaultValue)) {
+ if (visibleColAdded) {
+ visibleColumns.remove(propertyId);
+ }
+ return false;
+ }
+ if (!(items instanceof Container.PropertySetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return true;
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @param propertyId
+ * the Id of the proprty
+ * @param type
+ * the class of the property
+ * @param defaultValue
+ * the default value given for all existing items
+ * @param columnHeader
+ * the Explicit header of the column. If explicit header is not
+ * needed, this should be set null.
+ * @param columnIcon
+ * the Icon of the column. If icon is not needed, this should be
+ * set null.
+ * @param columnAlignment
+ * the Alignment of the column. Null implies align left.
+ * @throws UnsupportedOperationException
+ * if the operation is not supported.
+ * @see com.vaadin.data.Container#addContainerProperty(Object, Class,
+ * Object)
+ */
+ public boolean addContainerProperty(Object propertyId, Class> type,
+ Object defaultValue, String columnHeader, Resource columnIcon,
+ Align columnAlignment) throws UnsupportedOperationException {
+ if (!this.addContainerProperty(propertyId, type, defaultValue)) {
+ return false;
+ }
+ setColumnAlignment(propertyId, columnAlignment);
+ setColumnHeader(propertyId, columnHeader);
+ setColumnIcon(propertyId, columnIcon);
+ return true;
+ }
+
+ /**
+ * Adds a generated column to the Table.
+ * true
if ascending, false
if descending.
+ */
+ public boolean isSortAscending() {
+ return sortAscending;
+ }
+
+ /**
+ * Sets the table in ascending order.
+ *
+ * @param ascending
+ * true
if ascending, false
if
+ * descending.
+ */
+ public void setSortAscending(boolean ascending) {
+ setSortAscending(ascending, true);
+ }
+
+ /**
+ * Internal method to set sort ascending. With doSort flag actual sort can
+ * be bypassed.
+ *
+ * @param ascending
+ * @param doSort
+ */
+ private void setSortAscending(boolean ascending, boolean doSort) {
+ if (sortAscending != ascending) {
+ sortAscending = ascending;
+ if (doSort) {
+ sort();
+ // Assures the visual refresh. This should not be necessary as
+ // sort() calls refreshRowCache
+ refreshRenderedCells();
+ }
+ }
+ }
+
+ /**
+ * Is sorting disabled altogether.
+ *
+ * True iff no sortable columns are given even in the case where data source
+ * would support this.
+ *
+ * @return True iff sorting is disabled.
+ * @deprecated Use {@link #isSortEnabled()} instead
+ */
+ @Deprecated
+ public boolean isSortDisabled() {
+ return !isSortEnabled();
+ }
+
+ /**
+ * Checks if sorting is enabled.
+ *
+ * @return true if sorting by the user is allowed, false otherwise
+ */
+ public boolean isSortEnabled() {
+ return sortEnabled;
+ }
+
+ /**
+ * Disables the sorting by the user altogether.
+ *
+ * @param sortDisabled
+ * True iff sorting is disabled.
+ * @deprecated Use {@link #setSortEnabled(boolean)} instead
+ */
+ @Deprecated
+ public void setSortDisabled(boolean sortDisabled) {
+ setSortEnabled(!sortDisabled);
+ }
+
+ /**
+ * Enables or disables sorting.
+ *