* Copyright (C) 2011 Robert Futrell
* robert_futrell at users.sourceforge.net
* http://fifesoft.com/rsyntaxtextarea
* This library is distributed under a modified BSD license. See the included
* RSTALanguageSupport.License.txt file for details.
package org.fife.rsta.ac;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.regex.Pattern;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import org.fife.ui.autocomplete.Util;
* Base class for tree nodes in an {@link AbstractSourceTree}. They can be
* sorted and filtered based on user input.
* @see AbstractSourceTree
public class SourceTreeNode extends DefaultMutableTreeNode
implements Cloneable, Comparable {
private boolean sortable;
private Vector visibleChildren;
private int sortPriority;
* Creates an unsorted tree node.
* @param userObject The user data for this tree node.
public SourceTreeNode(Object userObject) {
* @param userObject The user data for this tree node.
* @param sorted Whether any child nodes added to this one should be
public SourceTreeNode(Object userObject, boolean sorted) {
visibleChildren = new Vector();
* Overridden to ensure the new child is only made visible if it is
* matched by the current filter.
* @param child The child node to add.
public void add(MutableTreeNode child) {
if(child!=null && child.getParent()==this) {
insert(child, super.getChildCount() - 1);
insert(child, super.getChildCount());
if (sortable && sorted) {
refreshVisibleChildren(); // TODO: Find index and add for performance
* Overridden to operate over visible children only.
* @return The visible children.
public Enumeration children() {
return visibleChildren.elements();
* Returns a clone of this tree node. The clone will not contain any child
* @return The clone of this node.
* @see #cloneWithChildren()
SourceTreeNode node = (SourceTreeNode)super.clone();
// Not based off original, no children!
node.visibleChildren = new Vector();
* Returns a clone of this tree node and all of its children.
* @return The clone of this node.
public SourceTreeNode cloneWithChildren() {
SourceTreeNode clone = (SourceTreeNode)clone();
for (int i=0; i<super.getChildCount(); i++) {
clone.add(((SourceTreeNode)super.getChildAt(i)).cloneWithChildren());
* Overridden to provide proper sorting of source tree nodes when the
* parent <code>AbstractSourceTree</code> is sorted. Sorting is done first
* by priority, and nodes with equal priority are then sorted by their
* string representations, ignoring case. Subclasses can override this
* method if they wish to do more intricate sorting.
* @param obj A tree node to compare to.
* @return How these tree nodes compare relative to each other.
public int compareTo(Object obj) {
if (obj instanceof SourceTreeNode) {
SourceTreeNode stn2 = (SourceTreeNode)obj;
res = getSortPriority() - stn2.getSortPriority();
if (res==0 && ((SourceTreeNode)getParent()).isSorted()) {
res = toString().compareToIgnoreCase(stn2.toString());
* Filters the children of this tree node based on the specified prefix.
* @param pattern The pattern that the child nodes must match. If this is
* <code>null</code>, all possible children are shown.
protected void filter(Pattern pattern) {
refreshVisibleChildren();
for (int i=0; i<super.getChildCount(); i++) {
Object child = children.get(i);
if (child instanceof SourceTreeNode) {
((SourceTreeNode)child).filter(pattern);
* Overridden to operate over visible children only.
* @param child The child node.
* @return The visible child after the specified node, or <code>null</code>
public TreeNode getChildAfter(TreeNode child) {
throw new IllegalArgumentException("child cannot be null");
int index = getIndex(child);
throw new IllegalArgumentException("child node not contained");
return index<getChildCount()-1 ? getChildAt(index+1) : null;
* Overridden to operate over visible children only.
* @param index The index of the visible child to retrieve.
* @return The visible child after the specified index.
public TreeNode getChildAt(int index) {
//System.out.println(index);
return (TreeNode)visibleChildren.get(index);
* Overridden to operate over visible children only.
* @param child The child node.
* @return The visible child before the specified node, or <code>null</code>
public TreeNode getChildBefore(TreeNode child) {
throw new IllegalArgumentException("child cannot be null");
int index = getIndex(child);
throw new IllegalArgumentException("child node not contained");
return index> 0 ? getChildAt(index - 1) : null;
* Overridden to operate over visible children only.
* @return The number of visible child nodes.
public int getChildCount() {
return visibleChildren.size();
* Overridden to operate over visible children only.
* @param child The child node.
* @return The index of the child, if it is visible. If the child node is
* not contained in this tree, or is simply not visible,
* <code>-1</code> is returned.
public int getIndex(TreeNode child) {
throw new IllegalArgumentException("child cannot be null");
for (int i=0; i<visibleChildren.size(); i++) {
TreeNode node = (TreeNode)visibleChildren.get(i);
if (node.equals(child)) {
* Returns the relative priority of this node against others when being
* sorted (lower is higher priority).
* @return The relative priority.
* @see #setSortPriority(int)
public int getSortPriority() {
* Returns whether this particular node's children can be sorted.
* @return Whether this node's children can be sorted.
* @see #setSortable(boolean)
public boolean isSortable() {
* Returns whether this node is sorted.
* @return Whether this node is sorted.
public boolean isSorted() {
protected void refresh() {
refreshVisibleChildren();
for (int i=0; i<getChildCount(); i++) {
TreeNode child = getChildAt(i);
if (child instanceof SourceTreeNode) {
((SourceTreeNode)child).refresh();
* Refreshes what children are visible in the tree.
private void refreshVisibleChildren() {
visibleChildren.addAll(children);
if (sortable && sorted) {
Collections.sort(visibleChildren);
for (Iterator i=visibleChildren.iterator(); i.hasNext(); ) {
TreeNode node = (TreeNode)i.next();
String text = node.toString();
text = Util.stripHtml(text);
if (!pattern.matcher(text).find()) {
//System.out.println(pattern + ": Removing tree node: " + text);
* Sets whether this particular node's children are sortable. Usually,
* only tree nodes containing only "leaves" should be sorted (for example,
* @param sortable Whether this node's children are sortable.
public void setSortable(boolean sortable) {
this.sortable = sortable;
* Sets whether this tree node (and any child sortable tree nodes) are
* sorting their children.
* @param sorted Whether sorting is enabled.
public void setSorted(boolean sorted) {
if (sorted!=this.sorted) {
// We must keep this state, even if we're not sortable, so that
// we can know when to toggle the sortable state of our children.
// This individual node may not be sortable...
refreshVisibleChildren();
// But its children could still be.
for (int i=0; i<super.getChildCount(); i++) {
Object child = children.get(i);
if (child instanceof SourceTreeNode) {
((SourceTreeNode)child).setSorted(sorted);
* Sets the relative sort priority of this tree node when it is compared
* against others (lower is higher priority).
* @param priority The relative priority.
* @see #getSortPriority()
public void setSortPriority(int priority) {
this.sortPriority = priority;