modules/controls/src/main/java/javafx/scene/control/skin/NestedTableColumnHeader.java
Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
*** 1,7 ****
/*
! * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
--- 1,7 ----
/*
! * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
*** 21,32 ****
* 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;
import javafx.collections.WeakListChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
--- 21,33 ----
* 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 javafx.scene.control.skin;
+ import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
import javafx.collections.WeakListChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
*** 49,58 ****
--- 50,63 ----
* that every TableView header is nested - even if it isn't. This allows for us
* to use the same code for building a single row of TableColumns as we would
* with a heavily nested sequences of TableColumns. Because of this, the
* TableHeaderRow class consists of just one instance of a NestedTableColumnHeader.
*
+ * @since 9
+ * @see TableColumnHeader
+ * @see TableHeaderRow
+ * @see TableColumnBase
*/
public class NestedTableColumnHeader extends TableColumnHeader {
/***************************************************************************
* *
*** 80,89 ****
--- 85,95 ----
private ObservableList<? extends TableColumnBase> columns;
private TableColumnHeader label;
private ObservableList<TableColumnHeader> columnHeaders;
+ private ObservableList<TableColumnHeader> unmodifiableColumnHeaders;
// used for column resizing
private double lastX = 0.0F;
private double dragAnchorX = 0.0;
*** 98,107 ****
--- 104,120 ----
* *
* Constructor *
* *
**************************************************************************/
+ /**
+ * Creates a new NestedTableColumnHeader 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 NestedTableColumnHeader(final TableViewSkinBase skin, final TableColumnBase tc) {
super(skin, tc);
getStyleClass().setAll("nested-column-header");
setFocusTraversable(false);
*** 111,124 ****
label.setTableHeaderRow(getTableHeaderRow());
label.setParentHeader(getParentHeader());
label.setNestedColumnHeader(this);
if (getTableColumn() != null) {
! changeListenerHandler.registerChangeListener(getTableColumn().textProperty(), "TABLE_COLUMN_TEXT");
}
! changeListenerHandler.registerChangeListener(skin.columnResizePolicyProperty(), "TABLE_VIEW_COLUMN_RESIZE_POLICY");
}
/***************************************************************************
--- 124,138 ----
label.setTableHeaderRow(getTableHeaderRow());
label.setParentHeader(getParentHeader());
label.setNestedColumnHeader(this);
if (getTableColumn() != null) {
! changeListenerHandler.registerChangeListener(getTableColumn().textProperty(), e ->
! label.setVisible(getTableColumn().getText() != null && ! getTableColumn().getText().isEmpty()));
}
! changeListenerHandler.registerChangeListener(skin.columnResizePolicyProperty(), e -> updateContent());
}
/***************************************************************************
*** 132,143 ****
};
private final WeakListChangeListener weakColumnsListener =
new WeakListChangeListener(columnsListener);
! private static final EventHandler<MouseEvent> rectMousePressed = new EventHandler<MouseEvent>() {
! @Override public void handle(MouseEvent me) {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (! header.isColumnResizingEnabled()) return;
--- 146,156 ----
};
private final WeakListChangeListener weakColumnsListener =
new WeakListChangeListener(columnsListener);
! private static final EventHandler<MouseEvent> rectMousePressed = me -> {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (! header.isColumnResizingEnabled()) return;
*** 153,234 ****
double startX = header.getTableHeaderRow().sceneToLocal(innerRect.localToScene(innerRect.getBoundsInLocal())).getMinX() + 2;
header.dragAnchorX = me.getSceneX();
header.columnResizingStarted(startX);
}
me.consume();
- }
};
! private static final EventHandler<MouseEvent> rectMouseDragged = new EventHandler<MouseEvent>() {
! @Override public void handle(MouseEvent me) {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (! header.isColumnResizingEnabled()) return;
header.columnResizing(column, me);
me.consume();
- }
};
! private static final EventHandler<MouseEvent> rectMouseReleased = new EventHandler<MouseEvent>() {
! @Override public void handle(MouseEvent me) {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (! header.isColumnResizingEnabled()) return;
header.columnResizingComplete(column, me);
me.consume();
- }
};
! private static final EventHandler<MouseEvent> rectCursorChangeListener = new EventHandler<MouseEvent>() {
! @Override public void handle(MouseEvent me) {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (header.getCursor() == null) { // If there's a cursor for the whole header, don't override it
rect.setCursor(header.isColumnResizingEnabled() && rect.isHover() &&
column.isResizable() ? Cursor.H_RESIZE : null);
}
- }
};
/***************************************************************************
* *
* Public Methods *
* *
**************************************************************************/
! @Override protected void handlePropertyChanged(String p) {
! super.handlePropertyChanged(p);
! if ("TABLE_VIEW_COLUMN_RESIZE_POLICY".equals(p)) {
! updateContent();
! } else if ("TABLE_COLUMN_TEXT".equals(p)) {
! label.setVisible(getTableColumn().getText() != null && ! getTableColumn().getText().isEmpty());
}
}
! @Override public void setTableHeaderRow(TableHeaderRow header) {
super.setTableHeaderRow(header);
label.setTableHeaderRow(header);
// tell all children columns what TableHeader they belong to
for (TableColumnHeader c : getColumnHeaders()) {
c.setTableHeaderRow(header);
}
}
! @Override public void setParentHeader(NestedTableColumnHeader parentHeader) {
super.setParentHeader(parentHeader);
label.setParentHeader(parentHeader);
}
ObservableList<? extends TableColumnBase> getColumns() {
--- 166,360 ----
double startX = header.getTableHeaderRow().sceneToLocal(innerRect.localToScene(innerRect.getBoundsInLocal())).getMinX() + 2;
header.dragAnchorX = me.getSceneX();
header.columnResizingStarted(startX);
}
me.consume();
};
! private static final EventHandler<MouseEvent> rectMouseDragged = me -> {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (! header.isColumnResizingEnabled()) return;
header.columnResizing(column, me);
me.consume();
};
! private static final EventHandler<MouseEvent> rectMouseReleased = me -> {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (! header.isColumnResizingEnabled()) return;
header.columnResizingComplete(column, me);
me.consume();
};
! private static final EventHandler<MouseEvent> rectCursorChangeListener = me -> {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (header.getCursor() == null) { // If there's a cursor for the whole header, don't override it
rect.setCursor(header.isColumnResizingEnabled() && rect.isHover() &&
column.isResizable() ? Cursor.H_RESIZE : null);
}
};
/***************************************************************************
* *
* Public Methods *
* *
**************************************************************************/
! /** {@inheritDoc} */
! @Override void dispose() {
! super.dispose();
! if (label != null) {
! label.dispose();
! }
!
! if (getColumns() != null) {
! getColumns().removeListener(weakColumnsListener);
! }
!
! for (int i = 0; i < getColumnHeaders().size(); i++) {
! TableColumnHeader header = getColumnHeaders().get(i);
! header.dispose();
! }
!
! for (Rectangle rect : dragRects.values()) {
! if (rect != null) {
! rect.visibleProperty().unbind();
! }
! }
! dragRects.clear();
! getChildren().clear();
!
! changeListenerHandler.dispose();
! }
!
! /**
! * Returns an unmodifiable list of the {@link TableColumnHeader} instances
! * that are children of this NestedTableColumnHeader.
! */
! @ReturnsUnmodifiableCollection
! public final ObservableList<TableColumnHeader> getColumnHeaders() {
! if (columnHeaders == null) {
! columnHeaders = FXCollections.<TableColumnHeader>observableArrayList();
! unmodifiableColumnHeaders = FXCollections.unmodifiableObservableList(columnHeaders);
}
+ return unmodifiableColumnHeaders;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void layoutChildren() {
+ double w = getWidth() - snappedLeftInset() - snappedRightInset();
+ double h = getHeight() - snappedTopInset() - snappedBottomInset();
+
+ int labelHeight = (int) label.prefHeight(-1);
+
+ if (label.isVisible()) {
+ // label gets to span whole width and sits at top
+ label.resize(w, labelHeight);
+ label.relocate(snappedLeftInset(), snappedTopInset());
}
! // children columns need to share the total available width
! double x = snappedLeftInset();
! int pos = 0;
! for (int i = 0, max = getColumnHeaders().size(); i < max; i++) {
! TableColumnHeader n = getColumnHeaders().get(i);
! if (! n.isVisible()) continue;
!
! double prefWidth = snapSize(n.prefWidth(-1));
! // double prefHeight = n.prefHeight(-1);
!
! // position the column header in the default location...
! n.resize(prefWidth, snapSize(h - labelHeight));
! n.relocate(x, labelHeight + snappedTopInset());
!
! // // ...but, if there are no children of this column, we should ensure
! // // that it is resized vertically such that it goes to the very
! // // bottom of the table header row.
! // if (getTableHeaderRow() != null && n.getCol().getColumns().isEmpty()) {
! // Bounds bounds = getTableHeaderRow().sceneToLocal(n.localToScene(n.getBoundsInLocal()));
! // prefHeight = getTableHeaderRow().getHeight() - bounds.getMinY();
! // n.resize(prefWidth, prefHeight);
! // }
!
! // shuffle along the x-axis appropriately
! x += prefWidth;
!
! // position drag overlay to intercept column resize requests
! Rectangle dragRect = dragRects.get(n.getTableColumn());
! if (dragRect != null) {
! dragRect.setHeight(n.getDragRectHeight());
! dragRect.relocate(x - DRAG_RECT_WIDTH / 2, snappedTopInset() + labelHeight);
! }
! }
! }
!
! // sum up all children columns
! /** {@inheritDoc} */
! @Override protected double computePrefWidth(double height) {
! checkState();
!
! double width = 0.0F;
!
! if (getColumns() != null) {
! for (TableColumnHeader c : getColumnHeaders()) {
! if (c.isVisible()) {
! width += snapSize(c.computePrefWidth(height));
! }
! }
! }
!
! return width;
! }
!
! /** {@inheritDoc} */
! @Override protected double computePrefHeight(double width) {
! checkState();
!
! double height = 0.0F;
!
! if (getColumnHeaders() != null) {
! for (TableColumnHeader n : getColumnHeaders()) {
! height = Math.max(height, n.prefHeight(-1));
! }
! }
!
! return height + label.prefHeight(-1) + snappedTopInset() + snappedBottomInset();
! }
!
!
!
! /***************************************************************************
! * *
! * Private Implementation *
! * *
! **************************************************************************/
!
! @Override void setTableHeaderRow(TableHeaderRow header) {
super.setTableHeaderRow(header);
label.setTableHeaderRow(header);
// tell all children columns what TableHeader they belong to
for (TableColumnHeader c : getColumnHeaders()) {
c.setTableHeaderRow(header);
}
}
! @Override void setParentHeader(NestedTableColumnHeader parentHeader) {
super.setParentHeader(parentHeader);
label.setParentHeader(parentHeader);
}
ObservableList<? extends TableColumnBase> getColumns() {
*** 275,285 ****
if (index >= 0 && index < parentColumnHeaders.size()) {
parentColumnHeaders.set(index, createColumnHeader(getTableColumn()));
}
} else {
// otherwise just remove all the columns
! getColumnHeaders().clear();
}
} else {
List<TableColumnHeader> oldHeaders = new ArrayList<>(getColumnHeaders());
List<TableColumnHeader> newHeaders = new ArrayList<>();
--- 401,411 ----
if (index >= 0 && index < parentColumnHeaders.size()) {
parentColumnHeaders.set(index, createColumnHeader(getTableColumn()));
}
} else {
// otherwise just remove all the columns
! columnHeaders.clear();
}
} else {
List<TableColumnHeader> oldHeaders = new ArrayList<>(getColumnHeaders());
List<TableColumnHeader> newHeaders = new ArrayList<>();
*** 302,312 ****
if (!found) {
newHeaders.add(createColumnHeader(column));
}
}
! getColumnHeaders().setAll(newHeaders);
// dispose all old headers
oldHeaders.removeAll(newHeaders);
for (int i = 0; i < oldHeaders.size(); i++) {
oldHeaders.get(i).dispose();
--- 428,438 ----
if (!found) {
newHeaders.add(createColumnHeader(column));
}
}
! columnHeaders.setAll(newHeaders);
// dispose all old headers
oldHeaders.removeAll(newHeaders);
for (int i = 0; i < oldHeaders.size(); i++) {
oldHeaders.get(i).dispose();
*** 320,453 ****
for (TableColumnHeader header : getColumnHeaders()) {
header.applyCss();
}
}
! @Override void dispose() {
! super.dispose();
!
! if (label != null) {
! label.dispose();
! }
!
! if (getColumns() != null) {
! getColumns().removeListener(weakColumnsListener);
! }
!
! for (int i = 0; i < getColumnHeaders().size(); i++) {
! TableColumnHeader header = getColumnHeaders().get(i);
! header.dispose();
! }
!
! for (Rectangle rect : dragRects.values()) {
! if (rect != null) {
! rect.visibleProperty().unbind();
! }
! }
! dragRects.clear();
! getChildren().clear();
!
! changeListenerHandler.dispose();
! }
!
! public ObservableList<TableColumnHeader> getColumnHeaders() {
! if (columnHeaders == null) columnHeaders = FXCollections.<TableColumnHeader>observableArrayList();
! return columnHeaders;
! }
!
! @Override protected void layoutChildren() {
! double w = getWidth() - snappedLeftInset() - snappedRightInset();
! double h = getHeight() - snappedTopInset() - snappedBottomInset();
!
! int labelHeight = (int) label.prefHeight(-1);
!
! if (label.isVisible()) {
! // label gets to span whole width and sits at top
! label.resize(w, labelHeight);
! label.relocate(snappedLeftInset(), snappedTopInset());
! }
!
! // children columns need to share the total available width
! double x = snappedLeftInset();
! int pos = 0;
! for (int i = 0, max = getColumnHeaders().size(); i < max; i++) {
! TableColumnHeader n = getColumnHeaders().get(i);
! if (! n.isVisible()) continue;
!
! double prefWidth = snapSize(n.prefWidth(-1));
! // double prefHeight = n.prefHeight(-1);
!
! // position the column header in the default location...
! n.resize(prefWidth, snapSize(h - labelHeight));
! n.relocate(x, labelHeight + snappedTopInset());
!
! // // ...but, if there are no children of this column, we should ensure
! // // that it is resized vertically such that it goes to the very
! // // bottom of the table header row.
! // if (getTableHeaderRow() != null && n.getCol().getColumns().isEmpty()) {
! // Bounds bounds = getTableHeaderRow().sceneToLocal(n.localToScene(n.getBoundsInLocal()));
! // prefHeight = getTableHeaderRow().getHeight() - bounds.getMinY();
! // n.resize(prefWidth, prefHeight);
! // }
!
! // shuffle along the x-axis appropriately
! x += prefWidth;
!
! // position drag overlay to intercept column resize requests
! Rectangle dragRect = dragRects.get(n.getTableColumn());
! if (dragRect != null) {
! dragRect.setHeight(n.getDragRectHeight());
! dragRect.relocate(x - DRAG_RECT_WIDTH / 2, snappedTopInset() + labelHeight);
! }
! }
! }
!
! @Override
! double getDragRectHeight() {
return label.prefHeight(-1);
}
! // sum up all children columns
! @Override protected double computePrefWidth(double height) {
! checkState();
!
! double width = 0.0F;
!
! if (getColumns() != null) {
! for (TableColumnHeader c : getColumnHeaders()) {
! if (c.isVisible()) {
! width += snapSize(c.computePrefWidth(height));
! }
! }
! }
!
! return width;
! }
!
! @Override protected double computePrefHeight(double width) {
! checkState();
!
! double height = 0.0F;
!
! if (getColumnHeaders() != null) {
! for (TableColumnHeader n : getColumnHeaders()) {
! height = Math.max(height, n.prefHeight(-1));
! }
! }
!
! return height + label.prefHeight(-1) + snappedTopInset() + snappedBottomInset();
! }
!
! // protected to allow subclasses to customise the column header types
! protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
return col.getColumns().isEmpty() ?
new TableColumnHeader(getTableViewSkin(), col) :
new NestedTableColumnHeader(getTableViewSkin(), col);
}
! // allowing subclasses to force an update on the headers
! protected void setHeadersNeedUpdate() {
updateColumns = true;
// go through children columns - they should update too
for (int i = 0; i < getColumnHeaders().size(); i++) {
TableColumnHeader header = getColumnHeaders().get(i);
--- 446,467 ----
for (TableColumnHeader header : getColumnHeaders()) {
header.applyCss();
}
}
! /** {@inheritDoc} */
! @Override double getDragRectHeight() {
return label.prefHeight(-1);
}
! TableColumnHeader createTableColumnHeader(TableColumnBase col) {
return col.getColumns().isEmpty() ?
new TableColumnHeader(getTableViewSkin(), col) :
new NestedTableColumnHeader(getTableViewSkin(), col);
}
! void setHeadersNeedUpdate() {
updateColumns = true;
// go through children columns - they should update too
for (int i = 0; i < getColumnHeaders().size(); i++) {
TableColumnHeader header = getColumnHeaders().get(i);
*** 456,473 ****
}
}
requestLayout();
}
-
-
- /***************************************************************************
- * *
- * Private Implementation *
- * *
- **************************************************************************/
-
private void updateContent() {
// create a temporary list so we only do addAll into the main content
// observableArrayList once.
final List<Node> content = new ArrayList<Node>();
--- 470,479 ----
*** 501,511 ****
if (columns == null) {
return;
}
! final TableViewSkinBase<?,?,?,?,?,?> skin = getTableViewSkin();
Callback<ResizeFeaturesBase, Boolean> columnResizePolicy = skin.columnResizePolicyProperty().get();
boolean isConstrainedResize =
skin instanceof TableViewSkin ? TableView.CONSTRAINED_RESIZE_POLICY.equals(columnResizePolicy) :
skin instanceof TreeTableViewSkin ? TreeTableView.CONSTRAINED_RESIZE_POLICY.equals(columnResizePolicy) :
false;
--- 507,517 ----
if (columns == null) {
return;
}
! final TableViewSkinBase<?,?,?,?,?> skin = getTableViewSkin();
Callback<ResizeFeaturesBase, Boolean> columnResizePolicy = skin.columnResizePolicyProperty().get();
boolean isConstrainedResize =
skin instanceof TableViewSkin ? TableView.CONSTRAINED_RESIZE_POLICY.equals(columnResizePolicy) :
skin instanceof TreeTableViewSkin ? TreeTableView.CONSTRAINED_RESIZE_POLICY.equals(columnResizePolicy) :
false;