KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > ElementTreePanel


1 /*
2  * @(#)ElementTreePanel.java 1.17 05/11/17
3  *
4  * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * -Redistribution of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  *
12  * -Redistribution in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of contributors may
17  * be used to endorse or promote products derived from this software without
18  * specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
22  * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23  * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
24  * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
25  * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
26  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
27  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
28  * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
29  * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
30  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31  *
32  * You acknowledge that this software is not designed, licensed or intended
33  * for use in the design, construction, operation or maintenance of any
34  * nuclear facility.
35  */

36
37 /*
38  * @(#)ElementTreePanel.java 1.17 05/11/17
39  */

40
41 import javax.swing.*;
42 import javax.swing.event.*;
43 import javax.swing.text.*;
44 import javax.swing.tree.*;
45 import javax.swing.undo.*;
46 import java.awt.*;
47 import java.beans.*;
48 import java.util.*;
49
50 /**
51  * Displays a tree showing all the elements in a text Document. Selecting
52  * a node will result in reseting the selection of the JTextComponent.
53  * This also becomes a CaretListener to know when the selection has changed
54  * in the text to update the selected item in the tree.
55  *
56  * @author Scott Violet
57  * @version 1.17 11/17/05
58  */

59 public class ElementTreePanel extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener, TreeSelectionListener {
60     /** Tree showing the documents element structure. */
61     protected JTree tree;
62     /** Text component showing elemenst for. */
63     protected JTextComponent editor;
64     /** Model for the tree. */
65     protected ElementTreeModel treeModel;
66     /** Set to true when updatin the selection. */
67     protected boolean updatingSelection;
68
69     public ElementTreePanel(JTextComponent editor) {
70     this.editor = editor;
71
72     Document document = editor.getDocument();
73
74     // Create the tree.
75
treeModel = new ElementTreeModel(document);
76     tree = new JTree(treeModel) {
77         public String JavaDoc convertValueToText(Object JavaDoc value, boolean selected,
78                          boolean expanded, boolean leaf,
79                          int row, boolean hasFocus) {
80         // Should only happen for the root
81
if(!(value instanceof Element))
82             return value.toString();
83
84         Element e = (Element)value;
85         AttributeSet as = e.getAttributes().copyAttributes();
86         String JavaDoc asString;
87
88         if(as != null) {
89             StringBuffer JavaDoc retBuffer = new StringBuffer JavaDoc("[");
90             Enumeration names = as.getAttributeNames();
91
92             while(names.hasMoreElements()) {
93             Object JavaDoc nextName = names.nextElement();
94
95             if(nextName != StyleConstants.ResolveAttribute) {
96                 retBuffer.append(" ");
97                 retBuffer.append(nextName);
98                 retBuffer.append("=");
99                 retBuffer.append(as.getAttribute(nextName));
100             }
101             }
102             retBuffer.append(" ]");
103             asString = retBuffer.toString();
104         }
105         else
106             asString = "[ ]";
107
108         if(e.isLeaf())
109             return e.getName() + " [" + e.getStartOffset() +
110             ", " + e.getEndOffset() +"] Attributes: " + asString;
111         return e.getName() + " [" + e.getStartOffset() +
112             ", " + e.getEndOffset() + "] Attributes: " +
113                 asString;
114         }
115     };
116     tree.addTreeSelectionListener(this);
117     tree.setDragEnabled(true);
118     // Don't show the root, it is fake.
119
tree.setRootVisible(false);
120     // Since the display value of every node after the insertion point
121
// changes every time the text changes and we don't generate a change
122
// event for all those nodes the display value can become off.
123
// This can be seen as '...' instead of the complete string value.
124
// This is a temporary workaround, increase the needed size by 15,
125
// hoping that will be enough.
126
tree.setCellRenderer(new DefaultTreeCellRenderer() {
127         public Dimension getPreferredSize() {
128         Dimension retValue = super.getPreferredSize();
129         if(retValue != null)
130             retValue.width += 15;
131         return retValue;
132         }
133     });
134     // become a listener on the document to update the tree.
135
document.addDocumentListener(this);
136
137     // become a PropertyChangeListener to know when the Document has
138
// changed.
139
editor.addPropertyChangeListener(this);
140
141     // Become a CaretListener
142
editor.addCaretListener(this);
143
144     // configure the panel and frame containing it.
145
setLayout(new BorderLayout());
146     add(new JScrollPane(tree), BorderLayout.CENTER);
147
148     // Add a label above tree to describe what is being shown
149
JLabel label = new JLabel("Elements that make up the current document", SwingConstants.CENTER);
150
151     label.setFont(new Font("Dialog", Font.BOLD, 14));
152     add(label, BorderLayout.NORTH);
153
154     setPreferredSize(new Dimension(400, 400));
155     }
156
157     /**
158      * Resets the JTextComponent to <code>editor</code>. This will update
159      * the tree accordingly.
160      */

161     public void setEditor(JTextComponent editor) {
162     if (this.editor == editor) {
163         return;
164     }
165
166     if (this.editor != null) {
167         Document oldDoc = this.editor.getDocument();
168
169         oldDoc.removeDocumentListener(this);
170         this.editor.removePropertyChangeListener(this);
171         this.editor.removeCaretListener(this);
172     }
173     this.editor = editor;
174     if (editor == null) {
175         treeModel = null;
176         tree.setModel(null);
177     }
178     else {
179         Document newDoc = editor.getDocument();
180
181         newDoc.addDocumentListener(this);
182         editor.addPropertyChangeListener(this);
183         editor.addCaretListener(this);
184         treeModel = new ElementTreeModel(newDoc);
185         tree.setModel(treeModel);
186     }
187     }
188
189     // PropertyChangeListener
190

191     /**
192      * Invoked when a property changes. We are only interested in when the
193      * Document changes to reset the DocumentListener.
194      */

195     public void propertyChange(PropertyChangeEvent e) {
196     if (e.getSource() == getEditor() &&
197         e.getPropertyName().equals("document")) {
198         JTextComponent editor = getEditor();
199         Document oldDoc = (Document)e.getOldValue();
200         Document newDoc = (Document)e.getNewValue();
201
202         // Reset the DocumentListener
203
oldDoc.removeDocumentListener(this);
204         newDoc.addDocumentListener(this);
205
206         // Recreate the TreeModel.
207
treeModel = new ElementTreeModel(newDoc);
208         tree.setModel(treeModel);
209     }
210     }
211
212
213     // DocumentListener
214

215     /**
216      * Gives notification that there was an insert into the document. The
217      * given range bounds the freshly inserted region.
218      *
219      * @param e the document event
220      */

221     public void insertUpdate(DocumentEvent e) {
222     updateTree(e);
223     }
224
225     /**
226      * Gives notification that a portion of the document has been
227      * removed. The range is given in terms of what the view last
228      * saw (that is, before updating sticky positions).
229      *
230      * @param e the document event
231      */

232     public void removeUpdate(DocumentEvent e) {
233     updateTree(e);
234     }
235
236     /**
237      * Gives notification that an attribute or set of attributes changed.
238      *
239      * @param e the document event
240      */

241     public void changedUpdate(DocumentEvent e) {
242     updateTree(e);
243     }
244
245     // CaretListener
246

247     /**
248      * Messaged when the selection in the editor has changed. Will update
249      * the selection in the tree.
250      */

251     public void caretUpdate(CaretEvent e) {
252     if(!updatingSelection) {
253         JTextComponent editor = getEditor();
254         int selBegin = Math.min(e.getDot(), e.getMark());
255         int end = Math.max(e.getDot(), e.getMark());
256         Vector paths = new Vector();
257         TreeModel model = getTreeModel();
258         Object JavaDoc root = model.getRoot();
259         int rootCount = model.getChildCount(root);
260
261         // Build an array of all the paths to all the character elements
262
// in the selection.
263
for(int counter = 0; counter < rootCount; counter++) {
264         int start = selBegin;
265
266         while(start <= end) {
267             TreePath path = getPathForIndex(start, root,
268                        (Element)model.getChild(root, counter));
269             Element charElement = (Element)path.
270                                    getLastPathComponent();
271
272             paths.addElement(path);
273             if(start >= charElement.getEndOffset())
274             start++;
275             else
276             start = charElement.getEndOffset();
277         }
278         }
279
280         // If a path was found, select it (them).
281
int numPaths = paths.size();
282
283         if(numPaths > 0) {
284         TreePath[] pathArray = new TreePath[numPaths];
285
286         paths.copyInto(pathArray);
287         updatingSelection = true;
288         try {
289             getTree().setSelectionPaths(pathArray);
290             getTree().scrollPathToVisible(pathArray[0]);
291         }
292         finally {
293             updatingSelection = false;
294         }
295         }
296     }
297     }
298
299     // TreeSelectionListener
300

301     /**
302       * Called whenever the value of the selection changes.
303       * @param e the event that characterizes the change.
304       */

305     public void valueChanged(TreeSelectionEvent e) {
306     JTree tree = getTree();
307
308     if(!updatingSelection && tree.getSelectionCount() == 1) {
309         TreePath selPath = tree.getSelectionPath();
310         Object JavaDoc lastPathComponent = selPath.getLastPathComponent();
311
312         if(!(lastPathComponent instanceof DefaultMutableTreeNode)) {
313         Element selElement = (Element)lastPathComponent;
314
315         updatingSelection = true;
316         try {
317             getEditor().select(selElement.getStartOffset(),
318                        selElement.getEndOffset());
319         }
320         finally {
321             updatingSelection = false;
322         }
323         }
324     }
325     }
326
327     // Local methods
328

329     /**
330      * @return tree showing elements.
331      */

332     protected JTree getTree() {
333     return tree;
334     }
335
336     /**
337      * @return JTextComponent showing elements for.
338      */

339     protected JTextComponent getEditor() {
340     return editor;
341     }
342
343     /**
344      * @return TreeModel implementation used to represent the elements.
345      */

346     public DefaultTreeModel getTreeModel() {
347     return treeModel;
348     }
349
350     /**
351      * Updates the tree based on the event type. This will invoke either
352      * updateTree with the root element, or handleChange.
353      */

354     protected void updateTree(DocumentEvent event) {
355     updatingSelection = true;
356     try {
357         TreeModel model = getTreeModel();
358         Object JavaDoc root = model.getRoot();
359
360         for(int counter = model.getChildCount(root) - 1; counter >= 0;
361         counter--) {
362         updateTree(event, (Element)model.getChild(root, counter));
363         }
364     }
365     finally {
366         updatingSelection = false;
367     }
368     }
369
370     /**
371      * Creates TreeModelEvents based on the DocumentEvent and messages
372      * the treemodel. This recursively invokes this method with children
373      * elements.
374      * @param event indicates what elements in the tree hierarchy have
375      * changed.
376      * @param element Current element to check for changes against.
377      */

378     protected void updateTree(DocumentEvent event, Element element) {
379         DocumentEvent.ElementChange ec = event.getChange(element);
380
381         if (ec != null) {
382         Element[] removed = ec.getChildrenRemoved();
383         Element[] added = ec.getChildrenAdded();
384         int startIndex = ec.getIndex();
385
386         // Check for removed.
387
if(removed != null && removed.length > 0) {
388         int[] indices = new int[removed.length];
389
390         for(int counter = 0; counter < removed.length; counter++) {
391             indices[counter] = startIndex + counter;
392         }
393         getTreeModel().nodesWereRemoved((TreeNode)element, indices,
394                         removed);
395         }
396         // check for added
397
if(added != null && added.length > 0) {
398         int[] indices = new int[added.length];
399
400         for(int counter = 0; counter < added.length; counter++) {
401             indices[counter] = startIndex + counter;
402         }
403         getTreeModel().nodesWereInserted((TreeNode)element, indices);
404         }
405         }
406     if(!element.isLeaf()) {
407         int startIndex = element.getElementIndex
408                                (event.getOffset());
409         int elementCount = element.getElementCount();
410         int endIndex = Math.min(elementCount - 1,
411                        element.getElementIndex
412                      (event.getOffset() + event.getLength()));
413
414         if(startIndex > 0 && startIndex < elementCount &&
415            element.getElement(startIndex).getStartOffset() ==
416            event.getOffset()) {
417         // Force checking the previous element.
418
startIndex--;
419         }
420         if(startIndex != -1 && endIndex != -1) {
421         for(int counter = startIndex; counter <= endIndex; counter++) {
422             updateTree(event, element.getElement(counter));
423         }
424         }
425     }
426     else {
427         // Element is a leaf, assume it changed
428
getTreeModel().nodeChanged((TreeNode)element);
429     }
430     }
431
432     /**
433      * Returns a TreePath to the element at <code>position</code>.
434      */

435     protected TreePath getPathForIndex(int position, Object JavaDoc root,
436                        Element rootElement) {
437     TreePath path = new TreePath(root);
438     Element child = rootElement.getElement
439                                 (rootElement.getElementIndex(position));
440
441     path = path.pathByAddingChild(rootElement);
442     path = path.pathByAddingChild(child);
443     while(!child.isLeaf()) {
444         child = child.getElement(child.getElementIndex(position));
445         path = path.pathByAddingChild(child);
446     }
447     return path;
448     }
449
450
451     /**
452      * ElementTreeModel is an implementation of TreeModel to handle displaying
453      * the Elements from a Document. AbstractDocument.AbstractElement is
454      * the default implementation used by the swing text package to implement
455      * Element, and it implements TreeNode. This makes it trivial to create
456      * a DefaultTreeModel rooted at a particular Element from the Document.
457      * Unfortunately each Document can have more than one root Element.
458      * Implying that to display all the root elements as a child of another
459      * root a fake node has be created. This class creates a fake node as
460      * the root with the children being the root elements of the Document
461      * (getRootElements).
462      * <p>This subclasses DefaultTreeModel. The majority of the TreeModel
463      * methods have been subclassed, primarily to special case the root.
464      */

465     public static class ElementTreeModel extends DefaultTreeModel {
466     protected Element[] rootElements;
467
468     public ElementTreeModel(Document document) {
469         super(new DefaultMutableTreeNode("root"), false);
470         rootElements = document.getRootElements();
471     }
472
473     /**
474      * Returns the child of <I>parent</I> at index <I>index</I> in
475      * the parent's child array. <I>parent</I> must be a node
476      * previously obtained from this data source. This should
477      * not return null if <i>index</i> is a valid index for
478      * <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i>
479      * < getChildCount(<i>parent</i>)).
480      *
481      * @param parent a node in the tree, obtained from this data source
482      * @return the child of <I>parent</I> at index <I>index</I>
483      */

484     public Object JavaDoc getChild(Object JavaDoc parent, int index) {
485         if(parent == root)
486         return rootElements[index];
487         return super.getChild(parent, index);
488     }
489
490
491     /**
492      * Returns the number of children of <I>parent</I>. Returns 0
493      * if the node is a leaf or if it has no children.
494      * <I>parent</I> must be a node previously obtained from this
495      * data source.
496      *
497      * @param parent a node in the tree, obtained from this data source
498      * @return the number of children of the node <I>parent</I>
499      */

500     public int getChildCount(Object JavaDoc parent) {
501         if(parent == root)
502         return rootElements.length;
503         return super.getChildCount(parent);
504     }
505
506
507     /**
508      * Returns true if <I>node</I> is a leaf. It is possible for
509      * this method to return false even if <I>node</I> has no
510      * children. A directory in a filesystem, for example, may
511      * contain no files; the node representing the directory is
512      * not a leaf, but it also has no children.
513      *
514      * @param node a node in the tree, obtained from this data source
515      * @return true if <I>node</I> is a leaf
516      */

517     public boolean isLeaf(Object JavaDoc node) {
518         if(node == root)
519         return false;
520         return super.isLeaf(node);
521     }
522
523     /**
524      * Returns the index of child in parent.
525      */

526     public int getIndexOfChild(Object JavaDoc parent, Object JavaDoc child) {
527         if(parent == root) {
528         for(int counter = rootElements.length - 1; counter >= 0;
529             counter--) {
530             if(rootElements[counter] == child)
531             return counter;
532         }
533         return -1;
534         }
535         return super.getIndexOfChild(parent, child);
536     }
537
538     /**
539      * Invoke this method after you've changed how node is to be
540      * represented in the tree.
541      */

542     public void nodeChanged(TreeNode node) {
543         if(listenerList != null && node != null) {
544         TreeNode parent = node.getParent();
545
546         if(parent == null && node != root) {
547             parent = root;
548         }
549         if(parent != null) {
550             int anIndex = getIndexOfChild(parent, node);
551
552             if(anIndex != -1) {
553             int[] cIndexs = new int[1];
554
555             cIndexs[0] = anIndex;
556             nodesChanged(parent, cIndexs);
557             }
558         }
559         }
560         }
561
562     /**
563      * Returns the path to a particluar node. This is recursive.
564      */

565     protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
566         TreeNode[] retNodes;
567
568         /* Check for null, in case someone passed in a null node, or
569            they passed in an element that isn't rooted at root. */

570         if(aNode == null) {
571         if(depth == 0)
572             return null;
573         else
574             retNodes = new TreeNode[depth];
575         }
576         else {
577         depth++;
578         if(aNode == root)
579             retNodes = new TreeNode[depth];
580         else {
581             TreeNode parent = aNode.getParent();
582
583             if(parent == null)
584             parent = root;
585             retNodes = getPathToRoot(parent, depth);
586         }
587         retNodes[retNodes.length - depth] = aNode;
588         }
589         return retNodes;
590     }
591     }
592 }
593
Popular Tags