diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/ComboBoxListViewSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/ComboBoxListViewSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/ComboBoxListViewSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/ComboBoxListViewSkin.java @@ -25,7 +25,6 @@ package javafx.scene.control.skin; -import com.sun.javafx.scene.control.behavior.BehaviorBase; import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior; import com.sun.javafx.scene.control.behavior.ComboBoxListViewBehavior; @@ -46,8 +45,6 @@ import javafx.scene.AccessibleRole; import javafx.scene.Node; import javafx.scene.Parent; -import javafx.scene.control.Accordion; -import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.Control; import javafx.scene.control.ListCell; @@ -502,7 +499,7 @@ if (getSkin() instanceof ListViewSkin) { ListViewSkin skin = (ListViewSkin)getSkin(); if (itemCountDirty) { - skin.updateRowCount(); + skin.updateItemCount(); itemCountDirty = false; } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java @@ -29,7 +29,6 @@ import java.util.List; import com.sun.javafx.scene.control.Properties; -import com.sun.javafx.scene.control.behavior.BehaviorBase; import javafx.beans.InvalidationListener; import javafx.beans.WeakInvalidationListener; import javafx.collections.FXCollections; @@ -43,7 +42,6 @@ import javafx.scene.AccessibleAction; import javafx.scene.AccessibleAttribute; import javafx.scene.Node; -import javafx.scene.control.Button; import javafx.scene.control.Control; import javafx.scene.control.FocusModel; import javafx.scene.control.IndexedCell; @@ -157,7 +155,7 @@ // fix for RT-37853 getSkinnable().edit(-1); - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); } }; @@ -228,7 +226,7 @@ flow.getVbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml); flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml); - updateRowCount(); + updateItemCount(); control.itemsProperty().addListener(new WeakInvalidationListener(itemsChangeListener)); @@ -317,12 +315,12 @@ } /** {@inheritDoc} */ - @Override int getItemCount() { + @Override protected int getItemCount() { return itemCount; } /** {@inheritDoc} */ - @Override void updateRowCount() { + @Override protected void updateItemCount() { if (flow == null) return; int oldCount = itemCount; @@ -450,7 +448,7 @@ listViewItems.addListener(weakListViewItemsListener); } - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkin.java @@ -26,24 +26,23 @@ package javafx.scene.control.skin; import com.sun.javafx.scene.control.behavior.BehaviorBase; +import javafx.beans.property.ReadOnlyObjectProperty; import javafx.scene.Node; -import javafx.scene.control.Accordion; -import javafx.scene.control.Button; -import javafx.scene.control.Control; -import javafx.scene.control.TableCell; +import javafx.scene.control.*; import com.sun.javafx.scene.control.behavior.TableCellBehavior; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyDoubleProperty; -import javafx.scene.control.TableColumn; /** * Default skin implementation for the {@link TableCell} control. * + * @param The type of the UI control (e.g. the type of the 'row'). + * @param The type of the content in the cell, based on its {@link TableColumn}. * @see TableCell * @since 9 */ -public class TableCellSkin extends TableCellSkinBase> { +public class TableCellSkin extends TableCellSkinBase> { /*************************************************************************** * * @@ -94,12 +93,7 @@ } /** {@inheritDoc} */ - @Override BooleanProperty columnVisibleProperty() { - return getSkinnable().getTableColumn().visibleProperty(); - } - - /** {@inheritDoc} */ - @Override ReadOnlyDoubleProperty columnWidthProperty() { - return getSkinnable().getTableColumn().widthProperty(); + @Override public ReadOnlyObjectProperty> tableColumnProperty() { + return getSkinnable().tableColumnProperty(); } } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkinBase.java b/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkinBase.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkinBase.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TableCellSkinBase.java @@ -30,21 +30,25 @@ import javafx.beans.WeakInvalidationListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyObjectProperty; import javafx.scene.Node; import javafx.scene.control.Accordion; import javafx.scene.control.Control; import javafx.scene.control.IndexedCell; +import javafx.scene.control.TableColumnBase; import javafx.scene.shape.Rectangle; /** * Base skin for table cell controls, for example: * {@link javafx.scene.control.TableCell} and {@link javafx.scene.control.TreeTableCell}. * + * @param The type of the UI control (e.g. the type of the 'row'). + * @param The type of the content in the cell, based on its {@link TableColumnBase}. * @see javafx.scene.control.TableCell * @see javafx.scene.control.TreeTableCell * @since 9 */ -public abstract class TableCellSkinBase extends CellSkinBase { +public abstract class TableCellSkinBase> extends CellSkinBase { /*************************************************************************** * * @@ -79,13 +83,11 @@ getSkinnable().setClip(clip); // --- end of RT-22038 - ReadOnlyDoubleProperty columnWidthProperty = columnWidthProperty(); - if (columnWidthProperty != null) { - columnWidthProperty.addListener(weakColumnWidthListener); + TableColumnBase tableColumn = getTableColumn(); + if (tableColumn != null) { + tableColumn.widthProperty().addListener(weakColumnWidthListener); } - registerChangeListener(control.visibleProperty(), e -> getSkinnable().setVisible(columnVisibleProperty().get())); - if (control.getProperties().containsKey(Properties.DEFER_TO_PARENT_PREF_WIDTH)) { isDeferToParentForPrefWidth = true; } @@ -112,11 +114,13 @@ * * **************************************************************************/ - // Equivalent to tableColumn.widthProperty() - abstract ReadOnlyDoubleProperty columnWidthProperty(); - - // Equivalent to tableColumn.visibleProperty() - abstract BooleanProperty columnVisibleProperty(); + /** + * The TableColumnBase instance that is responsible for this Cell. + */ + public abstract ReadOnlyObjectProperty> tableColumnProperty(); + public final TableColumnBase getTableColumn() { + return tableColumnProperty().get(); + } @@ -128,9 +132,9 @@ /** {@inheritDoc} */ @Override public void dispose() { - ReadOnlyDoubleProperty columnWidthProperty = columnWidthProperty(); - if (columnWidthProperty != null) { - columnWidthProperty.removeListener(weakColumnWidthListener); + TableColumnBase tableColumn = getTableColumn(); + if (tableColumn != null) { + tableColumn.widthProperty().removeListener(weakColumnWidthListener); } super.dispose(); @@ -150,6 +154,8 @@ if (isDeferToParentForPrefWidth) { return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); } - return columnWidthProperty().get(); + + TableColumnBase tableColumn = getTableColumn(); + return tableColumn == null ? 0 : tableColumn.getWidth(); } } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkin.java @@ -101,6 +101,22 @@ } } }); + + DoubleProperty fixedCellSizeProperty = getTableView().fixedCellSizeProperty(); + if (fixedCellSizeProperty != null) { + registerChangeListener(fixedCellSizeProperty, e -> { + fixedCellSize = fixedCellSizeProperty.get(); + fixedCellSizeEnabled = fixedCellSize > 0; + }); + fixedCellSize = fixedCellSizeProperty.get(); + fixedCellSizeEnabled = fixedCellSize > 0; + + // JDK-8144500: + // When in fixed cell size mode, we must listen to the width of the virtual flow, so + // that when it changes, we can appropriately add / remove cells that may or may not + // be required (because we remove all cells that are not visible). + registerChangeListener(getVirtualFlow().widthProperty(), e -> control.requestLayout()); + } } @@ -128,12 +144,12 @@ // (selectedCells could be big, cellsMap is much smaller) List selection = new ArrayList<>(); int index = getSkinnable().getIndex(); - for (TablePosition pos : getVirtualFlowOwner().getSelectionModel().getSelectedCells()) { + for (TablePosition pos : getTableView().getSelectionModel().getSelectedCells()) { if (pos.getRow() == index) { TableColumn column = pos.getTableColumn(); if (column == null) { /* This is the row-based case */ - column = getVirtualFlowOwner().getVisibleLeafColumn(0); + column = getTableView().getVisibleLeafColumn(0); } TableCell cell = cellsMap.get(column).get(); if (cell != null) selection.add(cell); @@ -143,19 +159,19 @@ } case CELL_AT_ROW_COLUMN: { int colIndex = (Integer)parameters[1]; - TableColumn column = getVirtualFlowOwner().getVisibleLeafColumn(colIndex); + TableColumn column = getTableView().getVisibleLeafColumn(colIndex); if (cellsMap.containsKey(column)) { return cellsMap.get(column).get(); } return null; } case FOCUS_ITEM: { - TableViewFocusModel fm = getVirtualFlowOwner().getFocusModel(); + TableViewFocusModel fm = getTableView().getFocusModel(); TablePosition focusedCell = fm.getFocusedCell(); TableColumn column = focusedCell.getTableColumn(); if (column == null) { /* This is the row-based case */ - column = getVirtualFlowOwner().getVisibleLeafColumn(0); + column = getTableView().getVisibleLeafColumn(0); } if (cellsMap.containsKey(column)) { return cellsMap.get(column).get(); @@ -175,7 +191,7 @@ **************************************************************************/ /** {@inheritDoc} */ - @Override TableCell getCell(TableColumnBase tcb) { + @Override protected TableCell createCell(TableColumnBase tcb) { TableColumn tableColumn = (TableColumn) tcb; TableCell cell = (TableCell) tableColumn.getCellFactory().call(tableColumn); @@ -188,37 +204,21 @@ } /** {@inheritDoc} */ - @Override ObservableList> getVisibleLeafColumns() { - return getVirtualFlowOwner().getVisibleLeafColumns(); + @Override protected ObservableList> getVisibleLeafColumns() { + return getTableView().getVisibleLeafColumns(); } /** {@inheritDoc} */ - @Override void updateCell(TableCell cell, TableRow row) { + @Override protected void updateCell(TableCell cell, TableRow row) { cell.updateTableRow(row); } /** {@inheritDoc} */ - @Override DoubleProperty fixedCellSizeProperty() { - return getVirtualFlowOwner().fixedCellSizeProperty(); - } - - /** {@inheritDoc} */ - @Override boolean isColumnPartiallyOrFullyVisible(TableColumnBase tc) { - return tableViewSkin == null ? false : tableViewSkin.isColumnPartiallyOrFullyVisible((TableColumn)tc); - } - - /** {@inheritDoc} */ - @Override TableColumn getTableColumnBase(TableCell cell) { + @Override protected TableColumn getTableColumn(TableCell cell) { return cell.getTableColumn(); } - /** {@inheritDoc} */ - @Override ObjectProperty graphicProperty() { - return null; - } - - /** {@inheritDoc} */ - @Override TableView getVirtualFlowOwner() { + private TableView getTableView() { return getSkinnable().getTableView(); } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkinBase.java b/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkinBase.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkinBase.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TableRowSkinBase.java @@ -31,6 +31,7 @@ import java.util.*; import com.sun.javafx.PlatformUtil; +import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection; import javafx.animation.FadeTransition; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -39,6 +40,7 @@ import javafx.collections.WeakListChangeListener; import javafx.css.StyleOrigin; import javafx.css.StyleableObjectProperty; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Parent; @@ -88,14 +90,14 @@ /* * This is rather hacky - but it is a quick workaround to resolve the * issue that we don't know maximum width of a disclosure node for a given - * TreeView. If we don't know the maximum width, we have no way to ensure - * consistent indentation for a given TreeView. + * control. If we don't know the maximum width, we have no way to ensure + * consistent indentation. * * To work around this, we create a single WeakHashMap to store a max - * disclosureNode width per TreeView. We use WeakHashMap to help prevent + * disclosureNode width per TableColumnBase. We use WeakHashMap to help prevent * any memory leaks. */ - static final Map maxDisclosureWidthMap = new WeakHashMap(); + static final Map, Double> maxDisclosureWidthMap = new WeakHashMap<>(); // Specifies the number of times we will call 'recreateCells()' before we blow // out the cellsMap structure and rebuild all cells. This helps to prevent @@ -132,8 +134,8 @@ boolean isDirty = false; boolean updateCells = false; - private double fixedCellSize; - private boolean fixedCellSizeEnabled; + double fixedCellSize; + boolean fixedCellSizeEnabled; @@ -177,21 +179,6 @@ requestCellUpdate(); } }); - - if (fixedCellSizeProperty() != null) { - registerChangeListener(fixedCellSizeProperty(), e -> { - fixedCellSize = fixedCellSizeProperty().get(); - fixedCellSizeEnabled = fixedCellSize > 0; - }); - fixedCellSize = fixedCellSizeProperty().get(); - fixedCellSizeEnabled = fixedCellSize > 0; - - // JDK-8144500: - // When in fixed cell size mode, we must listen to the width of the virtual flow, so - // that when it changes, we can appropriately add / remove cells that may or may not - // be required (because we remove all cells that are not visible). - registerChangeListener(getVirtualFlow().widthProperty(), e -> control.requestLayout()); - } } @@ -208,7 +195,7 @@ }; private WeakListChangeListener weakVisibleLeafColumnsListener = - new WeakListChangeListener(visibleLeafColumnsListener); + new WeakListChangeListener<>(visibleLeafColumnsListener); @@ -219,36 +206,47 @@ **************************************************************************/ /** + * Creates a new cell instance that is suitable for representing the given table column instance. + */ + protected abstract R createCell(TableColumnBase tc); + + /** + * A method to allow the given cell to be told that it is a member of the given row. + * How this is implemented is dependent on the actual cell implementation. + * @param cell The cell for which we want to inform it of its owner row. + * @param row The row which will be set on the given cell. + */ + protected abstract void updateCell(R cell, C row); + + /** + * Returns the {@link TableColumnBase} instance for the given cell instance. + * @param cell The cell for which a TableColumn is desired. + */ + protected abstract TableColumnBase getTableColumn(R cell); + + /** + * Returns an unmodifiable list containing the currently visible leaf columns. + */ + @ReturnsUnmodifiableCollection + protected abstract ObservableList*/> getVisibleLeafColumns(); + + + + /*************************************************************************** + * * + * Public Methods * + * * + **************************************************************************/ + + /** * Returns the graphic to draw on the inside of the disclosure node. Null * is acceptable when no graphic should be shown. Commonly this is the * graphic associated with a TreeItem (i.e. treeItem.getGraphic()), rather * than a graphic associated with a cell. */ - abstract ObjectProperty graphicProperty(); - - // return TableView / TreeTableView / etc - abstract Control getVirtualFlowOwner(); - - abstract ObservableList*/> getVisibleLeafColumns(); - - // cell.updateTableRow(skinnable); (i.e cell.updateTableRow(row)) - abstract void updateCell(R cell, C row); - - abstract DoubleProperty fixedCellSizeProperty(); - - abstract boolean isColumnPartiallyOrFullyVisible(TableColumnBase tc); - - abstract R getCell(TableColumnBase tc); - - abstract TableColumnBase getTableColumnBase(R cell); - - - - /*************************************************************************** - * * - * Public Methods * - * * - **************************************************************************/ + protected ObjectProperty graphicProperty() { + return null; + } /** {@inheritDoc} */ @Override protected void layoutChildren(double x, final double y, final double w, final double h) { @@ -287,9 +285,8 @@ leftMargin = indentationLevel * indentationPerLevel; // position the disclosure node so that it is at the proper indent - Control c = getVirtualFlowOwner(); - final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(c) ? - maxDisclosureWidthMap.get(c) : 0; + final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(treeColumn) ? + maxDisclosureWidthMap.get(treeColumn) : 0; disclosureWidth = defaultDisclosureWidth; disclosureNode = getDisclosureNode(); @@ -299,7 +296,7 @@ if (disclosureVisible) { disclosureWidth = disclosureNode.prefWidth(h); if (disclosureWidth > defaultDisclosureWidth) { - maxDisclosureWidthMap.put(c, disclosureWidth); + maxDisclosureWidthMap.put(treeColumn, disclosureWidth); // RT-36359: The recorded max width of the disclosure node // has increased. We need to go back and request all @@ -341,7 +338,7 @@ for (int column = 0, max = cells.size(); column < max; column++) { R tableCell = cells.get(column); - TableColumnBase tableColumn = getTableColumnBase(tableCell); + TableColumnBase tableColumn = getTableColumn(tableCell); boolean isVisible = true; if (fixedCellSizeEnabled) { @@ -495,12 +492,6 @@ return true; } - TableColumnBase getVisibleLeafColumn(int column) { - final List*/> visibleLeafColumns = getVisibleLeafColumns(); - if (column < 0 || column >= visibleLeafColumns.size()) return null; - return visibleLeafColumns.get(column); - } - void updateCells(boolean resetChildren) { // To avoid a potential memory leak (when the TableColumns in the // TableView are created/inserted/removed/deleted, we have a 'refresh @@ -538,7 +529,7 @@ if (cell == null) { // if the cell is null it means we don't have it in cache and // need to create it - cell = createCell(col); + cell = createCellAndCache(col); } updateCell(cell, skinnable); @@ -554,7 +545,7 @@ List toRemove = new ArrayList<>(); for (Node cell : getChildren()) { if (! (cell instanceof IndexedCell)) continue; - if (!getTableColumnBase((R)cell).isVisible()) { + if (!getTableColumn((R)cell).isVisible()) { toRemove.add(cell); } } @@ -564,7 +555,7 @@ } } - private VirtualFlow getVirtualFlow() { + VirtualFlow getVirtualFlow() { Parent p = getSkinnable(); while (p != null) { if (p instanceof VirtualFlow) { @@ -575,6 +566,7 @@ return null; } + /** {@inheritDoc} */ @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { double prefWidth = 0.0; @@ -586,6 +578,7 @@ return prefWidth; } + /** {@inheritDoc} */ @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { if (fixedCellSizeEnabled) { return fixedCellSize; @@ -615,6 +608,7 @@ return ph; } + /** {@inheritDoc} */ @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { if (fixedCellSizeEnabled) { return fixedCellSize; @@ -642,6 +636,7 @@ return minHeight; } + /** {@inheritDoc} */ @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { if (fixedCellSizeEnabled) { return fixedCellSize; @@ -668,6 +663,29 @@ * * **************************************************************************/ + private boolean isColumnPartiallyOrFullyVisible(TableColumnBase col) { + if (col == null || !col.isVisible()) return false; + + final VirtualFlow virtualFlow = getVirtualFlow(); + double scrollX = virtualFlow == null ? 0.0 : virtualFlow.getHbar().getValue(); + + // work out where this column header is, and it's width (start -> end) + double start = 0; + final ObservableList visibleLeafColumns = getVisibleLeafColumns(); + for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { + TableColumnBase c = visibleLeafColumns.get(i); + if (c.equals(col)) break; + start += c.getWidth(); + } + double end = start + col.getWidth(); + + // determine the width of the table + final Insets padding = getSkinnable().getPadding(); + double headerWidth = getSkinnable().getWidth() - padding.getLeft() + padding.getRight(); + + return (start >= scrollX || end > scrollX) && (start < (headerWidth + scrollX) || end <= (headerWidth + scrollX)); + } + private void requestCellUpdate() { updateCells = true; getSkinnable().requestLayout(); @@ -702,7 +720,7 @@ ObservableList*/> columns = getVisibleLeafColumns(); - cellsMap = new WeakHashMap>(columns.size()); + cellsMap = new WeakHashMap<>(columns.size()); fullRefreshCounter = DEFAULT_FULL_REFRESH_COUNTER; getChildren().clear(); @@ -713,16 +731,16 @@ // create a TableCell for this column and store it in the cellsMap // for future use - createCell(col); + createCellAndCache(col); } } - private R createCell(TableColumnBase col) { + private R createCellAndCache(TableColumnBase col) { // we must create a TableCell for this table column - R cell = getCell(col); + R cell = createCell(col); // and store this in our HashMap until needed - cellsMap.put(col, new WeakReference(cell)); + cellsMap.put(col, new WeakReference<>(cell)); return cell; } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkin.java @@ -224,7 +224,7 @@ } /** {@inheritDoc} */ - @Override int getItemCount() { + @Override protected int getItemCount() { TableView tableView = getSkinnable(); return tableView.getItems() == null ? 0 : tableView.getItems().size(); } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java b/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java @@ -208,7 +208,7 @@ ((TableView)getSkinnable()).edit(-1, null); } - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); }; @@ -560,7 +560,8 @@ return tableHeaderRow; } - @Override void updateRowCount() { + /** {@inheritDoc} */ + @Override protected void updateItemCount() { updatePlaceholderRegionVisibility(); int oldCount = itemCount; @@ -577,7 +578,7 @@ needCellsRecreated = true; forceCellRecreate = false; } else if (newCount != oldCount) { - // FIXME updateRowCount is called _a lot_. Perhaps we can make rebuildCells + // FIXME updateItemCount is called _a lot_. Perhaps we can make rebuildCells // smarter. Imagine if items has one million items added - do we really // need to rebuildCells a million times? Maybe this is better now that // we do rebuildCells instead of recreateCells. @@ -657,7 +658,7 @@ newList.addListener(weakRowCountListener); } - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); } @@ -756,28 +757,6 @@ || (! isFocusDriven && sm.getSelectedIndex() == index); } - boolean isColumnPartiallyOrFullyVisible(TC col) { - if (col == null || !col.isVisible()) return false; - - double scrollX = flow.getHbar().getValue(); - - // work out where this column header is, and it's width (start -> end) - double start = 0; - final ObservableList visibleLeafColumns = getVisibleLeafColumns(); - for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { - TableColumnBase c = visibleLeafColumns.get(i); - if (c.equals(col)) break; - start += c.getWidth(); - } - double end = start + col.getWidth(); - - // determine the width of the table - final Insets padding = getSkinnable().getPadding(); - double headerWidth = getSkinnable().getWidth() - padding.getLeft() + padding.getRight(); - - return (start >= scrollX || end > scrollX) && (start < (headerWidth + scrollX) || end <= (headerWidth + scrollX)); - } - /** * Keeps track of how many leaf columns are currently visible in this table. */ @@ -861,7 +840,7 @@ } private void refreshView() { - rowCountDirty = true; + markItemCountDirty(); Control c = getSkinnable(); if (c != null) { c.requestLayout(); diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableCellSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableCellSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableCellSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableCellSkin.java @@ -28,24 +28,19 @@ import com.sun.javafx.scene.control.behavior.BehaviorBase; import com.sun.javafx.scene.control.behavior.TreeTableCellBehavior; import java.util.Map; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyObjectProperty; import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Control; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeTableCell; -import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableRow; -import javafx.scene.control.TreeTableView; +import javafx.scene.control.*; /** * Default skin implementation for the {@link TreeTableCell} control. * + * @param The type of the UI control (e.g. the type of the 'row'), this is wrapped in a TreeItem. + * @param The type of the content in the cell, based on its {@link TreeTableColumn}. * @see TreeTableCell * @since 9 */ -public class TreeTableCellSkin extends TableCellSkinBase> { +public class TreeTableCellSkin extends TableCellSkinBase, T, TreeTableCell> { /*************************************************************************** * * @@ -104,13 +99,8 @@ **************************************************************************/ /** {@inheritDoc} */ - @Override BooleanProperty columnVisibleProperty() { - return getSkinnable().getTableColumn().visibleProperty(); - } - - /** {@inheritDoc} */ - @Override ReadOnlyDoubleProperty columnWidthProperty() { - return getSkinnable().getTableColumn().widthProperty(); + @Override public ReadOnlyObjectProperty> tableColumnProperty() { + return getSkinnable().tableColumnProperty(); } /** {@inheritDoc} */ @@ -154,8 +144,8 @@ leftPadding += nodeLevel * indentPerLevel; // add in the width of the disclosure node, if one exists - Map mdwp = TableRowSkinBase.maxDisclosureWidthMap; - leftPadding += mdwp.containsKey(treeTable) ? mdwp.get(treeTable) : 0; + Map, Double> mdwp = TableRowSkinBase.maxDisclosureWidthMap; + leftPadding += mdwp.containsKey(treeColumn) ? mdwp.get(treeColumn) : 0; // adding in the width of the graphic on the tree item Node graphic = treeItem.getGraphic(); @@ -163,14 +153,4 @@ return leftPadding; } - - /** {@inheritDoc} */ - @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - if (isDeferToParentForPrefWidth) { - // RT-27167: we must take into account the disclosure node and the - // indentation (which is not taken into account by the LabeledSkinBase. - return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); - } - return columnWidthProperty().get(); - } } diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableRowSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableRowSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableRowSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableRowSkin.java @@ -121,6 +121,22 @@ isDirty = true; getSkinnable().requestLayout(); }); + + DoubleProperty fixedCellSizeProperty = getTreeTableView().fixedCellSizeProperty(); + if (fixedCellSizeProperty != null) { + registerChangeListener(fixedCellSizeProperty, e -> { + fixedCellSize = fixedCellSizeProperty.get(); + fixedCellSizeEnabled = fixedCellSize > 0; + }); + fixedCellSize = fixedCellSizeProperty.get(); + fixedCellSizeEnabled = fixedCellSize > 0; + + // JDK-8144500: + // When in fixed cell size mode, we must listen to the width of the virtual flow, so + // that when it changes, we can appropriately add / remove cells that may or may not + // be required (because we remove all cells that are not visible). + registerChangeListener(getVirtualFlow().widthProperty(), e -> control.requestLayout()); + } } @@ -228,7 +244,7 @@ **************************************************************************/ /** {@inheritDoc} */ - @Override TreeTableCell getCell(TableColumnBase tcb) { + @Override protected TreeTableCell createCell(TableColumnBase tcb) { TreeTableColumn tableColumn = (TreeTableColumn) tcb; TreeTableCell cell = (TreeTableCell) tableColumn.getCellFactory().call(tableColumn); @@ -255,12 +271,12 @@ /** {@inheritDoc} */ @Override TableColumnBase getTreeColumn() { - return getSkinnable().getTreeTableView().getTreeColumn(); + return getTreeTableView().getTreeColumn(); } /** {@inheritDoc} */ @Override int getIndentationLevel(TreeTableRow control) { - return control.getTreeTableView().getTreeItemLevel(control.getTreeItem()); + return getTreeTableView().getTreeItemLevel(control.getTreeItem()); } /** {@inheritDoc} */ @@ -278,31 +294,26 @@ } @Override boolean isShowRoot() { - return getSkinnable().getTreeTableView().isShowRoot(); + return getTreeTableView().isShowRoot(); } /** {@inheritDoc} */ - @Override ObservableList> getVisibleLeafColumns() { - return getSkinnable().getTreeTableView().getVisibleLeafColumns(); + @Override protected ObservableList> getVisibleLeafColumns() { + return getTreeTableView().getVisibleLeafColumns(); } /** {@inheritDoc} */ - @Override void updateCell(TreeTableCell cell, TreeTableRow row) { + @Override protected void updateCell(TreeTableCell cell, TreeTableRow row) { cell.updateTreeTableRow(row); } /** {@inheritDoc} */ - @Override boolean isColumnPartiallyOrFullyVisible(TableColumnBase tc) { - return treeTableViewSkin == null ? false : treeTableViewSkin.isColumnPartiallyOrFullyVisible(tc); - } - - /** {@inheritDoc} */ - @Override TreeTableColumn getTableColumnBase(TreeTableCell cell) { + @Override protected TreeTableColumn getTableColumn(TreeTableCell cell) { return cell.getTableColumn(); } /** {@inheritDoc} */ - @Override ObjectProperty graphicProperty() { + @Override protected ObjectProperty graphicProperty() { TreeTableRow treeTableRow = getSkinnable(); if (treeTableRow == null) return null; if (treeItem == null) return null; @@ -310,16 +321,6 @@ return treeItem.graphicProperty(); } - /** {@inheritDoc} */ - @Override Control getVirtualFlowOwner() { - return getSkinnable().getTreeTableView(); - } - - /** {@inheritDoc} */ - @Override DoubleProperty fixedCellSizeProperty() { - return getSkinnable().getTreeTableView().fixedCellSizeProperty(); - } - private void updateTreeItem() { if (treeItem != null) { treeItem.graphicProperty().removeListener(graphicListener); @@ -330,6 +331,10 @@ } } + private TreeTableView getTreeTableView() { + return getSkinnable().getTreeTableView(); + } + private void updateDisclosureNodeAndGraphic() { if (getSkinnable().isEmpty()) return; diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableViewSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableViewSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableViewSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TreeTableViewSkin.java @@ -29,7 +29,6 @@ import com.sun.javafx.scene.control.Properties; import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; -import com.sun.javafx.scene.control.behavior.BehaviorBase; import com.sun.javafx.scene.control.skin.Utils; import javafx.event.WeakEventHandler; import javafx.scene.control.*; @@ -90,7 +89,7 @@ // no event being fired to the skin to be informed that the items // had changed. So, here we just watch for the case where the number // of items being added is equal to the number of items being removed. - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); } else if (e.getEventType().equals(TreeItem.valueChangedEvent())) { // Fix for RT-14971 and RT-15338. @@ -102,7 +101,7 @@ EventType eventType = e.getEventType(); while (eventType != null) { if (eventType.equals(TreeItem.expandedItemCountChangeEvent())) { - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); break; } @@ -189,10 +188,10 @@ getRoot().setExpanded(true); } // update the item count in the flow and behavior instances - updateRowCount(); + updateItemCount(); }); registerChangeListener(control.rowFactoryProperty(), e -> flow.recreateCells()); - registerChangeListener(control.expandedItemCountProperty(), e -> rowCountDirty = true); + registerChangeListener(control.expandedItemCountProperty(), e -> markItemCountDirty()); registerChangeListener(control.fixedCellSizeProperty(), e -> flow.setFixedCellSize(getSkinnable().getFixedCellSize())); } @@ -322,7 +321,7 @@ getRoot().addEventHandler(TreeItem.treeNotificationEvent(), weakRootListener); } - updateRowCount(); + updateItemCount(); } /** {@inheritDoc} */ @@ -474,7 +473,7 @@ } /** {@inheritDoc} */ - @Override int getItemCount() { + @Override protected int getItemCount() { return getSkinnable().getExpandedItemCount(); } @@ -487,7 +486,7 @@ } /** {@inheritDoc} */ - @Override void updateRowCount() { + @Override protected void updateItemCount() { updatePlaceholderRegionVisibility(); tableBackingList.resetSize(); diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java b/modules/controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java @@ -26,7 +26,6 @@ package javafx.scene.control.skin; import com.sun.javafx.scene.control.Properties; -import com.sun.javafx.scene.control.behavior.BehaviorBase; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.WeakInvalidationListener; @@ -115,7 +114,7 @@ // no event being fired to the skin to be informed that the items // had changed. So, here we just watch for the case where the number // of items being added is equal to the number of items being removed. - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); } else if (e.getEventType().equals(TreeItem.valueChangedEvent())) { // Fix for RT-14971 and RT-15338. @@ -127,7 +126,7 @@ EventType eventType = e.getEventType(); while (eventType != null) { if (eventType.equals(TreeItem.expandedItemCountChangeEvent())) { - rowCountDirty = true; + markItemCountDirty(); getSkinnable().requestLayout(); break; } @@ -215,12 +214,12 @@ getRoot().setExpanded(true); } // update the item count in the flow and behavior instances - updateRowCount(); + updateItemCount(); }); registerChangeListener(control.cellFactoryProperty(), e -> flow.recreateCells()); registerChangeListener(control.fixedCellSizeProperty(), e -> flow.setFixedCellSize(getSkinnable().getFixedCellSize())); - updateRowCount(); + updateItemCount(); } @@ -397,16 +396,16 @@ getRoot().addEventHandler(TreeItem.treeNotificationEvent(), weakRootListener); } - updateRowCount(); + updateItemCount(); } /** {@inheritDoc} */ - @Override int getItemCount() { + @Override protected int getItemCount() { return getSkinnable().getExpandedItemCount(); } /** {@inheritDoc} */ - @Override void updateRowCount() { + @Override protected void updateItemCount() { // int oldCount = flow.getCellCount(); int newCount = getItemCount(); diff --git a/modules/controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java b/modules/controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java --- a/modules/controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java +++ b/modules/controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java @@ -25,19 +25,17 @@ package javafx.scene.control.skin; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.scene.control.Cell; import javafx.scene.control.Control; import javafx.scene.control.IndexedCell; import javafx.scene.control.ScrollToEvent; import javafx.scene.control.SkinBase; -import javafx.util.Callback; /** * Parent class to control skins whose contents are virtualized and scrollable. * This class handles the interaction with the VirtualFlow class, which is the * main class handling the virtualization of the contents of this container. + * + * @since 9 */ public abstract class VirtualContainerBase extends SkinBase { @@ -47,7 +45,7 @@ * * **************************************************************************/ - boolean rowCountDirty; + private boolean itemCountDirty; /** * The virtualized container which handles the layout and scrolling of @@ -74,10 +72,10 @@ control.addEventHandler(ScrollToEvent.scrollToTopIndex(), event -> { // Fix for RT-24630: The row count in VirtualFlow was incorrect // (normally zero), so the scrollTo call was misbehaving. - if (rowCountDirty) { + if (itemCountDirty) { // update row count before we do a scroll - updateRowCount(); - rowCountDirty = false; + updateItemCount(); + itemCountDirty = false; } flow.scrollToTop(event.getScrollTarget()); }); @@ -95,9 +93,14 @@ * Returns the total number of items in this container, including those * that are currently hidden because they are out of view. */ - abstract int getItemCount(); + protected abstract int getItemCount(); - abstract void updateRowCount(); + /** + * This method is called when it is possible that the item count has changed (i.e. scrolling has occurred, + * the control has resized, etc). This method should recalculate the item count and store that for future + * use by the {@link #getItemCount} method. + */ + protected abstract void updateItemCount(); @@ -107,6 +110,13 @@ * * **************************************************************************/ + /** + * Call this method to indicate that the item count should be updated on the next pulse. + */ + protected final void markItemCountDirty() { + itemCountDirty = true; + } + /** {@inheritDoc} */ @Override protected void layoutChildren(double x, double y, double w, double h) { checkState(); @@ -147,9 +157,9 @@ } void checkState() { - if (rowCountDirty) { - updateRowCount(); - rowCountDirty = false; + if (itemCountDirty) { + updateItemCount(); + itemCountDirty = false; } } }