116 *
117 * // First getChildren() call, so we actually go off and
118 * // determine the children of the File contained in this TreeItem.
119 * super.getChildren().setAll(buildChildren(this));
120 * }
121 * return super.getChildren();
122 * }
123 *
124 * @Override public boolean isLeaf() {
125 * if (isFirstTimeLeaf) {
126 * isFirstTimeLeaf = false;
127 * File f = (File) getValue();
128 * isLeaf = f.isFile();
129 * }
130 *
131 * return isLeaf;
132 * }
133 *
134 * private ObservableList<TreeItem<File>> buildChildren(TreeItem<File> TreeItem) {
135 * File f = TreeItem.getValue();
136 * if (f != null && f.isDirectory()) {
137 * File[] files = f.listFiles();
138 * if (files != null) {
139 * ObservableList<TreeItem<File>> children = FXCollections.observableArrayList();
140 *
141 * for (File childFile : files) {
142 * children.add(createNode(childFile));
143 * }
144 *
145 * return children;
146 * }
147 * }
148 *
149 * return FXCollections.emptyObservableList();
150 * }
151 * };
152 * }</code></pre>
153 *
154 * <strong>TreeItem Events</strong>
155 * <p>TreeItem supports the same event bubbling concept as elsewhere in the
156 * scenegraph. This means that it is not necessary to listen for events on all
157 * TreeItems (and this is certainly not encouraged!). A better, and far more low
158 * cost solution is to instead attach event listeners to the TreeView
159 * {@link TreeView#rootProperty() root} item. As long as there is a path between
160 * where the event occurs and the root TreeItem, the event will be bubbled to the
161 * root item.
162 *
163 * <p>It is important to note however that a TreeItem is <strong>not</strong> a
164 * Node, which means that only the event types defined in TreeItem will be
165 * delivered. To listen to general events (for example mouse interactions), it is
166 * necessary to add the necessary listeners to the {@link Cell cells} contained
167 * within the TreeView (by providing a {@link TreeView#cellFactoryProperty()
168 * cell factory}).
169 *
170 * <p>The TreeItem class defines a number of events, with a defined hierarchy. These
171 * are shown below (follow the links to learn more about each event type):
172 *
173 * <ul>
174 * <li>{@link TreeItem#treeNotificationEvent() TreeItem.treeNotificationEvent()}</li>
175 * <ul>
176 * <li>{@link TreeItem#valueChangedEvent() TreeItem.valueChangedEvent()}</li>
177 * <li>{@link TreeItem#graphicChangedEvent() TreeItem.graphicChangedEvent()}</li>
178 * <li>{@link TreeItem#treeItemCountChangeEvent() TreeItem.treeItemCountChangeEvent()}</li>
179 * <ul>
180 * <li>{@link TreeItem#branchExpandedEvent() TreeItem.branchExpandedEvent()}</li>
181 * <li>{@link TreeItem#branchCollapsedEvent() TreeItem.branchCollapsedEvent()}</li>
182 * <li>{@link TreeItem#childrenModificationEvent() TreeItem.childrenModificationEvent()}</li>
183 * </ul>
184 * </ul>
185 * </ul>
186 *
187 * <p>The indentation shown above signifies the relationship between event types.
188 * For example, all TreeItem event types have
189 * {@link TreeItem#treeNotificationEvent() treeNotificationEvent()} as their
190 * parent event type, and the branch
191 * {@link TreeItem#branchExpandedEvent() expand} /
192 * {@link TreeItem#branchCollapsedEvent() collapse} event types are both
193 * {@link TreeItem#treeNotificationEvent() treeNotificationEvent()}. For
194 * performance reasons, it is encouraged to listen
195 * to only the events you need to listen to. This means that it is encouraged
196 * that it is better to listen to, for example,
197 * {@link TreeItem#valueChangedEvent() TreeItem.valueChangedEvent()},
198 * rather than {@link TreeItem#treeNotificationEvent() TreeItem.treeNotificationEvent()}.
199 *
200 * @param <T> The type of the {@link #getValue() value} property within TreeItem.
201 * @since JavaFX 2.0
202 */
203 public class TreeItem<T> implements EventTarget { //, Comparable<TreeItem<T>> {
204
205 /***************************************************************************
206 * *
207 * Static properties and methods *
208 * *
209 **************************************************************************/
210
211 /**
212 * The base EventType used to indicate that an event has occurred within a
213 * TreeItem. When an event occurs in a TreeItem, the event is fired to any
214 * listeners on the TreeItem that the event occurs, before it 'bubbles' up the
215 * TreeItem chain by following the TreeItem parent property. This repeats
216 * until a TreeItem whose parent TreeItem is null is reached At this point
217 * the event stops 'bubbling' and goes no further. This means that events
218 * that occur on a TreeItem can be relatively cheap, as a listener needs only
219 * be installed on the TreeView root node to be alerted of events happening
220 * at any point in the tree.
221 *
222 * @param <T> The type of the value contained within the TreeItem.
223 */
224 @SuppressWarnings("unchecked")
225 public static <T> EventType<TreeModificationEvent<T>> treeNotificationEvent() {
226 return (EventType<TreeModificationEvent<T>>) TREE_NOTIFICATION_EVENT;
227 }
228 private static final EventType<?> TREE_NOTIFICATION_EVENT
229 = new EventType<>(Event.ANY, "TreeNotificationEvent");
230
231 /**
232 * The general EventType used when the TreeItem receives a modification that
233 * results in the number of children being visible changes.
234 * This is normally achieved via one of the sub-types of this
235 * EventType (see {@link #branchExpandedEvent()},
236 * {@link #branchCollapsedEvent()} and {@link #childrenModificationEvent()}
237 * for the three sub-types).
238 *
239 * @param <T> The type of the value contained within the TreeItem.
240 * @since JavaFX 8.0
241 */
242 @SuppressWarnings("unchecked")
243 public static <T> EventType<TreeModificationEvent<T>> expandedItemCountChangeEvent() {
244 return (EventType<TreeModificationEvent<T>>) EXPANDED_ITEM_COUNT_CHANGE_EVENT;
245 }
246 private static final EventType<?> EXPANDED_ITEM_COUNT_CHANGE_EVENT
247 = new EventType<>(treeNotificationEvent(), "ExpandedItemCountChangeEvent");
248
249 /**
250 * An EventType used when the TreeItem receives a modification to its
251 * expanded property, such that the TreeItem is now in the expanded state.
252 *
253 * @param <T> The type of the value contained within the TreeItem.
254 */
255 @SuppressWarnings("unchecked")
256 public static <T> EventType<TreeModificationEvent<T>> branchExpandedEvent() {
257 return (EventType<TreeModificationEvent<T>>) BRANCH_EXPANDED_EVENT;
258 }
259 private static final EventType<?> BRANCH_EXPANDED_EVENT
260 = new EventType<>(expandedItemCountChangeEvent(), "BranchExpandedEvent");
261
262 /**
263 * An EventType used when the TreeItem receives a modification to its
264 * expanded property, such that the TreeItem is now in the collapsed state.
265 *
266 * @param <T> The type of the value contained within the TreeItem.
267 */
268 @SuppressWarnings("unchecked")
269 public static <T> EventType<TreeModificationEvent<T>> branchCollapsedEvent() {
270 return (EventType<TreeModificationEvent<T>>) BRANCH_COLLAPSED_EVENT;
271 }
272 private static final EventType<?> BRANCH_COLLAPSED_EVENT
273 = new EventType<>(expandedItemCountChangeEvent(), "BranchCollapsedEvent");
274
275 /**
276 * An EventType used when the TreeItem receives a direct modification to its
277 * children list.
278 *
279 * @param <T> The type of the value contained within the TreeItem.
280 */
281 @SuppressWarnings("unchecked")
282 public static <T> EventType<TreeModificationEvent<T>> childrenModificationEvent() {
283 return (EventType<TreeModificationEvent<T>>) CHILDREN_MODIFICATION_EVENT;
284 }
285 private static final EventType<?> CHILDREN_MODIFICATION_EVENT
286 = new EventType<>(expandedItemCountChangeEvent(), "ChildrenModificationEvent");
287
288 /**
289 * An EventType used when the TreeItem receives a modification to its
290 * value property.
291 *
292 * @param <T> The type of the value contained within the TreeItem.
293 */
294 @SuppressWarnings("unchecked")
295 public static <T> EventType<TreeModificationEvent<T>> valueChangedEvent() {
296 return (EventType<TreeModificationEvent<T>>) VALUE_CHANGED_EVENT;
297 }
298 private static final EventType<?> VALUE_CHANGED_EVENT
299 = new EventType<>(treeNotificationEvent(), "ValueChangedEvent");
300
301 /**
302 * An EventType used when the TreeItem receives a modification to its
303 * graphic property.
304 *
305 * @param <T> The type of the value contained within the TreeItem.
306 */
307 @SuppressWarnings("unchecked")
308 public static <T> EventType<TreeModificationEvent<T>> graphicChangedEvent() {
309 return (EventType<TreeModificationEvent<T>>) GRAPHIC_CHANGED_EVENT;
310 }
311 private static final EventType<?> GRAPHIC_CHANGED_EVENT
312 = new EventType<>(treeNotificationEvent(), "GraphicChangedEvent");
313
314
315
316 /***************************************************************************
317 * *
318 * Constructors *
319 * *
320 **************************************************************************/
321
322 /**
323 * Creates an empty TreeItem.
324 */
325 public TreeItem() {
408
409 // called whenever the contents of the children sequence changes
410 private ListChangeListener<TreeItem<T>> childrenListener = c -> {
411 expandedDescendentCountDirty = true;
412 updateChildren(c);
413 };
414
415
416
417 /***************************************************************************
418 * *
419 * Properties *
420 * *
421 **************************************************************************/
422
423 // --- Value
424 private ObjectProperty<T> value;
425
426 /**
427 * Sets the application-specific data represented by this TreeItem.
428 */
429 public final void setValue(T value) { valueProperty().setValue(value); }
430
431 /**
432 * Returns the application-specific data represented by this TreeItem.
433 * @return the data represented by this TreeItem
434 */
435 public final T getValue() { return value == null ? null : value.getValue(); }
436
437 /**
438 * A property representing the application-specific data contained within
439 * this TreeItem.
440 */
441 public final ObjectProperty<T> valueProperty() {
442 if (value == null) {
443 value = new ObjectPropertyBase<T>() {
444 @Override protected void invalidated() {
445 fireEvent(new TreeModificationEvent<T>(VALUE_CHANGED_EVENT, TreeItem.this, get()));
446 }
447
448 @Override public Object getBean() {
449 return TreeItem.this;
450 }
451
452 @Override public String getName() {
453 return "value";
454 }
455 };
456 }
457 return value;
458 }
459
463
464 /**
465 * Sets the node that is generally shown to the left of the value property.
466 * For best effect, this tends to be a 16x16 image.
467 *
468 * @param value The graphic node that will be displayed to the user.
469 */
470 public final void setGraphic(Node value) { graphicProperty().setValue(value); }
471
472 /**
473 * Returns the node that is generally shown to the left of the value property.
474 * For best effect, this tends to be a 16x16 image.
475 *
476 * @return The graphic node that will be displayed to the user.
477 */
478 public final Node getGraphic() { return graphic == null ? null : graphic.getValue(); }
479
480 /**
481 * The node that is generally shown to the left of the value property. For
482 * best effect, this tends to be a 16x16 image.
483 */
484 public final ObjectProperty<Node> graphicProperty() {
485 if (graphic == null) {
486 graphic = new ObjectPropertyBase<Node>() {
487 @Override protected void invalidated() {
488 fireEvent(new TreeModificationEvent<T>(GRAPHIC_CHANGED_EVENT, TreeItem.this));
489 }
490
491 @Override
492 public Object getBean() {
493 return TreeItem.this;
494 }
495
496 @Override
497 public String getName() {
498 return "graphic";
499 }
500 };
501 }
502 return graphic;
514 *
515 * @param value If this TreeItem has children, calling setExpanded with
516 * <code>true</code> will result in the children becoming visible.
517 * Calling setExpanded with <code>false</code> will hide any children
518 * belonging to the TreeItem.
519 */
520 public final void setExpanded(boolean value) {
521 if (! value && expanded == null) return;
522 expandedProperty().setValue(value);
523 }
524
525 /**
526 * Returns the expanded state of this TreeItem.
527 *
528 * @return Returns the expanded state of this TreeItem.
529 */
530 public final boolean isExpanded() { return expanded == null ? false : expanded.getValue(); }
531
532 /**
533 * The expanded state of this TreeItem.
534 */
535 public final BooleanProperty expandedProperty() {
536 if (expanded == null) {
537 expanded = new BooleanPropertyBase() {
538 @Override protected void invalidated() {
539 // We don't fire expanded events for leaf nodes (RT-32620)
540 if (isLeaf()) return;
541
542 EventType<?> evtType = isExpanded() ?
543 BRANCH_EXPANDED_EVENT : BRANCH_COLLAPSED_EVENT;
544
545 fireEvent(new TreeModificationEvent<T>(evtType, TreeItem.this, isExpanded()));
546 }
547
548 @Override
549 public Object getBean() {
550 return TreeItem.this;
551 }
552
553 @Override
560 }
561
562
563 // --- Leaf
564 private ReadOnlyBooleanWrapper leaf;
565 private void setLeaf(boolean value) {
566 if (value && leaf == null) {
567 return;
568 } else if (leaf == null) {
569 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true);
570 }
571 leaf.setValue(value);
572 }
573
574 /**
575 * A TreeItem is a leaf if it has no children. The isLeaf method may of
576 * course be overridden by subclasses to support alternate means of defining
577 * how a TreeItem may be a leaf, but the general premise is the same: a
578 * leaf can not be expanded by the user, and as such will not show a
579 * disclosure node or respond to expansion requests.
580 */
581 public boolean isLeaf() { return leaf == null ? true : leaf.getValue(); }
582
583 /**
584 * Represents the TreeItem leaf property, which is true if the TreeItem has no children.
585 */
586 public final ReadOnlyBooleanProperty leafProperty() {
587 if (leaf == null) {
588 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true);
589 }
590 return leaf.getReadOnlyProperty();
591 }
592
593
594 // --- Parent
595 private ReadOnlyObjectWrapper<TreeItem<T>> parent = new ReadOnlyObjectWrapper<TreeItem<T>>(this, "parent");
596 private void setParent(TreeItem<T> value) { parent.setValue(value); }
597
598 /**
599 * The parent of this TreeItem. Each TreeItem can have no more than one
600 * parent. If a TreeItem has no parent, it represents a root in the tree model.
601 *
602 * @return The parent of this TreeItem, or null if the TreeItem has no parent.
603 */
604 public final TreeItem<T> getParent() { return parent == null ? null : parent.getValue(); }
605
606 /**
607 * A property that represents the parent of this TreeItem.
608 */
609 public final ReadOnlyObjectProperty<TreeItem<T>> parentProperty() { return parent.getReadOnlyProperty(); }
610
611
612
613 /***********************************************************************
614 * *
615 * TreeItem API *
616 * *
617 **********************************************************************/
618
619 /**
620 * The children of this TreeItem. This method is called frequently, and
621 * it is therefore recommended that the returned list be cached by
622 * any TreeItem implementations.
623 *
624 * @return a list that contains the child TreeItems belonging to the TreeItem.
625 */
626 public ObservableList<TreeItem<T>> getChildren() {
627 if (children == null) {
755 /** {@inheritDoc} */
756 @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
757 // To allow for a TreeView (and its skin) to be notified of changes in the
758 // tree, this method recursively calls up to the root node, at which point
759 // it fires a ROOT_NOTIFICATION_EVENT, which the TreeView may be watching for.
760 if (getParent() != null) {
761 getParent().buildEventDispatchChain(tail);
762 }
763 return tail.append(eventHandlerManager);
764 }
765
766 /**
767 * Registers an event handler to this TreeItem. The TreeItem class allows
768 * registration of listeners which will be notified as the
769 * number of items changes, their position or if the values themselves change.
770 * Note however that a TreeItem is <b>not</b> a Node, and therefore no visual
771 * events will be fired on the TreeItem. To get these events, it is necessary to
772 * add relevant observers to the TreeCell instances (via a custom cell factory -
773 * see the {@link Cell} class documentation for more details).
774 *
775 * @param eventType the type of the events to receive by the handler
776 * @param eventHandler the handler to register
777 * @throws NullPointerException if the event type or handler is null
778 */
779 public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
780 eventHandlerManager.addEventHandler(eventType, eventHandler);
781 }
782
783 /**
784 * Unregisters a previously registered event handler from this TreeItem. One
785 * handler might have been registered for different event types, so the
786 * caller needs to specify the particular event type from which to
787 * unregister the handler.
788 *
789 * @param eventType the event type from which to unregister
790 * @param eventHandler the handler to unregister
791 * @throws NullPointerException if the event type or handler is null
792 */
793 public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
794 eventHandlerManager.removeEventHandler(eventType, eventHandler);
795 }
796
797
798
799 /***************************************************************************
800 * *
801 * private methods *
802 * *
803 **************************************************************************/
804
805 void sort() {
806 sort(children, lastComparator, lastSortMode);
807 }
808
942 // the parent of that TreeItem is set to null when it should in fact
943 // remain with the parent that it belongs to.
944 if (treeItem.parentLinkCount == 0) {
945 treeItem.setParent(newParent);
946 }
947
948 boolean parentMatch = currentParent != null && currentParent.equals(newParent);
949 if (parentMatch) {
950 if (newParent == null) {
951 treeItem.parentLinkCount--;
952 } else {
953 treeItem.parentLinkCount++;
954 }
955 }
956 }
957 }
958
959 /**
960 * An {@link Event} that contains relevant information for all forms of
961 * TreeItem modifications.
962 * @since JavaFX 2.0
963 */
964 public static class TreeModificationEvent<T> extends Event {
965 private static final long serialVersionUID = 4741889985221719579L;
966
967 /**
968 * Common supertype for all tree modification event types.
969 * @since JavaFX 8.0
970 */
971 public static final EventType<?> ANY = TREE_NOTIFICATION_EVENT;
972
973 private transient final TreeItem<T> treeItem;
974 private final T newValue;
975
976 private final List<? extends TreeItem<T>> added;
977 private final List<? extends TreeItem<T>> removed;
978 private final ListChangeListener.Change<? extends TreeItem<T>> change;
979
980 private final boolean wasExpanded;
981 private final boolean wasCollapsed;
1138 * @return The number of removed children items, or zero if no children
1139 * were removed.
1140 */
1141 public int getRemovedSize() {
1142 return getRemovedChildren().size();
1143 }
1144
1145 /**
1146 * Returns the number of children items that were added in this event,
1147 * or zero if no children were added.
1148 * @return The number of added children items, or zero if no children
1149 * were added.
1150 */
1151 public int getAddedSize() {
1152 return getAddedChildren().size();
1153 }
1154
1155 /**
1156 * Returns true if this event represents a TreeItem expansion event,
1157 * and false if the TreeItem was not expanded.
1158 */
1159 public boolean wasExpanded() { return wasExpanded; }
1160
1161 /**
1162 * Returns true if this event represents a TreeItem collapse event,
1163 * and false if the TreeItem was not collapsed.
1164 */
1165 public boolean wasCollapsed() { return wasCollapsed; }
1166
1167 /**
1168 * Returns true if this event represents a TreeItem event where children
1169 * TreeItems were added.
1170 */
1171 public boolean wasAdded() { return getAddedSize() > 0; }
1172
1173 /**
1174 * Returns true if this event represents a TreeItem event where children
1175 * TreeItems were removed.
1176 */
1177 public boolean wasRemoved() { return getRemovedSize() > 0; }
1178
1179 /**
1180 * Returns true if the order of the TreeItem children list has changed,
1181 * but that there have been no additions or removals.
1182 */
1183 public boolean wasPermutated() { return wasPermutated; }
1184
1185 int getFrom() { return change == null ? -1 : change.getFrom(); }
1186 int getTo() { return change == null ? -1 : change.getTo(); }
1187 ListChangeListener.Change<? extends TreeItem<T>> getChange() { return change; }
1188 }
1189 }
|
116 *
117 * // First getChildren() call, so we actually go off and
118 * // determine the children of the File contained in this TreeItem.
119 * super.getChildren().setAll(buildChildren(this));
120 * }
121 * return super.getChildren();
122 * }
123 *
124 * @Override public boolean isLeaf() {
125 * if (isFirstTimeLeaf) {
126 * isFirstTimeLeaf = false;
127 * File f = (File) getValue();
128 * isLeaf = f.isFile();
129 * }
130 *
131 * return isLeaf;
132 * }
133 *
134 * private ObservableList<TreeItem<File>> buildChildren(TreeItem<File> TreeItem) {
135 * File f = TreeItem.getValue();
136 * if (f != null && f.isDirectory()) {
137 * File[] files = f.listFiles();
138 * if (files != null) {
139 * ObservableList<TreeItem<File>> children = FXCollections.observableArrayList();
140 *
141 * for (File childFile : files) {
142 * children.add(createNode(childFile));
143 * }
144 *
145 * return children;
146 * }
147 * }
148 *
149 * return FXCollections.emptyObservableList();
150 * }
151 * };
152 * }</code></pre>
153 *
154 * <strong>TreeItem Events</strong>
155 * <p>TreeItem supports the same event bubbling concept as elsewhere in the
156 * scenegraph. This means that it is not necessary to listen for events on all
157 * TreeItems (and this is certainly not encouraged!). A better, and far more low
158 * cost solution is to instead attach event listeners to the TreeView
159 * {@link TreeView#rootProperty() root} item. As long as there is a path between
160 * where the event occurs and the root TreeItem, the event will be bubbled to the
161 * root item.
162 *
163 * <p>It is important to note however that a TreeItem is <strong>not</strong> a
164 * Node, which means that only the event types defined in TreeItem will be
165 * delivered. To listen to general events (for example mouse interactions), it is
166 * necessary to add the necessary listeners to the {@link Cell cells} contained
167 * within the TreeView (by providing a {@link TreeView#cellFactoryProperty()
168 * cell factory}).
169 *
170 * <p>The TreeItem class defines a number of events, with a defined hierarchy. These
171 * are shown below (follow the links to learn more about each event type):
172 *
173 * <ul>
174 * <li>{@link TreeItem#treeNotificationEvent() TreeItem.treeNotificationEvent()}
175 * <ul>
176 * <li>{@link TreeItem#valueChangedEvent() TreeItem.valueChangedEvent()}</li>
177 * <li>{@link TreeItem#graphicChangedEvent() TreeItem.graphicChangedEvent()}</li>
178 * <li>{@link TreeItem#expandedItemCountChangeEvent() TreeItem.expandedItemCountChangeEvent()}
179 * <ul>
180 * <li>{@link TreeItem#branchExpandedEvent() TreeItem.branchExpandedEvent()}</li>
181 * <li>{@link TreeItem#branchCollapsedEvent() TreeItem.branchCollapsedEvent()}</li>
182 * <li>{@link TreeItem#childrenModificationEvent() TreeItem.childrenModificationEvent()}</li>
183 * </ul>
184 * </li>
185 * </ul>
186 * </li>
187 * </ul>
188 *
189 * <p>The indentation shown above signifies the relationship between event types.
190 * For example, all TreeItem event types have
191 * {@link TreeItem#treeNotificationEvent() treeNotificationEvent()} as their
192 * parent event type, and the branch
193 * {@link TreeItem#branchExpandedEvent() expand} /
194 * {@link TreeItem#branchCollapsedEvent() collapse} event types are both
195 * {@link TreeItem#treeNotificationEvent() treeNotificationEvent()}. For
196 * performance reasons, it is encouraged to listen
197 * to only the events you need to listen to. This means that it is encouraged
198 * that it is better to listen to, for example,
199 * {@link TreeItem#valueChangedEvent() TreeItem.valueChangedEvent()},
200 * rather than {@link TreeItem#treeNotificationEvent() TreeItem.treeNotificationEvent()}.
201 *
202 * @param <T> The type of the {@link #getValue() value} property within TreeItem.
203 * @since JavaFX 2.0
204 */
205 public class TreeItem<T> implements EventTarget { //, Comparable<TreeItem<T>> {
206
207 /***************************************************************************
208 * *
209 * Static properties and methods *
210 * *
211 **************************************************************************/
212
213 /**
214 * The base EventType used to indicate that an event has occurred within a
215 * TreeItem. When an event occurs in a TreeItem, the event is fired to any
216 * listeners on the TreeItem that the event occurs, before it 'bubbles' up the
217 * TreeItem chain by following the TreeItem parent property. This repeats
218 * until a TreeItem whose parent TreeItem is null is reached At this point
219 * the event stops 'bubbling' and goes no further. This means that events
220 * that occur on a TreeItem can be relatively cheap, as a listener needs only
221 * be installed on the TreeView root node to be alerted of events happening
222 * at any point in the tree.
223 *
224 * @param <T> The type of the value contained within the TreeItem.
225 * @return The base EventType when an event has occurred a within a TreeItem
226 */
227 @SuppressWarnings("unchecked")
228 public static <T> EventType<TreeModificationEvent<T>> treeNotificationEvent() {
229 return (EventType<TreeModificationEvent<T>>) TREE_NOTIFICATION_EVENT;
230 }
231 private static final EventType<?> TREE_NOTIFICATION_EVENT
232 = new EventType<>(Event.ANY, "TreeNotificationEvent");
233
234 /**
235 * The general EventType used when the TreeItem receives a modification that
236 * results in the number of children being visible changes.
237 * This is normally achieved via one of the sub-types of this
238 * EventType (see {@link #branchExpandedEvent()},
239 * {@link #branchCollapsedEvent()} and {@link #childrenModificationEvent()}
240 * for the three sub-types).
241 *
242 * @param <T> The type of the value contained within the TreeItem.
243 * @return The general EventType when the TreeItem receives a modification
244 * @since JavaFX 8.0
245 */
246 @SuppressWarnings("unchecked")
247 public static <T> EventType<TreeModificationEvent<T>> expandedItemCountChangeEvent() {
248 return (EventType<TreeModificationEvent<T>>) EXPANDED_ITEM_COUNT_CHANGE_EVENT;
249 }
250 private static final EventType<?> EXPANDED_ITEM_COUNT_CHANGE_EVENT
251 = new EventType<>(treeNotificationEvent(), "ExpandedItemCountChangeEvent");
252
253 /**
254 * An EventType used when the TreeItem receives a modification to its
255 * expanded property, such that the TreeItem is now in the expanded state.
256 *
257 * @param <T> The type of the value contained within the TreeItem.
258 * @return The EventType used when the TreeItem receives a modification
259 */
260 @SuppressWarnings("unchecked")
261 public static <T> EventType<TreeModificationEvent<T>> branchExpandedEvent() {
262 return (EventType<TreeModificationEvent<T>>) BRANCH_EXPANDED_EVENT;
263 }
264 private static final EventType<?> BRANCH_EXPANDED_EVENT
265 = new EventType<>(expandedItemCountChangeEvent(), "BranchExpandedEvent");
266
267 /**
268 * An EventType used when the TreeItem receives a modification to its
269 * expanded property, such that the TreeItem is now in the collapsed state.
270 *
271 * @param <T> The type of the value contained within the TreeItem.
272 * @return The EventType when the TreeItem receives a modification
273 */
274 @SuppressWarnings("unchecked")
275 public static <T> EventType<TreeModificationEvent<T>> branchCollapsedEvent() {
276 return (EventType<TreeModificationEvent<T>>) BRANCH_COLLAPSED_EVENT;
277 }
278 private static final EventType<?> BRANCH_COLLAPSED_EVENT
279 = new EventType<>(expandedItemCountChangeEvent(), "BranchCollapsedEvent");
280
281 /**
282 * An EventType used when the TreeItem receives a direct modification to its
283 * children list.
284 *
285 * @param <T> The type of the value contained within the TreeItem.
286 * @return The EventType when the TreeItem receives a direct modification to
287 * its children list
288 */
289 @SuppressWarnings("unchecked")
290 public static <T> EventType<TreeModificationEvent<T>> childrenModificationEvent() {
291 return (EventType<TreeModificationEvent<T>>) CHILDREN_MODIFICATION_EVENT;
292 }
293 private static final EventType<?> CHILDREN_MODIFICATION_EVENT
294 = new EventType<>(expandedItemCountChangeEvent(), "ChildrenModificationEvent");
295
296 /**
297 * An EventType used when the TreeItem receives a modification to its
298 * value property.
299 *
300 * @param <T> The type of the value contained within the TreeItem.
301 * @return The EventType when the TreeItem receives a modification to its
302 * value property
303 */
304 @SuppressWarnings("unchecked")
305 public static <T> EventType<TreeModificationEvent<T>> valueChangedEvent() {
306 return (EventType<TreeModificationEvent<T>>) VALUE_CHANGED_EVENT;
307 }
308 private static final EventType<?> VALUE_CHANGED_EVENT
309 = new EventType<>(treeNotificationEvent(), "ValueChangedEvent");
310
311 /**
312 * An EventType used when the TreeItem receives a modification to its
313 * graphic property.
314 *
315 * @param <T> The type of the value contained within the TreeItem.
316 * @return The EventType when the TreeItem receives a modification to its
317 * graphic property
318 */
319 @SuppressWarnings("unchecked")
320 public static <T> EventType<TreeModificationEvent<T>> graphicChangedEvent() {
321 return (EventType<TreeModificationEvent<T>>) GRAPHIC_CHANGED_EVENT;
322 }
323 private static final EventType<?> GRAPHIC_CHANGED_EVENT
324 = new EventType<>(treeNotificationEvent(), "GraphicChangedEvent");
325
326
327
328 /***************************************************************************
329 * *
330 * Constructors *
331 * *
332 **************************************************************************/
333
334 /**
335 * Creates an empty TreeItem.
336 */
337 public TreeItem() {
420
421 // called whenever the contents of the children sequence changes
422 private ListChangeListener<TreeItem<T>> childrenListener = c -> {
423 expandedDescendentCountDirty = true;
424 updateChildren(c);
425 };
426
427
428
429 /***************************************************************************
430 * *
431 * Properties *
432 * *
433 **************************************************************************/
434
435 // --- Value
436 private ObjectProperty<T> value;
437
438 /**
439 * Sets the application-specific data represented by this TreeItem.
440 * @param value the application-specific data
441 */
442 public final void setValue(T value) { valueProperty().setValue(value); }
443
444 /**
445 * Returns the application-specific data represented by this TreeItem.
446 * @return the data represented by this TreeItem
447 */
448 public final T getValue() { return value == null ? null : value.getValue(); }
449
450 /**
451 * A property representing the application-specific data contained within
452 * this TreeItem.
453 * @return the property representing the application-specific data contained
454 * within this TreeItem
455 */
456 public final ObjectProperty<T> valueProperty() {
457 if (value == null) {
458 value = new ObjectPropertyBase<T>() {
459 @Override protected void invalidated() {
460 fireEvent(new TreeModificationEvent<T>(VALUE_CHANGED_EVENT, TreeItem.this, get()));
461 }
462
463 @Override public Object getBean() {
464 return TreeItem.this;
465 }
466
467 @Override public String getName() {
468 return "value";
469 }
470 };
471 }
472 return value;
473 }
474
478
479 /**
480 * Sets the node that is generally shown to the left of the value property.
481 * For best effect, this tends to be a 16x16 image.
482 *
483 * @param value The graphic node that will be displayed to the user.
484 */
485 public final void setGraphic(Node value) { graphicProperty().setValue(value); }
486
487 /**
488 * Returns the node that is generally shown to the left of the value property.
489 * For best effect, this tends to be a 16x16 image.
490 *
491 * @return The graphic node that will be displayed to the user.
492 */
493 public final Node getGraphic() { return graphic == null ? null : graphic.getValue(); }
494
495 /**
496 * The node that is generally shown to the left of the value property. For
497 * best effect, this tends to be a 16x16 image.
498 * @return The node that is generally shown to the left of the value property
499 */
500 public final ObjectProperty<Node> graphicProperty() {
501 if (graphic == null) {
502 graphic = new ObjectPropertyBase<Node>() {
503 @Override protected void invalidated() {
504 fireEvent(new TreeModificationEvent<T>(GRAPHIC_CHANGED_EVENT, TreeItem.this));
505 }
506
507 @Override
508 public Object getBean() {
509 return TreeItem.this;
510 }
511
512 @Override
513 public String getName() {
514 return "graphic";
515 }
516 };
517 }
518 return graphic;
530 *
531 * @param value If this TreeItem has children, calling setExpanded with
532 * <code>true</code> will result in the children becoming visible.
533 * Calling setExpanded with <code>false</code> will hide any children
534 * belonging to the TreeItem.
535 */
536 public final void setExpanded(boolean value) {
537 if (! value && expanded == null) return;
538 expandedProperty().setValue(value);
539 }
540
541 /**
542 * Returns the expanded state of this TreeItem.
543 *
544 * @return Returns the expanded state of this TreeItem.
545 */
546 public final boolean isExpanded() { return expanded == null ? false : expanded.getValue(); }
547
548 /**
549 * The expanded state of this TreeItem.
550 * @return The expanded state property of this TreeItem
551 */
552 public final BooleanProperty expandedProperty() {
553 if (expanded == null) {
554 expanded = new BooleanPropertyBase() {
555 @Override protected void invalidated() {
556 // We don't fire expanded events for leaf nodes (RT-32620)
557 if (isLeaf()) return;
558
559 EventType<?> evtType = isExpanded() ?
560 BRANCH_EXPANDED_EVENT : BRANCH_COLLAPSED_EVENT;
561
562 fireEvent(new TreeModificationEvent<T>(evtType, TreeItem.this, isExpanded()));
563 }
564
565 @Override
566 public Object getBean() {
567 return TreeItem.this;
568 }
569
570 @Override
577 }
578
579
580 // --- Leaf
581 private ReadOnlyBooleanWrapper leaf;
582 private void setLeaf(boolean value) {
583 if (value && leaf == null) {
584 return;
585 } else if (leaf == null) {
586 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true);
587 }
588 leaf.setValue(value);
589 }
590
591 /**
592 * A TreeItem is a leaf if it has no children. The isLeaf method may of
593 * course be overridden by subclasses to support alternate means of defining
594 * how a TreeItem may be a leaf, but the general premise is the same: a
595 * leaf can not be expanded by the user, and as such will not show a
596 * disclosure node or respond to expansion requests.
597 * @return true if this TreeItem has no children
598 */
599 public boolean isLeaf() { return leaf == null ? true : leaf.getValue(); }
600
601 /**
602 * Represents the TreeItem leaf property, which is true if the TreeItem has no children.
603 * @return the TreeItem leaf property
604 */
605 public final ReadOnlyBooleanProperty leafProperty() {
606 if (leaf == null) {
607 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true);
608 }
609 return leaf.getReadOnlyProperty();
610 }
611
612
613 // --- Parent
614 private ReadOnlyObjectWrapper<TreeItem<T>> parent = new ReadOnlyObjectWrapper<TreeItem<T>>(this, "parent");
615 private void setParent(TreeItem<T> value) { parent.setValue(value); }
616
617 /**
618 * The parent of this TreeItem. Each TreeItem can have no more than one
619 * parent. If a TreeItem has no parent, it represents a root in the tree model.
620 *
621 * @return The parent of this TreeItem, or null if the TreeItem has no parent.
622 */
623 public final TreeItem<T> getParent() { return parent == null ? null : parent.getValue(); }
624
625 /**
626 * A property that represents the parent of this TreeItem.
627 * @return the parent property of this TreeItem
628 */
629 public final ReadOnlyObjectProperty<TreeItem<T>> parentProperty() { return parent.getReadOnlyProperty(); }
630
631
632
633 /***********************************************************************
634 * *
635 * TreeItem API *
636 * *
637 **********************************************************************/
638
639 /**
640 * The children of this TreeItem. This method is called frequently, and
641 * it is therefore recommended that the returned list be cached by
642 * any TreeItem implementations.
643 *
644 * @return a list that contains the child TreeItems belonging to the TreeItem.
645 */
646 public ObservableList<TreeItem<T>> getChildren() {
647 if (children == null) {
775 /** {@inheritDoc} */
776 @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
777 // To allow for a TreeView (and its skin) to be notified of changes in the
778 // tree, this method recursively calls up to the root node, at which point
779 // it fires a ROOT_NOTIFICATION_EVENT, which the TreeView may be watching for.
780 if (getParent() != null) {
781 getParent().buildEventDispatchChain(tail);
782 }
783 return tail.append(eventHandlerManager);
784 }
785
786 /**
787 * Registers an event handler to this TreeItem. The TreeItem class allows
788 * registration of listeners which will be notified as the
789 * number of items changes, their position or if the values themselves change.
790 * Note however that a TreeItem is <b>not</b> a Node, and therefore no visual
791 * events will be fired on the TreeItem. To get these events, it is necessary to
792 * add relevant observers to the TreeCell instances (via a custom cell factory -
793 * see the {@link Cell} class documentation for more details).
794 *
795 * @param <E> The event
796 * @param eventType the type of the events to receive by the handler
797 * @param eventHandler the handler to register
798 * @throws NullPointerException if the event type or handler is null
799 */
800 public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
801 eventHandlerManager.addEventHandler(eventType, eventHandler);
802 }
803
804 /**
805 * Unregisters a previously registered event handler from this TreeItem. One
806 * handler might have been registered for different event types, so the
807 * caller needs to specify the particular event type from which to
808 * unregister the handler.
809 *
810 * @param <E> The event
811 * @param eventType the event type from which to unregister
812 * @param eventHandler the handler to unregister
813 * @throws NullPointerException if the event type or handler is null
814 */
815 public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
816 eventHandlerManager.removeEventHandler(eventType, eventHandler);
817 }
818
819
820
821 /***************************************************************************
822 * *
823 * private methods *
824 * *
825 **************************************************************************/
826
827 void sort() {
828 sort(children, lastComparator, lastSortMode);
829 }
830
964 // the parent of that TreeItem is set to null when it should in fact
965 // remain with the parent that it belongs to.
966 if (treeItem.parentLinkCount == 0) {
967 treeItem.setParent(newParent);
968 }
969
970 boolean parentMatch = currentParent != null && currentParent.equals(newParent);
971 if (parentMatch) {
972 if (newParent == null) {
973 treeItem.parentLinkCount--;
974 } else {
975 treeItem.parentLinkCount++;
976 }
977 }
978 }
979 }
980
981 /**
982 * An {@link Event} that contains relevant information for all forms of
983 * TreeItem modifications.
984 * @param <T> The TreeModificationEvent
985 * @since JavaFX 2.0
986 */
987 public static class TreeModificationEvent<T> extends Event {
988 private static final long serialVersionUID = 4741889985221719579L;
989
990 /**
991 * Common supertype for all tree modification event types.
992 * @since JavaFX 8.0
993 */
994 public static final EventType<?> ANY = TREE_NOTIFICATION_EVENT;
995
996 private transient final TreeItem<T> treeItem;
997 private final T newValue;
998
999 private final List<? extends TreeItem<T>> added;
1000 private final List<? extends TreeItem<T>> removed;
1001 private final ListChangeListener.Change<? extends TreeItem<T>> change;
1002
1003 private final boolean wasExpanded;
1004 private final boolean wasCollapsed;
1161 * @return The number of removed children items, or zero if no children
1162 * were removed.
1163 */
1164 public int getRemovedSize() {
1165 return getRemovedChildren().size();
1166 }
1167
1168 /**
1169 * Returns the number of children items that were added in this event,
1170 * or zero if no children were added.
1171 * @return The number of added children items, or zero if no children
1172 * were added.
1173 */
1174 public int getAddedSize() {
1175 return getAddedChildren().size();
1176 }
1177
1178 /**
1179 * Returns true if this event represents a TreeItem expansion event,
1180 * and false if the TreeItem was not expanded.
1181 * @return true if this event represents a TreeItem expansion event,
1182 * and false if the TreeItem was not expanded
1183 */
1184 public boolean wasExpanded() { return wasExpanded; }
1185
1186 /**
1187 * Returns true if this event represents a TreeItem collapse event,
1188 * and false if the TreeItem was not collapsed.
1189 * @return true if this event represents a TreeItem collapse event,
1190 * and false if the TreeItem was not collapsed
1191 */
1192 public boolean wasCollapsed() { return wasCollapsed; }
1193
1194 /**
1195 * Returns true if this event represents a TreeItem event where children
1196 * TreeItems were added.
1197 * @return true if this event represents a TreeItem event where children
1198 * TreeItems were added
1199 */
1200 public boolean wasAdded() { return getAddedSize() > 0; }
1201
1202 /**
1203 * Returns true if this event represents a TreeItem event where children
1204 * TreeItems were removed.
1205 * @return true if this event represents a TreeItem event where children
1206 * TreeItems were removed
1207 */
1208 public boolean wasRemoved() { return getRemovedSize() > 0; }
1209
1210 /**
1211 * Returns true if the order of the TreeItem children list has changed,
1212 * but that there have been no additions or removals.
1213 * @return true if the order of the TreeItem children list has changed,
1214 * but that there have been no additions or removals
1215 */
1216 public boolean wasPermutated() { return wasPermutated; }
1217
1218 int getFrom() { return change == null ? -1 : change.getFrom(); }
1219 int getTo() { return change == null ? -1 : change.getTo(); }
1220 ListChangeListener.Change<? extends TreeItem<T>> getChange() { return change; }
1221 }
1222 }
|