1 /*
   2  * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   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.skin.Utils;
  29 import javafx.beans.property.ObjectProperty;
  30 import javafx.collections.WeakListChangeListener;
  31 import java.util.ArrayList;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.WeakHashMap;
  35 import java.util.concurrent.atomic.AtomicBoolean;
  36 
  37 import javafx.collections.FXCollections;
  38 import javafx.collections.ListChangeListener;
  39 import javafx.collections.ObservableList;
  40 import javafx.event.EventHandler;
  41 import javafx.geometry.NodeOrientation;
  42 import javafx.scene.Cursor;
  43 import javafx.scene.Node;
  44 import javafx.scene.control.*;
  45 import javafx.scene.input.MouseEvent;
  46 import javafx.scene.paint.Color;
  47 import javafx.scene.shape.Rectangle;
  48 import javafx.util.Callback;
  49 
  50 /**
  51  * <p>This class is used to construct the header of a TableView. We take the approach
  52  * that every TableView header is nested - even if it isn't. This allows for us
  53  * to use the same code for building a single row of TableColumns as we would
  54  * with a heavily nested sequences of TableColumns. Because of this, the
  55  * TableHeaderRow class consists of just one instance of a NestedTableColumnHeader.
  56  *
  57  * @since 9
  58  * @see TableColumnHeader
  59  * @see TableHeaderRow
  60  * @see TableColumnBase
  61  */
  62 public class NestedTableColumnHeader extends TableColumnHeader {
  63 
  64     /***************************************************************************
  65      *                                                                         *
  66      * Static Fields                                                           *
  67      *                                                                         *
  68      **************************************************************************/
  69 
  70     static final String DEFAULT_STYLE_CLASS = "nested-column-header";
  71 
  72     private static final int DRAG_RECT_WIDTH = 4;
  73 
  74     private static final String TABLE_COLUMN_KEY = "TableColumn";
  75     private static final String TABLE_COLUMN_HEADER_KEY = "TableColumnHeader";
  76 
  77 
  78 
  79     /***************************************************************************
  80      *                                                                         *
  81      * Private Fields                                                          *
  82      *                                                                         *
  83      **************************************************************************/
  84 
  85     /**
  86      * Represents the actual columns directly contained in this nested column.
  87      * It does NOT include ANY of the children of these columns, if any exist.
  88      */
  89     private ObservableList<? extends TableColumnBase> columns;
  90 
  91     private TableColumnHeader label;
  92 
  93     private ObservableList<TableColumnHeader> columnHeaders;
  94     private ObservableList<TableColumnHeader> unmodifiableColumnHeaders;
  95 
  96     // used for column resizing
  97     private double lastX = 0.0F;
  98     private double dragAnchorX = 0.0;
  99 
 100     // drag rectangle overlays
 101     private Map<TableColumnBase<?,?>, Rectangle> dragRects = new WeakHashMap<>();
 102 
 103     boolean updateColumns = true;
 104 
 105 
 106 
 107     /***************************************************************************
 108      *                                                                         *
 109      * Constructor                                                             *
 110      *                                                                         *
 111      **************************************************************************/
 112 
 113     /**
 114      * Creates a new NestedTableColumnHeader instance to visually represent the given
 115      * {@link TableColumnBase} instance.
 116      *
 117      * @param tc The table column to be visually represented by this instance.
 118      */
 119     public NestedTableColumnHeader(final TableColumnBase tc) {
 120         super(tc);
 121 
 122         setFocusTraversable(false);
 123 
 124         // init UI
 125         label = createTableColumnHeader(getTableColumn());
 126         label.setTableHeaderRow(getTableHeaderRow());
 127         label.setParentHeader(getParentHeader());
 128         label.setNestedColumnHeader(this);
 129 
 130         if (getTableColumn() != null) {
 131             changeListenerHandler.registerChangeListener(getTableColumn().textProperty(), e ->
 132                     label.setVisible(getTableColumn().getText() != null && ! getTableColumn().getText().isEmpty()));
 133         }
 134     }
 135 
 136 
 137 
 138     /***************************************************************************
 139      *                                                                         *
 140      * Listeners                                                               *
 141      *                                                                         *
 142      **************************************************************************/
 143 
 144     private final ListChangeListener<TableColumnBase> columnsListener = c -> {
 145         setHeadersNeedUpdate();
 146     };
 147 
 148     private final WeakListChangeListener weakColumnsListener =
 149             new WeakListChangeListener(columnsListener);
 150 
 151     private static final EventHandler<MouseEvent> rectMousePressed = me -> {
 152         Rectangle rect = (Rectangle) me.getSource();
 153         TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
 154         NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
 155 
 156         if (! header.isColumnResizingEnabled()) return;
 157 
 158         // column reordering takes precedence over column resizing, but sometimes the mouse dragged events
 159         // can be received by both nodes, leading to less than ideal UX, hence the check here.
 160         if (header.getTableHeaderRow().columnDragLock) return;
 161 
 162         if (me.isConsumed()) return;
 163         me.consume();
 164 
 165         if (me.getClickCount() == 2 && me.isPrimaryButtonDown()) {
 166             // the user wants to resize the column such that its
 167             // width is equal to the widest element in the column
 168             TableSkinUtils.resizeColumnToFitContent(header.getTableSkin(), column, -1);
 169         } else {
 170             // rather than refer to the rect variable, we just grab
 171             // it from the source to prevent a small memory leak.
 172             Rectangle innerRect = (Rectangle) me.getSource();
 173             double startX = header.getTableHeaderRow().sceneToLocal(innerRect.localToScene(innerRect.getBoundsInLocal())).getMinX() + 2;
 174             header.dragAnchorX = me.getSceneX();
 175             header.columnResizingStarted(startX);
 176         }
 177     };
 178 
 179     private static final EventHandler<MouseEvent> rectMouseDragged = me -> {
 180         Rectangle rect = (Rectangle) me.getSource();
 181         TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
 182         NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
 183 
 184         if (! header.isColumnResizingEnabled()) return;
 185 
 186         // column reordering takes precedence over column resizing, but sometimes the mouse dragged events
 187         // can be received by both nodes, leading to less than ideal UX, hence the check here.
 188         if (header.getTableHeaderRow().columnDragLock) return;
 189 
 190         if (me.isConsumed()) return;
 191         me.consume();
 192 
 193         header.columnResizing(column, me);
 194     };
 195 
 196     private static final EventHandler<MouseEvent> rectMouseReleased = me -> {
 197         Rectangle rect = (Rectangle) me.getSource();
 198         TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
 199         NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
 200 
 201         if (! header.isColumnResizingEnabled()) return;
 202 
 203         // column reordering takes precedence over column resizing, but sometimes the mouse dragged events
 204         // can be received by both nodes, leading to less than ideal UX, hence the check here.
 205         if (header.getTableHeaderRow().columnDragLock) return;
 206 
 207         if (me.isConsumed()) return;
 208         me.consume();
 209 
 210         header.columnResizingComplete(column, me);
 211     };
 212 
 213     private static final EventHandler<MouseEvent> rectCursorChangeListener = me -> {
 214         Rectangle rect = (Rectangle) me.getSource();
 215         TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
 216         NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
 217 
 218         // column reordering takes precedence over column resizing, but sometimes the mouse dragged events
 219         // can be received by both nodes, leading to less than ideal UX, hence the check here.
 220         if (header.getTableHeaderRow().columnDragLock) return;
 221 
 222         if (header.getCursor() == null) { // If there's a cursor for the whole header, don't override it
 223             rect.setCursor(header.isColumnResizingEnabled() && rect.isHover() &&
 224                     column.isResizable() ? Cursor.H_RESIZE : null);
 225         }
 226     };
 227 
 228 
 229 
 230     /***************************************************************************
 231      *                                                                         *
 232      * Public Methods                                                          *
 233      *                                                                         *
 234      **************************************************************************/
 235 
 236     /** {@inheritDoc} */
 237     @Override void dispose() {
 238         super.dispose();
 239 
 240         if (label != null) {
 241             label.dispose();
 242         }
 243 
 244         if (getColumns() != null) {
 245             getColumns().removeListener(weakColumnsListener);
 246         }
 247 
 248         for (int i = 0; i < getColumnHeaders().size(); i++) {
 249             TableColumnHeader header = getColumnHeaders().get(i);
 250             header.dispose();
 251         }
 252 
 253         for (Rectangle rect : dragRects.values()) {
 254             if (rect != null) {
 255                 rect.visibleProperty().unbind();
 256             }
 257         }
 258         dragRects.clear();
 259         getChildren().clear();
 260 
 261         changeListenerHandler.dispose();
 262     }
 263 
 264     /**
 265      * Returns an unmodifiable list of the {@link TableColumnHeader} instances
 266      * that are children of this NestedTableColumnHeader.
 267      * @return the unmodifiable list of TableColumnHeader of this NestedTableColumnHeader
 268      */
 269     public final ObservableList<TableColumnHeader> getColumnHeaders() {
 270         if (columnHeaders == null) {
 271             columnHeaders = FXCollections.<TableColumnHeader>observableArrayList();
 272             unmodifiableColumnHeaders = FXCollections.unmodifiableObservableList(columnHeaders);
 273         }
 274         return unmodifiableColumnHeaders;
 275     }
 276 
 277     /** {@inheritDoc} */
 278     @Override protected void layoutChildren() {
 279         double w = getWidth() - snappedLeftInset() - snappedRightInset();
 280         double h = getHeight() - snappedTopInset() - snappedBottomInset();
 281 
 282         int labelHeight = (int) label.prefHeight(-1);
 283 
 284         if (label.isVisible()) {
 285             // label gets to span whole width and sits at top
 286             label.resize(w, labelHeight);
 287             label.relocate(snappedLeftInset(), snappedTopInset());
 288         }
 289 
 290         // children columns need to share the total available width
 291         double x = snappedLeftInset();
 292         final double height = snapSizeY(h - labelHeight);
 293         for (int i = 0, max = getColumnHeaders().size(); i < max; i++) {
 294             TableColumnHeader n = getColumnHeaders().get(i);
 295             if (! n.isVisible()) continue;
 296 
 297             double prefWidth = n.prefWidth(height);
 298 
 299             // position the column header in the default location...
 300             n.resize(prefWidth, height);
 301             n.relocate(x, labelHeight + snappedTopInset());
 302 
 303 //            // ...but, if there are no children of this column, we should ensure
 304 //            // that it is resized vertically such that it goes to the very
 305 //            // bottom of the table header row.
 306 //            if (getTableHeaderRow() != null && n.getCol().getColumns().isEmpty()) {
 307 //                Bounds bounds = getTableHeaderRow().sceneToLocal(n.localToScene(n.getBoundsInLocal()));
 308 //                prefHeight = getTableHeaderRow().getHeight() - bounds.getMinY();
 309 //                n.resize(prefWidth, prefHeight);
 310 //            }
 311 
 312             // shuffle along the x-axis appropriately
 313             x += prefWidth;
 314 
 315             // position drag overlay to intercept column resize requests
 316             Rectangle dragRect = dragRects.get(n.getTableColumn());
 317             if (dragRect != null) {
 318                 dragRect.setHeight(n.getDragRectHeight());
 319                 dragRect.relocate(x - DRAG_RECT_WIDTH / 2, snappedTopInset() + labelHeight);
 320             }
 321         }
 322     }
 323 
 324     // sum up all children columns
 325     /** {@inheritDoc} */
 326     @Override protected double computePrefWidth(double height) {
 327         checkState();
 328 
 329         double width = 0.0F;
 330 
 331         if (getColumns() != null) {
 332             for (TableColumnHeader c : getColumnHeaders()) {
 333                 if (c.isVisible()) {
 334                     width += c.computePrefWidth(height);
 335                 }
 336             }
 337         }
 338 
 339         return width;
 340     }
 341 
 342     /** {@inheritDoc} */
 343     @Override protected double computePrefHeight(double width) {
 344         checkState();
 345 
 346         double height = 0.0F;
 347 
 348         if (getColumnHeaders() != null) {
 349             for (TableColumnHeader n : getColumnHeaders()) {
 350                 height = Math.max(height, n.prefHeight(-1));
 351             }
 352         }
 353 
 354         return height + label.prefHeight(-1) + snappedTopInset() + snappedBottomInset();
 355     }
 356 
 357     /**
 358      * Creates a new TableColumnHeader instance for the given TableColumnBase instance. The general pattern for
 359      * implementing this method is as follows:
 360      *
 361      * <ul>
 362      *     <li>If the given TableColumnBase instance is null, has no child columns, or if the given TableColumnBase
 363      *         instance equals the TableColumnBase instance returned by calling {@link #getTableColumn()}, then it is
 364      *         suggested to return a {@link TableColumnHeader} instance comprised of the given column.</li>
 365      *     <li>Otherwise, we can presume that the given TableColumnBase instance has child columns, and in this case
 366      *         it is suggested to return a {@link NestedTableColumnHeader} instance instead.</li>
 367      * </ul>
 368      *
 369      * <strong>Note: </strong>In most circumstances this method should not be overridden, but in some circumstances it
 370      * makes sense (e.g. testing, or when extreme customization is desired).
 371      *
 372      * @param col the table column
 373      * @return A new TableColumnHeader instance.
 374      */
 375     protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
 376         return col == null || col.getColumns().isEmpty() || col == getTableColumn() ?
 377                 new TableColumnHeader(col) :
 378                 new NestedTableColumnHeader(col);
 379     }
 380 
 381 
 382 
 383     /***************************************************************************
 384      *                                                                         *
 385      * Private Implementation                                                  *
 386      *                                                                         *
 387      **************************************************************************/
 388 
 389     @Override void initStyleClasses() {
 390         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
 391         installTableColumnStyleClassListener();
 392     }
 393 
 394     @Override void setTableHeaderRow(TableHeaderRow header) {
 395         super.setTableHeaderRow(header);
 396 
 397         // it's only now that a skin might be available
 398         if (getTableSkin() != null) {
 399             changeListenerHandler.registerChangeListener(TableSkinUtils.columnResizePolicyProperty(getTableSkin()), e -> updateContent());
 400         }
 401 
 402         label.setTableHeaderRow(header);
 403 
 404         // tell all children columns what TableHeader they belong to
 405         for (TableColumnHeader c : getColumnHeaders()) {
 406             c.setTableHeaderRow(header);
 407         }
 408     }
 409 
 410     @Override void setParentHeader(NestedTableColumnHeader parentHeader) {
 411         super.setParentHeader(parentHeader);
 412         label.setParentHeader(parentHeader);
 413     }
 414 
 415     ObservableList<? extends TableColumnBase> getColumns() {
 416         return columns;
 417     }
 418 
 419     void setColumns(ObservableList<? extends TableColumnBase> newColumns) {
 420         if (this.columns != null) {
 421             this.columns.removeListener(weakColumnsListener);
 422         }
 423 
 424         this.columns = newColumns;
 425 
 426         if (this.columns != null) {
 427             this.columns.addListener(weakColumnsListener);
 428         }
 429     }
 430 
 431     void updateTableColumnHeaders() {
 432         // watching for changes to the view columns in either table or tableColumn.
 433         if (getTableColumn() == null && getTableSkin() != null) {
 434             setColumns(TableSkinUtils.getColumns(getTableSkin()));
 435         } else if (getTableColumn() != null) {
 436             setColumns(getTableColumn().getColumns());
 437         }
 438 
 439         // update the column headers...
 440 
 441         // iterate through all columns, unless we've got no child columns
 442         // any longer, in which case we should switch to a TableColumnHeader
 443         // instead
 444         if (getColumns().isEmpty()) {
 445             // iterate through all current headers, telling them to clean up
 446             for (int i = 0; i < getColumnHeaders().size(); i++) {
 447                 TableColumnHeader header = getColumnHeaders().get(i);
 448                 header.dispose();
 449             }
 450 
 451             // switch out to be a TableColumn instead, if we have a parent header
 452             NestedTableColumnHeader parentHeader = getParentHeader();
 453             if (parentHeader != null) {
 454                 List<TableColumnHeader> parentColumnHeaders = parentHeader.getColumnHeaders();
 455                 int index = parentColumnHeaders.indexOf(this);
 456                 if (index >= 0 && index < parentColumnHeaders.size()) {
 457                     parentColumnHeaders.set(index, createColumnHeader(getTableColumn()));
 458                 }
 459             } else {
 460                 // otherwise just remove all the columns
 461                 columnHeaders.clear();
 462             }
 463         } else {
 464             List<TableColumnHeader> oldHeaders = new ArrayList<>(getColumnHeaders());
 465             List<TableColumnHeader> newHeaders = new ArrayList<>();
 466 
 467             for (int i = 0; i < getColumns().size(); i++) {
 468                 TableColumnBase<?,?> column = getColumns().get(i);
 469                 if (column == null || ! column.isVisible()) continue;
 470 
 471                 // check if the header already exists and reuse it
 472                 boolean found = false;
 473                 for (int j = 0; j < oldHeaders.size(); j++) {
 474                     TableColumnHeader oldColumn = oldHeaders.get(j);
 475                     if (oldColumn.represents(column)) {
 476                         newHeaders.add(oldColumn);
 477                         found = true;
 478                         break;
 479                     }
 480                 }
 481 
 482                 // otherwise create a new table column header
 483                 if (!found) {
 484                     newHeaders.add(createColumnHeader(column));
 485                 }
 486             }
 487 
 488             columnHeaders.setAll(newHeaders);
 489 
 490             // dispose all old headers
 491             oldHeaders.removeAll(newHeaders);
 492             for (int i = 0; i < oldHeaders.size(); i++) {
 493                 oldHeaders.get(i).dispose();
 494             }
 495         }
 496 
 497         // update the content
 498         updateContent();
 499 
 500         // RT-33596: Do CSS now, as we are in the middle of layout pass and the headers are new Nodes w/o CSS done
 501         for (TableColumnHeader header : getColumnHeaders()) {
 502             header.applyCss();
 503         }
 504     }
 505 
 506     // Used to test whether this column header properly represents the given column.
 507     // In particular, whether it has child column headers for all child columns
 508     boolean represents(TableColumnBase<?, ?> column) {
 509         if (column.getColumns().isEmpty()) {
 510             // this column has no children, but we are in a NestedTableColumnHeader instance,
 511             // so the match is bad.
 512             return false;
 513         }
 514 
 515         if (column != getTableColumn()) {
 516             return false;
 517         }
 518 
 519         final int columnCount = column.getColumns().size();
 520         final int headerCount = getColumnHeaders().size();
 521         if (columnCount != headerCount) {
 522             return false;
 523         }
 524 
 525         for (int i = 0; i < columnCount; i++) {
 526             // we expect the order of all children to match the order of the headers
 527             TableColumnBase<?,?> childColumn = column.getColumns().get(i);
 528             TableColumnHeader childHeader = getColumnHeaders().get(i);
 529             if (!childHeader.represents(childColumn)) {
 530                 return false;
 531             }
 532         }
 533         return true;
 534     }
 535 
 536     /** {@inheritDoc} */
 537     @Override double getDragRectHeight() {
 538         return label.prefHeight(-1);
 539     }
 540 
 541     void setHeadersNeedUpdate() {
 542         updateColumns = true;
 543 
 544         // go through children columns - they should update too
 545         for (int i = 0; i < getColumnHeaders().size(); i++) {
 546             TableColumnHeader header = getColumnHeaders().get(i);
 547             if (header instanceof NestedTableColumnHeader) {
 548                 ((NestedTableColumnHeader)header).setHeadersNeedUpdate();
 549             }
 550         }
 551         requestLayout();
 552     }
 553 
 554     private void updateContent() {
 555         // create a temporary list so we only do addAll into the main content
 556         // observableArrayList once.
 557         final List<Node> content = new ArrayList<Node>();
 558 
 559         // the label is the region that sits above the children columns
 560         content.add(label);
 561 
 562         // all children columns
 563         content.addAll(getColumnHeaders());
 564 
 565         // Small transparent overlays that sit at the start and end of each
 566         // column to intercept user drag gestures to enable column resizing.
 567         if (isColumnResizingEnabled()) {
 568             rebuildDragRects();
 569             content.addAll(dragRects.values());
 570         }
 571 
 572         getChildren().setAll(content);
 573     }
 574 
 575     private void rebuildDragRects() {
 576         if (! isColumnResizingEnabled()) return;
 577 
 578         getChildren().removeAll(dragRects.values());
 579 
 580         for (Rectangle rect : dragRects.values()) {
 581             rect.visibleProperty().unbind();
 582         }
 583         dragRects.clear();
 584 
 585         List<? extends TableColumnBase> columns = getColumns();
 586 
 587         if (columns == null) {
 588             return;
 589         }
 590 
 591         boolean isConstrainedResize = false;
 592         TableViewSkinBase tableSkin = getTableSkin();
 593         Callback<ResizeFeaturesBase,Boolean> columnResizePolicy = TableSkinUtils.columnResizePolicyProperty(tableSkin).get();
 594         if (columnResizePolicy != null) {
 595             isConstrainedResize =
 596                     tableSkin instanceof TableViewSkin ? TableView.CONSTRAINED_RESIZE_POLICY.equals(columnResizePolicy) :
 597                     tableSkin instanceof TreeTableViewSkin ? TreeTableView.CONSTRAINED_RESIZE_POLICY.equals(columnResizePolicy) :
 598                     false;
 599         }
 600 
 601         // RT-32547 - don't show resize cursor when in constrained resize mode
 602         // and there is only one column
 603         if (isConstrainedResize && TableSkinUtils.getVisibleLeafColumns(tableSkin).size() == 1) {
 604             return;
 605         }
 606 
 607         for (int col = 0; col < columns.size(); col++) {
 608             if (isConstrainedResize && col == getColumns().size() - 1) {
 609                 break;
 610             }
 611 
 612             final TableColumnBase c = columns.get(col);
 613             final Rectangle rect = new Rectangle();
 614             rect.getProperties().put(TABLE_COLUMN_KEY, c);
 615             rect.getProperties().put(TABLE_COLUMN_HEADER_KEY, this);
 616             rect.setWidth(DRAG_RECT_WIDTH);
 617             rect.setHeight(getHeight() - label.getHeight());
 618             rect.setFill(Color.TRANSPARENT);
 619             rect.visibleProperty().bind(c.visibleProperty().and(c.resizableProperty()));
 620             rect.setOnMousePressed(rectMousePressed);
 621             rect.setOnMouseDragged(rectMouseDragged);
 622             rect.setOnMouseReleased(rectMouseReleased);
 623             rect.setOnMouseEntered(rectCursorChangeListener);
 624             rect.setOnMouseExited(rectCursorChangeListener);
 625 
 626             dragRects.put(c, rect);
 627         }
 628     }
 629 
 630     private void checkState() {
 631         if (updateColumns) {
 632             updateTableColumnHeaders();
 633             updateColumns = false;
 634         }
 635     }
 636 
 637     private TableColumnHeader createColumnHeader(TableColumnBase col) {
 638         TableColumnHeader newCol = createTableColumnHeader(col);
 639         newCol.setTableHeaderRow(getTableHeaderRow());
 640         newCol.setParentHeader(this);
 641         return newCol;
 642     }
 643 
 644 
 645 
 646     /***************************************************************************
 647      *                                                                         *
 648      * Private Implementation: Column Resizing                                 *
 649      *                                                                         *
 650      **************************************************************************/
 651 
 652     private boolean isColumnResizingEnabled() {
 653         // this used to check if ! PlatformUtil.isEmbedded(), but has been changed
 654         // to always return true (for now), as we want to support column resizing
 655         // everywhere
 656         return true;
 657     }
 658 
 659     private void columnResizingStarted(double startX) {
 660         setCursor(Cursor.H_RESIZE);
 661         columnReorderLine.setLayoutX(startX);
 662     }
 663 
 664     private void columnResizing(TableColumnBase col, MouseEvent me) {
 665         double draggedX = me.getSceneX() - dragAnchorX;
 666         if (getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
 667             draggedX = -draggedX;
 668         }
 669         double delta = draggedX - lastX;
 670         boolean allowed = TableSkinUtils.resizeColumn(getTableSkin(), col, delta);
 671         if (allowed) {
 672             lastX = draggedX;
 673         }
 674     }
 675 
 676     private void columnResizingComplete(TableColumnBase col, MouseEvent me) {
 677         setCursor(null);
 678         columnReorderLine.setTranslateX(0.0F);
 679         columnReorderLine.setLayoutX(0.0F);
 680         lastX = 0.0F;
 681     }
 682 }