/*
* 10/09/2011
*
* 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.Vector;
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.
*
* @author Robert Futrell
* @version 1.0
* @see AbstractSourceTree
*/
public class SourceTreeNode extends DefaultMutableTreeNode
implements Cloneable, Comparable {
private boolean sortable;
private boolean sorted;
private Pattern pattern;
private Vector visibleChildren;
private int sortPriority;
/**
* Creates an unsorted tree node.
*
* @param userObject The user data for this tree node.
*/
public SourceTreeNode(Object userObject) {
this(userObject, false);
}
/**
* Constructor.
*
* @param userObject The user data for this tree node.
* @param sorted Whether any child nodes added to this one should be
* sorted.
*/
public SourceTreeNode(Object userObject, boolean sorted) {
super(userObject);
visibleChildren = new Vector();
setSortable(true);
setSorted(sorted);
}
/**
* 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) {
//super.add(child);
if(child!=null && child.getParent()==this) {
insert(child, super.getChildCount() - 1);
}
else {
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
* nodes.
*
* @return The clone of this node.
* @see #cloneWithChildren()
*/
public Object clone() {
SourceTreeNode node = (SourceTreeNode)super.clone();
// Not based off original, no children!
node.visibleChildren = new Vector();
return node;
}
/**
* Returns a clone of this tree node and all of its children.
*
* @return The clone of this node.
* @see #clone()
*/
public SourceTreeNode cloneWithChildren() {
SourceTreeNode clone = (SourceTreeNode)clone();
for (int i=0; i<super.getChildCount(); i++) {
clone.add(((SourceTreeNode)super.getChildAt(i)).cloneWithChildren());
}
return clone;
}
/**
* 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) {
int res = -1;
if (obj instanceof SourceTreeNode) {
SourceTreeNode stn2 = (SourceTreeNode)obj;
res = getSortPriority() - stn2.getSortPriority();
if (res==0 && ((SourceTreeNode)getParent()).isSorted()) {
res = toString().compareToIgnoreCase(stn2.toString());
}
}
return res;
}
/**
* 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) {
this.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>
* if none.
*/
public TreeNode getChildAfter(TreeNode child) {
if (child==null) {
throw new IllegalArgumentException("child cannot be null");
}
int index = getIndex(child);
if (index==-1) {
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>
* if none.
*/
public TreeNode getChildBefore(TreeNode child) {
if (child==null) {
throw new IllegalArgumentException("child cannot be null");
}
int index = getIndex(child);
if (index==-1) {
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) {
if (child==null) {
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)) {
return i;
}
}
return -1;
}
/**
* 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() {
return sortPriority;
}
/**
* 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() {
return sortable;
}
/**
* Returns whether this node is sorted.
*
* @return Whether this node is sorted.
*/
public boolean isSorted() {
return sorted;
}
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.clear();
if (children!=null) {
visibleChildren.addAll(children);
if (sortable && sorted) {
Collections.sort(visibleChildren);
}
if (pattern!=null) {
for (Iterator i=visibleChildren.iterator(); i.hasNext(); ) {
TreeNode node = (TreeNode)i.next();
if (node.isLeaf()) {
String text = node.toString();
text = Util.stripHtml(text);
if (!pattern.matcher(text).find()) {
//System.out.println(pattern + ": Removing tree node: " + text);
i.remove();
}
}
}
}
}
}
/**
* Sets whether this particular node's children are sortable. Usually,
* only tree nodes containing only "leaves" should be sorted (for example,
* a "types" node).
*
* @param sortable Whether this node's children are sortable.
* @see #isSortable()
*/
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.
* @see #isSorted()
*/
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.sorted = sorted;
// This individual node may not be sortable...
if (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;
}
}