1 /*
   2  * Copyright (c) 2012, 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;
  27 
  28 import java.lang.ref.WeakReference;
  29 import java.text.Collator;
  30 import java.util.Comparator;
  31 
  32 import com.sun.javafx.beans.IDProperty;
  33 import com.sun.javafx.scene.control.ControlAcceleratorSupport;
  34 import javafx.beans.property.BooleanProperty;
  35 import javafx.beans.property.DoubleProperty;
  36 import javafx.beans.property.ObjectProperty;
  37 import javafx.beans.property.SimpleBooleanProperty;
  38 import javafx.beans.property.SimpleObjectProperty;
  39 import javafx.beans.property.SimpleStringProperty;
  40 import javafx.beans.property.StringProperty;
  41 import javafx.collections.FXCollections;
  42 import javafx.collections.ObservableList;
  43 import javafx.collections.ObservableSet;
  44 import javafx.css.PseudoClass;
  45 import javafx.css.Styleable;
  46 import javafx.event.Event;
  47 import javafx.event.EventDispatchChain;
  48 import javafx.event.EventHandler;
  49 import javafx.event.EventTarget;
  50 import javafx.event.EventType;
  51 import javafx.scene.Node;
  52 
  53 import com.sun.javafx.event.EventHandlerManager;
  54 import com.sun.javafx.scene.control.TableColumnBaseHelper;
  55 import java.util.HashMap;
  56 
  57 import javafx.beans.property.ReadOnlyDoubleProperty;
  58 import javafx.beans.property.ReadOnlyDoubleWrapper;
  59 import javafx.beans.property.ReadOnlyObjectProperty;
  60 import javafx.beans.property.ReadOnlyObjectWrapper;
  61 import javafx.beans.property.SimpleDoubleProperty;
  62 import javafx.beans.value.ObservableValue;
  63 import javafx.collections.ObservableMap;
  64 import com.sun.javafx.scene.control.skin.Utils;
  65 
  66 /**
  67  * Table-like controls (such as {@link TableView} and {@link TreeTableView}) are
  68  * made up of zero or more instances of a concrete TableColumnBase subclass
  69  * ({@link TableColumn} and {@link TreeTableColumn}, respectively). Each
  70  * table column in a table is responsible for displaying (and editing) the contents
  71  * of that column. As well as being responsible for displaying and editing data
  72  * for a single column, a table column also contains the necessary properties to:
  73  * <ul>
  74  *    <li>Be resized (using {@link #minWidthProperty() minWidth}/{@link #prefWidthProperty() prefWidth}/{@link #maxWidthProperty() maxWidth}
  75  *      and {@link #widthProperty() width} properties)
  76  *    <li>Have its {@link #visibleProperty() visibility} toggled
  77  *    <li>Display {@link #textProperty() header text}
  78  *    <li>Display any {@link #getColumns() nested columns} it may contain
  79  *    <li>Have a {@link #contextMenuProperty() context menu} when the user
  80  *      right-clicks the column header area
  81  *    <li>Have the contents of the table be sorted (using
  82  *      {@link #comparatorProperty() comparator}, {@link #sortable sortable} and
  83  *      sortType).
  84  * </ul>
  85  * </p>
  86  *
  87  * When instantiating a concrete subclass of TableColumnBase, perhaps the two
  88  * most important properties to set are the column {@link #textProperty() text}
  89  * (what to show in the column header area), and the column
  90  * {@code cell value factory} (which is used to populate individual cells in the
  91  * column). Refer to the class documentation for {@link TableColumn} and
  92  * {@link TreeTableColumn} for more information.
  93  *
  94  * @param <S> The type of the UI control (e.g. the type of the 'row').
  95  * @param <T> The type of the content in all cells in this table column.
  96  * @see TableColumn
  97  * @see TreeTableColumn
  98  * @see TablePositionBase
  99  * @since JavaFX 8.0
 100  */
 101 @IDProperty("id")
 102 public abstract class TableColumnBase<S,T> implements EventTarget, Styleable {
 103     static {
 104         TableColumnBaseHelper.setTableColumnBaseAccessor(
 105                 new TableColumnBaseHelper.TableColumnBaseAccessor() {
 106 
 107                     @Override
 108                     public void setWidth(TableColumnBase tableColumnBase, double width) {
 109                         tableColumnBase.doSetWidth(width);
 110                     }
 111 
 112                 });
 113     }
 114 
 115     /***************************************************************************
 116      *                                                                         *
 117      * Static properties and methods                                           *
 118      *                                                                         *
 119      **************************************************************************/
 120 
 121     // NOTE: If these numbers change, update the copy of this value in TableColumnHeader
 122     static final double DEFAULT_WIDTH = 80.0F;
 123     static final double DEFAULT_MIN_WIDTH = 10.0F;
 124     static final double DEFAULT_MAX_WIDTH = 5000.0F;
 125 
 126     /**
 127      * By default all columns will use this comparator to perform sorting. This
 128      * comparator simply performs null checks, and checks if the object is
 129      * {@link Comparable}. If it is, the {@link Comparable#compareTo(java.lang.Object)}
 130      * method is called, otherwise this method will defer to
 131      * {@link Collator#compare(java.lang.String, java.lang.String)}.
 132      */
 133     public static final Comparator DEFAULT_COMPARATOR = (obj1, obj2) -> {
 134         if (obj1 == null && obj2 == null) return 0;
 135         if (obj1 == null) return -1;
 136         if (obj2 == null) return 1;
 137 
 138         if (obj1 instanceof Comparable && (obj1.getClass() == obj2.getClass() || obj1.getClass().isAssignableFrom(obj2.getClass()))) {
 139             return (obj1 instanceof String) ? Collator.getInstance().compare(obj1, obj2) : ((Comparable)obj1).compareTo(obj2);
 140         }
 141 
 142         return Collator.getInstance().compare(obj1.toString(), obj2.toString());
 143     };
 144 
 145 
 146 
 147     /***************************************************************************
 148      *                                                                         *
 149      * Constructors                                                            *
 150      *                                                                         *
 151      **************************************************************************/
 152 
 153     /**
 154      * Creates a default TableColumn with default cell factory, comparator, and
 155      * onEditCommit implementation.
 156      */
 157     protected TableColumnBase() {
 158         this("");
 159     }
 160 
 161     /**
 162      * Creates a TableColumn with the text set to the provided string, with
 163      * default cell factory, comparator, and onEditCommit implementation.
 164      * @param text The string to show when the TableColumn is placed within the TableView.
 165      */
 166     protected TableColumnBase(String text) {
 167         setText(text);
 168     }
 169 
 170 
 171 
 172     /***************************************************************************
 173      *                                                                         *
 174      * Listeners                                                               *
 175      *                                                                         *
 176      **************************************************************************/
 177 
 178 
 179 
 180     /***************************************************************************
 181      *                                                                         *
 182      * Instance Variables                                                      *
 183      *                                                                         *
 184      **************************************************************************/
 185 
 186     final EventHandlerManager eventHandlerManager = new EventHandlerManager(this);
 187 
 188 
 189 
 190     /***************************************************************************
 191      *                                                                         *
 192      * Properties                                                              *
 193      *                                                                         *
 194      **************************************************************************/
 195 
 196 
 197     // --- Text
 198     /**
 199      * This is the text to show in the header for this column.
 200      */
 201     private StringProperty text = new SimpleStringProperty(this, "text", "");
 202     public final StringProperty textProperty() { return text; }
 203     public final void setText(String value) { text.set(value); }
 204     public final String getText() { return text.get(); }
 205 
 206 
 207     // --- Visible
 208     /**
 209      * Toggling this will immediately toggle the visibility of this column,
 210      * and all children columns.
 211      */
 212     private BooleanProperty visible = new SimpleBooleanProperty(this, "visible", true) {
 213         @Override protected void invalidated() {
 214             // set all children columns to be the same visibility. This isn't ideal,
 215             // for example if a child column is hidden, then the parent hidden and
 216             // shown, all columns will be visible again.
 217             //
 218             // TODO It may make sense for us to cache the visibility so that we may
 219             // return to exactly the same state.
 220             // set all children columns to be the same visibility. This isn't ideal,
 221             // for example if a child column is hidden, then the parent hidden and
 222             // shown, all columns will be visible again.
 223             //
 224             // TODO It may make sense for us to cache the visibility so that we may
 225             // return to exactly the same state.
 226             for (TableColumnBase<S,?> col : getColumns()) {
 227                 col.setVisible(isVisible());
 228             }
 229         }
 230     };
 231     public final void setVisible(boolean value) { visibleProperty().set(value); }
 232     public final boolean isVisible() { return visible.get(); }
 233     public final BooleanProperty visibleProperty() { return visible; }
 234 
 235 
 236     // --- Parent Column
 237     /**
 238      * This read-only property will always refer to the parent of this column,
 239      * in the situation where nested columns are being used.
 240      *
 241      * <p>In the currently existing subclasses, to create a nested
 242      * column is simply a matter of placing the relevant TableColumnBase instances
 243      * inside the columns ObservableList (for example, see
 244      * {@link javafx.scene.control.TableColumn#getColumns()} and
 245      * {@link javafx.scene.control.TreeTableColumn#getColumns()}.
 246      */
 247     private ReadOnlyObjectWrapper<TableColumnBase<S,?>> parentColumn;
 248     void setParentColumn(TableColumnBase<S,?> value) { parentColumnPropertyImpl().set(value); }
 249     public final TableColumnBase<S,?> getParentColumn() {
 250         return parentColumn == null ? null : parentColumn.get();
 251     }
 252 
 253     public final ReadOnlyObjectProperty<TableColumnBase<S,?>> parentColumnProperty() {
 254         return parentColumnPropertyImpl().getReadOnlyProperty();
 255     }
 256 
 257     private ReadOnlyObjectWrapper<TableColumnBase<S,?>> parentColumnPropertyImpl() {
 258         if (parentColumn == null) {
 259             parentColumn = new ReadOnlyObjectWrapper<TableColumnBase<S,?>>(this, "parentColumn");
 260         }
 261         return parentColumn;
 262     }
 263 
 264 
 265     // --- Menu
 266     /**
 267      * This menu will be shown whenever the user right clicks within the header
 268      * area of this TableColumnBase.
 269      */
 270     private ObjectProperty<ContextMenu> contextMenu;
 271     public final void setContextMenu(ContextMenu value) { contextMenuProperty().set(value); }
 272     public final ContextMenu getContextMenu() { return contextMenu == null ? null : contextMenu.get(); }
 273     public final ObjectProperty<ContextMenu> contextMenuProperty() {
 274         if (contextMenu == null) {
 275             contextMenu = new SimpleObjectProperty<ContextMenu>(this, "contextMenu") {
 276                 private WeakReference<ContextMenu> contextMenuRef;
 277 
 278                 @Override protected void invalidated() {
 279                     ContextMenu oldMenu = contextMenuRef == null ? null : contextMenuRef.get();
 280                     if (oldMenu != null) {
 281                         ControlAcceleratorSupport.removeAcceleratorsFromScene(oldMenu.getItems(), TableColumnBase.this);
 282                     }
 283 
 284                     ContextMenu ctx = get();
 285                     contextMenuRef = new WeakReference<>(ctx);
 286 
 287                     if (ctx != null) {
 288                         // if a context menu is set, we need to install any accelerators
 289                         // belonging to its menu items ASAP into the scene that this
 290                         // Control is in (if the control is not in a Scene, we will need
 291                         // to wait until it is and then do it).
 292                         ControlAcceleratorSupport.addAcceleratorsIntoScene(ctx.getItems(), TableColumnBase.this);
 293                     }
 294                 }
 295             };
 296         }
 297         return contextMenu;
 298     }
 299 
 300 
 301     // --- Id
 302     /**
 303      * The id of this TableColumnBase. This simple string identifier is useful
 304      * for finding a specific TableColumnBase within a UI control that uses
 305      * TableColumnBase instances. The default value is {@code null}.
 306      *
 307      * @defaultValue null
 308      */
 309     private StringProperty id;
 310     public final void setId(String value) { idProperty().set(value); }
 311     @Override public final String getId() { return id == null ? null : id.get(); }
 312     public final StringProperty idProperty() {
 313         if (id == null) {
 314             id = new SimpleStringProperty(this, "id");
 315         }
 316         return id;
 317     }
 318 
 319 
 320     // --- style
 321     /**
 322      * A string representation of the CSS style associated with this
 323      * TableColumnBase instance. This is analogous to the "style" attribute of an
 324      * HTML element. Note that, like the HTML style attribute, this
 325      * variable contains style properties and values and not the
 326      * selector portion of a style rule.
 327      * <p>
 328      * Parsing this style might not be supported on some limited
 329      * platforms. It is recommended to use a standalone CSS file instead.
 330      *
 331      * @defaultValue empty string
 332      */
 333     private StringProperty style;
 334     public final void setStyle(String value) { styleProperty().set(value); }
 335     @Override public final String getStyle() { return style == null ? "" : style.get(); }
 336     public final StringProperty styleProperty() {
 337         if (style == null) {
 338             style = new SimpleStringProperty(this, "style");
 339         }
 340         return style;
 341     }
 342 
 343 
 344     // --- Style class
 345     private final ObservableList<String> styleClass = FXCollections.observableArrayList();
 346     /**
 347      * A list of String identifiers which can be used to logically group
 348      * Nodes, specifically for an external style engine. This variable is
 349      * analogous to the "class" attribute on an HTML element and, as such,
 350      * each element of the list is a style class to which this Node belongs.
 351      *
 352      * @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a>
 353      */
 354     @Override public ObservableList<String> getStyleClass() {
 355         return styleClass;
 356     }
 357 
 358 
 359     // --- Graphic
 360     /**
 361      * <p>The graphic to show in the table column to allow the user to
 362      * indicate graphically what is in the column. </p>
 363      */
 364     private ObjectProperty<Node> graphic;
 365     public final void setGraphic(Node value) {
 366         graphicProperty().set(value);
 367     }
 368     public final Node getGraphic() {
 369         return graphic == null ? null : graphic.get();
 370     }
 371     public final ObjectProperty<Node> graphicProperty() {
 372         if (graphic == null) {
 373             graphic = new SimpleObjectProperty<Node>(this, "graphic");
 374         }
 375         return graphic;
 376     }
 377 
 378 
 379     // --- Sort node
 380     /**
 381      * <p>The node to use as the "sort arrow", shown to the user in situations where
 382      * the table column is part of the sort order. It may be the only item in
 383      * the sort order, or it may be a secondary, tertiary, or latter sort item,
 384      * and the node should reflect this visually. This is only used in the case of
 385      * the table column being in the sort order (refer to, for example,
 386      * {@link TableView#getSortOrder()} and {@link TreeTableView#getSortOrder()}).
 387      * If not specified, the table column skin implementation is responsible for
 388      * providing a default sort node.
 389      *
 390      * <p>The sort node is commonly seen represented as a triangle that rotates
 391      * on screen to indicate whether the table column is part of the sort order,
 392      * and if so, whether the sort is ascending or descending, and what position in
 393      * the sort order it is in.
 394      */
 395     private ObjectProperty<Node> sortNode = new SimpleObjectProperty<Node>(this, "sortNode");
 396     public final void setSortNode(Node value) { sortNodeProperty().set(value); }
 397     public final Node getSortNode() { return sortNode.get(); }
 398     public final ObjectProperty<Node> sortNodeProperty() { return sortNode; }
 399 
 400 
 401     // --- Width
 402     /**
 403      * The width of this column. Modifying this will result in the column width
 404      * adjusting visually. It is recommended to not bind this property to an
 405      * external property, as that will result in the column width not being
 406      * adjustable by the user through dragging the left and right borders of
 407      * column headers.
 408      */
 409     public final ReadOnlyDoubleProperty widthProperty() { return width.getReadOnlyProperty(); }
 410     public final double getWidth() { return width.get(); }
 411     void setWidth(double value) { width.set(value); }
 412     private ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper(this, "width", DEFAULT_WIDTH);
 413 
 414 
 415     // --- Minimum Width
 416     /**
 417      * The minimum width the table column is permitted to be resized to.
 418      */
 419     private DoubleProperty minWidth;
 420     public final void setMinWidth(double value) { minWidthProperty().set(value); }
 421     public final double getMinWidth() { return minWidth == null ? DEFAULT_MIN_WIDTH : minWidth.get(); }
 422     public final DoubleProperty minWidthProperty() {
 423         if (minWidth == null) {
 424             minWidth = new SimpleDoubleProperty(this, "minWidth", DEFAULT_MIN_WIDTH) {
 425                 @Override protected void invalidated() {
 426                     if (getMinWidth() < 0) {
 427                         setMinWidth(0.0F);
 428                     }
 429 
 430                     doSetWidth(getWidth());
 431                 }
 432             };
 433         }
 434         return minWidth;
 435     }
 436 
 437 
 438     // --- Preferred Width
 439     /**
 440      * The preferred width of the TableColumn.
 441      */
 442     public final DoubleProperty prefWidthProperty() { return prefWidth; }
 443     public final void setPrefWidth(double value) { prefWidthProperty().set(value); }
 444     public final double getPrefWidth() { return prefWidth.get(); }
 445     private final DoubleProperty prefWidth = new SimpleDoubleProperty(this, "prefWidth", DEFAULT_WIDTH) {
 446         @Override protected void invalidated() {
 447             doSetWidth(getPrefWidth());
 448         }
 449     };
 450 
 451 
 452     // --- Maximum Width
 453     // The table does not resize properly if this is set to Number.MAX_VALUE,
 454     // so I've arbitrarily chosen a better, smaller number.
 455     /**
 456      * The maximum width the table column is permitted to be resized to.
 457      */
 458     public final DoubleProperty maxWidthProperty() { return maxWidth; }
 459     public final void setMaxWidth(double value) { maxWidthProperty().set(value); }
 460     public final double getMaxWidth() { return maxWidth.get(); }
 461     private DoubleProperty maxWidth = new SimpleDoubleProperty(this, "maxWidth", DEFAULT_MAX_WIDTH) {
 462         @Override protected void invalidated() {
 463             doSetWidth(getWidth());
 464         }
 465     };
 466 
 467 
 468     // --- Resizable
 469     /**
 470      * Used to indicate whether the width of this column can change. It is up
 471      * to the resizing policy to enforce this however.
 472      */
 473     private BooleanProperty resizable;
 474     public final BooleanProperty resizableProperty() {
 475         if (resizable == null) {
 476             resizable = new SimpleBooleanProperty(this, "resizable", true);
 477         }
 478         return resizable;
 479     }
 480     public final void setResizable(boolean value) {
 481         resizableProperty().set(value);
 482     }
 483     public final boolean isResizable() {
 484         return resizable == null ? true : resizable.get();
 485     }
 486 
 487 
 488 
 489     // --- Sortable
 490     /**
 491      * <p>A boolean property to toggle on and off the 'sortability' of this column.
 492      * When this property is true, this column can be included in sort
 493      * operations. If this property is false, it will not be included in sort
 494      * operations, even if it is contained within the sort order list of the
 495      * underlying UI control (e.g. {@link TableView#getSortOrder()} or
 496      * {@link TreeTableView#getSortOrder()}).</p>
 497      *
 498      * <p>For example, iIf a TableColumn instance is contained within the TableView sortOrder
 499      * ObservableList, and its sortable property toggles state, it will force the
 500      * TableView to perform a sort, as it is likely the view will need updating.</p>
 501      */
 502     private BooleanProperty sortable;
 503     public final BooleanProperty sortableProperty() {
 504         if (sortable == null) {
 505             sortable = new SimpleBooleanProperty(this, "sortable", true);
 506         }
 507         return sortable;
 508     }
 509     public final void setSortable(boolean value) {
 510         sortableProperty().set(value);
 511     }
 512     public final boolean isSortable() {
 513         return sortable == null ? true : sortable.get();
 514     }
 515 
 516 
 517 
 518     // --- Reorderable
 519     /**
 520      * A boolean property to toggle on and off the 'reorderability' of this column
 521      * (with drag and drop - reordering by modifying the appropriate <code>columns</code>
 522      * list is always allowed). When this property is true, this column can be reordered by
 523      * users simply by dragging and dropping the columns into their desired positions.
 524      * When this property is false, this ability to drag and drop columns is not available.
 525      *
 526      * @since 9
 527      */
 528     private BooleanProperty reorderable;
 529     public final BooleanProperty reorderableProperty() {
 530         if (reorderable == null) {
 531             reorderable = new SimpleBooleanProperty(this, "reorderable", true);
 532         }
 533         return reorderable;
 534     }
 535     public final void setReorderable(boolean value) {
 536         reorderableProperty().set(value);
 537     }
 538     public final boolean isReorderable() {
 539         return reorderable == null ? true : reorderable.get();
 540     }
 541 
 542 
 543     // --- Comparator
 544     /**
 545      * Comparator function used when sorting this table column. The two Objects
 546      * given as arguments are the cell data for two individual cells in this
 547      * column.
 548      */
 549     private ObjectProperty<Comparator<T>> comparator;
 550     public final ObjectProperty<Comparator<T>> comparatorProperty() {
 551         if (comparator == null) {
 552             comparator = new SimpleObjectProperty<Comparator<T>>(this, "comparator", DEFAULT_COMPARATOR);
 553         }
 554         return comparator;
 555     }
 556     public final void setComparator(Comparator<T> value) {
 557         comparatorProperty().set(value);
 558     }
 559     public final Comparator<T> getComparator() {
 560         return comparator == null ? DEFAULT_COMPARATOR : comparator.get();
 561     }
 562 
 563 
 564     // --- Editable
 565     /**
 566      * Specifies whether this table column allows editing. This, unlike
 567      * {@link TableView#editableProperty()} and
 568      * {@link TreeTableView#editableProperty()}, is true by default.
 569      */
 570     private BooleanProperty editable;
 571     public final void setEditable(boolean value) {
 572         editableProperty().set(value);
 573     }
 574     public final boolean isEditable() {
 575         return editable == null ? true : editable.get();
 576     }
 577     public final BooleanProperty editableProperty() {
 578         if (editable == null) {
 579             editable = new SimpleBooleanProperty(this, "editable", true);
 580         }
 581         return editable;
 582     }
 583 
 584 
 585     // --- Properties
 586     private static final Object USER_DATA_KEY = new Object();
 587 
 588     // A map containing a set of properties for this TableColumn
 589     private ObservableMap<Object, Object> properties;
 590 
 591     /**
 592       * Returns an observable map of properties on this table column for use
 593       * primarily by application developers.
 594       *
 595       * @return an observable map of properties on this table column for use
 596       * primarily by application developers
 597      */
 598     public final ObservableMap<Object, Object> getProperties() {
 599         if (properties == null) {
 600             properties = FXCollections.observableMap(new HashMap<Object, Object>());
 601         }
 602         return properties;
 603     }
 604 
 605     /**
 606      * Tests if this table column has properties.
 607      * @return true if node has properties.
 608      */
 609     public boolean hasProperties() {
 610         return properties != null && ! properties.isEmpty();
 611     }
 612 
 613 
 614     // --- UserData
 615     /**
 616      * Convenience method for setting a single Object property that can be
 617      * retrieved at a later date. This is functionally equivalent to calling
 618      * the getProperties().put(Object key, Object value) method. This can later
 619      * be retrieved by calling {@link TableColumnBase#getUserData()}.
 620      *
 621      * @param value The value to be stored - this can later be retrieved by calling
 622      *          {@link TableColumnBase#getUserData()}.
 623      */
 624     public void setUserData(Object value) {
 625         getProperties().put(USER_DATA_KEY, value);
 626     }
 627 
 628     /**
 629      * Returns a previously set Object property, or null if no such property
 630      * has been set using the {@link TableColumnBase#setUserData(java.lang.Object)} method.
 631      *
 632      * @return The Object that was previously set, or null if no property
 633      *          has been set or if null was set.
 634      */
 635     public Object getUserData() {
 636         return getProperties().get(USER_DATA_KEY);
 637     }
 638 
 639 
 640     /***************************************************************************
 641      *                                                                         *
 642      * Public API                                                              *
 643      *                                                                         *
 644      **************************************************************************/
 645 
 646     /**
 647      * This enables support for nested columns, which can be useful to group
 648      * together related data. For example, we may have a 'Name' column with
 649      * two nested columns for 'First' and 'Last' names.
 650      *
 651      * <p>This has no impact on the table as such - all column indices point to the
 652      * leaf columns only, and it isn't possible to sort using the parent column,
 653      * just the leaf columns. In other words, this is purely a visual feature.</p>
 654      *
 655      * @return An ObservableList containing TableColumnBase instances (or subclasses)
 656      *      that are the children of this TableColumnBase. If these children
 657      *      TableColumnBase instances are set as visible, they will appear
 658      *      beneath this table column.
 659      */
 660     public abstract ObservableList<? extends TableColumnBase<S,?>> getColumns();
 661 
 662     /**
 663      * Returns the actual value for a cell at a given row index (and which
 664      * belongs to this table column).
 665      *
 666      * @param index The row index for which the data is required.
 667      * @return The data that belongs to the cell at the intersection of the given
 668      *      row index and the table column that this method is called on.
 669      */
 670     public final T getCellData(final int index) {
 671         ObservableValue<T> result = getCellObservableValue(index);
 672         return result == null ? null : result.getValue();
 673     }
 674 
 675     /**
 676      * Returns the actual value for a cell from the given item.
 677      *
 678      * @param item The item from which a value of type T should be extracted.
 679      * @return The data that should be used in a specific cell in this
 680      *      column, based on the item passed in as an argument.
 681      */
 682     public final T getCellData(final S item) {
 683         ObservableValue<T> result = getCellObservableValue(item);
 684         return result == null ? null : result.getValue();
 685     }
 686 
 687     /**
 688      * Attempts to return an ObservableValue&lt;T&gt; for the item in the given
 689      * index (which is of type S). In other words, this method expects to receive
 690      * an integer value that is greater than or equal to zero, and less than the
 691      * size of the underlying data model. If the index is
 692      * valid, this method will return an ObservableValue&lt;T&gt; for this
 693      * specific column.
 694      *
 695      * <p>This is achieved by calling the {@code cell value factory}, and
 696      * returning whatever it returns when passed a {@code CellDataFeatures} (see,
 697      * for example, the CellDataFeatures classes belonging to
 698      * {@link TableColumn.CellDataFeatures TableColumn} and
 699      * {@link TreeTableColumn.CellDataFeatures TreeTableColumn} for more
 700      * information).
 701      *
 702      * @param index The index of the item (of type S) for which an
 703      *      ObservableValue&lt;T&gt; is sought.
 704      * @return An ObservableValue&lt;T&gt; for this specific table column.
 705      */
 706     public abstract ObservableValue<T> getCellObservableValue(int index);
 707 
 708     /**
 709      * Attempts to return an ObservableValue&lt;T&gt; for the given item (which
 710      * is of type S). In other words, this method expects to receive an object from
 711      * the underlying data model for the entire 'row' in the table, and it must
 712      * return an ObservableValue&lt;T&gt; for the value in this specific column.
 713      *
 714      * <p>This is achieved by calling the {@code cell value factory}, and
 715      * returning whatever it returns when passed a {@code CellDataFeatures} (see,
 716      * for example, the CellDataFeatures classes belonging to
 717      * {@link TableColumn.CellDataFeatures TableColumn} and
 718      * {@link TreeTableColumn.CellDataFeatures TreeTableColumn} for more
 719      * information).
 720      *
 721      * @param item The item (of type S) for which an ObservableValue&lt;T&gt; is
 722      *      sought.
 723      * @return An ObservableValue&lt;T&gt; for this specific table column.
 724      */
 725     public abstract ObservableValue<T> getCellObservableValue(S item);
 726 
 727     /** {@inheritDoc} */
 728     @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
 729         return tail.prepend(eventHandlerManager);
 730     }
 731 
 732     /**
 733      * Registers an event handler to this table column. The TableColumnBase class allows
 734      * registration of listeners which will be notified when editing occurs.
 735      * Note however that TableColumnBase is <b>not</b> a Node, and therefore no visual
 736      * events will be fired on it.
 737      *
 738      * @param eventType the type of the events to receive by the handler
 739      * @param eventHandler the handler to register
 740      * @throws NullPointerException if the event type or handler is null
 741      */
 742     public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
 743         eventHandlerManager.addEventHandler(eventType, eventHandler);
 744     }
 745 
 746     /**
 747      * Unregisters a previously registered event handler from this table column. One
 748      * handler might have been registered for different event types, so the
 749      * caller needs to specify the particular event type from which to
 750      * unregister the handler.
 751      *
 752      * @param eventType the event type from which to unregister
 753      * @param eventHandler the handler to unregister
 754      * @throws NullPointerException if the event type or handler is null
 755      */
 756     public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
 757         eventHandlerManager.removeEventHandler(eventType, eventHandler);
 758     }
 759 
 760 
 761 
 762     /***************************************************************************
 763      *                                                                         *
 764      * Private Implementation                                                  *
 765      *                                                                         *
 766      **************************************************************************/
 767 
 768     void doSetWidth(double width) {
 769         setWidth(Utils.boundedSize(width, getMinWidth(), getMaxWidth()));
 770     }
 771 
 772     void updateColumnWidths() {
 773         if (! getColumns().isEmpty()) {
 774             // zero out the width and min width values, and iterate to
 775             // ensure the new value is equal to the sum of all children
 776             // columns
 777             double _minWidth = 0.0f;
 778             double _prefWidth = 0.0f;
 779             double _maxWidth = 0.0f;
 780 
 781             for (TableColumnBase<S, ?> col : getColumns()) {
 782                 col.setParentColumn(this);
 783 
 784                 _minWidth += col.getMinWidth();
 785                 _prefWidth += col.getPrefWidth();
 786                 _maxWidth += col.getMaxWidth();
 787             }
 788 
 789             setMinWidth(_minWidth);
 790             setPrefWidth(_prefWidth);
 791             setMaxWidth(_maxWidth);
 792         }
 793     }
 794 
 795 
 796     /***************************************************************************
 797      *                                                                         *
 798      * Stylesheet Handling                                                     *
 799      *                                                                         *
 800      **************************************************************************/
 801 
 802     /**
 803      * {@inheritDoc}
 804      */
 805     public final ObservableSet<PseudoClass> getPseudoClassStates() {
 806         return FXCollections.emptyObservableSet();
 807     }
 808 
 809 
 810 
 811     /***************************************************************************
 812      *                                                                         *
 813      * Support Interfaces                                                      *
 814      *                                                                         *
 815      **************************************************************************/
 816 
 817 }