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