jidefx.scene.control.searchable.TreeViewSearchable Maven / Gradle / Ivy
/*
* @(#)TreeViewSearchable.java 5/19/2013
*
* Copyright 2002 - 2013 JIDE Software Inc. All rights reserved.
*/
package jidefx.scene.control.searchable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import java.util.ArrayList;
import java.util.List;
/**
* {@code TreeViewSearchable} is an concrete implementation of {@link Searchable} that enables the search function
* in TreeView. It's very simple to use it. Assuming you have a TreeView, all you need to do is to call
*
{@code
* TreeView tree = ....;
* TreeViewSearchable searchable = new TreeViewSearchable(tree);
* }
* Now the TreeView will have the search function.
*
* There is very little customization you need to do to TreeSearchable. The only thing you might need is when the
* element in the TreeView needs a special conversion to convert to string. If so, you can override
* convertElementToString() to provide you own algorithm to do the conversion.
*
{@code
* TreeView tree = ....;
* TreeViewSearchable searchable = new TreeViewSearchable(tree) {
* protected String convertElementToString(Object object) {
* ...
* }
* };
* }
* The other customization you might want to is to call {@link #setRecursive} to true. It is false by default. If
* setting it true, we will automatically expand the TreeItem to look for the string to be searched. If your TreeView
* has a huge number or unlimited number of rows when fully expanded, please do not set it to true as it will for sure
* run out of memory.
*
* Additional customization can be done on the base Searchable class such as background and foreground color,
* keystrokes, case sensitivity.
*
* @param the element type in the TreeView.
*/
@SuppressWarnings({"Convert2Lambda", "unchecked"})
public class TreeViewSearchable extends Searchable> {
private BooleanProperty _recursiveProperty;
private transient List _treeItems;
private ChangeListener _rootChangeListener;
public TreeViewSearchable(TreeView treeView) {
super(treeView);
recursiveProperty().addListener(new ChangeListener() {
@Override
public void changed(ObservableValue extends Boolean> observable, Boolean oldValue, Boolean newValue) {
hidePopup();
resetTreeItems();
}
});
}
@Override
public void installListeners() {
super.installListeners();
if (_rootChangeListener == null) {
_rootChangeListener = new ChangeListener() {
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
hidePopup();
resetTreeItems();
}
};
}
((TreeView) _node).rootProperty().addListener(_rootChangeListener);
}
@Override
public void uninstallListeners() {
if (_rootChangeListener != null) {
((TreeView) _node).rootProperty().removeListener(_rootChangeListener);
_rootChangeListener = null;
}
super.uninstallListeners();
}
public BooleanProperty recursiveProperty() {
if (_recursiveProperty == null) {
_recursiveProperty = new SimpleBooleanProperty();
}
return _recursiveProperty;
}
/**
* Checks if the searchable is recursive.
*
* @return true if searchable is recursive.
*/
public boolean isRecursive() {
return recursiveProperty().get();
}
/**
* Sets the recursive attribute.
*
* If TreeSearchable is recursive, it will all tree nodes including those which are not visible to find the matching
* node. Obviously, if your tree has unlimited number of tree nodes or a potential huge number of tree nodes (such
* as a tree to represent file system), the recursive attribute should be false. To avoid this potential problem in
* this case, we default it to false.
*
* @param recursive the attribute
*/
public void setRecursive(boolean recursive) {
recursiveProperty().set(recursive);
}
@Override
protected void setSelectedIndex(int index, boolean incremental) {
TreeView treeView = (TreeView) _node;
if (!isRecursive()) {
if (incremental) {
treeView.getSelectionModel().select(index);
}
else {
treeView.getSelectionModel().clearAndSelect(index);
}
treeView.scrollTo(index);
}
else {
TreeItem item = getElementAt(index);
if (item != null) { // else case should never happen
if (incremental) {
treeView.getSelectionModel().select(item);
}
else {
treeView.getSelectionModel().clearSelection();
treeView.getSelectionModel().select(item);
}
treeView.scrollTo(treeView.getRow(item));
}
}
}
@Override
protected int getSelectedIndex() {
return ((TreeView) _node).getSelectionModel().getSelectedIndex();
}
@Override
protected TreeItem getElementAt(int index) {
if (index == -1) {
return null;
}
if (!isRecursive()) {
return ((TreeView) _node).getTreeItem(index);
}
else {
return getTreeItems().get(index);
}
}
@Override
protected int getElementCount() {
if (!isRecursive()) {
return ((TreeView) _node).getExpandedItemCount();
}
else {
return getTreeItems().size();
}
}
/**
* Recursively go through the tree to populate the tree paths into a list and cache them.
*
* Tree paths list is only used when recursive attribute is true.
*/
protected void populateTreePaths() {
_treeItems = new ArrayList<>();
TreeItem root = ((TreeView) _node).getRoot();
populateTreePaths0(root);
}
private void populateTreePaths0(TreeItem item) {
_treeItems.add(item);
ObservableList children = item.getChildren();
for (TreeItem child : children) {
populateTreePaths0(child);
}
}
/**
* Reset the cached tree paths list.
*
* Tree paths list is only used when recursive attributes true.
*/
protected void resetTreeItems() {
_treeItems = null;
}
/**
* Gets the cached tree paths list. If it has never been cached before, this method will create the cache.
*
* Tree paths list is only used when recursive attributes true.
*
* @return the tree paths list.
*/
protected List getTreeItems() {
if (_treeItems == null) {
populateTreePaths();
}
return _treeItems;
}
/**
* Converts the element in TreeView to string. The element by default is TreePath. The returned value will be
* {@code toString()} of the last path component in the TreePath.
*
* @param object the object to be converted
* @return the string representing the TreePath in the TreeView.
*/
@Override
protected String convertElementToString(TreeItem object) {
if (object != null) {
Object value = ((TreeItem) object).getValue();
return value != null ? value.toString() : "";
}
else {
return "";
}
}
}