package org.jabref.gui.util; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.scene.Node; import javafx.scene.control.TreeItem; import javafx.util.Callback; /** * Taken from https://gist.github.com/lestard/011e9ed4433f9eb791a8 */ public class RecursiveTreeItem<T> extends TreeItem<T> { private final Callback<T, BooleanProperty> expandedProperty; private Callback<T, ObservableList<T>> childrenFactory; private ObjectProperty<Predicate<T>> filter = new SimpleObjectProperty<>(); private FilteredList<T> children; public RecursiveTreeItem(final T value, Callback<T, ObservableList<T>> func) { this(value, func, null, null); } public RecursiveTreeItem(final T value, Callback<T, ObservableList<T>> func, Callback<T, BooleanProperty> expandedProperty, ObservableValue<Predicate<T>> filter) { this(value, null, func, expandedProperty, filter); } public RecursiveTreeItem(final T value, Callback<T, ObservableList<T>> func, ObservableValue<Predicate<T>> filter) { this(value, null, func, null, filter); } private RecursiveTreeItem(final T value, Node graphic, Callback<T, ObservableList<T>> func, Callback<T, BooleanProperty> expandedProperty, ObservableValue<Predicate<T>> filter) { super(value, graphic); this.childrenFactory = func; this.expandedProperty = expandedProperty; if (filter != null) { this.filter.bind(filter); } if (value != null) { addChildrenListener(value); bindExpandedProperty(value, expandedProperty); } valueProperty().addListener((obs, oldValue, newValue)-> { if (newValue != null) { addChildrenListener(newValue); bindExpandedProperty(newValue, expandedProperty); } }); } private void bindExpandedProperty(T value, Callback<T, BooleanProperty> expandedProperty) { if (expandedProperty != null) { expandedProperty().bindBidirectional(expandedProperty.call(value)); } } private void addChildrenListener(T value) { children = new FilteredList<>(childrenFactory.call(value)); children.predicateProperty().bind(Bindings.createObjectBinding(() -> this::showNode, filter)); children.forEach(this::addAsChild); children.addListener((ListChangeListener<T>) change -> { while (change.next()) { if (change.wasRemoved()) { change.getRemoved().forEach(t-> { final List<TreeItem<T>> itemsToRemove = RecursiveTreeItem.this.getChildren().stream().filter(treeItem -> treeItem.getValue().equals(t)).collect(Collectors.toList()); RecursiveTreeItem.this.getChildren().removeAll(itemsToRemove); }); } if (change.wasAdded()) { change.getAddedSubList().forEach(this::addAsChild); } } }); } private boolean addAsChild(T child) { return RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(child, getGraphic(), childrenFactory, expandedProperty, filter)); } private boolean showNode(T t) { if (filter.get() == null) { return true; } if (filter.get().test(t)) { // Node is directly matched -> so show it return true; } // Are there children (or children of children...) that are matched? If yes we also need to show this node return childrenFactory.call(t).stream().anyMatch(this::showNode); } }