* 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.Enumeration;
import java.util.regex.Pattern;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.fife.rsta.ac.java.tree.JavaOutlineTree;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
* A tree showing the structure of a source file being edited in an
* <code>RSyntaxTextArea</code>. This can be used to display an "outline view"
* of the code, for example.<p>
* Concrete implementations typically specialize in displaying code structure
* for a single language, and are registered to listen to code changes in an
* <code>RSyntaxTextArea</code> instance by calling
* {@link #listenTo(RSyntaxTextArea)}. They should then listen to document
* changes and adjust themselves to reflect the code structure of the current
* content as best as possible.<p>
* You should only add instances of {@link SourceTreeNode} or subclasses to
* this tree. You should also provide a no-argument constructor if you wish to
* use your subclass in {@link GoToMemberAction}.
* @see JavaScriptOutlineTree
public abstract class AbstractSourceTree extends JTree {
protected RSyntaxTextArea textArea;
private boolean gotoSelectedElementOnClick;
private boolean showMajorElementsOnly;
public AbstractSourceTree() {
getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
gotoSelectedElementOnClick = true;
showMajorElementsOnly = false;
* Expands all nodes in the specified tree. Subclasses should implement
* this in a way logical for the language.
public abstract void expandInitialNodes();
* An attempt to quickly expand all tree nodes. Only need to expand the
* "deepest" nodes, as they will auto-expand all parents to make sure they
* @return Whether an expandPath was called for the last node in the parent
protected boolean fastExpandAll(TreePath parent, boolean expand) {
TreeNode node = (TreeNode) parent.getLastPathComponent();
if (node.getChildCount() > 0) {
boolean childExpandCalled = false;
for (Enumeration e = node.children(); e.hasMoreElements();) {
TreeNode n = (TreeNode) e.nextElement();
TreePath path = parent.pathByAddingChild(n);
// The || order is important, don't let childExpand be first,
// or the fastExpandAll() call won't happen in some cases.
childExpandCalled = fastExpandAll(path, expand) || childExpandCalled;
// Only expand me if one of the children hasn't already expanded
// its path (which includes me).
if (!childExpandCalled) {
// Expansion or collapse must be done bottom-up, BUT only for
* Filters visible tree nodes based on the specified prefix.
* @param pattern The prefix, as a wildcard expression. If this is
* <code>null</code>, all possible children are shown.
public void filter(String pattern) {
if ((pattern==null && this.pattern!=null) ||
(pattern!=null && this.pattern==null) ||
(pattern!=null && !pattern.equals(this.pattern.pattern()))) {
this.pattern = (pattern==null || pattern.length()==0) ? null :
RSyntaxUtilities.wildcardToPattern("^" + pattern, false, false);
Object root = getModel().getRoot();
if (root instanceof SourceTreeNode) {
((SourceTreeNode)root).filter(this.pattern);
((DefaultTreeModel)getModel()).reload();
* Returns whether, when a source element is selected in this tree, the
* same source element should be selected in the editor.
* @return Whether to highlight the source element in the editor.
* @see #setGotoSelectedElementOnClick(boolean)
public boolean getGotoSelectedElementOnClick() {
return gotoSelectedElementOnClick;
* Returns whether only "major" structural elements are shown in this
* source tree. An example of a "minor" element could be a local variable
* in a function or method.
* @return Whether only major elements are shown in this source tree.
* @see #setShowMajorElementsOnly(boolean)
public boolean getShowMajorElementsOnly() {
return showMajorElementsOnly;
* Highlights the selected source element in the text editor, if any.
* @return Whether anything was selected in the tree.
public abstract boolean gotoSelectedElement();
* Returns whether the contents of this tree are sorted.
* @return Whether the contents of this tree are sorted.
* @see #setSorted(boolean)
public boolean isSorted() {
* Causes this outline tree to reflect the source code in the specified
* @param textArea The text area. This should have been registered with
* the {@link LanguageSupportFactory}, and be editing the language
public abstract void listenTo(RSyntaxTextArea textArea);
* Refreshes what children are visible in the tree. This should be called
* manually when updating a source tree with a new root, and is also called
* internally on filtering and sorting.
Object root = getModel().getRoot();
if (root instanceof SourceTreeNode) {
SourceTreeNode node = (SourceTreeNode)root;
((DefaultTreeModel)getModel()).reload();
* Selects the first visible tree node matching the filter text.
public void selectFirstNodeMatchingFilter() {
DefaultTreeModel model = (DefaultTreeModel)getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
Enumeration en = root.depthFirstEnumeration();
while (en.hasMoreElements()) {
SourceTreeNode stn = (SourceTreeNode)en.nextElement();
JLabel renderer = (JLabel)getCellRenderer().
getTreeCellRendererComponent(this, stn,
true, true, stn.isLeaf(), 0, true);
String text = renderer.getText();
if (text!=null && pattern.matcher(text).find()) {
setSelectionPath(new TreePath(model.getPathToRoot(stn)));
* Selects the next visible row.
* @see #selectPreviousVisibleRow()
public void selectNextVisibleRow() {
int currentRow = getLeadSelectionRow();
if (++currentRow<getRowCount()) {
TreePath path = getPathForRow(currentRow);
scrollPathToVisible(path);
* Selects the previous visible row.
* @see #selectNextVisibleRow()
public void selectPreviousVisibleRow() {
int currentRow = getLeadSelectionRow();
TreePath path = getPathForRow(currentRow);
scrollPathToVisible(path);
* Sets whether, when a source element is selected in this tree, the
* same source element should be selected in the editor.
* @param gotoSelectedElement Whether to highlight the source element in
* @see #getGotoSelectedElementOnClick()
public void setGotoSelectedElementOnClick(boolean gotoSelectedElement) {
gotoSelectedElementOnClick = gotoSelectedElement;
* Toggles whether only "major" structural elements should be shown in this
* source tree. An example of a "minor" element could be a local variable
* in a function or method.
* @param show Whether only major elements are shown in this source tree.
* @see #getShowMajorElementsOnly()
public void setShowMajorElementsOnly(boolean show) {
showMajorElementsOnly = show;
* Toggles whether the contents of this tree are sorted.
* @param sorted Whether the contents of this tree are sorted.
public void setSorted(boolean sorted) {
if (this.sorted!=sorted) {
Object root = getModel().getRoot();
if (root instanceof SourceTreeNode) {
((SourceTreeNode)root).setSorted(sorted);
((DefaultTreeModel)getModel()).reload();
* Makes this outline tree stop listening to its current text area.
* @see #listenTo(RSyntaxTextArea)
public abstract void uninstall();