Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 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 com.sun.javafx.scene.control.skin.ListCellSkin;
029import javafx.beans.InvalidationListener;
030import javafx.beans.Observable;
031import javafx.beans.WeakInvalidationListener;
032import javafx.beans.property.ReadOnlyObjectProperty;
033import javafx.beans.property.ReadOnlyObjectWrapper;
034import javafx.beans.value.ChangeListener;
035import javafx.beans.value.ObservableValue;
036import javafx.beans.value.WeakChangeListener;
037import javafx.collections.ListChangeListener;
038import javafx.collections.ObservableList;
039
040import javafx.collections.WeakListChangeListener;
041import java.lang.ref.WeakReference;
042import java.util.List;
043
044
045/**
046 * <p>The {@link Cell} type used within {@link ListView} instances. In addition 
047 * to the API defined on Cell and {@link IndexedCell}, the ListCell is more 
048 * tightly bound to a ListView, allowing for better support of editing events, 
049 * etc.
050 *
051 * <p>A ListView maintains selection, indicating which cell(s) have been selected,
052 * and focus, indicating the current focus owner for any given ListView. For each
053 * property, each ListCell has a boolean reflecting whether this specific cell is
054 * selected or focused. To achieve this, each ListCell has a reference back to
055 * the ListView that it is being used within. Each ListCell belongs to one and 
056 * only one ListView.
057 * 
058 * <p>Note that in the case of virtualized controls like ListView, when a cell
059 * has focus this is not in the same sense as application focus. When a ListCell 
060 * has focus it simply represents the fact that the cell will  receive keyboard
061 * events in the situation that the owning ListView actually contains focus. Of
062 * course, in the case where a cell has a Node set in the 
063 * {@link #graphicProperty() graphic} property, it is completely legal for this
064 * Node to request, and acquire focus as would normally be expected.
065 * 
066 * @param <T> The type of the item contained within the ListCell.
067 */
068// TODO add code examples
069public class ListCell<T> extends IndexedCell<T> {
070
071    /***************************************************************************
072     *                                                                         *
073     * Constructors                                                            *
074     *                                                                         *
075     **************************************************************************/
076
077    /**
078     * Creates a default ListCell with the default style class of 'list-cell'.
079     */
080    public ListCell() {
081        getStyleClass().addAll(DEFAULT_STYLE_CLASS);
082    }
083
084
085    /***************************************************************************
086     *                                                                         *
087     * Listeners                                                               *
088     *     We have to listen to a number of properties on the ListView itself  *
089     *     as well as attach listeners to a couple different ObservableLists.  *
090     *     We have to be sure to unhook these listeners whenever the reference *
091     *     to the ListView changes, or whenever one of the ObservableList      *
092     *     references changes (such as setting the selectionModel, focusModel, *
093     *     or items).                                                          *
094     *                                                                         *
095     **************************************************************************/
096
097    /**
098     * Listens to the editing index on the ListView. It is possible for the developer
099     * to call the ListView#edit(int) method and cause a specific cell to start
100     * editing. In such a case, we need to be notified so we can call startEdit
101     * on our side.
102     */
103    private final InvalidationListener editingListener = new InvalidationListener() {
104        @Override public void invalidated(Observable value) {
105            updateEditing();
106        }
107    };
108    private boolean updateEditingIndex = true;
109
110    /**
111     * Listens to the selection model on the ListView. Whenever the selection model
112     * is changed (updated), the selected property on the ListCell is updated accordingly.
113     */
114    private final ListChangeListener<Integer> selectedListener = new ListChangeListener<Integer>() {
115        @Override public void onChanged(ListChangeListener.Change<? extends Integer> c) {
116            updateSelection();
117        }
118    };
119
120    /**
121     * Listens to the selectionModel property on the ListView. Whenever the entire model is changed,
122     * we have to unhook the weakSelectedListener and update the selection.
123     */
124    private final ChangeListener<MultipleSelectionModel<T>> selectionModelPropertyListener = new ChangeListener<MultipleSelectionModel<T>>() {
125        @Override
126        public void changed(
127                ObservableValue<? extends MultipleSelectionModel<T>> observable,
128                MultipleSelectionModel<T> oldValue,
129                MultipleSelectionModel<T> newValue) {
130            
131            if (oldValue != null) {
132                oldValue.getSelectedIndices().removeListener(weakSelectedListener);
133            }
134            
135            if (newValue != null) {
136                newValue.getSelectedIndices().addListener(weakSelectedListener);
137            }
138            
139            updateSelection();
140        }
141        
142    };
143
144    /**
145     * Listens to the items on the ListView. Whenever the items are changed in such a way that
146     * it impacts the index of this ListCell, then we must update the item.
147     */
148    private final ListChangeListener<T> itemsListener = new ListChangeListener<T>() {
149        @Override public void onChanged(ListChangeListener.Change<? extends T> c) {
150            updateItem();
151        }
152    };
153
154    /**
155     * Listens to the items property on the ListView. Whenever the entire list is changed,
156     * we have to unhook the weakItemsListener and update the item.
157     */
158    private final ChangeListener<ObservableList<T>> itemsPropertyListener = new ChangeListener<ObservableList<T>>() {
159        @Override public void changed(ObservableValue<? extends ObservableList<T>> observable,
160                                      ObservableList<T> oldValue,
161                                      ObservableList<T> newValue) {
162            if (oldValue != null) {
163                oldValue.removeListener(weakItemsListener);
164            }
165            if (newValue != null) {
166                newValue.addListener(weakItemsListener);
167            }
168            updateItem();
169        }
170    };
171
172    /**
173     * Listens to the focus model on the ListView. Whenever the focus model changes,
174     * the focused property on the ListCell is updated
175     */
176    private final InvalidationListener focusedListener = new InvalidationListener() {
177        @Override public void invalidated(Observable value) {
178            updateFocus();
179        }
180    };
181
182    /**
183     * Listens to the focusModel property on the ListView. Whenever the entire model is changed,
184     * we have to unhook the weakFocusedListener and update the focus.
185     */
186    private final ChangeListener<FocusModel<T>> focusModelPropertyListener = new ChangeListener<FocusModel<T>>() {
187        @Override public void changed(ObservableValue<? extends FocusModel<T>> observable,
188                                      FocusModel<T> oldValue,
189                                      FocusModel<T> newValue) {
190            if (oldValue != null) {
191                oldValue.focusedIndexProperty().removeListener(weakFocusedListener);
192            }
193            if (newValue != null) {
194                newValue.focusedIndexProperty().addListener(weakFocusedListener);
195            }
196            updateFocus();
197        }
198    };
199
200
201    private final WeakInvalidationListener weakEditingListener = new WeakInvalidationListener(editingListener);
202    private final WeakListChangeListener<Integer> weakSelectedListener = new WeakListChangeListener<Integer>(selectedListener);
203    private final WeakChangeListener<MultipleSelectionModel<T>> weakSelectionModelPropertyListener = new WeakChangeListener<MultipleSelectionModel<T>>(selectionModelPropertyListener);
204    private final WeakListChangeListener<T> weakItemsListener = new WeakListChangeListener<T>(itemsListener);
205    private final WeakChangeListener<ObservableList<T>> weakItemsPropertyListener = new WeakChangeListener<ObservableList<T>>(itemsPropertyListener);
206    private final WeakInvalidationListener weakFocusedListener = new WeakInvalidationListener(focusedListener);
207    private final WeakChangeListener<FocusModel<T>> weakFocusModelPropertyListener = new WeakChangeListener<FocusModel<T>>(focusModelPropertyListener);
208
209    /***************************************************************************
210     *                                                                         *
211     * Properties                                                              *
212     *                                                                         *
213     **************************************************************************/
214    
215    /**
216     * The ListView associated with this Cell.
217     */
218    private ReadOnlyObjectWrapper<ListView<T>> listView = new ReadOnlyObjectWrapper<ListView<T>>(this, "listView") {
219        /**
220         * A weak reference to the ListView itself, such that whenever the ...
221         */
222        private WeakReference<ListView<T>> weakListViewRef = new WeakReference<ListView<T>>(null);
223
224        @Override protected void invalidated() {
225            // Get the current and old list view references
226            final ListView<T> currentListView = get();
227            final ListView<T> oldListView = weakListViewRef.get();
228
229            // If the currentListView is the same as the oldListView, then
230            // there is nothing to be done.
231            if (currentListView == oldListView) return;
232
233            // If the old list view is not null, then we must unhook all its listeners
234            if (oldListView != null) {
235                // If the old selection model isn't null, unhook it
236                final MultipleSelectionModel<T> sm = oldListView.getSelectionModel();
237                if (sm != null) {
238                    sm.getSelectedIndices().removeListener(weakSelectedListener);
239                }
240
241                // If the old focus model isn't null, unhook it
242                final FocusModel<T> fm = oldListView.getFocusModel();
243                if (fm != null) {
244                    fm.focusedIndexProperty().removeListener(weakFocusedListener);
245                }
246
247                // If the old items isn't null, unhook the listener
248                final ObservableList<T> items = oldListView.getItems();
249                if (items != null) {
250                    items.removeListener(weakItemsListener);
251                }
252
253                // Remove the listeners of the properties on ListView
254                oldListView.editingIndexProperty().removeListener(weakEditingListener);
255                oldListView.itemsProperty().removeListener(weakItemsPropertyListener);
256                oldListView.focusModelProperty().removeListener(weakFocusModelPropertyListener);
257                oldListView.selectionModelProperty().removeListener(weakSelectionModelPropertyListener);
258            }
259
260            if (currentListView != null) {
261                final MultipleSelectionModel<T> sm = currentListView.getSelectionModel();
262                if (sm != null) {
263                    sm.getSelectedIndices().addListener(weakSelectedListener);
264                }
265
266                final FocusModel<T> fm = currentListView.getFocusModel();
267                if (fm != null) {
268                    fm.focusedIndexProperty().addListener(weakFocusedListener);
269                }
270
271                final ObservableList<T> items = currentListView.getItems();
272                if (items != null) {
273                    items.addListener(weakItemsListener);
274                }
275
276                currentListView.editingIndexProperty().addListener(weakEditingListener);
277                currentListView.itemsProperty().addListener(weakItemsPropertyListener);
278                currentListView.focusModelProperty().addListener(weakFocusModelPropertyListener);
279                currentListView.selectionModelProperty().addListener(weakSelectionModelPropertyListener);
280
281                weakListViewRef = new WeakReference<ListView<T>>(currentListView);
282            }
283
284            updateItem();
285            updateSelection();
286            updateFocus();
287            requestLayout();
288        }
289    };
290    private void setListView(ListView<T> value) { listView.set(value); }
291    public final ListView<T> getListView() { return listView.get(); }
292    public final ReadOnlyObjectProperty<ListView<T>> listViewProperty() { return listView.getReadOnlyProperty(); }
293
294    
295    
296    /***************************************************************************
297     *                                                                         *
298     * Public API                                                              *
299     *                                                                         *
300     **************************************************************************/
301    
302    /** {@inheritDoc} */
303    @Override void indexChanged() {
304        super.indexChanged();
305        updateItem();
306        updateSelection();
307        updateFocus();
308    }
309
310    /** {@inheritDoc} */
311    @Override protected Skin<?> createDefaultSkin() {
312        return new ListCellSkin(this);
313    }
314
315
316    /***************************************************************************
317     *                                                                         *
318     * Editing API                                                             *
319     *                                                                         *
320     **************************************************************************/
321
322    /** {@inheritDoc} */
323    @Override public void startEdit() {
324        final ListView<T> list = getListView();
325        if (!isEditable() || (list != null && ! list.isEditable())) {
326            return;
327        }
328        
329        // it makes sense to get the cell into its editing state before firing
330        // the event to the ListView below, so that's what we're doing here
331        // by calling super.startEdit().
332        super.startEdit();
333        
334         // Inform the ListView of the edit starting.
335        if (list != null) {
336            list.fireEvent(new ListView.EditEvent<T>(list,
337                    ListView.<T>editStartEvent(),
338                    null,
339                    list.getEditingIndex()));
340            list.edit(getIndex());
341            list.requestFocus();
342        }
343    }
344
345    /** {@inheritDoc} */
346    @Override public void commitEdit(T newValue) {
347        if (! isEditing()) return;
348        ListView<T> list = getListView();
349        
350        if (list != null) {
351            // Inform the ListView of the edit being ready to be committed.
352            list.fireEvent(new ListView.EditEvent<T>(list,
353                    ListView.<T>editCommitEvent(),
354                    newValue,
355                    list.getEditingIndex()));
356        }
357        
358        // update the item within this cell, so that it represents the new value
359        updateItem(newValue, false);
360
361        // inform parent classes of the commit, so that they can switch us
362        // out of the editing state
363        super.commitEdit(newValue);
364        
365        if (list != null) {
366            // reset the editing index on the ListView. This must come after the
367            // event is fired so that the developer on the other side can consult
368            // the ListView editingIndex property (if they choose to do that
369            // rather than just grab the int from the event).
370            list.edit(-1);
371            list.requestFocus();
372        }
373    }
374    
375    /** {@inheritDoc} */
376    @Override public void cancelEdit() {
377        if (! isEditing()) return;
378        
379         // Inform the ListView of the edit being cancelled.
380        ListView<T> list = getListView();
381        
382        super.cancelEdit();
383
384        if (list != null) {
385            int editingIndex = list.getEditingIndex();
386            
387            // reset the editing index on the ListView
388            if (updateEditingIndex) list.edit(-1);
389            list.requestFocus();
390        
391            list.fireEvent(new ListView.EditEvent<T>(list,
392                    ListView.<T>editCancelEvent(),
393                    null,
394                    editingIndex));
395        }
396    }
397
398
399    /* *************************************************************************
400     *                                                                         *
401     * Private implementation                                                  *
402     *                                                                         *
403     **************************************************************************/
404   
405    private void updateItem() {
406        ListView<T> lv = getListView();
407        List<T> items = lv == null ? null : lv.getItems();
408        int index = getIndex();
409        
410        // Compute whether the index for this cell is for a real item
411        boolean valid = items != null && index >=0 && index < items.size();
412
413        // Cause the cell to update itself
414        if (valid) {
415            T oldValue = getItem();
416            T newValue = items.get(index);
417            
418            if ((newValue != null && ! newValue.equals(oldValue)) || 
419                    oldValue != null && ! oldValue.equals(newValue)) {
420                updateItem(newValue, false);
421            }
422        } else {
423            if (! isEmpty()) {
424                updateItem(null, true);
425            }
426        }
427    }
428    
429    /**
430     * Updates the ListView associated with this Cell.
431     *
432     * @expert This function is intended to be used by experts, primarily
433     *         by those implementing new Skins. It is not common
434     *         for developers or designers to access this function directly.
435     */
436    public final void updateListView(ListView<T> listView) {
437        setListView(listView);
438    }
439
440    private void updateSelection() {
441        if (isEmpty()) return;
442        int index = getIndex();
443        ListView<T> listView = getListView();
444        if (index == -1 || listView == null) return;
445        
446        SelectionModel<T> sm = listView.getSelectionModel();
447        if (sm == null) return;
448        
449        boolean isSelected = sm.isSelected(index);
450        if (isSelected() == isSelected) return;
451        
452        updateSelected(isSelected);
453    }
454
455    private void updateFocus() {
456        int index = getIndex();
457        ListView<T> listView = getListView();
458        if (index == -1 || listView == null) return;
459        
460        FocusModel<T> fm = listView.getFocusModel();
461        if (fm == null) return;
462        
463        setFocused(fm.isFocused(index));
464    }
465    
466    private void updateEditing() {
467        final int index = getIndex();
468        final ListView<T> list = getListView();
469        final int editIndex = list == null ? -1 : list.getEditingIndex();
470        final boolean editing = isEditing();
471
472        // Check that the list is specified, and my index is not -1
473        if (index != -1 && list != null) {
474            // If my index is the index being edited and I'm not currently in
475            // the edit mode, then I need to enter the edit mode
476            if (index == editIndex && !editing) {
477                startEdit();
478            } else if (index != editIndex && editing) {
479                // If my index is not the one being edited then I need to cancel
480                // the edit. The tricky thing here is that as part of this call
481                // I cannot end up calling list.edit(-1) the way that the standard
482                // cancelEdit method would do. Yet, I need to call cancelEdit
483                // so that subclasses which override cancelEdit can execute. So,
484                // I have to use a kind of hacky flag workaround.
485                updateEditingIndex = false;
486                cancelEdit();
487                updateEditingIndex = true;
488            }
489        }
490    }
491     
492
493    
494    /***************************************************************************
495     *                                                                         *
496     * Stylesheet Handling                                                     *
497     *                                                                         *
498     **************************************************************************/
499
500    private static final String DEFAULT_STYLE_CLASS = "list-cell";
501}
502