org.fife.rsta.ac.AbstractSourceTree Maven / Gradle / Ivy
/*
* 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
* LICENSE.md 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.event.TreeExpansionListener;
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.rsta.ac.js.tree.JavaScriptOutlineTree;
import org.fife.rsta.ac.xml.tree.XmlOutlineTree;
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
* RSyntaxTextArea
. This can be used to display an "outline view"
* of the code, for example.
*
* Concrete implementations typically specialize in displaying code structure
* for a single language, and are registered to listen to code changes in an
* RSyntaxTextArea
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.
*
* 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}.
*
* @author Robert Futrell
* @version 1.0
* @see SourceTreeNode
* @see JavaOutlineTree
* @see JavaScriptOutlineTree
* @see XmlOutlineTree
*/
public abstract class AbstractSourceTree extends JTree {
protected RSyntaxTextArea textArea;
private boolean sorted;
private Pattern pattern;
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
* are visible.
*
* @return Whether an expandPath was called for the last node in the parent
* path
*/
protected boolean fastExpandAll(TreePath parent, boolean expand) {
TreeExpansionListener[] listeners = getTreeExpansionListeners();
for (TreeExpansionListener listener : listeners) {
removeTreeExpansionListener(listener);
}
boolean result = fastExpandAllImpl(parent, expand);
for (TreeExpansionListener listener : listeners) {
addTreeExpansionListener(listener);
}
// Trigger an update for the TreeExpansionListeners
collapsePath(parent);
expandPath(parent);
return result;
}
private boolean fastExpandAllImpl(TreePath parent, boolean expand) {
// Traverse children
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 fastExpandAllImpl() call won't happen in some cases.
childExpandCalled = fastExpandAllImpl(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
// non-leaf nodes
if (expand) {
expandPath(parent);
}
else {
collapsePath(parent);
}
}
return true;
}
else {
return false;
}
}
/**
* Filters visible tree nodes based on the specified prefix.
*
* @param pattern The prefix, as a wildcard expression. If this is
* null
, 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();
expandInitialNodes();
}
}
/**
* 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() {
return sorted;
}
/**
* Causes this outline tree to reflect the source code in the specified
* text area.
*
* @param textArea The text area. This should have been registered with
* the {@link LanguageSupportFactory}, and be editing the language
* we're interested in.
* @see #uninstall()
*/
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.
*/
public void refresh() {
DefaultTreeModel model = (DefaultTreeModel)getModel();
Object root = model.getRoot();
if (root instanceof SourceTreeNode) {
SourceTreeNode node = (SourceTreeNode)root;
node.refresh();
//model.nodeStructureChanged(node);
model.reload();
expandInitialNodes();
}
}
/**
* Selects the first visible tree node matching the filter text.
*/
public void selectFirstNodeMatchingFilter() {
if (pattern==null) {
return;
}
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)));
return;
}
}
}
/**
* Selects the next visible row.
*
* @see #selectPreviousVisibleRow()
*/
public void selectNextVisibleRow() {
int currentRow = getLeadSelectionRow();
if (++currentRow=0) {
TreePath path = getPathForRow(currentRow);
setSelectionPath(path);
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
* the editor.
* @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.
* @see #isSorted()
*/
public void setSorted(boolean sorted) {
if (this.sorted!=sorted) {
this.sorted = sorted;
Object root = getModel().getRoot();
if (root instanceof SourceTreeNode) {
((SourceTreeNode)root).setSorted(sorted);
}
((DefaultTreeModel)getModel()).reload();
expandInitialNodes();
}
}
/**
* Makes this outline tree stop listening to its current text area.
*
* @see #listenTo(RSyntaxTextArea)
*/
public abstract void uninstall();
}