Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/yaftp/utils/ElementTreePanel.java


1   package com.yaftp.utils ;
2   
3   /*
4    * @(#)ElementTreePanel.java  1.9 99/04/23
5    *
6    * Copyright (c) 1998, 1999 by Sun Microsystems, Inc. All Rights Reserved.
7    *
8    * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
9    * modify and redistribute this software in source and binary code form,
10   * provided that i) this copyright notice and license appear on all copies of
11   * the software; and ii) Licensee does not utilize the software in a manner
12   * which is disparaging to Sun.
13   *
14   * This software is provided "AS IS," without a warranty of any kind. ALL
15   * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
16   * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
17   * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
18   * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
19   * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
20   * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
21   * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
22   * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
23   * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGES.
25   *
26   * This software is not designed or intended for use in on-line control of
27   * aircraft, air traffic, aircraft navigation or aircraft communications; or in
28   * the design, construction, operation or maintenance of any nuclear
29   * facility. Licensee represents and warrants that it will not use or
30   * redistribute the Software for such purposes.
31   */
32  
33  import javax.swing.*;
34  import javax.swing.event.*;
35  import javax.swing.text.*;
36  import javax.swing.tree.*;
37  import javax.swing.undo.*;
38  import java.awt.*;
39  import java.beans.*;
40  import java.util.*;
41  
42  /**
43   * Displays a tree showing all the elements in a text Document. Selecting
44   * a node will result in reseting the selection of the JTextComponent.
45   * This also becomes a CaretListener to know when the selection has changed
46   * in the text to update the selected item in the tree.
47   *
48   * @author Scott Violet
49   * @version 1.9 04/23/99
50   */
51  public class ElementTreePanel extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener, TreeSelectionListener {
52      /** Tree showing the documents element structure. */
53      protected JTree             tree;
54      /** Text component showing elemenst for. */
55      protected JTextComponent    editor;
56      /** Model for the tree. */
57      protected ElementTreeModel  treeModel;
58      /** Set to true when updatin the selection. */
59      protected boolean           updatingSelection;
60  
61      public ElementTreePanel(JTextComponent editor) {
62    this.editor = editor;
63  
64    Document document = editor.getDocument();
65  
66    // Create the tree.
67    treeModel = new ElementTreeModel(document);
68    tree = new JTree(treeModel) {
69        public String convertValueToText(Object value, boolean selected,
70                 boolean expanded, boolean leaf,
71                 int row, boolean hasFocus) {
72      // Should only happen for the root
73      if(!(value instanceof Element))
74          return value.toString();
75  
76      Element        e = (Element)value;
77      AttributeSet   as = e.getAttributes().copyAttributes();
78      String         asString;
79  
80      if(as != null) {
81          StringBuffer       retBuffer = new StringBuffer("[");
82          Enumeration        names = as.getAttributeNames();
83  
84          while(names.hasMoreElements()) {
85        Object        nextName = names.nextElement();
86  
87        if(nextName != StyleConstants.ResolveAttribute) {
88            retBuffer.append(" ");
89            retBuffer.append(nextName);
90            retBuffer.append("=");
91            retBuffer.append(as.getAttribute(nextName));
92        }
93          }
94          retBuffer.append(" ]");
95          asString = retBuffer.toString();
96      }
97      else
98          asString = "[ ]";
99  
100     if(e.isLeaf())
101         return e.getName() + " [" + e.getStartOffset() +
102       ", " + e.getEndOffset() +"] Attributes: " + asString;
103     return e.getName() + " [" + e.getStartOffset() +
104         ", " + e.getEndOffset() + "] Attributes: " +
105              asString;
106       }
107   };
108   tree.addTreeSelectionListener(this);
109   // Don't show the root, it is fake.
110   tree.setRootVisible(false);
111   // Since the display value of every node after the insertion point
112   // changes every time the text changes and we don't generate a change
113   // event for all those nodes the display value can become off.
114   // This can be seen as '...' instead of the complete string value.
115   // This is a temporary workaround, increase the needed size by 15,
116   // hoping that will be enough.
117   tree.setCellRenderer(new DefaultTreeCellRenderer() {
118       public Dimension getPreferredSize() {
119     Dimension retValue = super.getPreferredSize();
120     if(retValue != null)
121         retValue.width += 15;
122     return retValue;
123       }
124   });
125   // become a listener on the document to update the tree.
126   document.addDocumentListener(this);
127 
128   // become a PropertyChangeListener to know when the Document has
129   // changed.
130   editor.addPropertyChangeListener(this);
131 
132   // Become a CaretListener
133   editor.addCaretListener(this);
134 
135   // configure the panel and frame containing it.
136   setLayout(new BorderLayout());
137   add(new JScrollPane(tree), BorderLayout.CENTER);
138 
139   // Add a label above tree to describe what is being shown
140   JLabel     label = new JLabel("Elements that make up the current document", SwingConstants.CENTER);
141 
142   label.setFont(new Font("Dialog", Font.BOLD, 14));
143   add(label, BorderLayout.NORTH);
144 
145   setPreferredSize(new Dimension(400, 400));
146     }
147 
148     /**
149      * Resets the JTextComponent to <code>editor</code>. This will update
150      * the tree accordingly.
151      */
152     public void setEditor(JTextComponent editor) {
153   if (this.editor == editor) {
154       return;
155   }
156 
157   if (this.editor != null) {
158       Document      oldDoc = this.editor.getDocument();
159 
160       oldDoc.removeDocumentListener(this);
161       this.editor.removePropertyChangeListener(this);
162       this.editor.removeCaretListener(this);
163   }
164   this.editor = editor;
165   if (editor == null) {
166       treeModel = null;
167       tree.setModel(null);
168   }
169   else {
170       Document   newDoc = editor.getDocument();
171 
172       newDoc.addDocumentListener(this);
173       editor.addPropertyChangeListener(this);
174       editor.addCaretListener(this);
175       treeModel = new ElementTreeModel(newDoc);
176       tree.setModel(treeModel);
177   }
178     }
179 
180     // PropertyChangeListener
181 
182     /**
183      * Invoked when a property changes. We are only interested in when the
184      * Document changes to reset the DocumentListener.
185      */
186     public void propertyChange(PropertyChangeEvent e) {
187   if (e.getSource() == getEditor() &&
188       e.getPropertyName().equals("document")) {
189       JTextComponent      editor = getEditor();
190       Document            oldDoc = (Document)e.getOldValue();
191       Document            newDoc = (Document)e.getNewValue();
192 
193       // Reset the DocumentListener
194       oldDoc.removeDocumentListener(this);
195       newDoc.addDocumentListener(this);
196 
197       // Recreate the TreeModel.
198       treeModel = new ElementTreeModel(newDoc);
199       tree.setModel(treeModel);
200   }
201     }
202 
203 
204     // DocumentListener
205 
206     /**
207      * Gives notification that there was an insert into the document.  The
208      * given range bounds the freshly inserted region.
209      *
210      * @param e the document event
211      */
212     public void insertUpdate(DocumentEvent e) {
213   updateTree(e);
214     }
215 
216     /**
217      * Gives notification that a portion of the document has been
218      * removed.  The range is given in terms of what the view last
219      * saw (that is, before updating sticky positions).
220      *
221      * @param e the document event
222      */
223     public void removeUpdate(DocumentEvent e) {
224   updateTree(e);
225     }
226 
227     /**
228      * Gives notification that an attribute or set of attributes changed.
229      *
230      * @param e the document event
231      */
232     public void changedUpdate(DocumentEvent e) {
233   updateTree(e);
234     }
235 
236     // CaretListener
237 
238     /**
239      * Messaged when the selection in the editor has changed. Will update
240      * the selection in the tree.
241      */
242     public void caretUpdate(CaretEvent e) {
243   if(!updatingSelection) {
244       JTextComponent     editor = getEditor();
245       int                selBegin = Math.min(e.getDot(), e.getMark());
246       int                end = Math.max(e.getDot(), e.getMark());
247       Vector             paths = new Vector();
248       TreeModel          model = getTreeModel();
249       Object             root = model.getRoot();
250       int                rootCount = model.getChildCount(root);
251 
252       // Build an array of all the paths to all the character elements
253       // in the selection.
254       for(int counter = 0; counter < rootCount; counter++) {
255     int            start = selBegin;
256 
257     while(start <= end) {
258         TreePath    path = getPathForIndex(start, root,
259                (Element)model.getChild(root, counter));
260         Element     charElement = (Element)path.
261                              getLastPathComponent();
262 
263         paths.addElement(path);
264         if(start >= charElement.getEndOffset())
265       start++;
266         else
267       start = charElement.getEndOffset();
268     }
269       }
270 
271       // If a path was found, select it (them).
272       int               numPaths = paths.size();
273 
274       if(numPaths > 0) {
275     TreePath[]    pathArray = new TreePath[numPaths];
276 
277     paths.copyInto(pathArray);
278     updatingSelection = true;
279     try {
280         getTree().setSelectionPaths(pathArray);
281         getTree().scrollPathToVisible(pathArray[0]);
282     }
283     finally {
284         updatingSelection = false;
285     }
286       }
287   }
288     }
289 
290     // TreeSelectionListener
291 
292     /**
293       * Called whenever the value of the selection changes.
294       * @param e the event that characterizes the change.
295       */
296     public void valueChanged(TreeSelectionEvent e) {
297   JTree       tree = getTree();
298 
299   if(!updatingSelection && tree.getSelectionCount() == 1) {
300       TreePath      selPath = tree.getSelectionPath();
301       Object        lastPathComponent = selPath.getLastPathComponent();
302 
303       if(!(lastPathComponent instanceof DefaultMutableTreeNode)) {
304     Element       selElement = (Element)lastPathComponent;
305 
306     updatingSelection = true;
307     try {
308         getEditor().select(selElement.getStartOffset(),
309                selElement.getEndOffset());
310     }
311     finally {
312         updatingSelection = false;
313     }
314       }
315   }
316     }
317 
318     // Local methods
319 
320     /**
321      * @return tree showing elements.
322      */
323     protected JTree getTree() {
324   return tree;
325     }
326 
327     /**
328      * @return JTextComponent showing elements for.
329      */
330     protected JTextComponent getEditor() {
331   return editor;
332     }
333 
334     /**
335      * @return TreeModel implementation used to represent the elements.
336      */
337     public DefaultTreeModel getTreeModel() {
338   return treeModel;
339     }
340 
341     /**
342      * Updates the tree based on the event type. This will invoke either
343      * updateTree with the root element, or handleChange.
344      */
345     protected void updateTree(DocumentEvent event) {
346   updatingSelection = true;
347   try {
348       TreeModel        model = getTreeModel();
349       Object           root = model.getRoot();
350 
351       for(int counter = model.getChildCount(root) - 1; counter >= 0;
352     counter--) {
353     updateTree(event, (Element)model.getChild(root, counter));
354       }
355   }
356   finally {
357       updatingSelection = false;
358   }
359     }
360 
361     /**
362      * Creates TreeModelEvents based on the DocumentEvent and messages
363      * the treemodel. This recursively invokes this method with children
364      * elements.
365      * @param event indicates what elements in the tree hierarchy have
366      * changed.
367      * @param element Current element to check for changes against.
368      */
369     protected void updateTree(DocumentEvent event, Element element) {
370         DocumentEvent.ElementChange ec = event.getChange(element);
371 
372         if (ec != null) {
373       Element[]       removed = ec.getChildrenRemoved();
374       Element[]       added = ec.getChildrenAdded();
375       int             startIndex = ec.getIndex();
376 
377       // Check for removed.
378       if(removed != null && removed.length > 0) {
379     int[]            indices = new int[removed.length];
380 
381     for(int counter = 0; counter < removed.length; counter++) {
382         indices[counter] = startIndex + counter;
383     }
384     getTreeModel().nodesWereRemoved((TreeNode)element, indices,
385             removed);
386       }
387       // check for added
388       if(added != null && added.length > 0) {
389     int[]            indices = new int[added.length];
390 
391     for(int counter = 0; counter < added.length; counter++) {
392         indices[counter] = startIndex + counter;
393     }
394     getTreeModel().nodesWereInserted((TreeNode)element, indices);
395       }
396         }
397   if(!element.isLeaf()) {
398       int        startIndex = element.getElementIndex
399                            (event.getOffset());
400       int        elementCount = element.getElementCount();
401       int        endIndex = Math.min(elementCount - 1,
402              element.getElementIndex
403              (event.getOffset() + event.getLength()));
404 
405       if(startIndex > 0 && startIndex < elementCount &&
406          element.getElement(startIndex).getStartOffset() ==
407          event.getOffset()) {
408     // Force checking the previous element.
409     startIndex--;
410       }
411       if(startIndex != -1 && endIndex != -1) {
412     for(int counter = startIndex; counter <= endIndex; counter++) {
413         updateTree(event, element.getElement(counter));
414     }
415       }
416   }
417   else {
418       // Element is a leaf, assume it changed
419       getTreeModel().nodeChanged((TreeNode)element);
420   }
421     }
422 
423     /**
424      * Returns a TreePath to the element at <code>position</code>.
425      */
426     protected TreePath getPathForIndex(int position, Object root,
427                Element rootElement) {
428   TreePath         path = new TreePath(root);
429   Element          child = rootElement.getElement
430                               (rootElement.getElementIndex(position));
431 
432   path = path.pathByAddingChild(rootElement);
433   path = path.pathByAddingChild(child);
434   while(!child.isLeaf()) {
435       child = child.getElement(child.getElementIndex(position));
436       path = path.pathByAddingChild(child);
437   }
438   return path;
439     }
440 
441 
442     /**
443      * ElementTreeModel is an implementation of TreeModel to handle displaying
444      * the Elements from a Document. AbstractDocument.AbstractElement is
445      * the default implementation used by the swing text package to implement
446      * Element, and it implements TreeNode. This makes it trivial to create
447      * a DefaultTreeModel rooted at a particular Element from the Document.
448      * Unfortunately each Document can have more than one root Element.
449      * Implying that to display all the root elements as a child of another
450      * root a fake node has be created. This class creates a fake node as
451      * the root with the children being the root elements of the Document
452      * (getRootElements).
453      * <p>This subclasses DefaultTreeModel. The majority of the TreeModel
454      * methods have been subclassed, primarily to special case the root.
455      */
456     public static class ElementTreeModel extends DefaultTreeModel {
457   protected Element[]         rootElements;
458 
459   public ElementTreeModel(Document document) {
460       super(new DefaultMutableTreeNode("root"), false);
461       rootElements = document.getRootElements();
462   }
463 
464   /**
465    * Returns the child of <I>parent</I> at index <I>index</I> in
466    * the parent's child array.  <I>parent</I> must be a node
467    * previously obtained from this data source. This should
468    * not return null if <i>index</i> is a valid index for
469    * <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i>
470    * < getChildCount(<i>parent</i>)).
471    *
472    * @param   parent  a node in the tree, obtained from this data source
473    * @return  the child of <I>parent</I> at index <I>index</I>
474    */
475   public Object getChild(Object parent, int index) {
476       if(parent == root)
477     return rootElements[index];
478       return super.getChild(parent, index);
479   }
480 
481 
482   /**
483    * Returns the number of children of <I>parent</I>.  Returns 0
484    * if the node is a leaf or if it has no children.
485    * <I>parent</I> must be a node previously obtained from this
486    * data source.
487    *
488    * @param   parent  a node in the tree, obtained from this data source
489    * @return  the number of children of the node <I>parent</I>
490    */
491   public int getChildCount(Object parent) {
492       if(parent == root)
493     return rootElements.length;
494       return super.getChildCount(parent);
495   }
496 
497 
498   /**
499    * Returns true if <I>node</I> is a leaf.  It is possible for
500    * this method to return false even if <I>node</I> has no
501    * children.  A directory in a filesystem, for example, may
502    * contain no files; the node representing the directory is
503    * not a leaf, but it also has no children.
504    *
505    * @param   node    a node in the tree, obtained from this data source
506    * @return  true if <I>node</I> is a leaf
507    */
508   public boolean isLeaf(Object node) {
509       if(node == root)
510     return false;
511       return super.isLeaf(node);
512   }
513 
514   /**
515    * Returns the index of child in parent.
516    */
517   public int getIndexOfChild(Object parent, Object child) {
518       if(parent == root) {
519     for(int counter = rootElements.length - 1; counter >= 0;
520         counter--) {
521         if(rootElements[counter] == child)
522       return counter;
523     }
524     return -1;
525       }
526       return super.getIndexOfChild(parent, child);
527   }
528 
529   /**
530    * Invoke this method after you've changed how node is to be
531    * represented in the tree.
532    */
533   public void nodeChanged(TreeNode node) {
534       if(listenerList != null && node != null) {
535     TreeNode         parent = node.getParent();
536 
537     if(parent == null && node != root) {
538         parent = root;
539     }
540     if(parent != null) {
541         int        anIndex = getIndexOfChild(parent, node);
542 
543         if(anIndex != -1) {
544       int[]        cIndexs = new int[1];
545 
546       cIndexs[0] = anIndex;
547       nodesChanged(parent, cIndexs);
548         }
549     }
550       }
551         }
552 
553   /**
554    * Returns the path to a particluar node. This is recursive.
555    */
556   protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
557       TreeNode[]              retNodes;
558 
559       /* Check for null, in case someone passed in a null node, or
560          they passed in an element that isn't rooted at root. */
561       if(aNode == null) {
562     if(depth == 0)
563         return null;
564     else
565         retNodes = new TreeNode[depth];
566       }
567       else {
568     depth++;
569     if(aNode == root)
570         retNodes = new TreeNode[depth];
571     else {
572         TreeNode parent = aNode.getParent();
573 
574         if(parent == null)
575       parent = root;
576         retNodes = getPathToRoot(parent, depth);
577     }
578     retNodes[retNodes.length - depth] = aNode;
579       }
580       return retNodes;
581   }
582     }
583 }