modules/controls/src/main/java/javafx/scene/control/skin/TableColumnHeader.java
Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
@@ -21,13 +21,17 @@
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
-package com.sun.javafx.scene.control.skin;
+package javafx.scene.control.skin;
+import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler;
+import com.sun.javafx.scene.control.Properties;
import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.WritableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;
import javafx.css.CssMetaData;
@@ -57,12 +61,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
-import com.sun.javafx.css.converters.SizeConverter;
-import com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler;
+import javafx.css.converter.SizeConverter;
import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.getSortTypeName;
import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.getSortTypeProperty;
import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.isAscending;
import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.isDescending;
@@ -93,13 +96,12 @@
**************************************************************************/
private boolean autoSizeComplete = false;
private double dragOffset;
- private final TableViewSkinBase<?,?,?,?,?,TableColumnBase<?,?>> skin;
+ private final TableViewSkinBase<?,?,?,?,TableColumnBase<?,?>> skin;
private NestedTableColumnHeader nestedColumnHeader;
- private final TableColumnBase<?,?> column;
private TableHeaderRow tableHeaderRow;
private NestedTableColumnHeader parentHeader;
// work out where this column currently is within its parent
Label label;
@@ -122,57 +124,73 @@
private int newColumnPos;
// the line drawn in the table when a user presses and moves a column header
// to indicate where the column will be dropped. This is provided by the
// table skin, but manipulated by the header
- protected final Region columnReorderLine;
+ final Region columnReorderLine;
/***************************************************************************
* *
* Constructor *
* *
**************************************************************************/
+ /**
+ * Creates a new TableColumnHeader instance to visually represent the given
+ * {@link TableColumnBase} instance.
+ *
+ * @param skin The skin used by the UI control.
+ * @param tc The table column to be visually represented by this instance.
+ */
public TableColumnHeader(final TableViewSkinBase skin, final TableColumnBase tc) {
this.skin = skin;
- this.column = tc;
+ setTableColumn(tc);
this.columnReorderLine = skin.getColumnReorderLine();
setFocusTraversable(false);
updateColumnIndex();
initUI();
// change listener for multiple properties
- changeListenerHandler = new MultiplePropertyChangeListenerHandler(p -> {
- handlePropertyChanged(p);
- return null;
- });
- changeListenerHandler.registerChangeListener(sceneProperty(), "SCENE");
+ changeListenerHandler = new LambdaMultiplePropertyChangeListenerHandler();
+ changeListenerHandler.registerChangeListener(sceneProperty(), e -> updateScene());
- if (column != null && skin != null) {
+ if (getTableColumn() != null && skin != null) {
updateSortPosition();
skin.getSortOrder().addListener(weakSortOrderListener);
skin.getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener);
}
- if (column != null) {
- changeListenerHandler.registerChangeListener(column.idProperty(), "TABLE_COLUMN_ID");
- changeListenerHandler.registerChangeListener(column.styleProperty(), "TABLE_COLUMN_STYLE");
- changeListenerHandler.registerChangeListener(column.widthProperty(), "TABLE_COLUMN_WIDTH");
- changeListenerHandler.registerChangeListener(column.visibleProperty(), "TABLE_COLUMN_VISIBLE");
- changeListenerHandler.registerChangeListener(column.sortNodeProperty(), "TABLE_COLUMN_SORT_NODE");
- changeListenerHandler.registerChangeListener(column.sortableProperty(), "TABLE_COLUMN_SORTABLE");
- changeListenerHandler.registerChangeListener(column.textProperty(), "TABLE_COLUMN_TEXT");
- changeListenerHandler.registerChangeListener(column.graphicProperty(), "TABLE_COLUMN_GRAPHIC");
+ if (getTableColumn() != null) {
+ changeListenerHandler.registerChangeListener(tc.idProperty(), e -> setId(tc.getId()));
+ changeListenerHandler.registerChangeListener(tc.styleProperty(), e -> setStyle(tc.getStyle()));
+ changeListenerHandler.registerChangeListener(tc.widthProperty(), e -> {
+ // It is this that ensures that when a column is resized that the header
+ // visually adjusts its width as necessary.
+ isSizeDirty = true;
+ requestLayout();
+ });
+ changeListenerHandler.registerChangeListener(tc.visibleProperty(), e -> setVisible(getTableColumn().isVisible()));
+ changeListenerHandler.registerChangeListener(tc.sortNodeProperty(), e -> updateSortGrid());
+ changeListenerHandler.registerChangeListener(tc.sortableProperty(), e -> {
+ // we need to notify all headers that a sortable state has changed,
+ // in case the sort grid in other columns needs to be updated.
+ if (skin.getSortOrder().contains(getTableColumn())) {
+ NestedTableColumnHeader root = getTableHeaderRow().getRootHeader();
+ updateAllHeaders(root);
+ }
+ });
+ changeListenerHandler.registerChangeListener(tc.textProperty(), e -> label.setText(tc.getText()));
+ changeListenerHandler.registerChangeListener(tc.graphicProperty(), e -> label.setGraphic(tc.getGraphic()));
- column.getStyleClass().addListener(weakStyleClassListener);
+ tc.getStyleClass().addListener(weakStyleClassListener);
- setId(column.getId());
- setStyle(column.getStyle());
+ setId(tc.getId());
+ setStyle(tc.getStyle());
updateStyleClass();
/* Having TableColumn role parented by TableColumn causes VoiceOver to be unhappy */
setAccessibleRole(AccessibleRole.TABLE_COLUMN);
}
}
@@ -183,11 +201,11 @@
* *
* Listeners *
* *
**************************************************************************/
- protected final MultiplePropertyChangeListenerHandler changeListenerHandler;
+ final LambdaMultiplePropertyChangeListenerHandler changeListenerHandler;
private ListChangeListener<TableColumnBase<?,?>> sortOrderListener = c -> {
updateSortPosition();
};
@@ -262,15 +280,16 @@
* *
* Properties *
* *
**************************************************************************/
+ // --- size
private DoubleProperty size;
- private double getSize() {
+ private final double getSize() {
return size == null ? 20.0 : size.doubleValue();
}
- private DoubleProperty sizeProperty() {
+ private final DoubleProperty sizeProperty() {
if (size == null) {
size = new StyleableDoubleProperty(20) {
@Override
protected void invalidated() {
double value = get();
@@ -300,72 +319,31 @@
}
return size;
}
-
- /***************************************************************************
- * *
- * Public Methods *
- * *
- **************************************************************************/
-
- protected void handlePropertyChanged(String p) {
- if ("SCENE".equals(p)) {
- updateScene();
- } else if ("TABLE_COLUMN_VISIBLE".equals(p)) {
- setVisible(getTableColumn().isVisible());
- } else if ("TABLE_COLUMN_WIDTH".equals(p)) {
- // It is this that ensures that when a column is resized that the header
- // visually adjusts its width as necessary.
- isSizeDirty = true;
- requestLayout();
- } else if ("TABLE_COLUMN_ID".equals(p)) {
- setId(column.getId());
- } else if ("TABLE_COLUMN_STYLE".equals(p)) {
- setStyle(column.getStyle());
- } else if ("TABLE_COLUMN_SORT_TYPE".equals(p)) {
- updateSortGrid();
- if (arrow != null) {
- arrow.setRotate(isAscending(column) ? 180 : 0.0);
- }
- } else if ("TABLE_COLUMN_SORT_NODE".equals(p)) {
- updateSortGrid();
- } else if ("TABLE_COLUMN_SORTABLE".equals(p)) {
- // we need to notify all headers that a sortable state has changed,
- // in case the sort grid in other columns needs to be updated.
- if (skin.getSortOrder().contains(getTableColumn())) {
- NestedTableColumnHeader root = getTableHeaderRow().getRootHeader();
- updateAllHeaders(root);
- }
- } else if ("TABLE_COLUMN_TEXT".equals(p)) {
- label.setText(column.getText());
- } else if ("TABLE_COLUMN_GRAPHIC".equals(p)) {
- label.setGraphic(column.getGraphic());
+ /**
+ * A property that refers to the {@link TableColumnBase} instance that this
+ * header is visually represents.
+ */
+ // --- table column
+ private ReadOnlyObjectWrapper<TableColumnBase<?,?>> tableColumn = new ReadOnlyObjectWrapper<>(this, "tableColumn");
+ private final void setTableColumn(TableColumnBase<?,?> column) {
+ tableColumn.set(column);
}
+ public final TableColumnBase<?,?> getTableColumn() {
+ return tableColumn.get();
}
-
- protected TableViewSkinBase<?,?,?,?,?,TableColumnBase<?,?>> getTableViewSkin() {
- return skin;
+ public final ReadOnlyObjectProperty<TableColumnBase<?,?>> tableColumnProperty() {
+ return tableColumn.getReadOnlyProperty();
}
- NestedTableColumnHeader getNestedColumnHeader() { return nestedColumnHeader; }
- void setNestedColumnHeader(NestedTableColumnHeader nch) { nestedColumnHeader = nch; }
-
- public TableColumnBase getTableColumn() { return column; }
-
- TableHeaderRow getTableHeaderRow() { return tableHeaderRow; }
- void setTableHeaderRow(TableHeaderRow thr) { tableHeaderRow = thr; }
-
- NestedTableColumnHeader getParentHeader() { return parentHeader; }
- void setParentHeader(NestedTableColumnHeader ph) { parentHeader = ph; }
-
/***************************************************************************
* *
- * Layout *
+ * Public API *
* *
**************************************************************************/
/** {@inheritDoc} */
@Override protected void layoutChildren() {
@@ -402,17 +380,17 @@
/** {@inheritDoc} */
@Override protected double computePrefWidth(double height) {
if (getNestedColumnHeader() != null) {
double width = getNestedColumnHeader().prefWidth(height);
- if (column != null) {
- column.impl_setWidth(width);
+ if (getTableColumn() != null) {
+ getTableColumn().impl_setWidth(width);
}
return width;
- } else if (column != null && column.isVisible()) {
- return column.getWidth();
+ } else if (getTableColumn() != null && getTableColumn().isVisible()) {
+ return getTableColumn().getWidth();
}
return 0;
}
@@ -425,18 +403,45 @@
@Override protected double computePrefHeight(double width) {
if (getTableColumn() == null) return 0;
return Math.max(getSize(), label.prefHeight(-1));
}
+ /** {@inheritDoc} */
+ @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
+ return getClassCssMetaData();
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
+ switch (attribute) {
+ case INDEX: return getIndex(getTableColumn());
+ case TEXT: return getTableColumn() != null ? getTableColumn().getText() : null;
+ default: return super.queryAccessibleAttribute(attribute, parameters);
+ }
+ }
+
/***************************************************************************
* *
* Private Implementation *
* *
**************************************************************************/
+ TableViewSkinBase<?,?,?,?,TableColumnBase<?,?>> getTableViewSkin() {
+ return skin;
+ }
+
+ NestedTableColumnHeader getNestedColumnHeader() { return nestedColumnHeader; }
+ void setNestedColumnHeader(NestedTableColumnHeader nch) { nestedColumnHeader = nch; }
+
+ TableHeaderRow getTableHeaderRow() { return tableHeaderRow; }
+ void setTableHeaderRow(TableHeaderRow thr) { tableHeaderRow = thr; }
+
+ NestedTableColumnHeader getParentHeader() { return parentHeader; }
+ void setParentHeader(NestedTableColumnHeader ph) { parentHeader = ph; }
+
// RT-29682: When the sortable property of a TableColumnBase changes this
// may impact other TableColumnHeaders, as they may need to change their
// sort order representation. Rather than install listeners across all
// TableColumn in the sortOrder list for their sortable property, we simply
// update the sortPosition of all headers whenever the sortOrder property
@@ -454,11 +459,11 @@
private void updateStyleClass() {
// For now we leave the 'column-header' style class intact so that the
// appropriate border styles are shown, etc.
getStyleClass().setAll("column-header");
- getStyleClass().addAll(column.getStyleClass());
+ getStyleClass().addAll(getTableColumn().getStyleClass());
}
private void updateScene() {
// RT-17684: If the TableColumn widths are all currently the default,
// we attempt to 'auto-size' based on the preferred width of the first
@@ -493,29 +498,29 @@
return true;
}
private boolean isColumnReorderingEnabled() {
// we only allow for column reordering if there are more than one column,
- return !BehaviorSkinBase.IS_TOUCH_SUPPORTED && getTableViewSkin().getVisibleLeafColumns().size() > 1;
+ return !Properties.IS_TOUCH_SUPPORTED && getTableViewSkin().getVisibleLeafColumns().size() > 1;
}
private void initUI() {
// TableColumn will be null if we are dealing with the root NestedTableColumnHeader
- if (column == null) return;
+ if (getTableColumn() == null) return;
// set up mouse events
setOnMousePressed(mousePressedHandler);
setOnMouseDragged(mouseDraggedHandler);
setOnDragDetected(event -> event.consume());
setOnContextMenuRequested(contextMenuRequestedHandler);
setOnMouseReleased(mouseReleasedHandler);
// --- label
label = new Label();
- label.setText(column.getText());
- label.setGraphic(column.getGraphic());
- label.setVisible(column.isVisible());
+ label.setText(getTableColumn().getText());
+ label.setGraphic(getTableColumn().getGraphic());
+ label.setVisible(getTableColumn().isVisible());
// ---- container for the sort arrow (which is not supported on embedded
// platforms)
if (isSortingEnabled()) {
// put together the grid
@@ -531,11 +536,11 @@
getTableViewSkin().resizeColumnToFitContent(column, cellsToMeasure);
}
}
private void updateSortPosition() {
- this.sortPos = ! column.isSortable() ? -1 : getSortPosition();
+ this.sortPos = ! getTableColumn().isSortable() ? -1 : getSortPosition();
updateSortGrid();
}
private void updateSortGrid() {
// Fix for RT-14488
@@ -575,12 +580,17 @@
// if we are here, and the sort arrow is null, we better create it
if (arrow == null) {
arrow = new Region();
arrow.getStyleClass().setAll("arrow");
arrow.setVisible(true);
- arrow.setRotate(isAscending(column) ? 180.0F : 0.0F);
- changeListenerHandler.registerChangeListener(getSortTypeProperty(column), "TABLE_COLUMN_SORT_TYPE");
+ arrow.setRotate(isAscending(getTableColumn()) ? 180.0F : 0.0F);
+ changeListenerHandler.registerChangeListener(getSortTypeProperty(getTableColumn()), e -> {
+ updateSortGrid();
+ if (arrow != null) {
+ arrow.setRotate(isAscending(getTableColumn()) ? 180 : 0.0);
+ }
+ });
}
arrow.setVisible(isSortColumn);
if (sortPos > 2) {
@@ -605,11 +615,11 @@
sortOrderDots = new HBox(0);
sortOrderDots.getStyleClass().add("sort-order-dots-container");
}
// show the sort order dots
- boolean isAscending = isAscending(column);
+ boolean isAscending = isAscending(getTableColumn());
int arrowRow = isAscending ? 1 : 2;
int dotsRow = isAscending ? 2 : 1;
sortArrowGrid.add(arrow, 1, arrowRow);
GridPane.setHalignment(arrow, HPos.CENTER);
@@ -639,11 +649,11 @@
for (int i = 0; i <= sortPos; i++) {
Region r = new Region();
r.getStyleClass().add("sort-order-dot");
- String sortTypeName = getSortTypeName(column);
+ String sortTypeName = getSortTypeName(getTableColumn());
if (sortTypeName != null && ! sortTypeName.isEmpty()) {
r.getStyleClass().add(sortTypeName.toLowerCase(Locale.ROOT));
}
sortOrderDots.getChildren().add(r);
@@ -736,25 +746,25 @@
private void sortColumn(final boolean addColumn) {
if (! isSortingEnabled()) return;
// we only allow sorting on the leaf columns and columns
// that actually have comparators defined, and are sortable
- if (column == null || column.getColumns().size() != 0 || column.getComparator() == null || !column.isSortable()) return;
+ if (getTableColumn() == null || getTableColumn().getColumns().size() != 0 || getTableColumn().getComparator() == null || !getTableColumn().isSortable()) return;
// final int sortPos = getTable().getSortOrder().indexOf(column);
// final boolean isSortColumn = sortPos != -1;
final ObservableList<TableColumnBase<?,?>> sortOrder = getTableViewSkin().getSortOrder();
// addColumn is true e.g. when the user is holding down Shift
if (addColumn) {
if (!isSortColumn) {
- setSortType(column, TableColumn.SortType.ASCENDING);
- sortOrder.add(column);
- } else if (isAscending(column)) {
- setSortType(column, TableColumn.SortType.DESCENDING);
+ setSortType(getTableColumn(), TableColumn.SortType.ASCENDING);
+ sortOrder.add(getTableColumn());
+ } else if (isAscending(getTableColumn())) {
+ setSortType(getTableColumn(), TableColumn.SortType.DESCENDING);
} else {
- int i = sortOrder.indexOf(column);
+ int i = sortOrder.indexOf(getTableColumn());
if (i != -1) {
sortOrder.remove(i);
}
}
} else {
@@ -764,56 +774,56 @@
// the column is already being sorted, and it's the only column.
// We therefore move through the 2nd or 3rd states:
// 1st click: sort ascending
// 2nd click: sort descending
// 3rd click: natural sorting (sorting is switched off)
- if (isAscending(column)) {
- setSortType(column, TableColumn.SortType.DESCENDING);
+ if (isAscending(getTableColumn())) {
+ setSortType(getTableColumn(), TableColumn.SortType.DESCENDING);
} else {
// remove from sort
- sortOrder.remove(column);
+ sortOrder.remove(getTableColumn());
}
} else if (isSortColumn) {
// the column is already being used to sort, so we toggle its
// sortAscending property, and also make the column become the
// primary sort column
- if (isAscending(column)) {
- setSortType(column, TableColumn.SortType.DESCENDING);
- } else if (isDescending(column)) {
- setSortType(column, TableColumn.SortType.ASCENDING);
+ if (isAscending(getTableColumn())) {
+ setSortType(getTableColumn(), TableColumn.SortType.DESCENDING);
+ } else if (isDescending(getTableColumn())) {
+ setSortType(getTableColumn(), TableColumn.SortType.ASCENDING);
}
// to prevent multiple sorts, we make a copy of the sort order
// list, moving the column value from the current position to
// its new position at the front of the list
List<TableColumnBase<?,?>> sortOrderCopy = new ArrayList<TableColumnBase<?,?>>(sortOrder);
- sortOrderCopy.remove(column);
- sortOrderCopy.add(0, column);
- sortOrder.setAll(column);
+ sortOrderCopy.remove(getTableColumn());
+ sortOrderCopy.add(0, getTableColumn());
+ sortOrder.setAll(getTableColumn());
} else {
// add to the sort order, in ascending form
- setSortType(column, TableColumn.SortType.ASCENDING);
- sortOrder.setAll(column);
+ setSortType(getTableColumn(), TableColumn.SortType.ASCENDING);
+ sortOrder.setAll(getTableColumn());
}
}
}
// Because it is possible that some columns are in the sortOrder list but are
// not themselves sortable, we cannot just do sortOrderList.indexOf(column).
// Therefore, this method does the proper work required of iterating through
// and ignoring non-sortable (and null) columns in the sortOrder list.
private int getSortPosition() {
- if (column == null) {
+ if (getTableColumn() == null) {
return -1;
}
final List<TableColumnBase> sortOrder = getVisibleSortOrderColumns();
int pos = 0;
for (int i = 0; i < sortOrder.size(); i++) {
TableColumnBase _tc = sortOrder.get(i);
- if (column.equals(_tc)) {
+ if (getTableColumn().equals(_tc)) {
return pos;
}
pos++;
}
@@ -852,24 +862,24 @@
* *
**************************************************************************/
// package for testing
void columnReorderingStarted(double dragOffset) {
- if (! column.impl_isReorderable()) return;
+ if (! getTableColumn().impl_isReorderable()) return;
// Used to ensure the column ghost is positioned relative to where the
// user clicked on the column header
this.dragOffset = dragOffset;
// Note here that we only allow for reordering of 'root' columns
- getTableHeaderRow().setReorderingColumn(column);
+ getTableHeaderRow().setReorderingColumn(getTableColumn());
getTableHeaderRow().setReorderingRegion(this);
}
// package for testing
void columnReordering(double sceneX, double sceneY) {
- if (! column.impl_isReorderable()) return;
+ if (! getTableColumn().impl_isReorderable()) return;
// this is for handling the column drag to reorder columns.
// It shows a line to indicate where the 'drop' will be.
// indicate that we've started dragging so that the dragging
@@ -924,11 +934,11 @@
double midPoint = startX + (endX - startX) / 2;
boolean beforeMidPoint = x <= midPoint;
// Based on where the mouse actually is, we have to shuffle
// where we want the column to end up. This code handles that.
- int currentPos = getIndex(column);
+ int currentPos = getIndex(getTableColumn());
newColumnPos += newColumnPos > currentPos && beforeMidPoint ?
-1 : (newColumnPos < currentPos && !beforeMidPoint ? 1 : 0);
double lineX = getTableHeaderRow().sceneToLocal(hoverHeader.localToScene(hoverHeader.getBoundsInLocal())).getMinX();
lineX = lineX + ((beforeMidPoint) ? (0) : (hoverHeader.getWidth()));
@@ -946,11 +956,11 @@
getTableHeaderRow().setReordering(true);
}
// package for testing
void columnReorderingComplete() {
- if (! column.impl_isReorderable()) return;
+ if (! getTableColumn().impl_isReorderable()) return;
// Move col from where it is now to the new position.
moveColumn(getTableColumn(), newColumnPos);
// cleanup
@@ -963,10 +973,14 @@
getTableHeaderRow().setReorderingColumn(null);
getTableHeaderRow().setReorderingRegion(null);
dragOffset = 0.0F;
}
+ double getDragRectHeight() {
+ return getHeight();
+ }
+
/***************************************************************************
* *
* Stylesheet Handling *
@@ -974,14 +988,10 @@
**************************************************************************/
private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE =
PseudoClass.getPseudoClass("last-visible");
- double getDragRectHeight() {
- return getHeight();
- }
-
/**
* Super-lazy instantiation pattern from Bill Pugh.
* @treatAsPrivate implementation detail
*/
private static class StyleableProperties {
@@ -1010,29 +1020,12 @@
}
}
/**
- * @return The CssMetaData associated with this class, which may include the
+ * Returnst he CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
-
- /**
- * {@inheritDoc}
- */
- @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
- return getClassCssMetaData();
- }
-
- @Override
- public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
- switch (attribute) {
- case INDEX: return getIndex(column);
- case TEXT: return column != null ? column.getText() : null;
- default: return super.queryAccessibleAttribute(attribute, parameters);
- }
- }
-
}