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;