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