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


   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.control.skin;
  27 


  28 import javafx.beans.property.DoubleProperty;


  29 import javafx.beans.value.WritableValue;
  30 import javafx.collections.ListChangeListener;
  31 import javafx.collections.ObservableList;
  32 import javafx.collections.WeakListChangeListener;
  33 import javafx.css.CssMetaData;
  34 import javafx.css.PseudoClass;
  35 import javafx.css.Styleable;
  36 import javafx.css.StyleableDoubleProperty;
  37 import javafx.css.StyleableProperty;
  38 import javafx.event.EventHandler;
  39 import javafx.geometry.HPos;
  40 import javafx.geometry.Insets;
  41 import javafx.geometry.Pos;
  42 import javafx.geometry.VPos;
  43 import javafx.scene.AccessibleAttribute;
  44 import javafx.scene.AccessibleRole;
  45 import javafx.scene.Node;
  46 import javafx.scene.control.ContextMenu;
  47 import javafx.scene.control.Label;
  48 import javafx.scene.control.TableColumn;
  49 import javafx.scene.control.TableColumnBase;
  50 import javafx.scene.input.ContextMenuEvent;
  51 import javafx.scene.input.MouseEvent;
  52 import javafx.scene.layout.GridPane;
  53 import javafx.scene.layout.HBox;
  54 import javafx.scene.layout.Priority;
  55 import javafx.scene.layout.Region;
  56 
  57 import java.util.ArrayList;
  58 import java.util.Collections;
  59 import java.util.List;
  60 import java.util.Locale;
  61 
  62 import com.sun.javafx.css.converters.SizeConverter;
  63 import com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler;
  64 
  65 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.getSortTypeName;
  66 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.getSortTypeProperty;
  67 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.isAscending;
  68 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.isDescending;
  69 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.setSortType;
  70 
  71 
  72 /**
  73  * Region responsible for painting a single column header.
  74  */
  75 public class TableColumnHeader extends Region {
  76 
  77     /***************************************************************************
  78      *                                                                         *
  79      * Static Fields                                                           *
  80      *                                                                         *
  81      **************************************************************************/
  82 
  83     // Copied from TableColumn. The value here should always be in-sync with
  84     // the value in TableColumn
  85     static final double DEFAULT_COLUMN_WIDTH = 80.0F;
  86 
  87 
  88 
  89     /***************************************************************************
  90      *                                                                         *
  91      * Private Fields                                                          *
  92      *                                                                         *
  93      **************************************************************************/
  94     
  95     private boolean autoSizeComplete = false;
  96 
  97     private double dragOffset;
  98     private final TableViewSkinBase<?,?,?,?,?,TableColumnBase<?,?>> skin;
  99     private NestedTableColumnHeader nestedColumnHeader;
 100     private final TableColumnBase<?,?> column;
 101     private TableHeaderRow tableHeaderRow;
 102     private NestedTableColumnHeader parentHeader;
 103 
 104     // work out where this column currently is within its parent
 105     Label label;
 106 
 107     // sort order
 108     int sortPos = -1;
 109     private Region arrow;
 110     private Label sortOrderLabel;
 111     private HBox sortOrderDots;
 112     private Node sortArrow;
 113     private boolean isSortColumn;
 114 
 115     private boolean isSizeDirty = false;
 116 
 117     boolean isLastVisibleColumn = false;
 118 
 119     // package for testing
 120     int columnIndex = -1;
 121 
 122     private int newColumnPos;
 123 
 124     // the line drawn in the table when a user presses and moves a column header
 125     // to indicate where the column will be dropped. This is provided by the
 126     // table skin, but manipulated by the header
 127     protected final Region columnReorderLine;
 128 
 129 
 130     
 131     /***************************************************************************
 132      *                                                                         *
 133      * Constructor                                                             *
 134      *                                                                         *
 135      **************************************************************************/
 136 







 137     public TableColumnHeader(final TableViewSkinBase skin, final TableColumnBase tc) {
 138         this.skin = skin;
 139         this.column = tc;
 140         this.columnReorderLine = skin.getColumnReorderLine();
 141         
 142         setFocusTraversable(false);
 143 
 144         updateColumnIndex();
 145         initUI();
 146         
 147         // change listener for multiple properties
 148         changeListenerHandler = new MultiplePropertyChangeListenerHandler(p -> {
 149             handlePropertyChanged(p);
 150             return null;
 151         });
 152         changeListenerHandler.registerChangeListener(sceneProperty(), "SCENE");
 153         
 154         if (column != null && skin != null) {
 155             updateSortPosition();
 156             skin.getSortOrder().addListener(weakSortOrderListener);
 157             skin.getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener);
 158         }
 159 
 160         if (column != null) {
 161             changeListenerHandler.registerChangeListener(column.idProperty(), "TABLE_COLUMN_ID");
 162             changeListenerHandler.registerChangeListener(column.styleProperty(), "TABLE_COLUMN_STYLE");
 163             changeListenerHandler.registerChangeListener(column.widthProperty(), "TABLE_COLUMN_WIDTH");
 164             changeListenerHandler.registerChangeListener(column.visibleProperty(), "TABLE_COLUMN_VISIBLE");
 165             changeListenerHandler.registerChangeListener(column.sortNodeProperty(), "TABLE_COLUMN_SORT_NODE");
 166             changeListenerHandler.registerChangeListener(column.sortableProperty(), "TABLE_COLUMN_SORTABLE");
 167             changeListenerHandler.registerChangeListener(column.textProperty(), "TABLE_COLUMN_TEXT");
 168             changeListenerHandler.registerChangeListener(column.graphicProperty(), "TABLE_COLUMN_GRAPHIC");












 169 
 170             column.getStyleClass().addListener(weakStyleClassListener);
 171 
 172             setId(column.getId());
 173             setStyle(column.getStyle());
 174             updateStyleClass();
 175             /* Having TableColumn role parented by TableColumn causes VoiceOver to be unhappy */
 176             setAccessibleRole(AccessibleRole.TABLE_COLUMN);
 177         }
 178     }
 179     
 180     
 181     
 182     /***************************************************************************
 183      *                                                                         *
 184      * Listeners                                                               *
 185      *                                                                         *
 186      **************************************************************************/
 187     
 188     protected final MultiplePropertyChangeListenerHandler changeListenerHandler;
 189     
 190     private ListChangeListener<TableColumnBase<?,?>> sortOrderListener = c -> {
 191         updateSortPosition();
 192     };
 193     
 194     private ListChangeListener<TableColumnBase<?,?>> visibleLeafColumnsListener = c -> {
 195         updateColumnIndex();
 196         updateSortPosition();
 197     };
 198     
 199     private ListChangeListener<String> styleClassListener = c -> {
 200         updateStyleClass();
 201     };
 202     
 203     private WeakListChangeListener<TableColumnBase<?,?>> weakSortOrderListener =
 204             new WeakListChangeListener<TableColumnBase<?,?>>(sortOrderListener);
 205     private final WeakListChangeListener<TableColumnBase<?,?>> weakVisibleLeafColumnsListener =
 206             new WeakListChangeListener<TableColumnBase<?,?>>(visibleLeafColumnsListener);
 207     private final WeakListChangeListener<String> weakStyleClassListener =
 208             new WeakListChangeListener<String>(styleClassListener);


 247     
 248     private static final EventHandler<ContextMenuEvent> contextMenuRequestedHandler = me -> {
 249         TableColumnHeader header = (TableColumnHeader) me.getSource();
 250         TableColumnBase tableColumn = header.getTableColumn();
 251 
 252         ContextMenu menu = tableColumn.getContextMenu();
 253         if (menu != null) {
 254             menu.show(header, me.getScreenX(), me.getScreenY());
 255             me.consume();
 256         }
 257     };
 258 
 259 
 260 
 261     /***************************************************************************
 262      *                                                                         *
 263      * Properties                                                              *
 264      *                                                                         *
 265      **************************************************************************/
 266 

 267     private DoubleProperty size;
 268     private double getSize() {
 269         return size == null ? 20.0 : size.doubleValue();
 270     }
 271     private DoubleProperty sizeProperty() {
 272         if (size == null) {
 273             size = new StyleableDoubleProperty(20) {
 274                 @Override
 275                 protected void invalidated() {
 276                     double value = get();
 277                     if (value <= 0) {
 278                         if (isBound()) {
 279                             unbind();
 280                         }
 281                         set(20);
 282                         throw new IllegalArgumentException("Size cannot be 0 or negative");
 283                     }
 284                 }
 285                 
 286                 
 287 
 288                 @Override public Object getBean() {
 289                     return TableColumnHeader.this;
 290                 }
 291 
 292                 @Override public String getName() {
 293                     return "size";
 294                 }
 295 
 296                 @Override public CssMetaData<TableColumnHeader,Number> getCssMetaData() {
 297                     return StyleableProperties.SIZE;
 298                 }
 299             };
 300         }
 301         return size;
 302     }
 303 
 304 
 305     
 306     /***************************************************************************
 307      *                                                                         *
 308      * Public Methods                                                          *
 309      *                                                                         *
 310      **************************************************************************/
 311 
 312     protected void handlePropertyChanged(String p) {
 313         if ("SCENE".equals(p)) {
 314             updateScene();
 315         } else if ("TABLE_COLUMN_VISIBLE".equals(p)) {
 316             setVisible(getTableColumn().isVisible());
 317         } else if ("TABLE_COLUMN_WIDTH".equals(p)) {
 318             // It is this that ensures that when a column is resized that the header
 319             // visually adjusts its width as necessary.
 320             isSizeDirty = true;
 321             requestLayout();
 322         } else if ("TABLE_COLUMN_ID".equals(p)) {
 323             setId(column.getId());
 324         } else if ("TABLE_COLUMN_STYLE".equals(p)) {
 325             setStyle(column.getStyle());
 326         } else if ("TABLE_COLUMN_SORT_TYPE".equals(p)) {
 327             updateSortGrid();
 328             if (arrow != null) {
 329                 arrow.setRotate(isAscending(column) ? 180 : 0.0);
 330             }
 331         } else if ("TABLE_COLUMN_SORT_NODE".equals(p)) {
 332             updateSortGrid();
 333         } else if ("TABLE_COLUMN_SORTABLE".equals(p)) {
 334             // we need to notify all headers that a sortable state has changed,
 335             // in case the sort grid in other columns needs to be updated.
 336             if (skin.getSortOrder().contains(getTableColumn())) {
 337                 NestedTableColumnHeader root = getTableHeaderRow().getRootHeader();
 338                 updateAllHeaders(root);
 339             }
 340         } else if ("TABLE_COLUMN_TEXT".equals(p)) {
 341             label.setText(column.getText());
 342         } else if ("TABLE_COLUMN_GRAPHIC".equals(p)) {
 343             label.setGraphic(column.getGraphic());
 344         }


 345     }
 346     
 347     protected TableViewSkinBase<?,?,?,?,?,TableColumnBase<?,?>> getTableViewSkin() {
 348         return skin;
 349     }
 350 
 351     NestedTableColumnHeader getNestedColumnHeader() { return nestedColumnHeader; }
 352     void setNestedColumnHeader(NestedTableColumnHeader nch) { nestedColumnHeader = nch; }
 353 
 354     public TableColumnBase getTableColumn() { return column; }
 355 
 356     TableHeaderRow getTableHeaderRow() { return tableHeaderRow; }
 357     void setTableHeaderRow(TableHeaderRow thr) { tableHeaderRow = thr; }
 358 
 359     NestedTableColumnHeader getParentHeader() { return parentHeader; }
 360     void setParentHeader(NestedTableColumnHeader ph) { parentHeader = ph; }
 361 
 362 
 363 
 364     /***************************************************************************
 365      *                                                                         *
 366      * Layout                                                                  *
 367      *                                                                         *
 368      **************************************************************************/
 369 
 370     /** {@inheritDoc} */
 371     @Override protected void layoutChildren() {
 372         if (isSizeDirty) {
 373             resize(getTableColumn().getWidth(), getHeight());
 374             isSizeDirty = false;
 375         }
 376 
 377         double sortWidth = 0;
 378         double w = snapSize(getWidth()) - (snappedLeftInset() + snappedRightInset());
 379         double h = getHeight() - (snappedTopInset() + snappedBottomInset());
 380         double x = w;
 381 
 382         // a bit hacky, but we REALLY don't want the arrow shape to fluctuate
 383         // in size
 384         if (arrow != null) {
 385             arrow.setMaxSize(arrow.prefWidth(-1), arrow.prefHeight(-1));
 386         }
 387 
 388         if (sortArrow != null && sortArrow.isVisible()) {
 389             sortWidth = sortArrow.prefWidth(-1);
 390             x -= sortWidth;
 391             sortArrow.resize(sortWidth, sortArrow.prefHeight(-1));
 392             positionInArea(sortArrow, x, snappedTopInset(),
 393                     sortWidth, h, 0, HPos.CENTER, VPos.CENTER);
 394         }
 395 
 396         if (label != null) {
 397             double labelWidth = w - sortWidth;
 398             label.resizeRelocate(snappedLeftInset(), 0, labelWidth, getHeight());
 399         }
 400     }
 401 
 402     /** {@inheritDoc} */
 403     @Override protected double computePrefWidth(double height) {
 404         if (getNestedColumnHeader() != null) {
 405             double width = getNestedColumnHeader().prefWidth(height);
 406 
 407             if (column != null) {
 408                 column.impl_setWidth(width);
 409             }
 410 
 411             return width;
 412         } else if (column != null && column.isVisible()) {
 413             return column.getWidth();
 414         }
 415 
 416         return 0;
 417     }
 418 
 419     /** {@inheritDoc} */
 420     @Override protected double computeMinHeight(double width) {
 421         return label == null ? 0 : label.minHeight(width);
 422     }
 423 
 424     /** {@inheritDoc} */
 425     @Override protected double computePrefHeight(double width) {
 426         if (getTableColumn() == null) return 0;
 427         return Math.max(getSize(), label.prefHeight(-1));
 428     }
 429 














 430     
 431     
 432     /***************************************************************************
 433      *                                                                         *
 434      * Private Implementation                                                  *
 435      *                                                                         *
 436      **************************************************************************/   
 437     













 438     // RT-29682: When the sortable property of a TableColumnBase changes this
 439     // may impact other TableColumnHeaders, as they may need to change their
 440     // sort order representation. Rather than install listeners across all 
 441     // TableColumn in the sortOrder list for their sortable property, we simply
 442     // update the sortPosition of all headers whenever the sortOrder property
 443     // changes, assuming the column is within the sortOrder list.
 444     private void updateAllHeaders(TableColumnHeader header) {
 445         if (header instanceof NestedTableColumnHeader) {
 446             List<TableColumnHeader> children = ((NestedTableColumnHeader)header).getColumnHeaders();
 447             for (int i = 0; i < children.size(); i++) {
 448                 updateAllHeaders(children.get(i));
 449             }
 450         } else {
 451             header.updateSortPosition();
 452         }
 453     }
 454     
 455     private void updateStyleClass() {
 456         // For now we leave the 'column-header' style class intact so that the
 457         // appropriate border styles are shown, etc.
 458         getStyleClass().setAll("column-header");
 459         getStyleClass().addAll(column.getStyleClass());
 460     }
 461     
 462     private void updateScene() {
 463         // RT-17684: If the TableColumn widths are all currently the default,
 464         // we attempt to 'auto-size' based on the preferred width of the first
 465         // n rows (we can't do all rows, as that could conceivably be an unlimited
 466         // number of rows retrieved from a very slow (e.g. remote) data source.
 467         // Obviously, the bigger the value of n, the more likely the default 
 468         // width will be suitable for most values in the column
 469         final int n = 30;
 470         if (! autoSizeComplete) {
 471             if (getTableColumn() == null || getTableColumn().getWidth() != DEFAULT_COLUMN_WIDTH || getScene() == null) {
 472                 return;
 473             }
 474             doColumnAutoSize(getTableColumn(), n);
 475             autoSizeComplete = true;
 476         }
 477     }
 478     
 479     void dispose() {
 480         TableViewSkinBase skin = getTableViewSkin();
 481         if (skin != null) {
 482             skin.getVisibleLeafColumns().removeListener(weakVisibleLeafColumnsListener);
 483             skin.getSortOrder().removeListener(weakSortOrderListener);
 484         }
 485 
 486         changeListenerHandler.dispose();
 487     }
 488     
 489     private boolean isSortingEnabled() {
 490         // this used to check if ! PlatformUtil.isEmbedded(), but has been changed
 491         // to always return true (for now), as we want to support column sorting
 492         // everywhere
 493         return true;
 494     }
 495     
 496     private boolean isColumnReorderingEnabled() {
 497         // we only allow for column reordering if there are more than one column,
 498         return !BehaviorSkinBase.IS_TOUCH_SUPPORTED && getTableViewSkin().getVisibleLeafColumns().size() > 1;
 499     }
 500     
 501     private void initUI() {
 502         // TableColumn will be null if we are dealing with the root NestedTableColumnHeader
 503         if (column == null) return;
 504         
 505         // set up mouse events
 506         setOnMousePressed(mousePressedHandler);
 507         setOnMouseDragged(mouseDraggedHandler);
 508         setOnDragDetected(event -> event.consume());
 509         setOnContextMenuRequested(contextMenuRequestedHandler);
 510         setOnMouseReleased(mouseReleasedHandler);
 511 
 512         // --- label
 513         label = new Label();
 514         label.setText(column.getText());
 515         label.setGraphic(column.getGraphic());
 516         label.setVisible(column.isVisible());
 517 
 518         // ---- container for the sort arrow (which is not supported on embedded
 519         // platforms)
 520         if (isSortingEnabled()) {
 521             // put together the grid
 522             updateSortGrid();
 523         }
 524     }
 525 
 526     private void doColumnAutoSize(TableColumnBase<?,?> column, int cellsToMeasure) {
 527         double prefWidth = column.getPrefWidth();
 528 
 529         // if the prefWidth has been set, we do _not_ autosize columns
 530         if (prefWidth == DEFAULT_COLUMN_WIDTH) {
 531             getTableViewSkin().resizeColumnToFitContent(column, cellsToMeasure);
 532         }
 533     }
 534     
 535     private void updateSortPosition() {
 536         this.sortPos = ! column.isSortable() ? -1 : getSortPosition();
 537         updateSortGrid();
 538     }
 539     
 540     private void updateSortGrid() {
 541         // Fix for RT-14488
 542         if (this instanceof NestedTableColumnHeader) return;
 543         
 544         getChildren().clear();
 545         getChildren().add(label);
 546         
 547         // we do not support sorting in embedded devices
 548         if (! isSortingEnabled()) return;
 549         
 550         isSortColumn = sortPos != -1;
 551         if (! isSortColumn) {
 552             if (sortArrow != null) {
 553                 sortArrow.setVisible(false);
 554             }
 555             return;
 556         }


 560         if (visibleLeafIndex == -1) return;
 561         
 562         final int sortColumnCount = getVisibleSortOrderColumnCount();
 563         boolean showSortOrderDots = sortPos <= 3 && sortColumnCount > 1;
 564         
 565         Node _sortArrow = null;
 566         if (getTableColumn().getSortNode() != null) {
 567             _sortArrow = getTableColumn().getSortNode();
 568             getChildren().add(_sortArrow);
 569         } else {
 570             GridPane sortArrowGrid = new GridPane();
 571             _sortArrow = sortArrowGrid;
 572             sortArrowGrid.setPadding(new Insets(0, 3, 0, 0));
 573             getChildren().add(sortArrowGrid);
 574             
 575             // if we are here, and the sort arrow is null, we better create it
 576             if (arrow == null) {
 577                 arrow = new Region();
 578                 arrow.getStyleClass().setAll("arrow");
 579                 arrow.setVisible(true);
 580                 arrow.setRotate(isAscending(column) ? 180.0F : 0.0F);
 581                 changeListenerHandler.registerChangeListener(getSortTypeProperty(column), "TABLE_COLUMN_SORT_TYPE");





 582             }
 583             
 584             arrow.setVisible(isSortColumn);
 585             
 586             if (sortPos > 2) {
 587                 if (sortOrderLabel == null) {
 588                     // ---- sort order label (for sort positions greater than 3)
 589                     sortOrderLabel = new Label();
 590                     sortOrderLabel.getStyleClass().add("sort-order");
 591                 }
 592 
 593                 // only show the label if the sortPos is greater than 3 (for sortPos 
 594                 // values less than three, we show the sortOrderDots instead)
 595                 sortOrderLabel.setText("" + (sortPos + 1));
 596                 sortOrderLabel.setVisible(sortColumnCount > 1);
 597 
 598                 // update the grid layout
 599                 sortArrowGrid.add(arrow, 1, 1);
 600                 GridPane.setHgrow(arrow, Priority.NEVER);
 601                 GridPane.setVgrow(arrow, Priority.NEVER);
 602                 sortArrowGrid.add(sortOrderLabel, 2, 1);
 603             } else if (showSortOrderDots) {
 604                 if (sortOrderDots == null) {
 605                     sortOrderDots = new HBox(0);
 606                     sortOrderDots.getStyleClass().add("sort-order-dots-container");
 607                 }
 608 
 609                 // show the sort order dots
 610                 boolean isAscending = isAscending(column);
 611                 int arrowRow = isAscending ? 1 : 2;
 612                 int dotsRow = isAscending ? 2 : 1;
 613 
 614                 sortArrowGrid.add(arrow, 1, arrowRow);
 615                 GridPane.setHalignment(arrow, HPos.CENTER);
 616                 sortArrowGrid.add(sortOrderDots, 1, dotsRow);
 617 
 618                 updateSortOrderDots(sortPos);
 619             } else {
 620                 // only show the arrow
 621                 sortArrowGrid.add(arrow, 1, 1);
 622                 GridPane.setHgrow(arrow, Priority.NEVER);
 623                 GridPane.setVgrow(arrow, Priority.ALWAYS);
 624             }
 625         }
 626         
 627         sortArrow = _sortArrow;
 628         if (sortArrow != null) {
 629             sortArrow.setVisible(isSortColumn);
 630         }
 631         
 632         requestLayout();
 633     }
 634     
 635     private void updateSortOrderDots(int sortPos) {
 636         double arrowWidth = arrow.prefWidth(-1);
 637         
 638         sortOrderDots.getChildren().clear();
 639 
 640         for (int i = 0; i <= sortPos; i++) {
 641             Region r = new Region();
 642             r.getStyleClass().add("sort-order-dot");
 643             
 644             String sortTypeName = getSortTypeName(column);
 645             if (sortTypeName != null && ! sortTypeName.isEmpty()) {
 646                 r.getStyleClass().add(sortTypeName.toLowerCase(Locale.ROOT));
 647             }
 648             
 649             sortOrderDots.getChildren().add(r);
 650 
 651             // RT-34914: fine tuning the placement of the sort dots. We could have gone to a custom layout, but for now
 652             // this works fine.
 653             if (i < sortPos) {
 654                 Region spacer = new Region();
 655                 double rp = sortPos == 1 ? 1 : 1;
 656                 double lp = sortPos == 1 ? 1 : 0;
 657                 spacer.setPadding(new Insets(0, rp, 0, lp));
 658                 sortOrderDots.getChildren().add(spacer);
 659             }
 660         }
 661         
 662         sortOrderDots.setAlignment(Pos.TOP_CENTER);
 663         sortOrderDots.setMaxWidth(arrowWidth);
 664     }


 721 
 722     private void updateColumnIndex() {
 723 //        TableView tv = getTableView();
 724         TableViewSkinBase skin = getTableViewSkin();
 725         TableColumnBase tc = getTableColumn();
 726         columnIndex = skin == null || tc == null ? -1 : skin.getVisibleLeafIndex(tc);
 727         
 728         // update the pseudo class state regarding whether this is the last
 729         // visible cell (i.e. the right-most). 
 730         isLastVisibleColumn = getTableColumn() != null &&
 731                 columnIndex != -1 && 
 732                 columnIndex == getTableViewSkin().getVisibleLeafColumns().size() - 1;
 733         pseudoClassStateChanged(PSEUDO_CLASS_LAST_VISIBLE, isLastVisibleColumn);
 734     }
 735 
 736     private void sortColumn(final boolean addColumn) {
 737         if (! isSortingEnabled()) return;
 738 
 739         // we only allow sorting on the leaf columns and columns
 740         // that actually have comparators defined, and are sortable
 741         if (column == null || column.getColumns().size() != 0 || column.getComparator() == null || !column.isSortable()) return;
 742 //        final int sortPos = getTable().getSortOrder().indexOf(column);
 743 //        final boolean isSortColumn = sortPos != -1;
 744 
 745         final ObservableList<TableColumnBase<?,?>> sortOrder = getTableViewSkin().getSortOrder();
 746 
 747         // addColumn is true e.g. when the user is holding down Shift
 748         if (addColumn) {
 749             if (!isSortColumn) {
 750                 setSortType(column, TableColumn.SortType.ASCENDING);
 751                 sortOrder.add(column);
 752             } else if (isAscending(column)) {
 753                 setSortType(column, TableColumn.SortType.DESCENDING);
 754             } else {
 755                 int i = sortOrder.indexOf(column);
 756                 if (i != -1) {
 757                     sortOrder.remove(i);
 758                 }
 759             }
 760         } else {
 761             // the user has clicked on a column header - we should add this to
 762             // the TableView sortOrder list if it isn't already there.
 763             if (isSortColumn && sortOrder.size() == 1) {
 764                 // the column is already being sorted, and it's the only column.
 765                 // We therefore move through the 2nd or 3rd states:
 766                 //   1st click: sort ascending
 767                 //   2nd click: sort descending
 768                 //   3rd click: natural sorting (sorting is switched off)
 769                 if (isAscending(column)) {
 770                     setSortType(column, TableColumn.SortType.DESCENDING);
 771                 } else {
 772                     // remove from sort
 773                     sortOrder.remove(column);
 774                 }
 775             } else if (isSortColumn) {
 776                 // the column is already being used to sort, so we toggle its
 777                 // sortAscending property, and also make the column become the
 778                 // primary sort column
 779                 if (isAscending(column)) {
 780                     setSortType(column, TableColumn.SortType.DESCENDING);
 781                 } else if (isDescending(column)) {
 782                     setSortType(column, TableColumn.SortType.ASCENDING);
 783                 }
 784 
 785                 // to prevent multiple sorts, we make a copy of the sort order
 786                 // list, moving the column value from the current position to
 787                 // its new position at the front of the list
 788                 List<TableColumnBase<?,?>> sortOrderCopy = new ArrayList<TableColumnBase<?,?>>(sortOrder);
 789                 sortOrderCopy.remove(column);
 790                 sortOrderCopy.add(0, column);
 791                 sortOrder.setAll(column);
 792             } else {
 793                 // add to the sort order, in ascending form
 794                 setSortType(column, TableColumn.SortType.ASCENDING);
 795                 sortOrder.setAll(column);
 796             }
 797         }
 798     }
 799 
 800     // Because it is possible that some columns are in the sortOrder list but are
 801     // not themselves sortable, we cannot just do sortOrderList.indexOf(column).
 802     // Therefore, this method does the proper work required of iterating through
 803     // and ignoring non-sortable (and null) columns in the sortOrder list.
 804     private int getSortPosition() {
 805         if (column == null) {
 806             return -1;
 807         }
 808 
 809         final List<TableColumnBase> sortOrder = getVisibleSortOrderColumns();
 810         int pos = 0;
 811         for (int i = 0; i < sortOrder.size(); i++) {
 812             TableColumnBase _tc = sortOrder.get(i);
 813 
 814             if (column.equals(_tc)) {
 815                 return pos;
 816             }
 817 
 818             pos++;
 819         }
 820 
 821         return -1;
 822     }
 823 
 824     private List<TableColumnBase> getVisibleSortOrderColumns() {
 825         final ObservableList<TableColumnBase<?,?>> sortOrder = getTableViewSkin().getSortOrder();
 826 
 827         List<TableColumnBase> visibleSortOrderColumns = new ArrayList<>();
 828         for (int i = 0; i < sortOrder.size(); i++) {
 829             TableColumnBase _tc = sortOrder.get(i);
 830             if (_tc == null || ! _tc.isSortable() || ! _tc.isVisible()) {
 831                 continue;
 832             }
 833 
 834             visibleSortOrderColumns.add(_tc);


 837         return visibleSortOrderColumns;
 838     }
 839 
 840     // as with getSortPosition above, this method iterates through the sortOrder
 841     // list ignoring the null and non-sortable columns, so that we get the correct
 842     // number of columns in the sortOrder list.
 843     private int getVisibleSortOrderColumnCount() {
 844         return getVisibleSortOrderColumns().size();
 845     }
 846 
 847 
 848 
 849     /***************************************************************************
 850      *                                                                         *
 851      * Private Implementation: Column Reordering                               *
 852      *                                                                         *
 853      **************************************************************************/   
 854 
 855     // package for testing
 856     void columnReorderingStarted(double dragOffset) {
 857         if (! column.impl_isReorderable()) return;
 858         
 859         // Used to ensure the column ghost is positioned relative to where the
 860         // user clicked on the column header
 861         this.dragOffset = dragOffset;
 862 
 863         // Note here that we only allow for reordering of 'root' columns
 864         getTableHeaderRow().setReorderingColumn(column);
 865         getTableHeaderRow().setReorderingRegion(this);
 866     }
 867 
 868     // package for testing
 869     void columnReordering(double sceneX, double sceneY) {
 870         if (! column.impl_isReorderable()) return;
 871 
 872         // this is for handling the column drag to reorder columns.
 873         // It shows a line to indicate where the 'drop' will be.
 874 
 875         // indicate that we've started dragging so that the dragging
 876         // line overlay is shown
 877         getTableHeaderRow().setReordering(true);
 878 
 879         // Firstly we need to determine where to draw the line.
 880         // Find which column we're over
 881         TableColumnHeader hoverHeader = null;
 882 
 883         // x represents where the mouse is relative to the parent
 884         // NestedTableColumnHeader
 885         final double x = getParentHeader().sceneToLocal(sceneX, sceneY).getX();
 886 
 887         // calculate where the ghost column header should be
 888         double dragX = getTableViewSkin().getSkinnable().sceneToLocal(sceneX, sceneY).getX() - dragOffset;
 889         getTableHeaderRow().setDragHeaderX(dragX);
 890 


 909         }
 910 
 911         // hoverHeader will be null if the drag occurs outside of the
 912         // tableview. In this case we handle the newColumnPos specially
 913         // and then short-circuit. This results in the drop action
 914         // resulting in the correct result (the column will drop at
 915         // the start or end of the table).
 916         if (hoverHeader == null) {
 917             newColumnPos = x > headersWidth ? (getParentHeader().getColumns().size() - 1) : 0;
 918             return;
 919         }
 920 
 921         // This is the x-axis value midway through hoverHeader. It's
 922         // used to determine whether the drop should be to the left
 923         // or the right of hoverHeader.
 924         double midPoint = startX + (endX - startX) / 2;
 925         boolean beforeMidPoint = x <= midPoint;
 926 
 927         // Based on where the mouse actually is, we have to shuffle
 928         // where we want the column to end up. This code handles that.
 929         int currentPos = getIndex(column);
 930         newColumnPos += newColumnPos > currentPos && beforeMidPoint ?
 931             -1 : (newColumnPos < currentPos && !beforeMidPoint ? 1 : 0);
 932         
 933         double lineX = getTableHeaderRow().sceneToLocal(hoverHeader.localToScene(hoverHeader.getBoundsInLocal())).getMinX();
 934         lineX = lineX + ((beforeMidPoint) ? (0) : (hoverHeader.getWidth()));
 935         
 936         if (lineX >= -0.5 && lineX <= getTableViewSkin().getSkinnable().getWidth()) {
 937             columnReorderLine.setTranslateX(lineX);
 938 
 939             // then if this is the first event, we set the property to true
 940             // so that the line becomes visible until the drop is completed.
 941             // We also set reordering to true so that the various reordering
 942             // effects become visible (ghost, transparent overlay, etc).
 943             columnReorderLine.setVisible(true);
 944         }
 945         
 946         getTableHeaderRow().setReordering(true);
 947     }
 948 
 949     // package for testing
 950     void columnReorderingComplete() {
 951         if (! column.impl_isReorderable()) return;
 952         
 953         // Move col from where it is now to the new position.
 954         moveColumn(getTableColumn(), newColumnPos);
 955         
 956         // cleanup
 957         columnReorderLine.setTranslateX(0.0F);
 958         columnReorderLine.setLayoutX(0.0F);
 959         newColumnPos = 0;
 960 
 961         getTableHeaderRow().setReordering(false);
 962         columnReorderLine.setVisible(false);
 963         getTableHeaderRow().setReorderingColumn(null);
 964         getTableHeaderRow().setReorderingRegion(null);
 965         dragOffset = 0.0F;
 966     }
 967 




 968 
 969 
 970     /***************************************************************************
 971      *                                                                         *
 972      * Stylesheet Handling                                                     *
 973      *                                                                         *
 974      **************************************************************************/
 975     
 976     private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE = 
 977             PseudoClass.getPseudoClass("last-visible");
 978 
 979     double getDragRectHeight() {
 980         return getHeight();
 981     }
 982 
 983     /**
 984       * Super-lazy instantiation pattern from Bill Pugh.
 985       * @treatAsPrivate implementation detail
 986       */
 987      private static class StyleableProperties {
 988          private static final CssMetaData<TableColumnHeader,Number> SIZE =
 989             new CssMetaData<TableColumnHeader,Number>("-fx-size",
 990                  SizeConverter.getInstance(), 20.0) {
 991 
 992             @Override
 993             public boolean isSettable(TableColumnHeader n) {
 994                 return n.size == null || !n.size.isBound();
 995             }
 996 
 997             @Override
 998             public StyleableProperty<Number> getStyleableProperty(TableColumnHeader n) {
 999                 return (StyleableProperty<Number>)(WritableValue<Number>)n.sizeProperty();
1000             }
1001         };
1002 
1003          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1004          static {
1005 
1006             final List<CssMetaData<? extends Styleable, ?>> styleables = 
1007                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData());
1008             styleables.add(SIZE);
1009             STYLEABLES = Collections.unmodifiableList(styleables);
1010 
1011          }
1012     }
1013 
1014     /**
1015      * @return The CssMetaData associated with this class, which may include the
1016      * CssMetaData of its super classes.
1017      */
1018     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1019         return StyleableProperties.STYLEABLES;
1020     }
1021 
1022     /**
1023      * {@inheritDoc}
1024      */
1025     @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1026         return getClassCssMetaData();
1027     }
1028 
1029     @Override
1030     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1031         switch (attribute) {
1032             case INDEX: return getIndex(column);
1033             case TEXT: return column != null ? column.getText() : null;
1034             default: return super.queryAccessibleAttribute(attribute, parameters);
1035         }
1036     }
1037 
1038 }


   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control.skin;
  27 
  28 import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler;
  29 import com.sun.javafx.scene.control.Properties;
  30 import javafx.beans.property.DoubleProperty;
  31 import javafx.beans.property.ReadOnlyObjectProperty;
  32 import javafx.beans.property.ReadOnlyObjectWrapper;
  33 import javafx.beans.value.WritableValue;
  34 import javafx.collections.ListChangeListener;
  35 import javafx.collections.ObservableList;
  36 import javafx.collections.WeakListChangeListener;
  37 import javafx.css.CssMetaData;
  38 import javafx.css.PseudoClass;
  39 import javafx.css.Styleable;
  40 import javafx.css.StyleableDoubleProperty;
  41 import javafx.css.StyleableProperty;
  42 import javafx.event.EventHandler;
  43 import javafx.geometry.HPos;
  44 import javafx.geometry.Insets;
  45 import javafx.geometry.Pos;
  46 import javafx.geometry.VPos;
  47 import javafx.scene.AccessibleAttribute;
  48 import javafx.scene.AccessibleRole;
  49 import javafx.scene.Node;
  50 import javafx.scene.control.ContextMenu;
  51 import javafx.scene.control.Label;
  52 import javafx.scene.control.TableColumn;
  53 import javafx.scene.control.TableColumnBase;
  54 import javafx.scene.input.ContextMenuEvent;
  55 import javafx.scene.input.MouseEvent;
  56 import javafx.scene.layout.GridPane;
  57 import javafx.scene.layout.HBox;
  58 import javafx.scene.layout.Priority;
  59 import javafx.scene.layout.Region;
  60 
  61 import java.util.ArrayList;
  62 import java.util.Collections;
  63 import java.util.List;
  64 import java.util.Locale;
  65 
  66 import javafx.css.converter.SizeConverter;

  67 
  68 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.getSortTypeName;
  69 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.getSortTypeProperty;
  70 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.isAscending;
  71 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.isDescending;
  72 import static com.sun.javafx.scene.control.TableColumnSortTypeWrapper.setSortType;
  73 
  74 
  75 /**
  76  * Region responsible for painting a single column header.
  77  */
  78 public class TableColumnHeader extends Region {
  79 
  80     /***************************************************************************
  81      *                                                                         *
  82      * Static Fields                                                           *
  83      *                                                                         *
  84      **************************************************************************/
  85 
  86     // Copied from TableColumn. The value here should always be in-sync with
  87     // the value in TableColumn
  88     static final double DEFAULT_COLUMN_WIDTH = 80.0F;
  89 
  90 
  91 
  92     /***************************************************************************
  93      *                                                                         *
  94      * Private Fields                                                          *
  95      *                                                                         *
  96      **************************************************************************/
  97     
  98     private boolean autoSizeComplete = false;
  99 
 100     private double dragOffset;
 101     private final TableViewSkinBase<?,?,?,?,TableColumnBase<?,?>> skin;
 102     private NestedTableColumnHeader nestedColumnHeader;

 103     private TableHeaderRow tableHeaderRow;
 104     private NestedTableColumnHeader parentHeader;
 105 
 106     // work out where this column currently is within its parent
 107     Label label;
 108 
 109     // sort order
 110     int sortPos = -1;
 111     private Region arrow;
 112     private Label sortOrderLabel;
 113     private HBox sortOrderDots;
 114     private Node sortArrow;
 115     private boolean isSortColumn;
 116 
 117     private boolean isSizeDirty = false;
 118 
 119     boolean isLastVisibleColumn = false;
 120 
 121     // package for testing
 122     int columnIndex = -1;
 123 
 124     private int newColumnPos;
 125 
 126     // the line drawn in the table when a user presses and moves a column header
 127     // to indicate where the column will be dropped. This is provided by the
 128     // table skin, but manipulated by the header
 129     final Region columnReorderLine;
 130 
 131 
 132     
 133     /***************************************************************************
 134      *                                                                         *
 135      * Constructor                                                             *
 136      *                                                                         *
 137      **************************************************************************/
 138 
 139     /**
 140      * Creates a new TableColumnHeader instance to visually represent the given
 141      * {@link TableColumnBase} instance.
 142      *
 143      * @param skin The skin used by the UI control.
 144      * @param tc The table column to be visually represented by this instance.
 145      */
 146     public TableColumnHeader(final TableViewSkinBase skin, final TableColumnBase tc) {
 147         this.skin = skin;
 148         setTableColumn(tc);
 149         this.columnReorderLine = skin.getColumnReorderLine();
 150         
 151         setFocusTraversable(false);
 152 
 153         updateColumnIndex();
 154         initUI();
 155         
 156         // change listener for multiple properties
 157         changeListenerHandler = new LambdaMultiplePropertyChangeListenerHandler();
 158         changeListenerHandler.registerChangeListener(sceneProperty(), e -> updateScene());



 159         
 160         if (getTableColumn() != null && skin != null) {
 161             updateSortPosition();
 162             skin.getSortOrder().addListener(weakSortOrderListener);
 163             skin.getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener);
 164         }
 165 
 166         if (getTableColumn() != null) {
 167             changeListenerHandler.registerChangeListener(tc.idProperty(), e -> setId(tc.getId()));
 168             changeListenerHandler.registerChangeListener(tc.styleProperty(), e -> setStyle(tc.getStyle()));
 169             changeListenerHandler.registerChangeListener(tc.widthProperty(), e -> {
 170                 // It is this that ensures that when a column is resized that the header
 171                 // visually adjusts its width as necessary.
 172                 isSizeDirty = true;
 173                 requestLayout();
 174             });
 175             changeListenerHandler.registerChangeListener(tc.visibleProperty(), e -> setVisible(getTableColumn().isVisible()));
 176             changeListenerHandler.registerChangeListener(tc.sortNodeProperty(), e -> updateSortGrid());
 177             changeListenerHandler.registerChangeListener(tc.sortableProperty(), e -> {
 178                 // we need to notify all headers that a sortable state has changed,
 179                 // in case the sort grid in other columns needs to be updated.
 180                 if (skin.getSortOrder().contains(getTableColumn())) {
 181                     NestedTableColumnHeader root = getTableHeaderRow().getRootHeader();
 182                     updateAllHeaders(root);
 183                 }
 184             });
 185             changeListenerHandler.registerChangeListener(tc.textProperty(), e -> label.setText(tc.getText()));
 186             changeListenerHandler.registerChangeListener(tc.graphicProperty(), e -> label.setGraphic(tc.getGraphic()));
 187 
 188             tc.getStyleClass().addListener(weakStyleClassListener);
 189 
 190             setId(tc.getId());
 191             setStyle(tc.getStyle());
 192             updateStyleClass();
 193             /* Having TableColumn role parented by TableColumn causes VoiceOver to be unhappy */
 194             setAccessibleRole(AccessibleRole.TABLE_COLUMN);
 195         }
 196     }
 197 
 198     
 199     
 200     /***************************************************************************
 201      *                                                                         *
 202      * Listeners                                                               *
 203      *                                                                         *
 204      **************************************************************************/
 205     
 206     final LambdaMultiplePropertyChangeListenerHandler changeListenerHandler;
 207     
 208     private ListChangeListener<TableColumnBase<?,?>> sortOrderListener = c -> {
 209         updateSortPosition();
 210     };
 211     
 212     private ListChangeListener<TableColumnBase<?,?>> visibleLeafColumnsListener = c -> {
 213         updateColumnIndex();
 214         updateSortPosition();
 215     };
 216     
 217     private ListChangeListener<String> styleClassListener = c -> {
 218         updateStyleClass();
 219     };
 220     
 221     private WeakListChangeListener<TableColumnBase<?,?>> weakSortOrderListener =
 222             new WeakListChangeListener<TableColumnBase<?,?>>(sortOrderListener);
 223     private final WeakListChangeListener<TableColumnBase<?,?>> weakVisibleLeafColumnsListener =
 224             new WeakListChangeListener<TableColumnBase<?,?>>(visibleLeafColumnsListener);
 225     private final WeakListChangeListener<String> weakStyleClassListener =
 226             new WeakListChangeListener<String>(styleClassListener);


 265     
 266     private static final EventHandler<ContextMenuEvent> contextMenuRequestedHandler = me -> {
 267         TableColumnHeader header = (TableColumnHeader) me.getSource();
 268         TableColumnBase tableColumn = header.getTableColumn();
 269 
 270         ContextMenu menu = tableColumn.getContextMenu();
 271         if (menu != null) {
 272             menu.show(header, me.getScreenX(), me.getScreenY());
 273             me.consume();
 274         }
 275     };
 276 
 277 
 278 
 279     /***************************************************************************
 280      *                                                                         *
 281      * Properties                                                              *
 282      *                                                                         *
 283      **************************************************************************/
 284 
 285     // --- size
 286     private DoubleProperty size;
 287     private final double getSize() {
 288         return size == null ? 20.0 : size.doubleValue();
 289     }
 290     private final DoubleProperty sizeProperty() {
 291         if (size == null) {
 292             size = new StyleableDoubleProperty(20) {
 293                 @Override
 294                 protected void invalidated() {
 295                     double value = get();
 296                     if (value <= 0) {
 297                         if (isBound()) {
 298                             unbind();
 299                         }
 300                         set(20);
 301                         throw new IllegalArgumentException("Size cannot be 0 or negative");
 302                     }
 303                 }
 304                 
 305                 
 306 
 307                 @Override public Object getBean() {
 308                     return TableColumnHeader.this;
 309                 }
 310 
 311                 @Override public String getName() {
 312                     return "size";
 313                 }
 314 
 315                 @Override public CssMetaData<TableColumnHeader,Number> getCssMetaData() {
 316                     return StyleableProperties.SIZE;
 317                 }
 318             };
 319         }
 320         return size;
 321     }
 322 
 323 
 324     /**
 325      * A property that refers to the {@link TableColumnBase} instance that this
 326      * header is visually represents.
 327      */
 328     // --- table column
 329     private ReadOnlyObjectWrapper<TableColumnBase<?,?>> tableColumn = new ReadOnlyObjectWrapper<>(this, "tableColumn");
 330     private final void setTableColumn(TableColumnBase<?,?> column) {
 331         tableColumn.set(column);































 332     }
 333     public final TableColumnBase<?,?> getTableColumn() {
 334         return tableColumn.get();
 335     }
 336     public final ReadOnlyObjectProperty<TableColumnBase<?,?>> tableColumnProperty() {
 337         return tableColumn.getReadOnlyProperty();

 338     }
 339 











 340 
 341     
 342     /***************************************************************************
 343      *                                                                         *
 344      * Public API                                                              *
 345      *                                                                         *
 346      **************************************************************************/
 347 
 348     /** {@inheritDoc} */
 349     @Override protected void layoutChildren() {
 350         if (isSizeDirty) {
 351             resize(getTableColumn().getWidth(), getHeight());
 352             isSizeDirty = false;
 353         }
 354 
 355         double sortWidth = 0;
 356         double w = snapSize(getWidth()) - (snappedLeftInset() + snappedRightInset());
 357         double h = getHeight() - (snappedTopInset() + snappedBottomInset());
 358         double x = w;
 359 
 360         // a bit hacky, but we REALLY don't want the arrow shape to fluctuate
 361         // in size
 362         if (arrow != null) {
 363             arrow.setMaxSize(arrow.prefWidth(-1), arrow.prefHeight(-1));
 364         }
 365 
 366         if (sortArrow != null && sortArrow.isVisible()) {
 367             sortWidth = sortArrow.prefWidth(-1);
 368             x -= sortWidth;
 369             sortArrow.resize(sortWidth, sortArrow.prefHeight(-1));
 370             positionInArea(sortArrow, x, snappedTopInset(),
 371                     sortWidth, h, 0, HPos.CENTER, VPos.CENTER);
 372         }
 373 
 374         if (label != null) {
 375             double labelWidth = w - sortWidth;
 376             label.resizeRelocate(snappedLeftInset(), 0, labelWidth, getHeight());
 377         }
 378     }
 379 
 380     /** {@inheritDoc} */
 381     @Override protected double computePrefWidth(double height) {
 382         if (getNestedColumnHeader() != null) {
 383             double width = getNestedColumnHeader().prefWidth(height);
 384 
 385             if (getTableColumn() != null) {
 386                 getTableColumn().impl_setWidth(width);
 387             }
 388 
 389             return width;
 390         } else if (getTableColumn() != null && getTableColumn().isVisible()) {
 391             return getTableColumn().getWidth();
 392         }
 393 
 394         return 0;
 395     }
 396 
 397     /** {@inheritDoc} */
 398     @Override protected double computeMinHeight(double width) {
 399         return label == null ? 0 : label.minHeight(width);
 400     }
 401 
 402     /** {@inheritDoc} */
 403     @Override protected double computePrefHeight(double width) {
 404         if (getTableColumn() == null) return 0;
 405         return Math.max(getSize(), label.prefHeight(-1));
 406     }
 407 
 408     /** {@inheritDoc} */
 409     @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 410         return getClassCssMetaData();
 411     }
 412 
 413     /** {@inheritDoc} */
 414     @Override  public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 415         switch (attribute) {
 416             case INDEX: return getIndex(getTableColumn());
 417             case TEXT: return getTableColumn() != null ? getTableColumn().getText() : null;
 418             default: return super.queryAccessibleAttribute(attribute, parameters);
 419         }
 420     }
 421 
 422     
 423     
 424     /***************************************************************************
 425      *                                                                         *
 426      * Private Implementation                                                  *
 427      *                                                                         *
 428      **************************************************************************/
 429 
 430     TableViewSkinBase<?,?,?,?,TableColumnBase<?,?>> getTableViewSkin() {
 431         return skin;
 432     }
 433 
 434     NestedTableColumnHeader getNestedColumnHeader() { return nestedColumnHeader; }
 435     void setNestedColumnHeader(NestedTableColumnHeader nch) { nestedColumnHeader = nch; }
 436 
 437     TableHeaderRow getTableHeaderRow() { return tableHeaderRow; }
 438     void setTableHeaderRow(TableHeaderRow thr) { tableHeaderRow = thr; }
 439 
 440     NestedTableColumnHeader getParentHeader() { return parentHeader; }
 441     void setParentHeader(NestedTableColumnHeader ph) { parentHeader = ph; }
 442     
 443     // RT-29682: When the sortable property of a TableColumnBase changes this
 444     // may impact other TableColumnHeaders, as they may need to change their
 445     // sort order representation. Rather than install listeners across all 
 446     // TableColumn in the sortOrder list for their sortable property, we simply
 447     // update the sortPosition of all headers whenever the sortOrder property
 448     // changes, assuming the column is within the sortOrder list.
 449     private void updateAllHeaders(TableColumnHeader header) {
 450         if (header instanceof NestedTableColumnHeader) {
 451             List<TableColumnHeader> children = ((NestedTableColumnHeader)header).getColumnHeaders();
 452             for (int i = 0; i < children.size(); i++) {
 453                 updateAllHeaders(children.get(i));
 454             }
 455         } else {
 456             header.updateSortPosition();
 457         }
 458     }
 459     
 460     private void updateStyleClass() {
 461         // For now we leave the 'column-header' style class intact so that the
 462         // appropriate border styles are shown, etc.
 463         getStyleClass().setAll("column-header");
 464         getStyleClass().addAll(getTableColumn().getStyleClass());
 465     }
 466     
 467     private void updateScene() {
 468         // RT-17684: If the TableColumn widths are all currently the default,
 469         // we attempt to 'auto-size' based on the preferred width of the first
 470         // n rows (we can't do all rows, as that could conceivably be an unlimited
 471         // number of rows retrieved from a very slow (e.g. remote) data source.
 472         // Obviously, the bigger the value of n, the more likely the default 
 473         // width will be suitable for most values in the column
 474         final int n = 30;
 475         if (! autoSizeComplete) {
 476             if (getTableColumn() == null || getTableColumn().getWidth() != DEFAULT_COLUMN_WIDTH || getScene() == null) {
 477                 return;
 478             }
 479             doColumnAutoSize(getTableColumn(), n);
 480             autoSizeComplete = true;
 481         }
 482     }
 483     
 484     void dispose() {
 485         TableViewSkinBase skin = getTableViewSkin();
 486         if (skin != null) {
 487             skin.getVisibleLeafColumns().removeListener(weakVisibleLeafColumnsListener);
 488             skin.getSortOrder().removeListener(weakSortOrderListener);
 489         }
 490 
 491         changeListenerHandler.dispose();
 492     }
 493     
 494     private boolean isSortingEnabled() {
 495         // this used to check if ! PlatformUtil.isEmbedded(), but has been changed
 496         // to always return true (for now), as we want to support column sorting
 497         // everywhere
 498         return true;
 499     }
 500     
 501     private boolean isColumnReorderingEnabled() {
 502         // we only allow for column reordering if there are more than one column,
 503         return !Properties.IS_TOUCH_SUPPORTED && getTableViewSkin().getVisibleLeafColumns().size() > 1;
 504     }
 505     
 506     private void initUI() {
 507         // TableColumn will be null if we are dealing with the root NestedTableColumnHeader
 508         if (getTableColumn() == null) return;
 509         
 510         // set up mouse events
 511         setOnMousePressed(mousePressedHandler);
 512         setOnMouseDragged(mouseDraggedHandler);
 513         setOnDragDetected(event -> event.consume());
 514         setOnContextMenuRequested(contextMenuRequestedHandler);
 515         setOnMouseReleased(mouseReleasedHandler);
 516 
 517         // --- label
 518         label = new Label();
 519         label.setText(getTableColumn().getText());
 520         label.setGraphic(getTableColumn().getGraphic());
 521         label.setVisible(getTableColumn().isVisible());
 522 
 523         // ---- container for the sort arrow (which is not supported on embedded
 524         // platforms)
 525         if (isSortingEnabled()) {
 526             // put together the grid
 527             updateSortGrid();
 528         }
 529     }
 530 
 531     private void doColumnAutoSize(TableColumnBase<?,?> column, int cellsToMeasure) {
 532         double prefWidth = column.getPrefWidth();
 533 
 534         // if the prefWidth has been set, we do _not_ autosize columns
 535         if (prefWidth == DEFAULT_COLUMN_WIDTH) {
 536             getTableViewSkin().resizeColumnToFitContent(column, cellsToMeasure);
 537         }
 538     }
 539     
 540     private void updateSortPosition() {
 541         this.sortPos = ! getTableColumn().isSortable() ? -1 : getSortPosition();
 542         updateSortGrid();
 543     }
 544     
 545     private void updateSortGrid() {
 546         // Fix for RT-14488
 547         if (this instanceof NestedTableColumnHeader) return;
 548         
 549         getChildren().clear();
 550         getChildren().add(label);
 551         
 552         // we do not support sorting in embedded devices
 553         if (! isSortingEnabled()) return;
 554         
 555         isSortColumn = sortPos != -1;
 556         if (! isSortColumn) {
 557             if (sortArrow != null) {
 558                 sortArrow.setVisible(false);
 559             }
 560             return;
 561         }


 565         if (visibleLeafIndex == -1) return;
 566         
 567         final int sortColumnCount = getVisibleSortOrderColumnCount();
 568         boolean showSortOrderDots = sortPos <= 3 && sortColumnCount > 1;
 569         
 570         Node _sortArrow = null;
 571         if (getTableColumn().getSortNode() != null) {
 572             _sortArrow = getTableColumn().getSortNode();
 573             getChildren().add(_sortArrow);
 574         } else {
 575             GridPane sortArrowGrid = new GridPane();
 576             _sortArrow = sortArrowGrid;
 577             sortArrowGrid.setPadding(new Insets(0, 3, 0, 0));
 578             getChildren().add(sortArrowGrid);
 579             
 580             // if we are here, and the sort arrow is null, we better create it
 581             if (arrow == null) {
 582                 arrow = new Region();
 583                 arrow.getStyleClass().setAll("arrow");
 584                 arrow.setVisible(true);
 585                 arrow.setRotate(isAscending(getTableColumn()) ? 180.0F : 0.0F);
 586                 changeListenerHandler.registerChangeListener(getSortTypeProperty(getTableColumn()), e -> {
 587                     updateSortGrid();
 588                     if (arrow != null) {
 589                         arrow.setRotate(isAscending(getTableColumn()) ? 180 : 0.0);
 590                     }
 591                 });
 592             }
 593             
 594             arrow.setVisible(isSortColumn);
 595             
 596             if (sortPos > 2) {
 597                 if (sortOrderLabel == null) {
 598                     // ---- sort order label (for sort positions greater than 3)
 599                     sortOrderLabel = new Label();
 600                     sortOrderLabel.getStyleClass().add("sort-order");
 601                 }
 602 
 603                 // only show the label if the sortPos is greater than 3 (for sortPos 
 604                 // values less than three, we show the sortOrderDots instead)
 605                 sortOrderLabel.setText("" + (sortPos + 1));
 606                 sortOrderLabel.setVisible(sortColumnCount > 1);
 607 
 608                 // update the grid layout
 609                 sortArrowGrid.add(arrow, 1, 1);
 610                 GridPane.setHgrow(arrow, Priority.NEVER);
 611                 GridPane.setVgrow(arrow, Priority.NEVER);
 612                 sortArrowGrid.add(sortOrderLabel, 2, 1);
 613             } else if (showSortOrderDots) {
 614                 if (sortOrderDots == null) {
 615                     sortOrderDots = new HBox(0);
 616                     sortOrderDots.getStyleClass().add("sort-order-dots-container");
 617                 }
 618 
 619                 // show the sort order dots
 620                 boolean isAscending = isAscending(getTableColumn());
 621                 int arrowRow = isAscending ? 1 : 2;
 622                 int dotsRow = isAscending ? 2 : 1;
 623 
 624                 sortArrowGrid.add(arrow, 1, arrowRow);
 625                 GridPane.setHalignment(arrow, HPos.CENTER);
 626                 sortArrowGrid.add(sortOrderDots, 1, dotsRow);
 627 
 628                 updateSortOrderDots(sortPos);
 629             } else {
 630                 // only show the arrow
 631                 sortArrowGrid.add(arrow, 1, 1);
 632                 GridPane.setHgrow(arrow, Priority.NEVER);
 633                 GridPane.setVgrow(arrow, Priority.ALWAYS);
 634             }
 635         }
 636         
 637         sortArrow = _sortArrow;
 638         if (sortArrow != null) {
 639             sortArrow.setVisible(isSortColumn);
 640         }
 641         
 642         requestLayout();
 643     }
 644     
 645     private void updateSortOrderDots(int sortPos) {
 646         double arrowWidth = arrow.prefWidth(-1);
 647         
 648         sortOrderDots.getChildren().clear();
 649 
 650         for (int i = 0; i <= sortPos; i++) {
 651             Region r = new Region();
 652             r.getStyleClass().add("sort-order-dot");
 653             
 654             String sortTypeName = getSortTypeName(getTableColumn());
 655             if (sortTypeName != null && ! sortTypeName.isEmpty()) {
 656                 r.getStyleClass().add(sortTypeName.toLowerCase(Locale.ROOT));
 657             }
 658             
 659             sortOrderDots.getChildren().add(r);
 660 
 661             // RT-34914: fine tuning the placement of the sort dots. We could have gone to a custom layout, but for now
 662             // this works fine.
 663             if (i < sortPos) {
 664                 Region spacer = new Region();
 665                 double rp = sortPos == 1 ? 1 : 1;
 666                 double lp = sortPos == 1 ? 1 : 0;
 667                 spacer.setPadding(new Insets(0, rp, 0, lp));
 668                 sortOrderDots.getChildren().add(spacer);
 669             }
 670         }
 671         
 672         sortOrderDots.setAlignment(Pos.TOP_CENTER);
 673         sortOrderDots.setMaxWidth(arrowWidth);
 674     }


 731 
 732     private void updateColumnIndex() {
 733 //        TableView tv = getTableView();
 734         TableViewSkinBase skin = getTableViewSkin();
 735         TableColumnBase tc = getTableColumn();
 736         columnIndex = skin == null || tc == null ? -1 : skin.getVisibleLeafIndex(tc);
 737         
 738         // update the pseudo class state regarding whether this is the last
 739         // visible cell (i.e. the right-most). 
 740         isLastVisibleColumn = getTableColumn() != null &&
 741                 columnIndex != -1 && 
 742                 columnIndex == getTableViewSkin().getVisibleLeafColumns().size() - 1;
 743         pseudoClassStateChanged(PSEUDO_CLASS_LAST_VISIBLE, isLastVisibleColumn);
 744     }
 745 
 746     private void sortColumn(final boolean addColumn) {
 747         if (! isSortingEnabled()) return;
 748 
 749         // we only allow sorting on the leaf columns and columns
 750         // that actually have comparators defined, and are sortable
 751         if (getTableColumn() == null || getTableColumn().getColumns().size() != 0 || getTableColumn().getComparator() == null || !getTableColumn().isSortable()) return;
 752 //        final int sortPos = getTable().getSortOrder().indexOf(column);
 753 //        final boolean isSortColumn = sortPos != -1;
 754 
 755         final ObservableList<TableColumnBase<?,?>> sortOrder = getTableViewSkin().getSortOrder();
 756 
 757         // addColumn is true e.g. when the user is holding down Shift
 758         if (addColumn) {
 759             if (!isSortColumn) {
 760                 setSortType(getTableColumn(), TableColumn.SortType.ASCENDING);
 761                 sortOrder.add(getTableColumn());
 762             } else if (isAscending(getTableColumn())) {
 763                 setSortType(getTableColumn(), TableColumn.SortType.DESCENDING);
 764             } else {
 765                 int i = sortOrder.indexOf(getTableColumn());
 766                 if (i != -1) {
 767                     sortOrder.remove(i);
 768                 }
 769             }
 770         } else {
 771             // the user has clicked on a column header - we should add this to
 772             // the TableView sortOrder list if it isn't already there.
 773             if (isSortColumn && sortOrder.size() == 1) {
 774                 // the column is already being sorted, and it's the only column.
 775                 // We therefore move through the 2nd or 3rd states:
 776                 //   1st click: sort ascending
 777                 //   2nd click: sort descending
 778                 //   3rd click: natural sorting (sorting is switched off)
 779                 if (isAscending(getTableColumn())) {
 780                     setSortType(getTableColumn(), TableColumn.SortType.DESCENDING);
 781                 } else {
 782                     // remove from sort
 783                     sortOrder.remove(getTableColumn());
 784                 }
 785             } else if (isSortColumn) {
 786                 // the column is already being used to sort, so we toggle its
 787                 // sortAscending property, and also make the column become the
 788                 // primary sort column
 789                 if (isAscending(getTableColumn())) {
 790                     setSortType(getTableColumn(), TableColumn.SortType.DESCENDING);
 791                 } else if (isDescending(getTableColumn())) {
 792                     setSortType(getTableColumn(), TableColumn.SortType.ASCENDING);
 793                 }
 794 
 795                 // to prevent multiple sorts, we make a copy of the sort order
 796                 // list, moving the column value from the current position to
 797                 // its new position at the front of the list
 798                 List<TableColumnBase<?,?>> sortOrderCopy = new ArrayList<TableColumnBase<?,?>>(sortOrder);
 799                 sortOrderCopy.remove(getTableColumn());
 800                 sortOrderCopy.add(0, getTableColumn());
 801                 sortOrder.setAll(getTableColumn());
 802             } else {
 803                 // add to the sort order, in ascending form
 804                 setSortType(getTableColumn(), TableColumn.SortType.ASCENDING);
 805                 sortOrder.setAll(getTableColumn());
 806             }
 807         }
 808     }
 809 
 810     // Because it is possible that some columns are in the sortOrder list but are
 811     // not themselves sortable, we cannot just do sortOrderList.indexOf(column).
 812     // Therefore, this method does the proper work required of iterating through
 813     // and ignoring non-sortable (and null) columns in the sortOrder list.
 814     private int getSortPosition() {
 815         if (getTableColumn() == null) {
 816             return -1;
 817         }
 818 
 819         final List<TableColumnBase> sortOrder = getVisibleSortOrderColumns();
 820         int pos = 0;
 821         for (int i = 0; i < sortOrder.size(); i++) {
 822             TableColumnBase _tc = sortOrder.get(i);
 823 
 824             if (getTableColumn().equals(_tc)) {
 825                 return pos;
 826             }
 827 
 828             pos++;
 829         }
 830 
 831         return -1;
 832     }
 833 
 834     private List<TableColumnBase> getVisibleSortOrderColumns() {
 835         final ObservableList<TableColumnBase<?,?>> sortOrder = getTableViewSkin().getSortOrder();
 836 
 837         List<TableColumnBase> visibleSortOrderColumns = new ArrayList<>();
 838         for (int i = 0; i < sortOrder.size(); i++) {
 839             TableColumnBase _tc = sortOrder.get(i);
 840             if (_tc == null || ! _tc.isSortable() || ! _tc.isVisible()) {
 841                 continue;
 842             }
 843 
 844             visibleSortOrderColumns.add(_tc);


 847         return visibleSortOrderColumns;
 848     }
 849 
 850     // as with getSortPosition above, this method iterates through the sortOrder
 851     // list ignoring the null and non-sortable columns, so that we get the correct
 852     // number of columns in the sortOrder list.
 853     private int getVisibleSortOrderColumnCount() {
 854         return getVisibleSortOrderColumns().size();
 855     }
 856 
 857 
 858 
 859     /***************************************************************************
 860      *                                                                         *
 861      * Private Implementation: Column Reordering                               *
 862      *                                                                         *
 863      **************************************************************************/   
 864 
 865     // package for testing
 866     void columnReorderingStarted(double dragOffset) {
 867         if (! getTableColumn().impl_isReorderable()) return;
 868         
 869         // Used to ensure the column ghost is positioned relative to where the
 870         // user clicked on the column header
 871         this.dragOffset = dragOffset;
 872 
 873         // Note here that we only allow for reordering of 'root' columns
 874         getTableHeaderRow().setReorderingColumn(getTableColumn());
 875         getTableHeaderRow().setReorderingRegion(this);
 876     }
 877 
 878     // package for testing
 879     void columnReordering(double sceneX, double sceneY) {
 880         if (! getTableColumn().impl_isReorderable()) return;
 881 
 882         // this is for handling the column drag to reorder columns.
 883         // It shows a line to indicate where the 'drop' will be.
 884 
 885         // indicate that we've started dragging so that the dragging
 886         // line overlay is shown
 887         getTableHeaderRow().setReordering(true);
 888 
 889         // Firstly we need to determine where to draw the line.
 890         // Find which column we're over
 891         TableColumnHeader hoverHeader = null;
 892 
 893         // x represents where the mouse is relative to the parent
 894         // NestedTableColumnHeader
 895         final double x = getParentHeader().sceneToLocal(sceneX, sceneY).getX();
 896 
 897         // calculate where the ghost column header should be
 898         double dragX = getTableViewSkin().getSkinnable().sceneToLocal(sceneX, sceneY).getX() - dragOffset;
 899         getTableHeaderRow().setDragHeaderX(dragX);
 900 


 919         }
 920 
 921         // hoverHeader will be null if the drag occurs outside of the
 922         // tableview. In this case we handle the newColumnPos specially
 923         // and then short-circuit. This results in the drop action
 924         // resulting in the correct result (the column will drop at
 925         // the start or end of the table).
 926         if (hoverHeader == null) {
 927             newColumnPos = x > headersWidth ? (getParentHeader().getColumns().size() - 1) : 0;
 928             return;
 929         }
 930 
 931         // This is the x-axis value midway through hoverHeader. It's
 932         // used to determine whether the drop should be to the left
 933         // or the right of hoverHeader.
 934         double midPoint = startX + (endX - startX) / 2;
 935         boolean beforeMidPoint = x <= midPoint;
 936 
 937         // Based on where the mouse actually is, we have to shuffle
 938         // where we want the column to end up. This code handles that.
 939         int currentPos = getIndex(getTableColumn());
 940         newColumnPos += newColumnPos > currentPos && beforeMidPoint ?
 941             -1 : (newColumnPos < currentPos && !beforeMidPoint ? 1 : 0);
 942         
 943         double lineX = getTableHeaderRow().sceneToLocal(hoverHeader.localToScene(hoverHeader.getBoundsInLocal())).getMinX();
 944         lineX = lineX + ((beforeMidPoint) ? (0) : (hoverHeader.getWidth()));
 945         
 946         if (lineX >= -0.5 && lineX <= getTableViewSkin().getSkinnable().getWidth()) {
 947             columnReorderLine.setTranslateX(lineX);
 948 
 949             // then if this is the first event, we set the property to true
 950             // so that the line becomes visible until the drop is completed.
 951             // We also set reordering to true so that the various reordering
 952             // effects become visible (ghost, transparent overlay, etc).
 953             columnReorderLine.setVisible(true);
 954         }
 955         
 956         getTableHeaderRow().setReordering(true);
 957     }
 958 
 959     // package for testing
 960     void columnReorderingComplete() {
 961         if (! getTableColumn().impl_isReorderable()) return;
 962         
 963         // Move col from where it is now to the new position.
 964         moveColumn(getTableColumn(), newColumnPos);
 965         
 966         // cleanup
 967         columnReorderLine.setTranslateX(0.0F);
 968         columnReorderLine.setLayoutX(0.0F);
 969         newColumnPos = 0;
 970 
 971         getTableHeaderRow().setReordering(false);
 972         columnReorderLine.setVisible(false);
 973         getTableHeaderRow().setReorderingColumn(null);
 974         getTableHeaderRow().setReorderingRegion(null);
 975         dragOffset = 0.0F;
 976     }
 977 
 978     double getDragRectHeight() {
 979         return getHeight();
 980     }
 981 
 982 
 983 
 984     /***************************************************************************
 985      *                                                                         *
 986      * Stylesheet Handling                                                     *
 987      *                                                                         *
 988      **************************************************************************/
 989     
 990     private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE = 
 991             PseudoClass.getPseudoClass("last-visible");
 992 




 993     /**
 994       * Super-lazy instantiation pattern from Bill Pugh.
 995       * @treatAsPrivate implementation detail
 996       */
 997      private static class StyleableProperties {
 998          private static final CssMetaData<TableColumnHeader,Number> SIZE =
 999             new CssMetaData<TableColumnHeader,Number>("-fx-size",
1000                  SizeConverter.getInstance(), 20.0) {
1001 
1002             @Override
1003             public boolean isSettable(TableColumnHeader n) {
1004                 return n.size == null || !n.size.isBound();
1005             }
1006 
1007             @Override
1008             public StyleableProperty<Number> getStyleableProperty(TableColumnHeader n) {
1009                 return (StyleableProperty<Number>)(WritableValue<Number>)n.sizeProperty();
1010             }
1011         };
1012 
1013          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1014          static {
1015 
1016             final List<CssMetaData<? extends Styleable, ?>> styleables = 
1017                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData());
1018             styleables.add(SIZE);
1019             STYLEABLES = Collections.unmodifiableList(styleables);
1020 
1021          }
1022     }
1023 
1024     /**
1025      * Returnst he CssMetaData associated with this class, which may include the
1026      * CssMetaData of its super classes.
1027      */
1028     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1029         return StyleableProperties.STYLEABLES;
1030     }

















1031 }