home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgramD2.iso / J A V A / Java Development Kit V1.2 / jdk12-win32(1).exe / data1.cab / demos / demo / jfc / Stylepad / ElementTreePanel.java < prev    next >
Encoding:
Java Source  |  1998-12-01  |  15.0 KB  |  506 lines

  1. /*
  2.  * @(#)ElementTreePanel.java    1.6 98/08/26
  3.  *
  4.  * Copyright 1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  *
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. import javax.swing.*;
  16. import javax.swing.event.*;
  17. import javax.swing.text.*;
  18. import javax.swing.tree.*;
  19. import javax.swing.undo.*;
  20. import java.awt.*;
  21. import java.util.*;
  22.  
  23. /**
  24.  * Displays a tree showing all the elements in a text Document. Selecting
  25.  * a node will result in reseting the selection of the JTextComponent.
  26.  * This also becomes a CaretListener to know when the selection has changed
  27.  * in the text to update the selected item in the tree.
  28.  *
  29.  * @author Scott Violet
  30.  * @version 1.6 08/26/98
  31.  */
  32. public class ElementTreePanel extends JPanel implements CaretListener, DocumentListener, TreeSelectionListener {
  33.     /** Tree showing the documents element structure. */
  34.     protected JTree             tree;
  35.     /** Text component showing elemenst for. */
  36.     protected JTextComponent    editor;
  37.     /** Model for the tree. */
  38.     protected ElementTreeModel  treeModel;
  39.     /** Set to true when updatin the selection. */
  40.     protected boolean           updatingSelection;
  41.  
  42.     public ElementTreePanel(JTextComponent editor) {
  43.     this.editor = editor;
  44.  
  45.     Document document = editor.getDocument();
  46.  
  47.     // Create the tree.
  48.     treeModel = new ElementTreeModel(document);
  49.     tree = new JTree(treeModel) {
  50.         public String convertValueToText(Object value, boolean selected,
  51.                          boolean expanded, boolean leaf,
  52.                          int row, boolean hasFocus) {
  53.         // Should only happen for the root
  54.         if(!(value instanceof Element))
  55.             return value.toString();
  56.  
  57.         Element        e = (Element)value;
  58.         AttributeSet   as = e.getAttributes().copyAttributes();
  59.         String         asString;
  60.  
  61.         if(as != null) {
  62.             StringBuffer       retBuffer = new StringBuffer("[");
  63.             Enumeration        names = as.getAttributeNames();
  64.  
  65.             while(names.hasMoreElements()) {
  66.             Object        nextName = names.nextElement();
  67.  
  68.             if(nextName != StyleConstants.ResolveAttribute) {
  69.                 retBuffer.append(" ");
  70.                 retBuffer.append(nextName);
  71.                 retBuffer.append("=");
  72.                 retBuffer.append(as.getAttribute(nextName));
  73.             }
  74.             }
  75.             retBuffer.append(" ]");
  76.             asString = retBuffer.toString();
  77.         }
  78.         else
  79.             asString = "[ ]";
  80.  
  81.         if(e.isLeaf())
  82.             return e.getName() + " [" + e.getStartOffset() +
  83.             ", " + e.getEndOffset() +"] Attributes: " + asString;
  84.         return e.getName() + " [" + e.getStartOffset() +
  85.             ", " + e.getEndOffset() + "] Attributes: " +
  86.                  asString;
  87.         }
  88.     };
  89.     tree.addTreeSelectionListener(this);
  90.     // Don't show the root, it is fake.
  91.     tree.setRootVisible(false);
  92.     // Since the display value of every node after the insertion point
  93.     // changes every time the text changes and we don't generate a change
  94.     // event for all those nodes the display value can become off.
  95.     // This can be seen as '...' instead of the complete string value.
  96.     // This is a temporary workaround, increase the needed size by 15,
  97.     // hoping that will be enough.
  98.     tree.setCellRenderer(new DefaultTreeCellRenderer() {
  99.         public Dimension getPreferredSize() {
  100.         Dimension retValue = super.getPreferredSize();
  101.         if(retValue != null)
  102.             retValue.width += 15;
  103.         return retValue;
  104.         }
  105.     });
  106.     // become a listener on the document to update the tree.
  107.     document.addDocumentListener(this);
  108.  
  109.     // Become a CaretListener
  110.     editor.addCaretListener(this);
  111.  
  112.     // configure the panel and frame containing it.
  113.     setLayout(new BorderLayout());
  114.     add(new JScrollPane(tree), BorderLayout.CENTER);
  115.  
  116.     // Add a label above tree to describe what is being shown
  117.     JLabel     label = new JLabel("Elements that make up the current document", SwingConstants.CENTER);
  118.  
  119.     label.setFont(new Font("Dialog", Font.BOLD, 14));
  120.     add(label, BorderLayout.NORTH);
  121.  
  122.     setPreferredSize(new Dimension(400, 400));
  123.     }
  124.  
  125.  
  126.     // DocumentListener
  127.  
  128.     /**
  129.      * Gives notification that there was an insert into the document.  The
  130.      * given range bounds the freshly inserted region.
  131.      *
  132.      * @param e the document event
  133.      */
  134.     public void insertUpdate(DocumentEvent e) {
  135.     updateTree(e);
  136.     }
  137.  
  138.     /**
  139.      * Gives notification that a portion of the document has been
  140.      * removed.  The range is given in terms of what the view last
  141.      * saw (that is, before updating sticky positions).
  142.      *
  143.      * @param e the document event
  144.      */
  145.     public void removeUpdate(DocumentEvent e) {
  146.     updateTree(e);
  147.     }
  148.  
  149.     /**
  150.      * Gives notification that an attribute or set of attributes changed.
  151.      *
  152.      * @param e the document event
  153.      */
  154.     public void changedUpdate(DocumentEvent e) {
  155.     updateTree(e);
  156.     }
  157.  
  158.     // CaretListener
  159.  
  160.     /**
  161.      * Messaged when the selection in the editor has changed. Will update
  162.      * the selection in the tree.
  163.      */
  164.     public void caretUpdate(CaretEvent e) {
  165.     if(!updatingSelection) {
  166.         JTextComponent     editor = getEditor();
  167.         int                selBegin = Math.min(e.getDot(), e.getMark());
  168.         int                end = Math.max(e.getDot(), e.getMark());
  169.         Vector             paths = new Vector();
  170.         TreeModel          model = getTreeModel();
  171.         Object             root = model.getRoot();
  172.         int                rootCount = model.getChildCount(root);
  173.  
  174.         // Build an array of all the paths to all the character elements
  175.         // in the selection.
  176.         for(int counter = 0; counter < rootCount; counter++) {
  177.         int            start = selBegin;
  178.  
  179.         while(start <= end) {
  180.             TreePath    path = getPathForIndex(start, root,
  181.                        (Element)model.getChild(root, counter));
  182.             Element     charElement = (Element)path.
  183.                                    getLastPathComponent();
  184.  
  185.             paths.addElement(path);
  186.             if(start >= charElement.getEndOffset())
  187.             start++;
  188.             else
  189.             start = charElement.getEndOffset();
  190.         }
  191.         }
  192.  
  193.         // If a path was found, select it (them).
  194.         int               numPaths = paths.size();
  195.  
  196.         if(numPaths > 0) {
  197.         TreePath[]    pathArray = new TreePath[numPaths];
  198.  
  199.         paths.copyInto(pathArray);
  200.         updatingSelection = true;
  201.         try {
  202.             getTree().setSelectionPaths(pathArray);
  203.             getTree().scrollPathToVisible(pathArray[0]);
  204.         }
  205.         finally {
  206.             updatingSelection = false;
  207.         }
  208.         }
  209.     }
  210.     }
  211.  
  212.     // TreeSelectionListener
  213.  
  214.     /**
  215.       * Called whenever the value of the selection changes.
  216.       * @param e the event that characterizes the change.
  217.       */
  218.     public void valueChanged(TreeSelectionEvent e) {
  219.     JTree       tree = getTree();
  220.  
  221.     if(!updatingSelection && tree.getSelectionCount() == 1) {
  222.         TreePath      selPath = tree.getSelectionPath();
  223.         Object        lastPathComponent = selPath.getLastPathComponent();
  224.  
  225.         if(!(lastPathComponent instanceof DefaultMutableTreeNode)) {
  226.         Element       selElement = (Element)lastPathComponent;
  227.  
  228.         updatingSelection = true;
  229.         try {
  230.             getEditor().select(selElement.getStartOffset(),
  231.                        selElement.getEndOffset());
  232.         }
  233.         finally {
  234.             updatingSelection = false;
  235.         }
  236.         }
  237.     }
  238.     }
  239.  
  240.     // Local methods
  241.  
  242.     /**
  243.      * @return tree showing elements.
  244.      */
  245.     protected JTree getTree() {
  246.     return tree;
  247.     }
  248.  
  249.     /**
  250.      * @return JTextComponent showing elements for.
  251.      */
  252.     protected JTextComponent getEditor() {
  253.     return editor;
  254.     }
  255.  
  256.     /**
  257.      * @return TreeModel implementation used to represent the elements.
  258.      */
  259.     public DefaultTreeModel getTreeModel() {
  260.     return treeModel;
  261.     }
  262.  
  263.     /**
  264.      * Updates the tree based on the event type. This will invoke either
  265.      * updateTree with the root element, or handleChange.
  266.      */
  267.     protected void updateTree(DocumentEvent event) {
  268.     updatingSelection = true;
  269.     try {
  270.         TreeModel        model = getTreeModel();
  271.         Object           root = model.getRoot();
  272.  
  273.         for(int counter = model.getChildCount(root) - 1; counter >= 0;
  274.         counter--) {
  275.         updateTree(event, (Element)model.getChild(root, counter));
  276.         }
  277.     }
  278.     finally {
  279.         updatingSelection = false;
  280.     }
  281.     }
  282.  
  283.     /**
  284.      * Creates TreeModelEvents based on the DocumentEvent and messages
  285.      * the treemodel. This recursively invokes this method with children
  286.      * elements.
  287.      * @param event indicates what elements in the tree hierarchy have
  288.      * changed.
  289.      * @param element Current element to check for changes against.
  290.      */
  291.     protected void updateTree(DocumentEvent event, Element element) {
  292.         DocumentEvent.ElementChange ec = event.getChange(element);
  293.  
  294.         if (ec != null) {
  295.         Element[]       removed = ec.getChildrenRemoved();
  296.         Element[]       added = ec.getChildrenAdded();
  297.         int             startIndex = ec.getIndex();
  298.  
  299.         // Check for removed.
  300.         if(removed != null && removed.length > 0) {
  301.         int[]            indices = new int[removed.length];
  302.  
  303.         for(int counter = 0; counter < removed.length; counter++) {
  304.             indices[counter] = startIndex + counter;
  305.         }
  306.         getTreeModel().nodesWereRemoved((TreeNode)element, indices,
  307.                         removed);
  308.         }
  309.         // check for added
  310.         if(added != null && added.length > 0) {
  311.         int[]            indices = new int[added.length];
  312.  
  313.         for(int counter = 0; counter < added.length; counter++) {
  314.             indices[counter] = startIndex + counter;
  315.         }
  316.         getTreeModel().nodesWereInserted((TreeNode)element, indices);
  317.         }
  318.         }
  319.     if(!element.isLeaf()) {
  320.         int        startIndex = element.getElementIndex
  321.                                (event.getOffset());
  322.         int        elementCount = element.getElementCount();
  323.         int        endIndex = Math.min(elementCount - 1,
  324.                        element.getElementIndex
  325.                      (event.getOffset() + event.getLength()));
  326.  
  327.         if(startIndex > 0 && startIndex < elementCount &&
  328.            element.getElement(startIndex).getStartOffset() ==
  329.            event.getOffset()) {
  330.         // Force checking the previous element.
  331.         startIndex--;
  332.         }
  333.         if(startIndex != -1 && endIndex != -1) {
  334.         for(int counter = startIndex; counter <= endIndex; counter++) {
  335.             updateTree(event, element.getElement(counter));
  336.         }
  337.         }
  338.     }
  339.     else {
  340.         // Element is a leaf, assume it changed
  341.         getTreeModel().nodeChanged((TreeNode)element);
  342.     }
  343.     }
  344.  
  345.     /**
  346.      * Returns a TreePath to the element at <code>position</code>.
  347.      */
  348.     protected TreePath getPathForIndex(int position, Object root,
  349.                        Element rootElement) {
  350.     TreePath         path = new TreePath(root);
  351.     Element          child = rootElement.getElement
  352.                                 (rootElement.getElementIndex(position));
  353.  
  354.     path = path.pathByAddingChild(rootElement);
  355.     path = path.pathByAddingChild(child);
  356.     while(!child.isLeaf()) {
  357.         child = child.getElement(child.getElementIndex(position));
  358.         path = path.pathByAddingChild(child);
  359.     }
  360.     return path;
  361.     }
  362.  
  363.  
  364.     /**
  365.      * ElementTreeModel is an implementation of TreeModel to handle displaying
  366.      * the Elements from a Document. AbstractDocument.AbstractElement is
  367.      * the default implementation used by the swing text package to implement
  368.      * Element, and it implements TreeNode. This makes it trivial to create
  369.      * a DefaultTreeModel rooted at a particular Element from the Document.
  370.      * Unfortunately each Document can have more than one root Element.
  371.      * Implying that to display all the root elements as a child of another
  372.      * root a fake node has be created. This class creates a fake node as
  373.      * the root with the children being the root elements of the Document
  374.      * (getRootElements).
  375.      * <p>This subclasses DefaultTreeModel. The majority of the TreeModel
  376.      * methods have been subclassed, primarily to special case the root.
  377.      */
  378.     public static class ElementTreeModel extends DefaultTreeModel {
  379.     protected Element[]         rootElements;
  380.  
  381.     public ElementTreeModel(Document document) {
  382.         super(new DefaultMutableTreeNode("root"), false);
  383.         rootElements = document.getRootElements();
  384.     }
  385.  
  386.     /**
  387.      * Returns the child of <I>parent</I> at index <I>index</I> in
  388.      * the parent's child array.  <I>parent</I> must be a node
  389.      * previously obtained from this data source. This should
  390.      * not return null if <i>index</i> is a valid index for
  391.      * <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i>
  392.      * < getChildCount(<i>parent</i>)).
  393.      *
  394.      * @param   parent  a node in the tree, obtained from this data source
  395.      * @return  the child of <I>parent</I> at index <I>index</I>
  396.      */
  397.     public Object getChild(Object parent, int index) {
  398.         if(parent == root)
  399.         return rootElements[index];
  400.         return super.getChild(parent, index);
  401.     }
  402.  
  403.  
  404.     /**
  405.      * Returns the number of children of <I>parent</I>.  Returns 0
  406.      * if the node is a leaf or if it has no children.
  407.      * <I>parent</I> must be a node previously obtained from this
  408.      * data source.
  409.      *
  410.      * @param   parent  a node in the tree, obtained from this data source
  411.      * @return  the number of children of the node <I>parent</I>
  412.      */
  413.     public int getChildCount(Object parent) {
  414.         if(parent == root)
  415.         return rootElements.length;
  416.         return super.getChildCount(parent);
  417.     }
  418.  
  419.  
  420.     /**
  421.      * Returns true if <I>node</I> is a leaf.  It is possible for
  422.      * this method to return false even if <I>node</I> has no
  423.      * children.  A directory in a filesystem, for example, may
  424.      * contain no files; the node representing the directory is
  425.      * not a leaf, but it also has no children.
  426.      *
  427.      * @param   node    a node in the tree, obtained from this data source
  428.      * @return  true if <I>node</I> is a leaf
  429.      */
  430.     public boolean isLeaf(Object node) {
  431.         if(node == root)
  432.         return false;
  433.         return super.isLeaf(node);
  434.     }
  435.  
  436.     /**
  437.      * Returns the index of child in parent.
  438.      */
  439.     public int getIndexOfChild(Object parent, Object child) {
  440.         if(parent == root) {
  441.         for(int counter = rootElements.length - 1; counter >= 0;
  442.             counter--) {
  443.             if(rootElements[counter] == child)
  444.             return counter;
  445.         }
  446.         return -1;
  447.         }
  448.         return super.getIndexOfChild(parent, child);
  449.     }
  450.  
  451.     /**
  452.      * Invoke this method after you've changed how node is to be
  453.      * represented in the tree.
  454.      */
  455.     public void nodeChanged(TreeNode node) {
  456.         if(listenerList != null && node != null) {
  457.         TreeNode         parent = node.getParent();
  458.  
  459.         if(parent == null && node != root) {
  460.             parent = root;
  461.         }
  462.         if(parent != null) {
  463.             int        anIndex = getIndexOfChild(parent, node);
  464.  
  465.             if(anIndex != -1) {
  466.             int[]        cIndexs = new int[1];
  467.  
  468.             cIndexs[0] = anIndex;
  469.             nodesChanged(parent, cIndexs);
  470.             }
  471.         }
  472.         }
  473.         }
  474.  
  475.     /**
  476.      * Returns the path to a particluar node. This is recursive.
  477.      */
  478.     protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
  479.         TreeNode[]              retNodes;
  480.  
  481.         /* Check for null, in case someone passed in a null node, or
  482.            they passed in an element that isn't rooted at root. */
  483.         if(aNode == null) {
  484.         if(depth == 0)
  485.             return null;
  486.         else
  487.             retNodes = new TreeNode[depth];
  488.         }
  489.         else {
  490.         depth++;
  491.         if(aNode == root)
  492.             retNodes = new TreeNode[depth];
  493.         else {
  494.             TreeNode parent = aNode.getParent();
  495.  
  496.             if(parent == null)
  497.             parent = root;
  498.             retNodes = getPathToRoot(parent, depth);
  499.         }
  500.         retNodes[retNodes.length - depth] = aNode;
  501.         }
  502.         return retNodes;
  503.     }
  504.     }
  505. }
  506.