001/*
002 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.control.cell;
027
028import javafx.css.CssMetaData;
029import javafx.beans.binding.Bindings;
030import javafx.beans.property.BooleanProperty;
031import javafx.beans.property.ObjectProperty;
032import javafx.beans.property.SimpleObjectProperty;
033import javafx.beans.value.ObservableValue;
034import javafx.geometry.Pos;
035import javafx.scene.control.CheckBox;
036import javafx.scene.control.TreeTableCell;
037import javafx.scene.control.TreeTableColumn;
038import javafx.util.Callback;
039import javafx.util.StringConverter;
040
041/**
042 * A class containing a {@link TreeTableCell} implementation that draws a 
043 * {@link CheckBox} node inside the cell, optionally with a label to indicate 
044 * what the checkbox represents.
045 * 
046 * <p>By default, the CheckBoxTreeTableCell is rendered with a CheckBox centred in 
047 * the TreeTableColumn. If a label is required, it is necessary to provide a 
048 * non-null StringConverter instance to the 
049 * {@link #CheckBoxTreeTableCell(Callback, StringConverter)} constructor.
050 * 
051 * <p>To construct an instance of this class, it is necessary to provide a 
052 * {@link Callback} that, given an object of type T, will return an 
053 * {@code ObservableProperty<Boolean>} that represents whether the given item is
054 * selected or not. This ObservableValue will be bound bidirectionally (meaning 
055 * that the CheckBox in the cell will set/unset this property based on user 
056 * interactions, and the CheckBox will reflect the state of the ObservableValue, 
057 * if it changes externally).
058 * 
059 * @param <T> The type of the elements contained within the TreeTableColumn.
060 * @since 8.0
061 */
062public class CheckBoxTreeTableCell<S,T> extends TreeTableCell<S,T> {
063    
064    /***************************************************************************
065     *                                                                         *
066     * Static cell factories                                                   *
067     *                                                                         *
068     **************************************************************************/
069    
070    /**
071     * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 
072     * This method requires that the TreeTableColumn be of type {@link Boolean}.
073     * 
074     * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 
075     * CheckBox centered in the column.
076     * 
077     * <p>The {@code ObservableValue<Boolean>} contained within each cell in the 
078     * column will be bound bidirectionally. This means that the  CheckBox in 
079     * the cell will set/unset this property based on user interactions, and the 
080     * CheckBox will reflect the state of the {@code ObservableValue<Boolean>}, 
081     * if it changes externally).</li>
082     * 
083     * @return A {@link Callback} that will return a {@link TreeTableCell} that is 
084     *      able to work on the type of element contained within the TreeTableColumn.
085     */
086    public static <S> Callback<TreeTableColumn<S,Boolean>, TreeTableCell<S,Boolean>> forTreeTableColumn(
087            final TreeTableColumn<S, Boolean> column) {
088        return forTreeTableColumn(null, null);
089    }
090    
091    /**
092     * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 
093     * This method requires that the TreeTableColumn be of type
094     * {@code ObservableValue<Boolean>}.
095     * 
096     * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 
097     * CheckBox centered in the column.
098     * 
099     * @param <T> The type of the elements contained within the {@link TreeTableColumn} 
100     *      instance.
101     * @param getSelectedProperty A Callback that, given an object of 
102     *      type {@code TreeTableColumn<S,T>}, will return an 
103     *      {@code ObservableValue<Boolean>} 
104     *      that represents whether the given item is selected or not. This 
105     *      {@code ObservableValue<Boolean>} will be bound bidirectionally 
106     *      (meaning that the CheckBox in the cell will set/unset this property 
107     *      based on user interactions, and the CheckBox will reflect the state of 
108     *      the {@code ObservableValue<Boolean>}, if it changes externally).
109     * @return A {@link Callback} that will return a {@link TreeTableCell} that is 
110     *      able to work on the type of element contained within the TreeTableColumn.
111     */
112    public static <S,T> Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>> forTreeTableColumn(
113            final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) {
114        return forTreeTableColumn(getSelectedProperty, null);
115    }
116    
117    /**
118     * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 
119     * This method requires that the TreeTableColumn be of type
120     * {@code ObservableValue<Boolean>}.
121     * 
122     * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 
123     * CheckBox centered in the column.
124     * 
125     * @param <T> The type of the elements contained within the {@link TreeTableColumn} 
126     *      instance.
127     * @param getSelectedProperty A Callback that, given an object of 
128     *      type {@code TreeTableColumn<S,T>}, will return an 
129     *      {@code ObservableValue<Boolean>} 
130     *      that represents whether the given item is selected or not. This 
131     *      {@code ObservableValue<Boolean>} will be bound bidirectionally 
132     *      (meaning that the CheckBox in the cell will set/unset this property 
133     *      based on user interactions, and the CheckBox will reflect the state of 
134     *      the {@code ObservableValue<Boolean>}, if it changes externally).
135     * @param showLabel In some cases, it may be desirable to show a label in 
136     *      the TreeTableCell beside the {@link CheckBox}. By default a label is not 
137     *      shown, but by setting this to true the item in the cell will also 
138     *      have toString() called on it. If this is not the desired behavior, 
139     *      consider using 
140     *      {@link #forTreeTableColumn(javafx.util.Callback, javafx.util.StringConverter) }, 
141     *      which allows for you to provide a callback that specifies the label for a 
142     *      given row item.
143     * @return A {@link Callback} that will return a {@link TreeTableCell} that is 
144     *      able to work on the type of element contained within the TreeTableColumn.
145     */
146    public static <S,T> Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>> forTreeTableColumn(
147            final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 
148            final boolean showLabel) {
149        StringConverter<T> converter = ! showLabel ? 
150                null : CellUtils.<T>defaultStringConverter();
151        return forTreeTableColumn(getSelectedProperty, converter);
152    }
153    
154    /**
155     * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 
156     * This method requires that the TreeTableColumn be of type
157     * {@code ObservableValue<Boolean>}.
158     * 
159     * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 
160     * CheckBox centered in the column.
161     * 
162     * @param <T> The type of the elements contained within the {@link TreeTableColumn} 
163     *      instance.
164     * @param getSelectedProperty A Callback that, given an object of type 
165     *      {@code TreeTableColumn<S,T>}, will return an 
166     *      {@code ObservableValue<Boolean>} that represents whether the given 
167     *      item is selected or not. This {@code ObservableValue<Boolean>} will 
168     *      be bound bidirectionally (meaning that the CheckBox in the cell will 
169     *      set/unset this property based on user interactions, and the CheckBox 
170     *      will reflect the state of the {@code ObservableValue<Boolean>}, if 
171     *      it changes externally).
172     * @param converter A StringConverter that, give an object of type T, will return a 
173     *      String that can be used to represent the object visually. The default 
174     *      implementation in {@link #forTreeTableColumn(Callback, boolean)} (when 
175     *      showLabel is true) is to simply call .toString() on all non-null 
176     *      items (and to just return an empty string in cases where the given 
177     *      item is null).
178     * @return A {@link Callback} that will return a {@link TreeTableCell} that is 
179     *      able to work on the type of element contained within the TreeTableColumn.
180     */
181    public static <S,T> Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>> forTreeTableColumn(
182            final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 
183            final StringConverter<T> converter) {
184        return new Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>>() {
185            @Override public TreeTableCell<S,T> call(TreeTableColumn<S,T> list) {
186                return new CheckBoxTreeTableCell<S,T>(getSelectedProperty, converter);
187            }
188        };
189    }
190    
191    
192
193    /***************************************************************************
194     *                                                                         *
195     * Fields                                                                  *
196     *                                                                         *
197     **************************************************************************/
198    private final CheckBox checkBox;
199    
200    private boolean showLabel;
201    
202    private ObservableValue<Boolean> booleanProperty;
203    
204    
205    
206    /***************************************************************************
207     *                                                                         *
208     * Constructors                                                            *
209     *                                                                         *
210     **************************************************************************/
211    
212    /** 
213     * Creates a default CheckBoxTreeTableCell.
214     */
215    public CheckBoxTreeTableCell() {
216        this(null, null);
217    }
218    
219    /**
220     * Creates a default CheckBoxTreeTableCell with a custom {@link Callback} to 
221     * retrieve an ObservableValue for a given cell index.
222     * 
223     * @param getSelectedProperty A {@link Callback} that will return an {@link 
224     *      ObservableValue} given an index from the TreeTableColumn.
225     */
226    public CheckBoxTreeTableCell(
227            final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) {
228        this(getSelectedProperty, null);
229    }
230
231    /**
232     * Creates a CheckBoxTreeTableCell with a custom string converter.
233     * 
234     * @param getSelectedProperty A {@link Callback} that will return a {@link 
235     *      ObservableValue} given an index from the TreeTableColumn.
236     * @param converter A StringConverter that, given an object of type T, will return a 
237     *      String that can be used to represent the object visually.
238     */
239    public CheckBoxTreeTableCell(
240            final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 
241            final StringConverter<T> converter) {
242        // we let getSelectedProperty be null here, as we can always defer to the
243        // TreeTableColumn
244        this.getStyleClass().add("check-box-tree-table-cell");
245        
246        this.checkBox = new CheckBox();
247        setGraphic(checkBox);
248        
249        setSelectedStateCallback(getSelectedProperty);
250        setConverter(converter);
251        
252//        // alignment is styleable through css. Calling setAlignment
253//        // makes it look to css like the user set the value and css will not 
254//        // override. Initializing alignment by calling set on the 
255//        // CssMetaData ensures that css will be able to override the value.
256//        final CssMetaData prop = CssMetaData.getCssMetaData(alignmentProperty());
257//        prop.set(this, Pos.CENTER);
258    }
259    
260    
261    /***************************************************************************
262     *                                                                         *
263     * Properties                                                              *
264     *                                                                         *
265     **************************************************************************/
266    
267    // --- converter
268    private ObjectProperty<StringConverter<T>> converter = 
269            new SimpleObjectProperty<StringConverter<T>>(this, "converter") {
270        protected void invalidated() {
271            updateShowLabel();
272        }
273    };
274
275    /**
276     * The {@link StringConverter} property.
277     */
278    public final ObjectProperty<StringConverter<T>> converterProperty() { 
279        return converter; 
280    }
281    
282    /** 
283     * Sets the {@link StringConverter} to be used in this cell.
284     */
285    public final void setConverter(StringConverter<T> value) { 
286        converterProperty().set(value); 
287    }
288    
289    /**
290     * Returns the {@link StringConverter} used in this cell.
291     */
292    public final StringConverter<T> getConverter() { 
293        return converterProperty().get(); 
294    }
295    
296    
297    
298    // --- selected state callback property
299    private ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> 
300            selectedStateCallback = 
301            new SimpleObjectProperty<Callback<Integer, ObservableValue<Boolean>>>(
302            this, "selectedStateCallback");
303
304    /**
305     * Property representing the {@link Callback} that is bound to by the 
306     * CheckBox shown on screen.
307     */
308    public final ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> selectedStateCallbackProperty() { 
309        return selectedStateCallback; 
310    }
311    
312    /** 
313     * Sets the {@link Callback} that is bound to by the CheckBox shown on screen.
314     */
315    public final void setSelectedStateCallback(Callback<Integer, ObservableValue<Boolean>> value) { 
316        selectedStateCallbackProperty().set(value); 
317    }
318    
319    /**
320     * Returns the {@link Callback} that is bound to by the CheckBox shown on screen.
321     */
322    public final Callback<Integer, ObservableValue<Boolean>> getSelectedStateCallback() { 
323        return selectedStateCallbackProperty().get(); 
324    }
325    
326    
327    
328    /***************************************************************************
329     *                                                                         *
330     * Public API                                                              *
331     *                                                                         *
332     **************************************************************************/
333    
334    /** {@inheritDoc} */
335    @Override public void updateItem(T item, boolean empty) {
336        super.updateItem(item, empty);
337        
338        if (empty) {
339            setText(null);
340            setGraphic(null);
341        } else {
342            StringConverter c = getConverter();
343            
344            if (showLabel) {
345                setText(c.toString(item));
346            }
347            setGraphic(checkBox);
348            
349            if (booleanProperty instanceof BooleanProperty) {
350                checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
351            }
352            ObservableValue obsValue = getSelectedProperty();
353            if (obsValue instanceof BooleanProperty) {
354                booleanProperty = obsValue;
355                checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
356            }
357            
358            checkBox.disableProperty().bind(Bindings.not(
359                    getTreeTableView().editableProperty().and(
360                    getTableColumn().editableProperty()).and(
361                    editableProperty())
362                ));
363        }
364    }
365    
366    
367    
368    /***************************************************************************
369     *                                                                         *
370     * Private implementation                                                  *
371     *                                                                         *
372     **************************************************************************/
373    
374    private void updateShowLabel() {
375        this.showLabel = converter != null;
376        this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER);
377    }
378    
379    private ObservableValue getSelectedProperty() {
380        return getSelectedStateCallback() != null ?
381                getSelectedStateCallback().call(getIndex()) :
382                getTableColumn().getCellObservableValue(getIndex());
383    }
384}