modules/controls/src/main/java/javafx/scene/control/skin/TableHeaderRow.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization

@@ -21,18 +21,22 @@
  * 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 java.util.*;
 
 import javafx.beans.InvalidationListener;
 import javafx.beans.WeakInvalidationListener;
 import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.StringProperty;
 import javafx.collections.ListChangeListener;
 import javafx.collections.WeakListChangeListener;
 import javafx.geometry.HPos;
 import javafx.geometry.Insets;

@@ -51,10 +55,16 @@
 
 import com.sun.javafx.scene.control.skin.resources.ControlResources;
 
 /**
  * Region responsible for painting the entire row of column headers.
+ *
+ * @since 9
+ * @see TableView
+ * @see TableViewSkin
+ * @see TreeTableView
+ * @see TreeTableViewSkin
  */
 public class TableHeaderRow extends StackPane {
 
     /***************************************************************************
      *                                                                         *

@@ -72,43 +82,24 @@
      * Private Fields                                                          *
      *                                                                         *
      **************************************************************************/
 
     private final VirtualFlow flow;
-
-    private final TableViewSkinBase tableSkin;
-
+    private final TableViewSkinBase<?,?,?,?,?> tableSkin;
     private Map<TableColumnBase, CheckMenuItem> columnMenuItems = new HashMap<TableColumnBase, CheckMenuItem>();
-
-    // Vertical line that is shown when columns are being reordered
-//    private Region columnReorderLine;
-
     private double scrollX;
-
     private double tableWidth;
-
     private Rectangle clip;
-
     private TableColumnHeader reorderingRegion;
 
     /**
      * This is the ghosted region representing the table column that is being
      * dragged. It moves along the x-axis but is fixed in the y-axis.
      */
     private StackPane dragHeader;
     private final Label dragHeaderLabel = new Label();
 
-    /*
-     * The header row is actually just one NestedTableColumnHeader that spans
-     * the entire width. Nested within this is the TableColumnHeader's and
-     * NestedTableColumnHeader's, as necessary. This makes it nice and clean
-     * to handle column reordering - we basically enforce the rule that column
-     * reordering only occurs within a single NestedTableColumnHeader, and only
-     * at that level.
-     */
-    private final NestedTableColumnHeader header;
-
     private Region filler;
 
     /**
      * This is the region where the user can interact with to show/hide columns.
      * It is positioned in the top-right hand corner of the TableHeaderRow, and

@@ -120,33 +111,69 @@
      * PopupMenu shown to users to allow for them to hide/show columns in the
      * table.
      */
     private ContextMenu columnPopupMenu;
 
-    private BooleanProperty reordering = new SimpleBooleanProperty(this, "reordering", false) {
-        @Override protected void invalidated() {
-            TableColumnHeader r = getReorderingRegion();
-            if (r != null) {
-                double dragHeaderHeight = r.getNestedColumnHeader() != null ?
-                        r.getNestedColumnHeader().getHeight() :
-                        getReorderingRegion().getHeight();
 
-                dragHeader.resize(dragHeader.getWidth(), dragHeaderHeight);
-                dragHeader.setTranslateY(getHeight() - dragHeaderHeight);
+
+    /***************************************************************************
+     *                                                                         *
+     * Listeners                                                               *
+     *                                                                         *
+     **************************************************************************/
+
+    private InvalidationListener tableWidthListener = o -> updateTableWidth();
+
+    private InvalidationListener tablePaddingListener = o -> updateTableWidth();
+
+    // This is necessary for RT-20300 (but was updated for RT-20840)
+    private ListChangeListener visibleLeafColumnsListener = c -> getRootHeader().setHeadersNeedUpdate();
+
+    private final ListChangeListener tableColumnsListener = c -> {
+        while (c.next()) {
+            updateTableColumnListeners(c.getAddedSubList(), c.getRemoved());
             }
-            dragHeader.setVisible(isReordering());
+    };
+
+    private final InvalidationListener columnTextListener = observable -> {
+        TableColumnBase<?,?> column = (TableColumnBase<?,?>) ((StringProperty)observable).getBean();
+        CheckMenuItem menuItem = columnMenuItems.get(column);
+        if (menuItem != null) {
+            menuItem.setText(getText(column.getText(), column));
         }
     };
 
+    private final WeakInvalidationListener weakTableWidthListener =
+            new WeakInvalidationListener(tableWidthListener);
+
+    private final WeakInvalidationListener weakTablePaddingListener =
+            new WeakInvalidationListener(tablePaddingListener);
+
+    private final WeakListChangeListener weakVisibleLeafColumnsListener =
+            new WeakListChangeListener(visibleLeafColumnsListener);
+
+    private final WeakListChangeListener weakTableColumnsListener =
+            new WeakListChangeListener(tableColumnsListener);
+
+    private final WeakInvalidationListener weakColumnTextListener =
+            new WeakInvalidationListener(columnTextListener);
+
 
 
     /***************************************************************************
      *                                                                         *
      * Constructor                                                             *
      *                                                                         *
      **************************************************************************/
     
+    /**
+     * Creates a new TableHeaderRow instance to visually represent the column
+     * header area of controls such as {@link javafx.scene.control.TableView} and
+     * {@link javafx.scene.control.TreeTableView}.
+     *
+     * @param skin The skin used by the UI control.
+     */
     public TableHeaderRow(final TableViewSkinBase skin) {
         this.tableSkin = skin;
         this.flow = skin.flow;
 
         getStyleClass().setAll("column-header-background");

@@ -175,13 +202,14 @@
         dragHeader.getStyleClass().setAll("column-drag-header");
         dragHeader.setManaged(false);
         dragHeader.getChildren().add(dragHeaderLabel);
 
         // the header lives inside a NestedTableColumnHeader
-        header = createRootHeader();
-        header.setFocusTraversable(false);
-        header.setTableHeaderRow(this);
+        NestedTableColumnHeader rootHeader = new NestedTableColumnHeader(tableSkin, null);
+        setRootHeader(rootHeader);
+        rootHeader.setFocusTraversable(false);
+        rootHeader.setTableHeaderRow(this);
 
         // The 'filler' area that extends from the right-most column to the edge
         // of the tableview, or up to the 'column control' button
         filler = new Region();
         filler.getStyleClass().setAll("filler");

@@ -221,82 +249,83 @@
 
         // the actual header
         // the region that is anchored above the vertical scrollbar
         // a 'ghost' of the header being dragged by the user to force column
         // reordering
-        getChildren().addAll(filler, header, cornerRegion, dragHeader);
+        getChildren().addAll(filler, rootHeader, cornerRegion, dragHeader);
     }
     
 
 
     /***************************************************************************
      *                                                                         *
-     * Listeners                                                               *
+     * Properties                                                              *
      *                                                                         *
      **************************************************************************/    
     
-    private InvalidationListener tableWidthListener = valueModel -> {
-        updateTableWidth();
-    };
-
-    private InvalidationListener tablePaddingListener = valueModel -> {
-        updateTableWidth();
-    };
+    // --- reordering
+    private BooleanProperty reordering = new SimpleBooleanProperty(this, "reordering", false) {
+        @Override protected void invalidated() {
+            TableColumnHeader r = getReorderingRegion();
+            if (r != null) {
+                double dragHeaderHeight = r.getNestedColumnHeader() != null ?
+                        r.getNestedColumnHeader().getHeight() :
+                        getReorderingRegion().getHeight();
     
-    private ListChangeListener visibleLeafColumnsListener = new ListChangeListener<TableColumn<?,?>>() {
-        @Override public void onChanged(ListChangeListener.Change<? extends TableColumn<?,?>> c) {
-            // This is necessary for RT-20300 (but was updated for RT-20840)
-            header.setHeadersNeedUpdate();
+                dragHeader.resize(dragHeader.getWidth(), dragHeaderHeight);
+                dragHeader.setTranslateY(getHeight() - dragHeaderHeight);
         }
-    };
-    
-    private final ListChangeListener tableColumnsListener = c -> {
-        while (c.next()) {
-            updateTableColumnListeners(c.getAddedSubList(), c.getRemoved());
+            dragHeader.setVisible(isReordering());
         }
     };
-    
-    private final InvalidationListener columnTextListener = observable -> {
-        TableColumnBase<?,?> column = (TableColumnBase<?,?>) ((StringProperty)observable).getBean();
-        CheckMenuItem menuItem = columnMenuItems.get(column);
-        if (menuItem != null) {
-            menuItem.setText(getText(column.getText(), column));
+    final void setReordering(boolean value) {
+        this.reordering.set(value);
+    }
+    final boolean isReordering() {
+        return reordering.get();
+    }
+    final BooleanProperty reorderingProperty() {
+        return reordering;
         }
-    };
-    
-    private final WeakInvalidationListener weakTableWidthListener = 
-            new WeakInvalidationListener(tableWidthListener);
-
-    private final WeakInvalidationListener weakTablePaddingListener =
-            new WeakInvalidationListener(tablePaddingListener);
-
-    private final WeakListChangeListener weakVisibleLeafColumnsListener =
-            new WeakListChangeListener(visibleLeafColumnsListener);
-    
-    private final WeakListChangeListener weakTableColumnsListener =
-            new WeakListChangeListener(tableColumnsListener);
     
-    private final WeakInvalidationListener weakColumnTextListener = 
-            new WeakInvalidationListener(columnTextListener);
+    // --- root header
+    /*
+     * The header row is actually just one NestedTableColumnHeader that spans
+     * the entire width. Nested within this is the TableColumnHeader's and
+     * NestedTableColumnHeader's, as necessary. This makes it nice and clean
+     * to handle column reordering - we basically enforce the rule that column
+     * reordering only occurs within a single NestedTableColumnHeader, and only
+     * at that level.
+     */
+    private ReadOnlyObjectWrapper<NestedTableColumnHeader> rootHeader = new ReadOnlyObjectWrapper<>(this, "rootHeader");
+    private final ReadOnlyObjectProperty<NestedTableColumnHeader> rootHeaderProperty() {
+        return rootHeader.getReadOnlyProperty();
+    }
+    final NestedTableColumnHeader getRootHeader() {
+        return rootHeader.get();
+    }
+    private final void setRootHeader(NestedTableColumnHeader value) {
+        rootHeader.set(value);
+    }
 
 
 
     /***************************************************************************
      *                                                                         *
-     * Public Methods                                                          *
+     * Public API                                                              *
      *                                                                         *
      **************************************************************************/
 
     /** {@inheritDoc} */
     @Override protected void layoutChildren() {
         double x = scrollX;
-        double headerWidth = snapSize(header.prefWidth(-1));
+        double headerWidth = snapSize(getRootHeader().prefWidth(-1));
         double prefHeight = getHeight() - snappedTopInset() - snappedBottomInset();
         double cornerWidth = snapSize(flow.getVbar().prefWidth(-1));
 
         // position the main nested header
-        header.resizeRelocate(x, snappedTopInset(), headerWidth, prefHeight);
+        getRootHeader().resizeRelocate(x, snappedTopInset(), headerWidth, prefHeight);
         
         // position the filler region
         final Control control = tableSkin.getSkinnable();
         if (control == null) {
             return;

@@ -314,11 +343,11 @@
         cornerRegion.resizeRelocate(tableWidth - cornerWidth, snappedTopInset(), cornerWidth, prefHeight);
     }
 
     /** {@inheritDoc} */
     @Override protected double computePrefWidth(double height) {
-        return header.prefWidth(height);
+        return getRootHeader().prefWidth(height);
     }
 
     /** {@inheritDoc} */
     @Override protected double computeMinHeight(double width) {
         return computePrefHeight(width);

@@ -326,89 +355,72 @@
 
     /** {@inheritDoc} */
     @Override protected double computePrefHeight(double width) {
         // we hardcode 24.0 here to avoid RT-37616, where the
         // entire header row would disappear when all columns were hidden. 
-        double headerPrefHeight = header.prefHeight(width);
+        double headerPrefHeight = getRootHeader().prefHeight(width);
         headerPrefHeight = headerPrefHeight == 0.0 ? 24.0 : headerPrefHeight;
         return snappedTopInset() + headerPrefHeight + snappedBottomInset();
     }
 
-    // protected to allow subclasses to provide a custom root header
-    protected NestedTableColumnHeader createRootHeader() {
-        return new NestedTableColumnHeader(tableSkin, null);
-    }
-
-    // protected to allow subclasses access to the TableViewSkinBase instance
-    protected TableViewSkinBase<?,?,?,?,?,?> getTableSkin() {
-        return this.tableSkin;
-    }
-
-    // protected to allow subclasses to modify the horizontal scrolling
-    protected void updateScrollX() {
+    // used to be protected to allow subclasses to modify the horizontal scrolling,
+    // but made private again for JDK 9
+    void updateScrollX() {
         scrollX = flow.getHbar().isVisible() ? -flow.getHbar().getValue() : 0.0F;
         requestLayout();
 
         // Fix for RT-36392: without this call even though we call requestLayout()
         // we don't seem to ever see the layoutChildren() method above called,
         // which means the layout is not always updated to use the latest scrollX.
         layout();
     }
 
-    public final void setReordering(boolean value) {
-        this.reordering.set(value);
+    // used to be protected to allow subclass to customise the width, to allow for features
+    // such as row headers, but made private again for JDK 9
+    private void updateTableWidth() {
+        // snapping added for RT-19428
+        final Control c = tableSkin.getSkinnable();
+        if (c == null) {
+            this.tableWidth = 0;
+        } else {
+            Insets insets = c.getInsets() == null ? Insets.EMPTY : c.getInsets();
+            double padding = snapSize(insets.getLeft()) + snapSize(insets.getRight());
+            this.tableWidth = snapSize(c.getWidth()) - padding;
     }
 
-    public final boolean isReordering() {
-        return reordering.get();
+        clip.setWidth(tableWidth);
     }
 
-    public final BooleanProperty reorderingProperty() {
-        return reordering;
-    }
 
-    public TableColumnHeader getReorderingRegion() {
+
+    /***************************************************************************
+     *                                                                         *
+     * Private Implementation                                                  *
+     *                                                                         *
+     **************************************************************************/
+
+    TableColumnHeader getReorderingRegion() {
         return reorderingRegion;
     }
 
-    public void setReorderingColumn(TableColumnBase rc) {
+    void setReorderingColumn(TableColumnBase rc) {
         dragHeaderLabel.setText(rc == null ? "" : rc.getText());
     }
 
-    public void setReorderingRegion(TableColumnHeader reorderingRegion) {
+    void setReorderingRegion(TableColumnHeader reorderingRegion) {
         this.reorderingRegion = reorderingRegion;
 
         if (reorderingRegion != null) {
             dragHeader.resize(reorderingRegion.getWidth(), dragHeader.getHeight());
         }
     }
 
-    public void setDragHeaderX(double dragHeaderX) {
+    void setDragHeaderX(double dragHeaderX) {
         dragHeader.setTranslateX(dragHeaderX);
     }
 
-    public NestedTableColumnHeader getRootHeader() {
-        return header;
-    }
-
-    // protected to allow subclass to customise the width, to allow for features
-    // such as row headers
-    protected void updateTableWidth() {
-        // snapping added for RT-19428
-        final Control c = tableSkin.getSkinnable();
-        if (c == null) {
-            this.tableWidth = 0;
-        } else {
-            Insets insets = c.getInsets() == null ? Insets.EMPTY : c.getInsets();
-            double padding = snapSize(insets.getLeft()) + snapSize(insets.getRight());
-            this.tableWidth = snapSize(c.getWidth()) - padding;
-        }
-
-        clip.setWidth(tableWidth);
-    }
-
-    public TableColumnHeader getColumnHeaderFor(final TableColumnBase<?,?> col) {
+    TableColumnHeader getColumnHeaderFor(final TableColumnBase<?,?> col) {
         if (col == null) return null;
         List<TableColumnBase<?,?>> columnChain = new ArrayList<>();
         columnChain.add(col);
 
         TableColumnBase<?,?> parent = col.getParentColumn();

@@ -429,11 +441,11 @@
             currentHeader = getColumnHeaderFor(column, currentHeader);
         }
         return currentHeader;
     }
 
-    public TableColumnHeader getColumnHeaderFor(final TableColumnBase<?,?> col, TableColumnHeader currentHeader) {
+    private TableColumnHeader getColumnHeaderFor(final TableColumnBase<?,?> col, TableColumnHeader currentHeader) {
         if (currentHeader instanceof NestedTableColumnHeader) {
             List<TableColumnHeader> headers = ((NestedTableColumnHeader)currentHeader).getColumnHeaders();
 
             for (int i = 0; i < headers.size(); i++) {
                 TableColumnHeader header = headers.get(i);

@@ -444,17 +456,10 @@
         }
 
         return null;
     }
 
-
-    /***************************************************************************
-     *                                                                         *
-     * Private Implementation                                                  *
-     *                                                                         *
-     **************************************************************************/
-
     private void updateTableColumnListeners(List<? extends TableColumnBase<?,?>> added, List<? extends TableColumnBase<?,?>> removed) {
         // remove binding from all removed items
         for (TableColumnBase tc : removed) {
             remove(tc);
         }

@@ -481,11 +486,11 @@
     }
 
     private void rebuildColumnMenu() {
         columnPopupMenu.getItems().clear();
 
-        for (TableColumnBase<?,?> col : getTableSkin().getColumns()) {
+        for (TableColumnBase<?,?> col : tableSkin.getColumns()) {
             // we only create menu items for leaf columns, visible or not
             if (col.getColumns().isEmpty()) {
                 createMenuItem(col);
             } else {
                 List<TableColumnBase<?,?>> leafColumns = getLeafColumns(col);