/* * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Oracle nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* * This source code is provided to illustrate the usage of a given feature * or technique and has been deliberately simplified. Additional steps * required for a production-quality application, such as security checks, * input validation and proper error handling, might not be present in * this sample code.
*/
/** * Displays a tree showing all the elements in a text Document. Selecting * a node will result in reseting the selection of the JTextComponent. * This also becomes a CaretListener to know when the selection has changed * in the text to update the selected item in the tree. * * @author Scott Violet
*/
@SuppressWarnings("serial") publicclass ElementTreePanel extends JPanel implements CaretListener,
DocumentListener, PropertyChangeListener, TreeSelectionListener {
/** Tree showing the documents element structure. */ protected JTree tree; /** Text component showing elemenst for. */ protected JTextComponent editor; /** Model for the tree. */ protected ElementTreeModel treeModel; /** Set to true when updatin the selection. */ protectedboolean updatingSelection;
@SuppressWarnings("LeakingThisInConstructor") public ElementTreePanel(JTextComponent editor) { this.editor = editor;
Document document = editor.getDocument();
// Create the tree.
treeModel = new ElementTreeModel(document);
tree = new JTree(treeModel) {
@Override public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { // Should only happen for the root if (!(value instanceof Element)) { return value.toString();
}
Element e = (Element) value;
AttributeSet as = e.getAttributes().copyAttributes();
String asString;
if (as != null) {
StringBuilder retBuffer = new StringBuilder("[");
Enumeration<?> names = as.getAttributeNames();
while (names.hasMoreElements()) {
Object nextName = names.nextElement();
if (e.isLeaf()) { return e.getName() + " [" + e.getStartOffset() + ", " + e.
getEndOffset() + "] Attributes: " + asString;
} return e.getName() + " [" + e.getStartOffset() + ", " + e.
getEndOffset() + "] Attributes: " + asString;
}
};
tree.addTreeSelectionListener(this);
tree.setDragEnabled(true); // Don't show the root, it is fake.
tree.setRootVisible(false); // Since the display value of every node after the insertion point // changes every time the text changes and we don't generate a change // event for all those nodes the display value can become off. // This can be seen as '...' instead of the complete string value. // This is a temporary workaround, increase the needed size by 15, // hoping that will be enough.
tree.setCellRenderer(new DefaultTreeCellRenderer() {
@Override public Dimension getPreferredSize() {
Dimension retValue = super.getPreferredSize(); if (retValue != null) {
retValue.width += 15;
} return retValue;
}
}); // become a listener on the document to update the tree.
document.addDocumentListener(this);
// become a PropertyChangeListener to know when the Document has // changed.
editor.addPropertyChangeListener(this);
// Become a CaretListener
editor.addCaretListener(this);
// configure the panel and frame containing it.
setLayout(new BorderLayout());
add(new JScrollPane(tree), BorderLayout.CENTER);
// Add a label above tree to describe what is being shown
JLabel label = new JLabel("Elements that make up the current document",
SwingConstants.CENTER);
/** * Resets the JTextComponent to <code>editor</code>. This will update * the tree accordingly.
*/ publicvoid setEditor(JTextComponent editor) { if (this.editor == editor) { return;
}
if (this.editor != null) {
Document oldDoc = this.editor.getDocument();
newDoc.addDocumentListener(this);
editor.addPropertyChangeListener(this);
editor.addCaretListener(this);
treeModel = new ElementTreeModel(newDoc);
tree.setModel(treeModel);
}
}
// PropertyChangeListener /** * Invoked when a property changes. We are only interested in when the * Document changes to reset the DocumentListener.
*/ publicvoid propertyChange(PropertyChangeEvent e) { if (e.getSource() == getEditor() && e.getPropertyName().equals( "document")) {
Document oldDoc = (Document) e.getOldValue();
Document newDoc = (Document) e.getNewValue();
// Reset the DocumentListener
oldDoc.removeDocumentListener(this);
newDoc.addDocumentListener(this);
// Recreate the TreeModel.
treeModel = new ElementTreeModel(newDoc);
tree.setModel(treeModel);
}
}
// DocumentListener /** * Gives notification that there was an insert into the document. The * given range bounds the freshly inserted region. * * @param e the document event
*/ publicvoid insertUpdate(DocumentEvent e) {
updateTree(e);
}
/** * Gives notification that a portion of the document has been * removed. The range is given in terms of what the view last * saw (that is, before updating sticky positions). * * @param e the document event
*/ publicvoid removeUpdate(DocumentEvent e) {
updateTree(e);
}
/** * Gives notification that an attribute or set of attributes changed. * * @param e the document event
*/ publicvoid changedUpdate(DocumentEvent e) {
updateTree(e);
}
// CaretListener /** * Messaged when the selection in the editor has changed. Will update * the selection in the tree.
*/ publicvoid caretUpdate(CaretEvent e) { if (!updatingSelection) { int selBegin = Math.min(e.getDot(), e.getMark()); int end = Math.max(e.getDot(), e.getMark());
List<TreePath> paths = new ArrayList<TreePath>();
TreeModel model = getTreeModel();
Object root = model.getRoot(); int rootCount = model.getChildCount(root);
// Build an array of all the paths to all the character elements // in the selection. for (int counter = 0; counter < rootCount; counter++) { int start = selBegin;
while (start <= end) {
TreePath path = getPathForIndex(start, root,
(Element) model.getChild(root, counter));
Element charElement = (Element) path.getLastPathComponent();
// TreeSelectionListener /** * Called whenever the value of the selection changes. * @param e the event that characterizes the change.
*/ publicvoid valueChanged(TreeSelectionEvent e) {
/** * @return TreeModel implementation used to represent the elements.
*/ public DefaultTreeModel getTreeModel() { return treeModel;
}
/** * Updates the tree based on the event type. This will invoke either * updateTree with the root element, or handleChange.
*/ protectedvoid updateTree(DocumentEvent event) {
updatingSelection = true; try {
TreeModel model = getTreeModel();
Object root = model.getRoot();
/** * Creates TreeModelEvents based on the DocumentEvent and messages * the treemodel. This recursively invokes this method with children * elements. * @param event indicates what elements in the tree hierarchy have * changed. * @param element Current element to check for changes against.
*/ protectedvoid updateTree(DocumentEvent event, Element element) {
DocumentEvent.ElementChange ec = event.getChange(element);
if (ec != null) {
Element[] removed = ec.getChildrenRemoved();
Element[] added = ec.getChildrenAdded(); int startIndex = ec.getIndex();
// Check for removed. if (removed != null && removed.length > 0) { int[] indices = newint[removed.length];
for (int counter = 0; counter < removed.length; counter++) {
indices[counter] = startIndex + counter;
}
getTreeModel().nodesWereRemoved((TreeNode) element, indices,
removed);
} // check for added if (added != null && added.length > 0) { int[] indices = newint[added.length];
for (int counter = 0; counter < added.length; counter++) {
indices[counter] = startIndex + counter;
}
getTreeModel().nodesWereInserted((TreeNode) element, indices);
}
} if (!element.isLeaf()) { int startIndex = element.getElementIndex(event.getOffset()); int elementCount = element.getElementCount(); int endIndex = Math.min(elementCount - 1,
element.getElementIndex(event.getOffset()
+ event.getLength()));
if (startIndex > 0 && startIndex < elementCount && element.
getElement(startIndex).getStartOffset() == event.getOffset()) { // Force checking the previous element.
startIndex--;
} if (startIndex != -1 && endIndex != -1) { for (int counter = startIndex; counter <= endIndex; counter++) {
updateTree(event, element.getElement(counter));
}
}
} else { // Element is a leaf, assume it changed
getTreeModel().nodeChanged((TreeNode) element);
}
}
/** * Returns a TreePath to the element at <code>position</code>.
*/ protected TreePath getPathForIndex(int position, Object root,
Element rootElement) {
TreePath path = new TreePath(root);
Element child = rootElement.getElement(rootElement.getElementIndex(
position));
/** * ElementTreeModel is an implementation of TreeModel to handle displaying * the Elements from a Document. AbstractDocument.AbstractElement is * the default implementation used by the swing text package to implement * Element, and it implements TreeNode. This makes it trivial to create * a DefaultTreeModel rooted at a particular Element from the Document. * Unfortunately each Document can have more than one root Element. * Implying that to display all the root elements as a child of another * root a fake node has be created. This class creates a fake node as * the root with the children being the root elements of the Document * (getRootElements). * <p>This subclasses DefaultTreeModel. The majority of the TreeModel * methods have been subclassed, primarily to special case the root.
*/ publicstaticclass ElementTreeModel extends DefaultTreeModel {
/** * Returns the child of <I>parent</I> at index <I>index</I> in * the parent's child array. <I>parent</I> must be a node * previously obtained from this data source. This should * not return null if <i>index</i> is a valid index for * <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i> * < getChildCount(<i>parent</i>)). * * @param parent a node in the tree, obtained from this data source * @return the child of <I>parent</I> at index <I>index</I>
*/
@Override public Object getChild(Object parent, int index) { if (parent == root) { return rootElements[index];
} returnsuper.getChild(parent, index);
}
/** * Returns the number of children of <I>parent</I>. Returns 0 * if the node is a leaf or if it has no children. * <I>parent</I> must be a node previously obtained from this * data source. * * @param parent a node in the tree, obtained from this data source * @return the number of children of the node <I>parent</I>
*/
@Override publicint getChildCount(Object parent) { if (parent == root) { return rootElements.length;
} returnsuper.getChildCount(parent);
}
/** * Returns true if <I>node</I> is a leaf. It is possible for * this method to return false even if <I>node</I> has no * children. A directory in a filesystem, for example, may * contain no files; the node representing the directory is * not a leaf, but it also has no children. * * @param node a node in the tree, obtained from this data source * @return true if <I>node</I> is a leaf
*/
@Override publicboolean isLeaf(Object node) { if (node == root) { returnfalse;
} returnsuper.isLeaf(node);
}
/** * Returns the index of child in parent.
*/
@Override publicint getIndexOfChild(Object parent, Object child) { if (parent == root) { for (int counter = rootElements.length - 1; counter >= 0;
counter--) { if (rootElements[counter] == child) { return counter;
}
} return -1;
} returnsuper.getIndexOfChild(parent, child);
}
/** * Invoke this method after you've changed how node is to be * represented in the tree.
*/
@Override publicvoid nodeChanged(TreeNode node) { if (listenerList != null && node != null) {
TreeNode parent = node.getParent();
if (parent == null && node != root) {
parent = root;
} if (parent != null) { int anIndex = getIndexOfChild(parent, node);
/** * Returns the path to a particluar node. This is recursive.
*/
@Override protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
TreeNode[] retNodes;
/* Check for null, in case someone passed in a null node, or
they passed in an element that isn't rooted at root. */ if (aNode == null) { if (depth == 0) { returnnull;
} else {
retNodes = new TreeNode[depth];
}
} else {
depth++; if (aNode == root) {
retNodes = new TreeNode[depth];
} else {
TreeNode parent = aNode.getParent();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.