home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / JBuilder8.iso / Solaris / resource / jre / demo / plugin / jfc / Notepad / src / ElementTreePanel.java next >
Encoding:
Java Source  |  2002-09-06  |  18.0 KB  |  592 lines

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