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