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