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