Spec-Zone .ru
спецификации, руководства, описания, API
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;
027
028import javafx.css.PseudoClass;
029import com.sun.javafx.scene.control.skin.TreeTableRowSkin;
030import java.lang.ref.WeakReference;
031import javafx.beans.InvalidationListener;
032import javafx.beans.Observable;
033import javafx.beans.WeakInvalidationListener;
034import javafx.beans.property.BooleanProperty;
035import javafx.beans.property.ObjectProperty;
036import javafx.beans.property.ReadOnlyObjectProperty;
037import javafx.beans.property.ReadOnlyObjectWrapper;
038import javafx.beans.property.SimpleObjectProperty;
039import javafx.collections.ListChangeListener;
040import javafx.collections.WeakListChangeListener;
041import javafx.scene.Node;
042import javafx.scene.control.TreeTableView.TreeTableViewFocusModel;
043import javafx.scene.control.TreeTableView.TreeTableViewSelectionModel;
044
045/**
046 * <p>TreeTableRow is an {@link javafx.scene.control.IndexedCell IndexedCell}, but
047 * rarely needs to be used by developers creating TreeTableView instances. The only
048 * time TreeTableRow is likely to be encountered at all by a developer is if they
049 * wish to create a custom {@link TreeTableView#rowFactoryProperty() rowFactory} 
050 * that replaces an entire row of a TreeTableView.</p>
051 *
052 * <p>More often than not, it is actually easier for a developer to customize
053 * individual cells in a row, rather than the whole row itself. To do this,
054 * you can specify a custom {@link TreeTableColumn#cellFactoryProperty() cellFactory} 
055 * on each TreeTableColumn instance.</p>
056 *
057 * @see TreeTableView
058 * @see TreeTableColumn
059 * @see TreeTableCell
060 * @see IndexedCell
061 * @see Cell
062 * @param <T> The type of the item contained within the Cell.
063 */
064public class TreeTableRow<T> extends IndexedCell<T> {
065    
066    
067    /***************************************************************************
068     *                                                                         *
069     * Constructors                                                            *
070     *                                                                         *
071     **************************************************************************/
072
073    /**
074     * Creates a default TreeTableRow instance.
075     */
076    public TreeTableRow() {
077        getStyleClass().addAll(DEFAULT_STYLE_CLASS);
078    }
079
080
081
082    /***************************************************************************
083     *                                                                         *
084     * Callbacks and events                                                    *
085     *                                                                         *
086     **************************************************************************/
087    
088    private final ListChangeListener<Integer> selectedListener = new ListChangeListener<Integer>() {
089        @Override public void onChanged(ListChangeListener.Change<? extends Integer> c) {
090            updateSelection();
091        }
092    };
093
094    private final InvalidationListener focusedListener = new InvalidationListener() {
095        @Override public void invalidated(Observable valueModel) {
096            updateFocus();
097        }
098    };
099
100    private final InvalidationListener editingListener = new InvalidationListener() {
101        @Override public void invalidated(Observable valueModel) {
102            updateEditing();
103        }
104    };
105    
106    private final InvalidationListener leafListener = new InvalidationListener() {
107        @Override public void invalidated(Observable valueModel) {
108            // necessary to update the disclosure node in the skin when the
109            // leaf property changes
110            TreeItem<T> treeItem = getTreeItem();
111            if (treeItem != null) {
112                requestLayout();
113            }
114        }
115    };
116    
117    private final InvalidationListener treeItemExpandedInvalidationListener = new InvalidationListener() {
118        @Override public void invalidated(Observable o) {
119            final boolean expanded = ((BooleanProperty)o).get();
120            pseudoClassStateChanged(EXPANDED_PSEUDOCLASS_STATE,   expanded);
121            pseudoClassStateChanged(COLLAPSED_PSEUDOCLASS_STATE, !expanded);
122        }
123    };
124    
125    private final WeakListChangeListener<Integer> weakSelectedListener = 
126            new WeakListChangeListener<Integer>(selectedListener);
127    private final WeakInvalidationListener weakFocusedListener = 
128            new WeakInvalidationListener(focusedListener);
129    private final WeakInvalidationListener weakEditingListener = 
130            new WeakInvalidationListener(editingListener);
131    private final WeakInvalidationListener weakLeafListener = 
132            new WeakInvalidationListener(leafListener);
133    private final WeakInvalidationListener weakTreeItemExpandedInvalidationListener = 
134            new WeakInvalidationListener(treeItemExpandedInvalidationListener);
135    
136    
137    
138    /***************************************************************************
139     *                                                                         *
140     * Properties                                                              *
141     *                                                                         *
142     **************************************************************************/
143    
144    // --- TreeItem
145    private ReadOnlyObjectWrapper<TreeItem<T>> treeItem = 
146        new ReadOnlyObjectWrapper<TreeItem<T>>(this, "treeItem") {
147            
148            TreeItem<T> oldValue = null;
149            
150            @Override protected void invalidated() {
151                if (oldValue != null) {
152                    oldValue.expandedProperty().removeListener(weakTreeItemExpandedInvalidationListener);
153                }
154                
155                oldValue = get(); 
156                
157                if (oldValue != null) {
158                    oldValue.expandedProperty().addListener(weakTreeItemExpandedInvalidationListener);
159                    // fake an invalidation to ensure updated pseudo-class state
160                    weakTreeItemExpandedInvalidationListener.invalidated(oldValue.expandedProperty());            
161                }
162            }
163    };
164    private void setTreeItem(TreeItem<T> value) {
165        treeItem.set(value); 
166    }
167    
168    /**
169     * Returns the TreeItem currently set in this TreeCell.
170     */
171    public final TreeItem<T> getTreeItem() { return treeItem.get(); }
172    
173    /**
174     * Each TreeTableCell represents at most a single {@link TreeItem}, which is
175     * represented by this property.
176     */
177    public final ReadOnlyObjectProperty<TreeItem<T>> treeItemProperty() { return treeItem.getReadOnlyProperty(); }
178
179    
180    
181    // --- Disclosure Node
182    private ObjectProperty<Node> disclosureNode = new SimpleObjectProperty<Node>(this, "disclosureNode");
183
184    /**
185     * The node to use as the "disclosure" triangle, or toggle, used for
186     * expanding and collapsing items. This is only used in the case of
187     * an item in the tree which contains child items. If not specified, the
188     * TreeTableCell's Skin implementation is responsible for providing a default
189     * disclosure node.
190     */
191    public final void setDisclosureNode(Node value) { disclosureNodeProperty().set(value); }
192    
193    /**
194     * Returns the current disclosure node set in this TreeTableCell.
195     */
196    public final Node getDisclosureNode() { return disclosureNode.get(); }
197    
198    /**
199     * The disclosure node is commonly seen represented as a triangle that rotates
200     * on screen to indicate whether or not the TreeItem that it is placed
201     * beside is expanded or collapsed.
202     */
203    public final ObjectProperty<Node> disclosureNodeProperty() { return disclosureNode; }
204    
205    
206    // --- TreeView
207    private ReadOnlyObjectWrapper<TreeTableView<T>> treeTableView = new ReadOnlyObjectWrapper<TreeTableView<T>>(this, "treeTableView") {
208        private WeakReference<TreeTableView<T>> weakTreeTableViewRef;
209        @Override protected void invalidated() {
210            TreeTableViewSelectionModel<T> sm;
211            TreeTableViewFocusModel<T> fm;
212            
213            if (weakTreeTableViewRef != null) {
214                TreeTableView<T> oldTreeTableView = weakTreeTableViewRef.get();
215                if (oldTreeTableView != null) {
216                    // remove old listeners
217                    sm = oldTreeTableView.getSelectionModel();
218                    if (sm != null) {
219                        sm.getSelectedIndices().removeListener(weakSelectedListener);
220                    }
221
222                    fm = oldTreeTableView.getFocusModel();
223                    if (fm != null) {
224                        fm.focusedIndexProperty().removeListener(weakFocusedListener);
225                    }
226
227                    oldTreeTableView.editingItemProperty().removeListener(weakEditingListener);
228                }
229                
230                weakTreeTableViewRef = null;
231            }
232
233            if (get() != null) {
234                sm = get().getSelectionModel();
235                if (sm != null) {
236                    // listening for changes to treeView.selectedIndex and IndexedCell.index,
237                    // to determine if this cell is selected
238                    sm.getSelectedIndices().addListener(weakSelectedListener);
239                }
240
241                fm = get().getFocusModel();
242                if (fm != null) {
243                    // similar to above, but this time for focus
244                    fm.focusedIndexProperty().addListener(weakFocusedListener);
245                }
246
247                get().editingItemProperty().addListener(weakEditingListener);
248                
249                weakTreeTableViewRef = new WeakReference<TreeTableView<T>>(get());
250            }
251
252            updateItem();
253            requestLayout();
254        }
255    };
256    
257    private void setTreeTableView(TreeTableView<T> value) { treeTableView.set(value); }
258
259    /**
260     * Returns the TreeTableView associated with this TreeTableCell.
261     */
262    public final TreeTableView<T> getTreeTableView() { return treeTableView.get(); }
263    
264    /**
265     * A TreeTableCell is explicitly linked to a single {@link TreeTableView} instance,
266     * which is represented by this property.
267     */
268    public final ReadOnlyObjectProperty<TreeTableView<T>> treeTableViewProperty() { return treeTableView.getReadOnlyProperty(); }
269
270
271
272
273    /***************************************************************************
274     *                                                                         *
275     * Public API                                                              *
276     *                                                                         *
277     **************************************************************************/
278
279    
280    @Override void indexChanged() {
281        index = getIndex();
282
283        // when the cell index changes, this may result in the cell
284        // changing state to be selected and/or focused.
285        updateItem();
286        updateSelection();
287        updateFocus();
288//        oldIndex = index;
289    }
290    
291
292    /** {@inheritDoc} */
293    @Override public void startEdit() {
294        final TreeTableView<T> treeTable = getTreeTableView();
295        if (! isEditable() || (treeTable != null && ! treeTable.isEditable())) {
296            return;
297        }
298        
299        // it makes sense to get the cell into its editing state before firing
300        // the event to the TreeView below, so that's what we're doing here
301        // by calling super.startEdit().
302        super.startEdit();
303        
304         // Inform the TreeView of the edit starting.
305        if (treeTable != null) {
306            treeTable.fireEvent(new TreeTableView.EditEvent<T>(treeTable,
307                    TreeTableView.<T>editStartEvent(),
308                    getTreeItem(),
309                    getItem(),
310                    null));
311            
312            treeTable.requestFocus();
313        }
314    }
315
316     /** {@inheritDoc} */
317    @Override public void commitEdit(T newValue) {
318        if (! isEditing()) return;
319        final TreeItem<T> treeItem = getTreeItem();
320        final TreeTableView<T> treeTable = getTreeTableView();
321        if (treeTable != null) {
322            // Inform the TreeView of the edit being ready to be committed.
323            treeTable.fireEvent(new TreeTableView.EditEvent<T>(treeTable,
324                    TreeTableView.<T>editCommitEvent(),
325                    treeItem,
326                    getItem(),
327                    newValue));
328        }
329        
330        // update the item within this cell, so that it represents the new value
331        if (treeItem != null) {
332            treeItem.setValue(newValue);
333            updateTreeItem(treeItem);
334            updateItem(newValue, false);
335        }
336        
337        // inform parent classes of the commit, so that they can switch us
338        // out of the editing state
339        super.commitEdit(newValue);
340
341        if (treeTable != null) {
342            // reset the editing item in the TreetView
343            treeTable.edit(null);
344            treeTable.requestFocus();
345        }
346    }
347
348    /** {@inheritDoc} */
349    @Override public void cancelEdit() {
350        if (! isEditing()) return;
351        
352        TreeTableView<T> treeTable = getTreeTableView();
353        if (treeTable != null) {
354            treeTable.fireEvent(new TreeTableView.EditEvent<T>(treeTable,
355                    TreeTableView.<T>editCancelEvent(),
356                    getTreeItem(),
357                    getItem(),
358                    null));
359        }
360
361        super.cancelEdit();
362
363        if (treeTable != null) {
364            // reset the editing index on the TreeView
365            treeTable.edit(null);
366            treeTable.requestFocus();
367        }
368    }
369
370
371
372    /***************************************************************************
373     *                                                                         *
374     * Private Implementation                                                  *
375     *                                                                         *
376     **************************************************************************/
377    
378    private int index = -1;
379    
380    private void updateItem() {
381        TreeTableView<T> tv = getTreeTableView();
382        if (tv == null) return;
383        
384        // Compute whether the index for this cell is for a real item
385        boolean valid = index >=0 && index < tv.getExpandedItemCount();
386
387        // Cause the cell to update itself
388        if (valid) {
389            // update the TreeCell state.
390            // get the new treeItem that is about to go in to the TreeCell
391            TreeItem<T> treeItem = tv.getTreeItem(index);
392            
393            // For the sake of RT-14279, it is important that the order of these
394            // method calls is as shown below. If the order is switched, it is
395            // likely that events will be fired where the item is null, even
396            // though calling cell.getTreeItem().getValue() returns the value
397            // as expected
398            updateTreeItem(treeItem);
399            updateItem(treeItem == null ? null : treeItem.getValue(), false);
400        } else {
401            updateTreeItem(null);
402            updateItem(null, true);
403        }
404    }
405
406    private void updateSelection() {
407        if (isEmpty()) return;
408        if (index == -1 || getTreeTableView() == null) return;
409        if (getTreeTableView().getSelectionModel() == null) return;
410        
411        boolean isSelected = getTreeTableView().getSelectionModel().isSelected(index);
412        if (isSelected() == isSelected) return;
413        
414        updateSelected(isSelected);
415    }
416
417    private void updateFocus() {
418        if (getIndex() == -1 || getTreeTableView() == null) return;
419        if (getTreeTableView().getFocusModel() == null) return;
420        
421        setFocused(getTreeTableView().getFocusModel().isFocused(getIndex()));
422    }
423
424    private void updateEditing() {
425        if (getIndex() == -1 || getTreeTableView() == null || getTreeItem() == null) return;
426        
427        TreeItem<T> editItem = getTreeTableView().getEditingItem();
428        if (! isEditing() && getTreeItem().equals(editItem)) {
429            startEdit();
430        } else if (isEditing() && ! getTreeItem().equals(editItem)) {
431            cancelEdit();
432        }
433    }
434
435
436
437    /***************************************************************************
438     *                                                                         *
439     * Expert API                                                              *
440     *                                                                         *
441     **************************************************************************/
442
443    /**
444     * Updates the TreeTableView associated with this TreeTableCell.
445     * 
446     * @param tree The new TreeTableView that should be associated with this 
447     *         TreeTableCell.
448     * @expert This function is intended to be used by experts, primarily
449     *         by those implementing new Skins. It is not common
450     *         for developers or designers to access this function directly.
451     */
452    public final void updateTreeTableView(TreeTableView<T> treeTable) {
453        setTreeTableView(treeTable); 
454    }
455
456    /**
457     * Updates the TreeItem associated with this TreeTableCell.
458     *
459     * @param treeItem The new TreeItem that should be associated with this 
460     *      TreeTableCell.
461     * @expert This function is intended to be used by experts, primarily
462     *      by those implementing new Skins. It is not common
463     *      for developers or designers to access this function directly.
464     */
465    public final void updateTreeItem(TreeItem<T> treeItem) {
466        TreeItem<T> _treeItem = getTreeItem();
467        if (_treeItem != null) {
468            _treeItem.leafProperty().removeListener(weakLeafListener);
469        }
470        setTreeItem(treeItem);
471        if (treeItem != null) {
472            treeItem.leafProperty().addListener(weakLeafListener);
473        }
474    }
475
476
477    
478    /***************************************************************************
479     *                                                                         *
480     * Stylesheet Handling                                                     *
481     *                                                                         *
482     **************************************************************************/
483
484    private static final String DEFAULT_STYLE_CLASS = "tree-table-row-cell";
485
486    private static final PseudoClass EXPANDED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("expanded");
487    private static final PseudoClass COLLAPSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("collapsed");
488    
489    /** {@inheritDoc} */
490    @Override protected Skin<?> createDefaultSkin() {
491        return new TreeTableRowSkin<T>(this);
492    }
493}