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 +1,7 @@
 /*
- * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
+ * 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,12 +21,13 @@
  * 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.collections.annotations.ReturnsUnmodifiableCollection;
 import javafx.collections.WeakListChangeListener;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.WeakHashMap;

@@ -49,10 +50,14 @@
  * 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,10 +85,11 @@
     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,10 +104,17 @@
      *                                                                         *
      * 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,14 +124,15 @@
         label.setTableHeaderRow(getTableHeaderRow());
         label.setParentHeader(getParentHeader());
         label.setNestedColumnHeader(this);
 
         if (getTableColumn() != null) {
-            changeListenerHandler.registerChangeListener(getTableColumn().textProperty(), "TABLE_COLUMN_TEXT");
+            changeListenerHandler.registerChangeListener(getTableColumn().textProperty(), e ->
+                    label.setVisible(getTableColumn().getText() != null && ! getTableColumn().getText().isEmpty()));
         }
 
-        changeListenerHandler.registerChangeListener(skin.columnResizePolicyProperty(), "TABLE_VIEW_COLUMN_RESIZE_POLICY");
+        changeListenerHandler.registerChangeListener(skin.columnResizePolicyProperty(), e -> updateContent());
     }
 
     
     
     /***************************************************************************

@@ -132,12 +146,11 @@
     };
     
     private final WeakListChangeListener weakColumnsListener =
             new WeakListChangeListener(columnsListener);
 
-    private static final EventHandler<MouseEvent> rectMousePressed = new EventHandler<MouseEvent>() {
-        @Override public void handle(MouseEvent me) {
+    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,82 +166,195 @@
                 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) {
+    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 = new EventHandler<MouseEvent>() {
-        @Override public void handle(MouseEvent me) {
+    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 = new EventHandler<MouseEvent>() {
-        @Override public void handle(MouseEvent me) {
+    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                                                          *
      *                                                                         *
      **************************************************************************/
 
-    @Override protected void handlePropertyChanged(String p) {
-        super.handlePropertyChanged(p);
+    /** {@inheritDoc} */
+    @Override void dispose() {
+        super.dispose();
 
-        if ("TABLE_VIEW_COLUMN_RESIZE_POLICY".equals(p)) {
-            updateContent();
-        } else if ("TABLE_COLUMN_TEXT".equals(p)) {
-            label.setVisible(getTableColumn().getText() != null && ! getTableColumn().getText().isEmpty());
+        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());
     }
 
-    @Override public void setTableHeaderRow(TableHeaderRow header) {
+        // 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 public void setParentHeader(NestedTableColumnHeader parentHeader) {
+    @Override void setParentHeader(NestedTableColumnHeader parentHeader) {
         super.setParentHeader(parentHeader);
         label.setParentHeader(parentHeader);
     }
 
     ObservableList<? extends TableColumnBase> getColumns() {

@@ -275,11 +401,11 @@
                 if (index >= 0 && index < parentColumnHeaders.size()) {
                     parentColumnHeaders.set(index, createColumnHeader(getTableColumn()));
                 }
             } else {
                 // otherwise just remove all the columns
-                getColumnHeaders().clear();
+                columnHeaders.clear();
             }
         } else {
             List<TableColumnHeader> oldHeaders = new ArrayList<>(getColumnHeaders());
             List<TableColumnHeader> newHeaders = new ArrayList<>();
             

@@ -302,11 +428,11 @@
                 if (!found) {
                     newHeaders.add(createColumnHeader(column));
                 }
             }
             
-            getColumnHeaders().setAll(newHeaders);
+            columnHeaders.setAll(newHeaders);
 
             // dispose all old headers
             oldHeaders.removeAll(newHeaders);
             for (int i = 0; i < oldHeaders.size(); i++) {
                 oldHeaders.get(i).dispose();

@@ -320,134 +446,22 @@
         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() {
+    /** {@inheritDoc} */
+    @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) {
+    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() {
+    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,18 +470,10 @@
             }
         }
         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>();
 

@@ -501,11 +507,11 @@
 
         if (columns == null) {
             return;
         }
 
-        final TableViewSkinBase<?,?,?,?,?,?> skin = getTableViewSkin();
+        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;