mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			2527 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			2527 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* DefaultStyledDocument.java --
 | |
|    Copyright (C) 2004, 2005 Free Software Foundation, Inc.
 | |
| 
 | |
| This file is part of GNU Classpath.
 | |
| 
 | |
| GNU Classpath is free software; you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation; either version 2, or (at your option)
 | |
| any later version.
 | |
| 
 | |
| GNU Classpath is distributed in the hope that it will be useful, but
 | |
| WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| General Public License for more details.
 | |
| 
 | |
| You should have received a copy of the GNU General Public License
 | |
| along with GNU Classpath; see the file COPYING.  If not, write to the
 | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | |
| 02110-1301 USA.
 | |
| 
 | |
| Linking this library statically or dynamically with other modules is
 | |
| making a combined work based on this library.  Thus, the terms and
 | |
| conditions of the GNU General Public License cover the whole
 | |
| combination.
 | |
| 
 | |
| As a special exception, the copyright holders of this library give you
 | |
| permission to link this library with independent modules to produce an
 | |
| executable, regardless of the license terms of these independent
 | |
| modules, and to copy and distribute the resulting executable under
 | |
| terms of your choice, provided that you also meet, for each linked
 | |
| independent module, the terms and conditions of the license of that
 | |
| module.  An independent module is a module which is not derived from
 | |
| or based on this library.  If you modify this library, you may extend
 | |
| this exception to your version of the library, but you are not
 | |
| obligated to do so.  If you do not wish to do so, delete this
 | |
| exception statement from your version. */
 | |
| 
 | |
| 
 | |
| package javax.swing.text;
 | |
| 
 | |
| import gnu.java.lang.CPStringBuilder;
 | |
| 
 | |
| import java.awt.Color;
 | |
| import java.awt.Font;
 | |
| import java.io.Serializable;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Enumeration;
 | |
| import java.util.Iterator;
 | |
| import java.util.Stack;
 | |
| import java.util.Vector;
 | |
| 
 | |
| import javax.swing.event.ChangeEvent;
 | |
| import javax.swing.event.ChangeListener;
 | |
| import javax.swing.event.DocumentEvent;
 | |
| import javax.swing.event.UndoableEditEvent;
 | |
| import javax.swing.undo.AbstractUndoableEdit;
 | |
| import javax.swing.undo.UndoableEdit;
 | |
| 
 | |
| /**
 | |
|  * The default implementation of {@link StyledDocument}. The document is
 | |
|  * modeled as an {@link Element} tree, which has a {@link SectionElement} as
 | |
|  * single root, which has one or more {@link AbstractDocument.BranchElement}s
 | |
|  * as paragraph nodes and each paragraph node having one or more
 | |
|  * {@link AbstractDocument.LeafElement}s as content nodes.
 | |
|  *
 | |
|  * @author Michael Koch (konqueror@gmx.de)
 | |
|  * @author Roman Kennke (roman@kennke.org)
 | |
|  */
 | |
| public class DefaultStyledDocument extends AbstractDocument implements
 | |
|     StyledDocument
 | |
| {
 | |
| 
 | |
|   /**
 | |
|    * An {@link UndoableEdit} that can undo attribute changes to an element.
 | |
|    *
 | |
|    * @author Roman Kennke (kennke@aicas.com)
 | |
|    */
 | |
|   public static class AttributeUndoableEdit extends AbstractUndoableEdit
 | |
|   {
 | |
|     /**
 | |
|      * A copy of the old attributes.
 | |
|      */
 | |
|     protected AttributeSet copy;
 | |
| 
 | |
|     /**
 | |
|      * The new attributes.
 | |
|      */
 | |
|     protected AttributeSet newAttributes;
 | |
| 
 | |
|     /**
 | |
|      * If the new attributes replaced the old attributes or if they only were
 | |
|      * added to them.
 | |
|      */
 | |
|     protected boolean isReplacing;
 | |
| 
 | |
|     /**
 | |
|      * The element that has changed.
 | |
|      */
 | |
|     protected Element element;
 | |
| 
 | |
|     /**
 | |
|      * Creates a new <code>AttributeUndoableEdit</code>.
 | |
|      *
 | |
|      * @param el
 | |
|      *          the element that changes attributes
 | |
|      * @param newAtts
 | |
|      *          the new attributes
 | |
|      * @param replacing
 | |
|      *          if the new attributes replace the old or only append to them
 | |
|      */
 | |
|     public AttributeUndoableEdit(Element el, AttributeSet newAtts,
 | |
|                                  boolean replacing)
 | |
|     {
 | |
|       element = el;
 | |
|       newAttributes = newAtts;
 | |
|       isReplacing = replacing;
 | |
|       copy = el.getAttributes().copyAttributes();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Undos the attribute change. The <code>copy</code> field is set as
 | |
|      * attributes on <code>element</code>.
 | |
|      */
 | |
|     public void undo()
 | |
|     {
 | |
|       super.undo();
 | |
|       AttributeSet atts = element.getAttributes();
 | |
|       if (atts instanceof MutableAttributeSet)
 | |
|         {
 | |
|           MutableAttributeSet mutable = (MutableAttributeSet) atts;
 | |
|           mutable.removeAttributes(atts);
 | |
|           mutable.addAttributes(copy);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Redos an attribute change. This adds <code>newAttributes</code> to the
 | |
|      * <code>element</code>'s attribute set, possibly clearing all attributes
 | |
|      * if <code>isReplacing</code> is true.
 | |
|      */
 | |
|     public void redo()
 | |
|     {
 | |
|       super.undo();
 | |
|       AttributeSet atts = element.getAttributes();
 | |
|       if (atts instanceof MutableAttributeSet)
 | |
|         {
 | |
|           MutableAttributeSet mutable = (MutableAttributeSet) atts;
 | |
|           if (isReplacing)
 | |
|             mutable.removeAttributes(atts);
 | |
|           mutable.addAttributes(newAttributes);
 | |
|         }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Carries specification information for new {@link Element}s that should be
 | |
|    * created in {@link ElementBuffer}. This allows the parsing process to be
 | |
|    * decoupled from the <code>Element</code> creation process.
 | |
|    */
 | |
|   public static class ElementSpec
 | |
|   {
 | |
|     /**
 | |
|      * This indicates a start tag. This is a possible value for {@link #getType}.
 | |
|      */
 | |
|     public static final short StartTagType = 1;
 | |
| 
 | |
|     /**
 | |
|      * This indicates an end tag. This is a possible value for {@link #getType}.
 | |
|      */
 | |
|     public static final short EndTagType = 2;
 | |
| 
 | |
|     /**
 | |
|      * This indicates a content element. This is a possible value for
 | |
|      * {@link #getType}.
 | |
|      */
 | |
|     public static final short ContentType = 3;
 | |
| 
 | |
|     /**
 | |
|      * This indicates that the data associated with this spec should be joined
 | |
|      * with what precedes it. This is a possible value for {@link #getDirection}.
 | |
|      */
 | |
|     public static final short JoinPreviousDirection = 4;
 | |
| 
 | |
|     /**
 | |
|      * This indicates that the data associated with this spec should be joined
 | |
|      * with what follows it. This is a possible value for {@link #getDirection}.
 | |
|      */
 | |
|     public static final short JoinNextDirection = 5;
 | |
| 
 | |
|     /**
 | |
|      * This indicates that the data associated with this spec should be used to
 | |
|      * create a new element. This is a possible value for {@link #getDirection}.
 | |
|      */
 | |
|     public static final short OriginateDirection = 6;
 | |
| 
 | |
|     /**
 | |
|      * This indicates that the data associated with this spec should be joined
 | |
|      * to the fractured element. This is a possible value for
 | |
|      * {@link #getDirection}.
 | |
|      */
 | |
|     public static final short JoinFractureDirection = 7;
 | |
| 
 | |
|     /**
 | |
|      * The type of the tag.
 | |
|      */
 | |
|     short type;
 | |
| 
 | |
|     /**
 | |
|      * The direction of the tag.
 | |
|      */
 | |
|     short direction;
 | |
| 
 | |
|     /**
 | |
|      * The offset of the content.
 | |
|      */
 | |
|     int offset;
 | |
| 
 | |
|     /**
 | |
|      * The length of the content.
 | |
|      */
 | |
|     int length;
 | |
| 
 | |
|     /**
 | |
|      * The actual content.
 | |
|      */
 | |
|     char[] content;
 | |
| 
 | |
|     /**
 | |
|      * The attributes for the tag.
 | |
|      */
 | |
|     AttributeSet attributes;
 | |
| 
 | |
|     /**
 | |
|      * Creates a new <code>ElementSpec</code> with no content, length or
 | |
|      * offset. This is most useful for start and end tags.
 | |
|      *
 | |
|      * @param a
 | |
|      *          the attributes for the element to be created
 | |
|      * @param type
 | |
|      *          the type of the tag
 | |
|      */
 | |
|     public ElementSpec(AttributeSet a, short type)
 | |
|     {
 | |
|       this(a, type, 0);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates a new <code>ElementSpec</code> that specifies the length but
 | |
|      * not the offset of an element. Such <code>ElementSpec</code>s are
 | |
|      * processed sequentially from a known starting point.
 | |
|      *
 | |
|      * @param a
 | |
|      *          the attributes for the element to be created
 | |
|      * @param type
 | |
|      *          the type of the tag
 | |
|      * @param len
 | |
|      *          the length of the element
 | |
|      */
 | |
|     public ElementSpec(AttributeSet a, short type, int len)
 | |
|     {
 | |
|       this(a, type, null, 0, len);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates a new <code>ElementSpec</code> with document content.
 | |
|      *
 | |
|      * @param a
 | |
|      *          the attributes for the element to be created
 | |
|      * @param type
 | |
|      *          the type of the tag
 | |
|      * @param txt
 | |
|      *          the actual content
 | |
|      * @param offs
 | |
|      *          the offset into the <code>txt</code> array
 | |
|      * @param len
 | |
|      *          the length of the element
 | |
|      */
 | |
|     public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)
 | |
|     {
 | |
|       attributes = a;
 | |
|       this.type = type;
 | |
|       offset = offs;
 | |
|       length = len;
 | |
|       content = txt;
 | |
|       direction = OriginateDirection;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the type of the element.
 | |
|      *
 | |
|      * @param type
 | |
|      *          the type of the element to be set
 | |
|      */
 | |
|     public void setType(short type)
 | |
|     {
 | |
|       this.type = type;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the type of the element.
 | |
|      *
 | |
|      * @return the type of the element
 | |
|      */
 | |
|     public short getType()
 | |
|     {
 | |
|       return type;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the direction of the element.
 | |
|      *
 | |
|      * @param dir
 | |
|      *          the direction of the element to be set
 | |
|      */
 | |
|     public void setDirection(short dir)
 | |
|     {
 | |
|       direction = dir;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the direction of the element.
 | |
|      *
 | |
|      * @return the direction of the element
 | |
|      */
 | |
|     public short getDirection()
 | |
|     {
 | |
|       return direction;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the attributes of the element.
 | |
|      *
 | |
|      * @return the attributes of the element
 | |
|      */
 | |
|     public AttributeSet getAttributes()
 | |
|     {
 | |
|       return attributes;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the actual content of the element.
 | |
|      *
 | |
|      * @return the actual content of the element
 | |
|      */
 | |
|     public char[] getArray()
 | |
|     {
 | |
|       return content;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the offset of the content.
 | |
|      *
 | |
|      * @return the offset of the content
 | |
|      */
 | |
|     public int getOffset()
 | |
|     {
 | |
|       return offset;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the length of the content.
 | |
|      *
 | |
|      * @return the length of the content
 | |
|      */
 | |
|     public int getLength()
 | |
|     {
 | |
|       return length;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns a String representation of this <code>ElementSpec</code>
 | |
|      * describing the type, direction and length of this
 | |
|      * <code>ElementSpec</code>.
 | |
|      *
 | |
|      * @return a String representation of this <code>ElementSpec</code>
 | |
|      */
 | |
|     public String toString()
 | |
|     {
 | |
|       CPStringBuilder b = new CPStringBuilder();
 | |
|       switch (type)
 | |
|         {
 | |
|         case StartTagType:
 | |
|           b.append("StartTag");
 | |
|           break;
 | |
|         case EndTagType:
 | |
|           b.append("EndTag");
 | |
|           break;
 | |
|         case ContentType:
 | |
|           b.append("Content");
 | |
|           break;
 | |
|         default:
 | |
|           b.append("??");
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|       b.append(':');
 | |
| 
 | |
|       switch (direction)
 | |
|         {
 | |
|         case JoinPreviousDirection:
 | |
|           b.append("JoinPrevious");
 | |
|           break;
 | |
|         case JoinNextDirection:
 | |
|           b.append("JoinNext");
 | |
|           break;
 | |
|         case OriginateDirection:
 | |
|           b.append("Originate");
 | |
|           break;
 | |
|         case JoinFractureDirection:
 | |
|           b.append("Fracture");
 | |
|           break;
 | |
|         default:
 | |
|           b.append("??");
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|       b.append(':');
 | |
|       b.append(length);
 | |
| 
 | |
|       return b.toString();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Performs all <em>structural</code> changes to the <code>Element</code>
 | |
|    * hierarchy.  This class was implemented with much help from the document:
 | |
|    * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html.
 | |
|    */
 | |
|   public class ElementBuffer implements Serializable
 | |
|   {
 | |
|     /**
 | |
|      * Instance of all editing information for an object in the Vector. This class
 | |
|      * is used to add information to the DocumentEvent associated with an
 | |
|      * insertion/removal/change as well as to store the changes that need to be
 | |
|      * made so they can be made all at the same (appropriate) time.
 | |
|      */
 | |
|     class Edit
 | |
|     {
 | |
|       /** The element to edit . */
 | |
|       Element e;
 | |
| 
 | |
|       /** The index of the change. */
 | |
|       int index;
 | |
| 
 | |
|       /** The removed elements. */
 | |
|       ArrayList removed = new ArrayList();
 | |
| 
 | |
|       /** The added elements. */
 | |
|       ArrayList added = new ArrayList();
 | |
| 
 | |
|       /**
 | |
|        * Indicates if this edit contains a fracture.
 | |
|        */
 | |
|       boolean isFracture;
 | |
| 
 | |
|       /**
 | |
|        * Creates a new Edit for the specified element at index i.
 | |
|        *
 | |
|        * @param el the element
 | |
|        * @param i the index
 | |
|        */
 | |
|       Edit(Element el, int i)
 | |
|       {
 | |
|         this(el, i, false);
 | |
|       }
 | |
| 
 | |
|       /**
 | |
|        * Creates a new Edit for the specified element at index i.
 | |
|        *
 | |
|        * @param el the element
 | |
|        * @param i the index
 | |
|        * @param frac if this is a fracture edit or not
 | |
|        */
 | |
|       Edit(Element el, int i, boolean frac)
 | |
|       {
 | |
|         e = el;
 | |
|         index = i;
 | |
|         isFracture = frac;
 | |
|       }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /** The serialization UID (compatible with JDK1.5). */
 | |
|     private static final long serialVersionUID = 1688745877691146623L;
 | |
| 
 | |
|     /** The root element of the hierarchy. */
 | |
|     private Element root;
 | |
| 
 | |
|     /** Holds the offset for structural changes. */
 | |
|     private int offset;
 | |
| 
 | |
|     /** Holds the end offset for structural changes. */
 | |
|     private int endOffset;
 | |
| 
 | |
|     /** Holds the length of structural changes. */
 | |
|     private int length;
 | |
| 
 | |
|     /** Holds the position of the change. */
 | |
|     private int pos;
 | |
| 
 | |
|     /**
 | |
|      * The parent of the fracture.
 | |
|      */
 | |
|     private Element fracturedParent;
 | |
| 
 | |
|     /**
 | |
|      * The fractured child.
 | |
|      */
 | |
|     private Element fracturedChild;
 | |
| 
 | |
|     /**
 | |
|      * Indicates if a fracture has been created.
 | |
|      */
 | |
|     private boolean createdFracture;
 | |
| 
 | |
|     /**
 | |
|      * The current position in the element tree. This is used for bulk inserts
 | |
|      * using ElementSpecs.
 | |
|      */
 | |
|     private Stack elementStack;
 | |
| 
 | |
|     private Edit[] insertPath;
 | |
| 
 | |
|     private boolean recreateLeafs;
 | |
| 
 | |
|     /**
 | |
|      * Vector that contains all the edits. Maybe replace by a HashMap.
 | |
|      */
 | |
|     private ArrayList edits;
 | |
| 
 | |
|     private boolean offsetLastIndex;
 | |
|     private boolean offsetLastIndexReplace;
 | |
| 
 | |
|     /**
 | |
|      * Creates a new <code>ElementBuffer</code> for the specified
 | |
|      * <code>root</code> element.
 | |
|      *
 | |
|      * @param root
 | |
|      *          the root element for this <code>ElementBuffer</code>
 | |
|      */
 | |
|     public ElementBuffer(Element root)
 | |
|     {
 | |
|       this.root = root;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the root element of this <code>ElementBuffer</code>.
 | |
|      *
 | |
|      * @return the root element of this <code>ElementBuffer</code>
 | |
|      */
 | |
|     public Element getRootElement()
 | |
|     {
 | |
|       return root;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Removes the content. This method sets some internal parameters and
 | |
|      * delegates the work to {@link #removeUpdate}.
 | |
|      *
 | |
|      * @param offs
 | |
|      *          the offset from which content is remove
 | |
|      * @param len
 | |
|      *          the length of the removed content
 | |
|      * @param ev
 | |
|      *          the document event that records the changes
 | |
|      */
 | |
|     public void remove(int offs, int len, DefaultDocumentEvent ev)
 | |
|     {
 | |
|       prepareEdit(offs, len);
 | |
|       removeUpdate();
 | |
|       finishEdit(ev);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Updates the element structure of the document in response to removal of
 | |
|      * content. It removes the affected {@link Element}s from the document
 | |
|      * structure.
 | |
|      */
 | |
|     protected void removeUpdate()
 | |
|     {
 | |
|       removeElements(root, offset, endOffset);
 | |
|     }
 | |
| 
 | |
|     private boolean removeElements(Element elem, int rmOffs0, int rmOffs1)
 | |
|     {
 | |
|       boolean ret = false;
 | |
|       if (! elem.isLeaf())
 | |
|         {
 | |
|           // Update stack for changes.
 | |
|           int index0 = elem.getElementIndex(rmOffs0);
 | |
|           int index1 = elem.getElementIndex(rmOffs1);
 | |
|           elementStack.push(new Edit(elem, index0));
 | |
|           Edit ec = (Edit) elementStack.peek();
 | |
| 
 | |
|           // If the range is contained by one element,
 | |
|           // we just forward the request
 | |
|           if (index0 == index1)
 | |
|             {
 | |
|               Element child0 = elem.getElement(index0);
 | |
|               if(rmOffs0 <= child0.getStartOffset()
 | |
|                   && rmOffs1 >= child0.getEndOffset())
 | |
|                 {
 | |
|                   // Element totally removed.
 | |
|                   ec.removed.add(child0);
 | |
|                 }
 | |
|               else if (removeElements(child0, rmOffs0, rmOffs1))
 | |
|                 {
 | |
|                   ec.removed.add(child0);
 | |
|                 }
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               // The removal range spans elements.  If we can join
 | |
|               // the two endpoints, do it.  Otherwise we remove the
 | |
|               // interior and forward to the endpoints.
 | |
|               Element child0 = elem.getElement(index0);
 | |
|               Element child1 = elem.getElement(index1);
 | |
|               boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
 | |
|           if (containsOffs1 && canJoin(child0, child1))
 | |
|             {
 | |
|               // Remove and join.
 | |
|               for (int i = index0; i <= index1; i++)
 | |
|                 {
 | |
|                   ec.removed.add(elem.getElement(i));
 | |
|                 }
 | |
|               Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
 | |
|               ec.added.add(e);
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               // Remove interior and forward.
 | |
|               int rmIndex0 = index0 + 1;
 | |
|               int rmIndex1 = index1 - 1;
 | |
|               if (child0.getStartOffset() == rmOffs0
 | |
|                   || (index0 == 0 && child0.getStartOffset() > rmOffs0
 | |
|                       && child0.getEndOffset() <= rmOffs1))
 | |
|                 {
 | |
|                   // Start element completely consumed.
 | |
|                   child0 = null;
 | |
|                   rmIndex0 = index0;
 | |
|                 }
 | |
|               if (! containsOffs1)
 | |
|                 {
 | |
|                   child1 = null;
 | |
|                   rmIndex1++;
 | |
|               }
 | |
|               else if (child1.getStartOffset() == rmOffs1)
 | |
|                 {
 | |
|                   // End element not touched.
 | |
|                   child1 = null;
 | |
|                 }
 | |
|               if (rmIndex0 <= rmIndex1)
 | |
|                 {
 | |
|                   ec.index = rmIndex0;
 | |
|                 }
 | |
|               for (int i = rmIndex0; i <= rmIndex1; i++)
 | |
|                 {
 | |
|                   ec.removed.add(elem.getElement(i));
 | |
|                 }
 | |
|               if (child0 != null)
 | |
|                 {
 | |
|                   if(removeElements(child0, rmOffs0, rmOffs1))
 | |
|                     {
 | |
|                       ec.removed.add(0, child0);
 | |
|                       ec.index = index0;
 | |
|                     }
 | |
|                 }
 | |
|               if (child1 != null)
 | |
|                 {
 | |
|                   if(removeElements(child1, rmOffs0, rmOffs1))
 | |
|                     {
 | |
|                       ec.removed.add(child1);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             }
 | |
| 
 | |
|           // Perform changes.
 | |
|           pop();
 | |
| 
 | |
|           // Return true if we no longer have any children.
 | |
|           if(elem.getElementCount() == (ec.removed.size() - ec.added.size()))
 | |
|             ret = true;
 | |
|         }
 | |
|       return ret;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates a document in response to a call to
 | |
|      * {@link DefaultStyledDocument#create(ElementSpec[])}.
 | |
|      *
 | |
|      * @param len the length of the inserted text
 | |
|      * @param data the specs for the elements
 | |
|      * @param ev the document event
 | |
|      */
 | |
|     void create(int len, ElementSpec[] data, DefaultDocumentEvent ev)
 | |
|     {
 | |
|       prepareEdit(offset, len);
 | |
|       Element el = root;
 | |
|       int index = el.getElementIndex(0);
 | |
|       while (! el.isLeaf())
 | |
|         {
 | |
|           Element child = el.getElement(index);
 | |
|           Edit edit = new Edit(el, index, false);
 | |
|           elementStack.push(edit);
 | |
|           el = child;
 | |
|           index = el.getElementIndex(0);
 | |
|         }
 | |
|       Edit ed = (Edit) elementStack.peek();
 | |
|       Element child = ed.e.getElement(ed.index);
 | |
|       ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(),
 | |
|                                      child.getEndOffset()));
 | |
|       ed.removed.add(child);
 | |
|       while (elementStack.size() > 1)
 | |
|         pop();
 | |
|       int n = data.length;
 | |
| 
 | |
|       // Reset root element's attributes.
 | |
|       AttributeSet newAtts = null;
 | |
|       if (n > 0 && data[0].getType() == ElementSpec.StartTagType)
 | |
|         newAtts = data[0].getAttributes();
 | |
|       if (newAtts == null)
 | |
|         newAtts = SimpleAttributeSet.EMPTY;
 | |
|       MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes();
 | |
|       ev.addEdit(new AttributeUndoableEdit(root, newAtts, true));
 | |
|       mAtts.removeAttributes(mAtts);
 | |
|       mAtts.addAttributes(newAtts);
 | |
| 
 | |
|       // Insert the specified elements.
 | |
|       for (int i = 1; i < n; i++)
 | |
|         insertElement(data[i]);
 | |
| 
 | |
|       // Pop remaining stack.
 | |
|       while (elementStack.size() > 0)
 | |
|         pop();
 | |
| 
 | |
|       finishEdit(ev);
 | |
|     }
 | |
| 
 | |
|     private boolean canJoin(Element e0, Element e1)
 | |
|     {
 | |
|       boolean ret = false;
 | |
|       if ((e0 != null) && (e1 != null))
 | |
|         {
 | |
|           // Don't join a leaf to a branch.
 | |
|           boolean isLeaf0 = e0.isLeaf();
 | |
|           boolean isLeaf1 = e1.isLeaf();
 | |
|           if(isLeaf0 == isLeaf1)
 | |
|             {
 | |
|               if (isLeaf0)
 | |
|                 {
 | |
|                   // Only join leaves if the attributes match, otherwise
 | |
|                   // style information will be lost.
 | |
|                   ret = e0.getAttributes().isEqual(e1.getAttributes());
 | |
|                 }
 | |
|               else
 | |
|                 {
 | |
|                   // Only join non-leafs if the names are equal. This may result
 | |
|                   // in loss of style information, but this is typically
 | |
|                   // acceptable for non-leafs.
 | |
|                   String name0 = e0.getName();
 | |
|                   String name1 = e1.getName();
 | |
|                   if (name0 != null)
 | |
|                     ret = name0.equals(name1);
 | |
|                   else if (name1 != null)
 | |
|                     ret = name1.equals(name0);
 | |
|                   else // Both names null.
 | |
|                     ret = true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|       return ret;
 | |
|     }
 | |
| 
 | |
|     private Element join(Element p, Element left, Element right, int rmOffs0,
 | |
|                          int rmOffs1)
 | |
|     {
 | |
|       Element joined = null;
 | |
|       if (left.isLeaf() && right.isLeaf())
 | |
|         {
 | |
|           joined = createLeafElement(p, left.getAttributes(),
 | |
|                                      left.getStartOffset(),
 | |
|                                      right.getEndOffset());
 | |
|         }
 | |
|       else if ((! left.isLeaf()) && (! right.isLeaf()))
 | |
|         {
 | |
|           // Join two branch elements.  This copies the children before
 | |
|           // the removal range on the left element, and after the removal
 | |
|           // range on the right element.  The two elements on the edge
 | |
|           // are joined if possible and needed.
 | |
|           joined = createBranchElement(p, left.getAttributes());
 | |
|           int ljIndex = left.getElementIndex(rmOffs0);
 | |
|           int rjIndex = right.getElementIndex(rmOffs1);
 | |
|           Element lj = left.getElement(ljIndex);
 | |
|           if (lj.getStartOffset() >= rmOffs0)
 | |
|             {
 | |
|               lj = null;
 | |
|             }
 | |
|           Element rj = right.getElement(rjIndex);
 | |
|           if (rj.getStartOffset() == rmOffs1)
 | |
|             {
 | |
|               rj = null;
 | |
|             }
 | |
|           ArrayList children = new ArrayList();
 | |
|           // Transfer the left.
 | |
|           for (int i = 0; i < ljIndex; i++)
 | |
|             {
 | |
|               children.add(clone(joined, left.getElement(i)));
 | |
|             }
 | |
| 
 | |
|           // Transfer the join/middle.
 | |
|           if (canJoin(lj, rj))
 | |
|             {
 | |
|               Element e = join(joined, lj, rj, rmOffs0, rmOffs1);
 | |
|               children.add(e);
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               if (lj != null)
 | |
|                 {
 | |
|                   children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1));
 | |
|                 }
 | |
|               if (rj != null)
 | |
|                 {
 | |
|                   children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1));
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|           // Transfer the right.
 | |
|           int n = right.getElementCount();
 | |
|           for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++)
 | |
|             {
 | |
|               children.add(clone(joined, right.getElement(i)));
 | |
|             }
 | |
| 
 | |
|           // Install the children.
 | |
|           Element[] c = new Element[children.size()];
 | |
|           c = (Element[]) children.toArray(c);
 | |
|           ((BranchElement) joined).replace(0, 0, c);
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           assert false : "Must not happen";
 | |
|         }
 | |
|       return joined;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Performs the actual work for {@link #change}. The elements at the
 | |
|      * interval boundaries are split up (if necessary) so that the interval
 | |
|      * boundaries are located at element boundaries.
 | |
|      */
 | |
|     protected void changeUpdate()
 | |
|     {
 | |
|       boolean didEnd = split(offset, length);
 | |
|       if (! didEnd)
 | |
|         {
 | |
|           // need to do the other end
 | |
|           while (elementStack.size() != 0)
 | |
|             {
 | |
|               pop();
 | |
|             }
 | |
|           split(offset + length, 0);
 | |
|         }
 | |
|       while (elementStack.size() != 0)
 | |
|         {
 | |
|           pop();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Modifies the element structure so that the specified interval starts and
 | |
|      * ends at an element boundary. Content and paragraph elements are split and
 | |
|      * created as necessary. This also updates the
 | |
|      * <code>DefaultDocumentEvent</code> to reflect the structural changes.
 | |
|      * The bulk work is delegated to {@link #changeUpdate()}.
 | |
|      *
 | |
|      * @param offset
 | |
|      *          the start index of the interval to be changed
 | |
|      * @param length
 | |
|      *          the length of the interval to be changed
 | |
|      * @param ev
 | |
|      *          the <code>DefaultDocumentEvent</code> describing the change
 | |
|      */
 | |
|     public void change(int offset, int length, DefaultDocumentEvent ev)
 | |
|     {
 | |
|       prepareEdit(offset, length);
 | |
|       changeUpdate();
 | |
|       finishEdit(ev);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates and returns a deep clone of the specified <code>clonee</code>
 | |
|      * with the specified parent as new parent.
 | |
|      *
 | |
|      * This method can only clone direct instances of {@link BranchElement}
 | |
|      * or {@link LeafElement}.
 | |
|      *
 | |
|      * @param parent the new parent
 | |
|      * @param clonee the element to be cloned
 | |
|      *
 | |
|      * @return the cloned element with the new parent
 | |
|      */
 | |
|     public Element clone(Element parent, Element clonee)
 | |
|     {
 | |
|       Element clone = clonee;
 | |
|       // We can only handle AbstractElements here.
 | |
|       if (clonee instanceof BranchElement)
 | |
|         {
 | |
|           BranchElement branchEl = (BranchElement) clonee;
 | |
|           BranchElement branchClone =
 | |
|             new BranchElement(parent, branchEl.getAttributes());
 | |
|           // Also clone all of the children.
 | |
|           int numChildren = branchClone.getElementCount();
 | |
|           Element[] cloneChildren = new Element[numChildren];
 | |
|           for (int i = 0; i < numChildren; ++i)
 | |
|             {
 | |
|               cloneChildren[i] = clone(branchClone,
 | |
|                                        branchClone.getElement(i));
 | |
|             }
 | |
|           branchClone.replace(0, 0, cloneChildren);
 | |
|           clone = branchClone;
 | |
|         }
 | |
|       else if (clonee instanceof LeafElement)
 | |
|         {
 | |
|           clone = new LeafElement(parent, clonee.getAttributes(),
 | |
|                                   clonee.getStartOffset(),
 | |
|                                   clonee.getEndOffset());
 | |
|         }
 | |
|       return clone;
 | |
|     }
 | |
| 
 | |
|     private Element cloneAsNecessary(Element parent, Element clonee,
 | |
|                                      int rmOffs0, int rmOffs1)
 | |
|     {
 | |
|       Element cloned;
 | |
|       if (clonee.isLeaf())
 | |
|         {
 | |
|           cloned = createLeafElement(parent, clonee.getAttributes(),
 | |
|                                      clonee.getStartOffset(),
 | |
|                                      clonee.getEndOffset());
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           Element e = createBranchElement(parent, clonee.getAttributes());
 | |
|           int n = clonee.getElementCount();
 | |
|           ArrayList childrenList = new ArrayList(n);
 | |
|           for (int i = 0; i < n; i++)
 | |
|             {
 | |
|               Element elem = clonee.getElement(i);
 | |
|               if (elem.getStartOffset() < rmOffs0
 | |
|                   || elem.getEndOffset() > rmOffs1)
 | |
|                 {
 | |
|                   childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
 | |
|                                                     rmOffs1));
 | |
|                 }
 | |
|             }
 | |
|           Element[] children = new Element[childrenList.size()];
 | |
|           children = (Element[]) childrenList.toArray(children);
 | |
|           ((BranchElement) e).replace(0, 0, children);
 | |
|           cloned = e;
 | |
|         }
 | |
|       return cloned;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Inserts new <code>Element</code> in the document at the specified
 | |
|      * position. Most of the work is done by {@link #insertUpdate}, after some
 | |
|      * fields have been prepared for it.
 | |
|      *
 | |
|      * @param offset
 | |
|      *          the location in the document at which the content is inserted
 | |
|      * @param length
 | |
|      *          the length of the inserted content
 | |
|      * @param data
 | |
|      *          the element specifications for the content to be inserted
 | |
|      * @param ev
 | |
|      *          the document event that is updated to reflect the structural
 | |
|      *          changes
 | |
|      */
 | |
|     public void insert(int offset, int length, ElementSpec[] data,
 | |
|                        DefaultDocumentEvent ev)
 | |
|     {
 | |
|       if (length > 0)
 | |
|         {
 | |
|           prepareEdit(offset, length);
 | |
|           insertUpdate(data);
 | |
|           finishEdit(ev);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Prepares the state of this object for performing an insert.
 | |
|      *
 | |
|      * @param offset the offset at which is inserted
 | |
|      * @param length the length of the inserted region
 | |
|      */
 | |
|     private void prepareEdit(int offset, int length)
 | |
|     {
 | |
|       this.offset = offset;
 | |
|       this.pos = offset;
 | |
|       this.endOffset = offset + length;
 | |
|       this.length = length;
 | |
| 
 | |
|       if (edits == null)
 | |
|         edits = new ArrayList();
 | |
|       else
 | |
|         edits.clear();
 | |
| 
 | |
|       if (elementStack == null)
 | |
|         elementStack = new Stack();
 | |
|       else
 | |
|         elementStack.clear();
 | |
| 
 | |
|       fracturedParent = null;
 | |
|       fracturedChild = null;
 | |
|       offsetLastIndex = false;
 | |
|       offsetLastIndexReplace = false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Finishes an insert. This applies all changes and updates
 | |
|      * the DocumentEvent.
 | |
|      *
 | |
|      * @param ev the document event
 | |
|      */
 | |
|     private void finishEdit(DefaultDocumentEvent ev)
 | |
|     {
 | |
|       // This for loop applies all the changes that were made and updates the
 | |
|       // DocumentEvent.
 | |
|       for (Iterator i = edits.iterator(); i.hasNext();)
 | |
|         {
 | |
|           Edit edits = (Edit) i.next();
 | |
|           Element[] removed = new Element[edits.removed.size()];
 | |
|           removed = (Element[]) edits.removed.toArray(removed);
 | |
|           Element[] added = new Element[edits.added.size()];
 | |
|           added = (Element[]) edits.added.toArray(added);
 | |
|           int index = edits.index;
 | |
|           BranchElement parent = (BranchElement) edits.e;
 | |
|           parent.replace(index, removed.length, added);
 | |
|           ElementEdit ee = new ElementEdit(parent, index, removed, added);
 | |
|           ev.addEdit(ee);
 | |
|         }
 | |
|       edits.clear();
 | |
|       elementStack.clear();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Inserts new content.
 | |
|      *
 | |
|      * @param data the element specifications for the elements to be inserted
 | |
|      */
 | |
|     protected void insertUpdate(ElementSpec[] data)
 | |
|     {
 | |
|       // Push the current path to the stack.
 | |
|       Element current = root;
 | |
|       int index = current.getElementIndex(offset);
 | |
|       while (! current.isLeaf())
 | |
|         {
 | |
|           Element child = current.getElement(index);
 | |
|           int editIndex = child.isLeaf() ? index : index + 1;
 | |
|           Edit edit = new Edit(current, editIndex);
 | |
|           elementStack.push(edit);
 | |
|           current = child;
 | |
|           index = current.getElementIndex(offset);
 | |
|         }
 | |
| 
 | |
|       // Create a copy of the original path.
 | |
|       insertPath = new Edit[elementStack.size()];
 | |
|       insertPath = (Edit[]) elementStack.toArray(insertPath);
 | |
| 
 | |
|       // No fracture yet.
 | |
|       createdFracture = false;
 | |
| 
 | |
|       // Insert first content tag.
 | |
|       int i = 0;
 | |
|       recreateLeafs = false;
 | |
|       int type = data[0].getType();
 | |
|       if (type == ElementSpec.ContentType)
 | |
|         {
 | |
|           // If the first tag is content we must treat it separately to allow
 | |
|           // for joining properly to previous Elements and to ensure that
 | |
|           // no extra LeafElements are erroneously inserted.
 | |
|           insertFirstContentTag(data);
 | |
|           pos += data[0].length;
 | |
|           i = 1;
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           createFracture(data);
 | |
|           i = 0;
 | |
|         }
 | |
| 
 | |
|       // Handle each ElementSpec individually.
 | |
|       for (; i < data.length; i++)
 | |
|         {
 | |
|           insertElement(data[i]);
 | |
|         }
 | |
| 
 | |
|       // Fracture if we haven't done yet.
 | |
|       if (! createdFracture)
 | |
|         fracture(-1);
 | |
| 
 | |
|       // Pop the remaining stack.
 | |
|       while (elementStack.size() != 0)
 | |
|         pop();
 | |
| 
 | |
|       // Offset last index if necessary.
 | |
|       if (offsetLastIndex && offsetLastIndexReplace)
 | |
|         insertPath[insertPath.length - 1].index++;
 | |
| 
 | |
|       // Make sure we havea an Edit for each path item that has a change.
 | |
|       for (int p = insertPath.length - 1; p >= 0; p--)
 | |
|         {
 | |
|           Edit edit = insertPath[p];
 | |
|           if (edit.e == fracturedParent)
 | |
|             edit.added.add(fracturedChild);
 | |
|           if ((edit.added.size() > 0 || edit.removed.size() > 0)
 | |
|               && ! edits.contains(edit))
 | |
|             edits.add(edit);
 | |
|         }
 | |
| 
 | |
|       // Remove element that would be created by an insert at 0 with
 | |
|       // an initial end tag.
 | |
|       if (offset == 0 && fracturedParent != null
 | |
|           && data[0].getType() == ElementSpec.EndTagType)
 | |
|         {
 | |
|           int p;
 | |
|           for (p = 0;
 | |
|                p < data.length && data[p].getType() == ElementSpec.EndTagType;
 | |
|                p++)
 | |
|             ;
 | |
| 
 | |
|           Edit edit = insertPath[insertPath.length - p - 1];
 | |
|           edit.index--;
 | |
|           edit.removed.add(0, edit.e.getElement(edit.index));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private void pop()
 | |
|     {
 | |
|       Edit edit = (Edit) elementStack.peek();
 | |
|       elementStack.pop();
 | |
|       if ((edit.added.size() > 0) || (edit.removed.size() > 0))
 | |
|         {
 | |
|           edits.add(edit);
 | |
|         }
 | |
|       else if (! elementStack.isEmpty())
 | |
|         {
 | |
|           Element e = edit.e;
 | |
|           if (e.getElementCount() == 0)
 | |
|             {
 | |
|               // If we pushed a branch element that didn't get
 | |
|               // used, make sure its not marked as having been added.
 | |
|               edit = (Edit) elementStack.peek();
 | |
|               edit.added.remove(e);
 | |
|           }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     private void insertElement(ElementSpec spec)
 | |
|     {
 | |
|       if (elementStack.isEmpty())
 | |
|         return;
 | |
| 
 | |
|       Edit edit = (Edit) elementStack.peek();
 | |
|       switch (spec.getType())
 | |
|         {
 | |
|         case ElementSpec.StartTagType:
 | |
|           switch (spec.getDirection())
 | |
|             {
 | |
|             case ElementSpec.JoinFractureDirection:
 | |
|               // Fracture the tree and ensure the appropriate element
 | |
|               // is on top of the stack.
 | |
|               if (! createdFracture)
 | |
|                 {
 | |
|                   fracture(elementStack.size() - 1);
 | |
|                 }
 | |
|               if (! edit.isFracture)
 | |
|                 {
 | |
|                   // If the parent isn't a fracture, then the fracture is
 | |
|                   // in fracturedChild.
 | |
|                   Edit newEdit = new Edit(fracturedChild, 0, true);
 | |
|                   elementStack.push(newEdit);
 | |
|                 }
 | |
|               else
 | |
|                 {
 | |
|                   // Otherwise use the parent's first child.
 | |
|                   Element el = edit.e.getElement(0);
 | |
|                   Edit newEdit = new Edit(el, 0, true);
 | |
|                   elementStack.push(newEdit);
 | |
|                 }
 | |
|               break;
 | |
|             case ElementSpec.JoinNextDirection:
 | |
|               // Push the next paragraph element onto the stack so
 | |
|               // future insertions are added to it.
 | |
|               Element parent = edit.e.getElement(edit.index);
 | |
|               if (parent.isLeaf())
 | |
|                 {
 | |
|                   if (edit.index + 1 < edit.e.getElementCount())
 | |
|                     parent = edit.e.getElement(edit.index + 1);
 | |
|                   else
 | |
|                     assert false; // Must not happen.
 | |
|                 }
 | |
|               elementStack.push(new Edit(parent, 0, true));
 | |
|               break;
 | |
|             default:
 | |
|               Element branch = createBranchElement(edit.e,
 | |
|                                                    spec.getAttributes());
 | |
|               edit.added.add(branch);
 | |
|               elementStack.push(new Edit(branch, 0));
 | |
|               break;
 | |
|             }
 | |
|           break;
 | |
|         case ElementSpec.EndTagType:
 | |
|           pop();
 | |
|           break;
 | |
|         case ElementSpec.ContentType:
 | |
|           insertContentTag(spec, edit);
 | |
|           break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Inserts the first tag into the document.
 | |
|      *
 | |
|      * @param data -
 | |
|      *          the data to be inserted.
 | |
|      */
 | |
|     private void insertFirstContentTag(ElementSpec[] data)
 | |
|     {
 | |
|       ElementSpec first = data[0];
 | |
|       Edit edit = (Edit) elementStack.peek();
 | |
|       Element current = edit.e.getElement(edit.index);
 | |
|       int firstEndOffset = offset + first.length;
 | |
|       boolean onlyContent = data.length == 1;
 | |
|       switch (first.getDirection())
 | |
|         {
 | |
|         case ElementSpec.JoinPreviousDirection:
 | |
|           if (current.getEndOffset() != firstEndOffset && ! onlyContent)
 | |
|             {
 | |
|               Element newEl1 = createLeafElement(edit.e,
 | |
|                                                  current.getAttributes(),
 | |
|                                                  current.getStartOffset(),
 | |
|                                                  firstEndOffset);
 | |
|               edit.added.add(newEl1);
 | |
|               edit.removed.add(current);
 | |
|               if (current.getEndOffset() != endOffset)
 | |
|                 recreateLeafs = true;
 | |
|               else
 | |
|                 offsetLastIndex = true;
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               offsetLastIndex = true;
 | |
|               offsetLastIndexReplace = true;
 | |
|             }
 | |
|           break;
 | |
|         case ElementSpec.JoinNextDirection:
 | |
|           if (offset != 0)
 | |
|             {
 | |
|               Element newEl1 = createLeafElement(edit.e,
 | |
|                                                  current.getAttributes(),
 | |
|                                                  current.getStartOffset(),
 | |
|                                                  offset);
 | |
|               edit.added.add(newEl1);
 | |
|               Element next = edit.e.getElement(edit.index + 1);
 | |
|               if (onlyContent)
 | |
|                 newEl1 = createLeafElement(edit.e, next.getAttributes(),
 | |
|                                            offset, next.getEndOffset());
 | |
|               else
 | |
|                 {
 | |
|                   newEl1 = createLeafElement(edit.e, next.getAttributes(),
 | |
|                                              offset, firstEndOffset);
 | |
|                 }
 | |
|               edit.added.add(newEl1);
 | |
|               edit.removed.add(current);
 | |
|               edit.removed.add(next);
 | |
|             }
 | |
|           break;
 | |
|         default: // OriginateDirection.
 | |
|           if (current.getStartOffset() != offset)
 | |
|             {
 | |
|               Element newEl = createLeafElement(edit.e,
 | |
|                                                 current.getAttributes(),
 | |
|                                                 current.getStartOffset(),
 | |
|                                                 offset);
 | |
|               edit.added.add(newEl);
 | |
|             }
 | |
|           edit.removed.add(current);
 | |
|           Element newEl1 = createLeafElement(edit.e, first.getAttributes(),
 | |
|                                              offset, firstEndOffset);
 | |
|           edit.added.add(newEl1);
 | |
|           if (current.getEndOffset() != endOffset)
 | |
|             recreateLeafs = true;
 | |
|           else
 | |
|             offsetLastIndex = true;
 | |
|           break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Inserts a content element into the document structure.
 | |
|      *
 | |
|      * @param tag -
 | |
|      *          the element spec
 | |
|      */
 | |
|     private void insertContentTag(ElementSpec tag, Edit edit)
 | |
|     {
 | |
|       int len = tag.getLength();
 | |
|       int dir = tag.getDirection();
 | |
|       if (dir == ElementSpec.JoinNextDirection)
 | |
|         {
 | |
|           if (! edit.isFracture)
 | |
|             {
 | |
|               Element first = null;
 | |
|               if (insertPath != null)
 | |
|                 {
 | |
|                   for (int p = insertPath.length - 1; p >= 0; p--)
 | |
|                     {
 | |
|                       if (insertPath[p] == edit)
 | |
|                         {
 | |
|                           if (p != insertPath.length - 1)
 | |
|                             first = edit.e.getElement(edit.index);
 | |
|                           break;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|               if (first == null)
 | |
|                 first = edit.e.getElement(edit.index + 1);
 | |
|               Element leaf = createLeafElement(edit.e, first.getAttributes(),
 | |
|                                                pos, first.getEndOffset());
 | |
|               edit.added.add(leaf);
 | |
|               edit.removed.add(first);
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               Element first = edit.e.getElement(0);
 | |
|               Element leaf = createLeafElement(edit.e, first.getAttributes(),
 | |
|                                                pos, first.getEndOffset());
 | |
|               edit.added.add(leaf);
 | |
|               edit.removed.add(first);
 | |
|             }
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos,
 | |
|                                            pos + len);
 | |
|           edit.added.add(leaf);
 | |
|         }
 | |
| 
 | |
|       pos += len;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * This method fractures bottomost leaf in the elementStack. This
 | |
|      * happens when the first inserted tag is not content.
 | |
|      *
 | |
|      * @param data
 | |
|      *          the ElementSpecs used for the entire insertion
 | |
|      */
 | |
|     private void createFracture(ElementSpec[] data)
 | |
|     {
 | |
|       Edit edit = (Edit) elementStack.peek();
 | |
|       Element child = edit.e.getElement(edit.index);
 | |
|       if (offset != 0)
 | |
|         {
 | |
|           Element newChild = createLeafElement(edit.e, child.getAttributes(),
 | |
|                                                child.getStartOffset(), offset);
 | |
|           edit.added.add(newChild);
 | |
|         }
 | |
|       edit.removed.add(child);
 | |
|       if (child.getEndOffset() != endOffset)
 | |
|         recreateLeafs = true;
 | |
|       else
 | |
|         offsetLastIndex = true;
 | |
|     }
 | |
| 
 | |
|     private void fracture(int depth)
 | |
|     {
 | |
|       int len = insertPath.length;
 | |
|       int lastIndex = -1;
 | |
|       boolean recreate = recreateLeafs;
 | |
|       Edit lastEdit = insertPath[len - 1];
 | |
|       boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount();
 | |
|       int deepestChangedIndex = recreate ? len : - 1;
 | |
|       int lastChangedIndex = len - 1;
 | |
|       createdFracture = true;
 | |
|       for (int i = len - 2; i >= 0; i--)
 | |
|         {
 | |
|           Edit edit = insertPath[i];
 | |
|           if (edit.added.size() > 0 || i == depth)
 | |
|             {
 | |
|               lastIndex = i;
 | |
|               if (! recreate && childChanged)
 | |
|                 {
 | |
|                   recreate = true;
 | |
|                   if (deepestChangedIndex == -1)
 | |
|                     deepestChangedIndex = lastChangedIndex + 1;
 | |
|                 }
 | |
|             }
 | |
|           if (! childChanged && edit.index < edit.e.getElementCount())
 | |
|             {
 | |
|               childChanged = true;
 | |
|               lastChangedIndex = i;
 | |
|             }
 | |
|         }
 | |
|       if (recreate)
 | |
|         {
 | |
|           if (lastIndex == -1)
 | |
|             lastIndex = len - 1;
 | |
|           recreate(lastIndex, deepestChangedIndex);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private void recreate(int startIndex, int endIndex)
 | |
|     {
 | |
|       // Recreate the element representing the inserted index.
 | |
|       Edit edit = insertPath[startIndex];
 | |
|       Element child;
 | |
|       Element newChild;
 | |
|       int changeLength = insertPath.length;
 | |
| 
 | |
|       if (startIndex + 1 == changeLength)
 | |
|         child = edit.e.getElement(edit.index);
 | |
|       else
 | |
|         child = edit.e.getElement(edit.index - 1);
 | |
| 
 | |
|       if(child.isLeaf())
 | |
|         {
 | |
|           newChild = createLeafElement(edit.e, child.getAttributes(),
 | |
|                                    Math.max(endOffset, child.getStartOffset()),
 | |
|                                    child.getEndOffset());
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           newChild = createBranchElement(edit.e, child.getAttributes());
 | |
|         }
 | |
|       fracturedParent = edit.e;
 | |
|       fracturedChild = newChild;
 | |
| 
 | |
|       // Recreate all the elements to the right of the insertion point.
 | |
|       Element parent = newChild;
 | |
|       while (++startIndex < endIndex)
 | |
|         {
 | |
|           boolean isEnd = (startIndex + 1) == endIndex;
 | |
|           boolean isEndLeaf = (startIndex + 1) == changeLength;
 | |
| 
 | |
|           // Create the newChild, a duplicate of the elment at
 | |
|           // index. This isn't done if isEnd and offsetLastIndex are true
 | |
|           // indicating a join previous was done.
 | |
|           edit = insertPath[startIndex];
 | |
| 
 | |
|           // Determine the child to duplicate, won't have to duplicate
 | |
|           // if at end of fracture, or offseting index.
 | |
|           if(isEnd)
 | |
|             {
 | |
|               if(offsetLastIndex || ! isEndLeaf)
 | |
|                 child = null;
 | |
|               else
 | |
|                 child = edit.e.getElement(edit.index);
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               child = edit.e.getElement(edit.index - 1);
 | |
|             }
 | |
| 
 | |
|           // Duplicate it.
 | |
|           if(child != null)
 | |
|             {
 | |
|               if(child.isLeaf())
 | |
|                 {
 | |
|                   newChild = createLeafElement(parent, child.getAttributes(),
 | |
|                                    Math.max(endOffset, child.getStartOffset()),
 | |
|                                    child.getEndOffset());
 | |
|                 }
 | |
|               else
 | |
|                 {
 | |
|                   newChild = createBranchElement(parent,
 | |
|                                                  child.getAttributes());
 | |
|                 }
 | |
|             }
 | |
|           else
 | |
|             newChild = null;
 | |
| 
 | |
|         // Recreate the remaining children (there may be none).
 | |
|         int childrenToMove = edit.e.getElementCount() - edit.index;
 | |
|         Element[] children;
 | |
|         int moveStartIndex;
 | |
|         int childStartIndex = 1;
 | |
| 
 | |
|         if (newChild == null)
 | |
|           {
 | |
|             // Last part of fracture.
 | |
|             if (isEndLeaf)
 | |
|               {
 | |
|                 childrenToMove--;
 | |
|                 moveStartIndex = edit.index + 1;
 | |
|               }
 | |
|             else
 | |
|               {
 | |
|                 moveStartIndex = edit.index;
 | |
|               }
 | |
|             childStartIndex = 0;
 | |
|             children = new Element[childrenToMove];
 | |
|           }
 | |
|         else
 | |
|           {
 | |
|             if (! isEnd)
 | |
|               {
 | |
|                 // Branch.
 | |
|                 childrenToMove++;
 | |
|                 moveStartIndex = edit.index;
 | |
|             }
 | |
|             else
 | |
|               {
 | |
|                 // Last leaf, need to recreate part of it.
 | |
|                 moveStartIndex = edit.index + 1;
 | |
|               }
 | |
|             children = new Element[childrenToMove];
 | |
|             children[0] = newChild;
 | |
|         }
 | |
| 
 | |
|         for (int c = childStartIndex; c < childrenToMove; c++)
 | |
|           {
 | |
|             Element toMove = edit.e.getElement(moveStartIndex++);
 | |
|             children[c] = recreateFracturedElement(parent, toMove);
 | |
|             edit.removed.add(toMove);
 | |
|           }
 | |
|         ((BranchElement) parent).replace(0, 0, children);
 | |
|         parent = newChild;
 | |
|       }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     private Element recreateFracturedElement(Element parent, Element toCopy)
 | |
|     {
 | |
|       Element recreated;
 | |
|       if(toCopy.isLeaf())
 | |
|         {
 | |
|           recreated = createLeafElement(parent, toCopy.getAttributes(),
 | |
|                                   Math.max(toCopy.getStartOffset(), endOffset),
 | |
|                                   toCopy.getEndOffset());
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           Element newParent = createBranchElement(parent,
 | |
|                                                   toCopy.getAttributes());
 | |
|           int childCount = toCopy.getElementCount();
 | |
|           Element[] newChildren = new Element[childCount];
 | |
|           for (int i = 0; i < childCount; i++)
 | |
|             {
 | |
|               newChildren[i] = recreateFracturedElement(newParent,
 | |
|                                                         toCopy.getElement(i));
 | |
|             }
 | |
|           ((BranchElement) newParent).replace(0, 0, newChildren);
 | |
|           recreated = newParent;
 | |
|         }
 | |
|       return recreated;
 | |
|     }
 | |
| 
 | |
|     private boolean split(int offs, int len)
 | |
|     {
 | |
|       boolean splitEnd = false;
 | |
|       // Push the path to the stack.
 | |
|       Element e = root;
 | |
|       int index = e.getElementIndex(offs);
 | |
|       while (! e.isLeaf())
 | |
|         {
 | |
|           elementStack.push(new Edit(e, index));
 | |
|           e = e.getElement(index);
 | |
|           index = e.getElementIndex(offs);
 | |
|         }
 | |
| 
 | |
|       Edit ec = (Edit) elementStack.peek();
 | |
|       Element child = ec.e.getElement(ec.index);
 | |
|       // Make sure there is something to do. If the
 | |
|       // offset is already at a boundary then there is
 | |
|       // nothing to do.
 | |
|       if (child.getStartOffset() < offs && offs < child.getEndOffset())
 | |
|         {
 | |
|           // We need to split, now see if the other end is within
 | |
|           // the same parent.
 | |
|           int index0 = ec.index;
 | |
|           int index1 = index0;
 | |
|           if (((offs + len) < ec.e.getEndOffset()) && (len != 0))
 | |
|             {
 | |
|               // It's a range split in the same parent.
 | |
|               index1 = ec.e.getElementIndex(offs+len);
 | |
|               if (index1 == index0)
 | |
|                 {
 | |
|                   // It's a three-way split.
 | |
|                   ec.removed.add(child);
 | |
|                   e = createLeafElement(ec.e, child.getAttributes(),
 | |
|                                         child.getStartOffset(), offs);
 | |
|                   ec.added.add(e);
 | |
|                   e = createLeafElement(ec.e, child.getAttributes(),
 | |
|                                         offs, offs + len);
 | |
|                   ec.added.add(e);
 | |
|                   e = createLeafElement(ec.e, child.getAttributes(),
 | |
|                                         offs + len, child.getEndOffset());
 | |
|                   ec.added.add(e);
 | |
|                   return true;
 | |
|                 }
 | |
|               else
 | |
|                 {
 | |
|                   child = ec.e.getElement(index1);
 | |
|                   if ((offs + len) == child.getStartOffset())
 | |
|                     {
 | |
|                       // End is already on a boundary.
 | |
|                       index1 = index0;
 | |
|                     }
 | |
|                 }
 | |
|               splitEnd = true;
 | |
|             }
 | |
| 
 | |
|           // Split the first location.
 | |
|           pos = offs;
 | |
|           child = ec.e.getElement(index0);
 | |
|           ec.removed.add(child);
 | |
|           e = createLeafElement(ec.e, child.getAttributes(),
 | |
|                                 child.getStartOffset(), pos);
 | |
|           ec.added.add(e);
 | |
|           e = createLeafElement(ec.e, child.getAttributes(),
 | |
|                                 pos, child.getEndOffset());
 | |
|           ec.added.add(e);
 | |
| 
 | |
|           // Pick up things in the middle.
 | |
|           for (int i = index0 + 1; i < index1; i++)
 | |
|             {
 | |
|               child = ec.e.getElement(i);
 | |
|               ec.removed.add(child);
 | |
|               ec.added.add(child);
 | |
|             }
 | |
| 
 | |
|           if (index1 != index0)
 | |
|             {
 | |
|               child = ec.e.getElement(index1);
 | |
|               pos = offs + len;
 | |
|               ec.removed.add(child);
 | |
|               e = createLeafElement(ec.e, child.getAttributes(),
 | |
|                                     child.getStartOffset(), pos);
 | |
|               ec.added.add(e);
 | |
|               e = createLeafElement(ec.e, child.getAttributes(),
 | |
|                                     pos, child.getEndOffset());
 | |
| 
 | |
|               ec.added.add(e);
 | |
|             }
 | |
|         }
 | |
|       return splitEnd;
 | |
| 
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * An element type for sections. This is a simple BranchElement with a unique
 | |
|    * name.
 | |
|    */
 | |
|   protected class SectionElement extends BranchElement
 | |
|   {
 | |
|     /**
 | |
|      * Creates a new SectionElement.
 | |
|      */
 | |
|     public SectionElement()
 | |
|     {
 | |
|       super(null, null);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the name of the element. This method always returns
 | |
|      * "section".
 | |
|      *
 | |
|      * @return the name of the element
 | |
|      */
 | |
|     public String getName()
 | |
|     {
 | |
|       return SectionElementName;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Receives notification when any of the document's style changes and calls
 | |
|    * {@link DefaultStyledDocument#styleChanged(Style)}.
 | |
|    *
 | |
|    * @author Roman Kennke (kennke@aicas.com)
 | |
|    */
 | |
|   private class StyleChangeListener implements ChangeListener
 | |
|   {
 | |
| 
 | |
|     /**
 | |
|      * Receives notification when any of the document's style changes and calls
 | |
|      * {@link DefaultStyledDocument#styleChanged(Style)}.
 | |
|      *
 | |
|      * @param event
 | |
|      *          the change event
 | |
|      */
 | |
|     public void stateChanged(ChangeEvent event)
 | |
|     {
 | |
|       Style style = (Style) event.getSource();
 | |
|       styleChanged(style);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /** The serialization UID (compatible with JDK1.5). */
 | |
|   private static final long serialVersionUID = 940485415728614849L;
 | |
| 
 | |
|   /**
 | |
|    * The default size to use for new content buffers.
 | |
|    */
 | |
|   public static final int BUFFER_SIZE_DEFAULT = 4096;
 | |
| 
 | |
|   /**
 | |
|    * The <code>EditorBuffer</code> that is used to manage to
 | |
|    * <code>Element</code> hierarchy.
 | |
|    */
 | |
|   protected DefaultStyledDocument.ElementBuffer buffer;
 | |
| 
 | |
|   /**
 | |
|    * Listens for changes on this document's styles and notifies styleChanged().
 | |
|    */
 | |
|   private StyleChangeListener styleChangeListener;
 | |
| 
 | |
|   /**
 | |
|    * Creates a new <code>DefaultStyledDocument</code>.
 | |
|    */
 | |
|   public DefaultStyledDocument()
 | |
|   {
 | |
|     this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Creates a new <code>DefaultStyledDocument</code> that uses the specified
 | |
|    * {@link StyleContext}.
 | |
|    *
 | |
|    * @param context
 | |
|    *          the <code>StyleContext</code> to use
 | |
|    */
 | |
|   public DefaultStyledDocument(StyleContext context)
 | |
|   {
 | |
|     this(new GapContent(BUFFER_SIZE_DEFAULT), context);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Creates a new <code>DefaultStyledDocument</code> that uses the specified
 | |
|    * {@link StyleContext} and {@link Content} buffer.
 | |
|    *
 | |
|    * @param content
 | |
|    *          the <code>Content</code> buffer to use
 | |
|    * @param context
 | |
|    *          the <code>StyleContext</code> to use
 | |
|    */
 | |
|   public DefaultStyledDocument(AbstractDocument.Content content,
 | |
|                                StyleContext context)
 | |
|   {
 | |
|     super(content, context);
 | |
|     buffer = new ElementBuffer(createDefaultRoot());
 | |
|     setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adds a style into the style hierarchy. Unspecified style attributes can be
 | |
|    * resolved in the <code>parent</code> style, if one is specified. While it
 | |
|    * is legal to add nameless styles (<code>nm == null</code),
 | |
|    * you must be aware that the client application is then responsible
 | |
|    * for managing the style hierarchy, since unnamed styles cannot be
 | |
|    * looked up by their name.
 | |
|    *
 | |
|    * @param nm the name of the style or <code>null</code> if the style should
 | |
|    *           be unnamed
 | |
|    * @param parent the parent in which unspecified style attributes are
 | |
|    *           resolved, or <code>null</code> if that is not necessary
 | |
|    *
 | |
|    * @return the newly created <code>Style</code>
 | |
|    */
 | |
|   public Style addStyle(String nm, Style parent)
 | |
|   {
 | |
|     StyleContext context = (StyleContext) getAttributeContext();
 | |
|     Style newStyle = context.addStyle(nm, parent);
 | |
| 
 | |
|     // Register change listener.
 | |
|     if (styleChangeListener == null)
 | |
|       styleChangeListener = new StyleChangeListener();
 | |
|     newStyle.addChangeListener(styleChangeListener);
 | |
| 
 | |
|     return newStyle;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Create the default root element for this kind of <code>Document</code>.
 | |
|    *
 | |
|    * @return the default root element for this kind of <code>Document</code>
 | |
|    */
 | |
|   protected AbstractDocument.AbstractElement createDefaultRoot()
 | |
|   {
 | |
|     Element[] tmp;
 | |
|     SectionElement section = new SectionElement();
 | |
| 
 | |
|     BranchElement paragraph = new BranchElement(section, null);
 | |
|     tmp = new Element[1];
 | |
|     tmp[0] = paragraph;
 | |
|     section.replace(0, 0, tmp);
 | |
| 
 | |
|     Element leaf = new LeafElement(paragraph, null, 0, 1);
 | |
|     tmp = new Element[1];
 | |
|     tmp[0] = leaf;
 | |
|     paragraph.replace(0, 0, tmp);
 | |
| 
 | |
|     return section;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the <code>Element</code> that corresponds to the character at the
 | |
|    * specified position.
 | |
|    *
 | |
|    * @param position
 | |
|    *          the position of which we query the corresponding
 | |
|    *          <code>Element</code>
 | |
|    * @return the <code>Element</code> that corresponds to the character at the
 | |
|    *         specified position
 | |
|    */
 | |
|   public Element getCharacterElement(int position)
 | |
|   {
 | |
|     Element element = getDefaultRootElement();
 | |
| 
 | |
|     while (!element.isLeaf())
 | |
|       {
 | |
|         int index = element.getElementIndex(position);
 | |
|         element = element.getElement(index);
 | |
|       }
 | |
| 
 | |
|     return element;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Extracts a background color from a set of attributes.
 | |
|    *
 | |
|    * @param attributes
 | |
|    *          the attributes from which to get a background color
 | |
|    * @return the background color that correspond to the attributes
 | |
|    */
 | |
|   public Color getBackground(AttributeSet attributes)
 | |
|   {
 | |
|     StyleContext context = (StyleContext) getAttributeContext();
 | |
|     return context.getBackground(attributes);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the default root element.
 | |
|    *
 | |
|    * @return the default root element
 | |
|    */
 | |
|   public Element getDefaultRootElement()
 | |
|   {
 | |
|     return buffer.getRootElement();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Extracts a font from a set of attributes.
 | |
|    *
 | |
|    * @param attributes
 | |
|    *          the attributes from which to get a font
 | |
|    * @return the font that correspond to the attributes
 | |
|    */
 | |
|   public Font getFont(AttributeSet attributes)
 | |
|   {
 | |
|     StyleContext context = (StyleContext) getAttributeContext();
 | |
|     return context.getFont(attributes);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Extracts a foreground color from a set of attributes.
 | |
|    *
 | |
|    * @param attributes
 | |
|    *          the attributes from which to get a foreground color
 | |
|    * @return the foreground color that correspond to the attributes
 | |
|    */
 | |
|   public Color getForeground(AttributeSet attributes)
 | |
|   {
 | |
|     StyleContext context = (StyleContext) getAttributeContext();
 | |
|     return context.getForeground(attributes);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the logical <code>Style</code> for the specified position.
 | |
|    *
 | |
|    * @param position
 | |
|    *          the position from which to query to logical style
 | |
|    * @return the logical <code>Style</code> for the specified position
 | |
|    */
 | |
|   public Style getLogicalStyle(int position)
 | |
|   {
 | |
|     Element paragraph = getParagraphElement(position);
 | |
|     AttributeSet attributes = paragraph.getAttributes();
 | |
|     AttributeSet a = attributes.getResolveParent();
 | |
|     // If the resolve parent is not of type Style, we return null.
 | |
|     if (a instanceof Style)
 | |
|       return (Style) a;
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the paragraph element for the specified position. If the position
 | |
|    * is outside the bounds of the document's root element, then the closest
 | |
|    * element is returned. That is the last paragraph if
 | |
|    * <code>position >= endIndex</code> or the first paragraph if
 | |
|    * <code>position < startIndex</code>.
 | |
|    *
 | |
|    * @param position
 | |
|    *          the position for which to query the paragraph element
 | |
|    * @return the paragraph element for the specified position
 | |
|    */
 | |
|   public Element getParagraphElement(int position)
 | |
|   {
 | |
|     Element e = getDefaultRootElement();
 | |
|     while (!e.isLeaf())
 | |
|       e = e.getElement(e.getElementIndex(position));
 | |
| 
 | |
|     if (e != null)
 | |
|       return e.getParentElement();
 | |
|     return e;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Looks up and returns a named <code>Style</code>.
 | |
|    *
 | |
|    * @param nm
 | |
|    *          the name of the <code>Style</code>
 | |
|    * @return the found <code>Style</code> of <code>null</code> if no such
 | |
|    *         <code>Style</code> exists
 | |
|    */
 | |
|   public Style getStyle(String nm)
 | |
|   {
 | |
|     StyleContext context = (StyleContext) getAttributeContext();
 | |
|     return context.getStyle(nm);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes a named <code>Style</code> from the style hierarchy.
 | |
|    *
 | |
|    * @param nm
 | |
|    *          the name of the <code>Style</code> to be removed
 | |
|    */
 | |
|   public void removeStyle(String nm)
 | |
|   {
 | |
|     StyleContext context = (StyleContext) getAttributeContext();
 | |
|     context.removeStyle(nm);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets text attributes for the fragment specified by <code>offset</code>
 | |
|    * and <code>length</code>.
 | |
|    *
 | |
|    * @param offset
 | |
|    *          the start offset of the fragment
 | |
|    * @param length
 | |
|    *          the length of the fragment
 | |
|    * @param attributes
 | |
|    *          the text attributes to set
 | |
|    * @param replace
 | |
|    *          if <code>true</code>, the attributes of the current selection
 | |
|    *          are overridden, otherwise they are merged
 | |
|    */
 | |
|   public void setCharacterAttributes(int offset, int length,
 | |
|                                      AttributeSet attributes, boolean replace)
 | |
|   {
 | |
|     // Exit early if length is 0, so no DocumentEvent is created or fired.
 | |
|     if (length == 0)
 | |
|       return;
 | |
|     try
 | |
|       {
 | |
|         // Must obtain a write lock for this method. writeLock() and
 | |
|         // writeUnlock() should always be in try/finally block to make
 | |
|         // sure that locking happens in a balanced manner.
 | |
|         writeLock();
 | |
|         DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
 | |
|                                                            length,
 | |
|                                                            DocumentEvent.EventType.CHANGE);
 | |
| 
 | |
|         // Modify the element structure so that the interval begins at an
 | |
|         // element
 | |
|         // start and ends at an element end.
 | |
|         buffer.change(offset, length, ev);
 | |
| 
 | |
|         // Visit all paragraph elements within the specified interval
 | |
|         int end = offset + length;
 | |
|         Element curr;
 | |
|         for (int pos = offset; pos < end;)
 | |
|           {
 | |
|             // Get the CharacterElement at offset pos.
 | |
|             curr = getCharacterElement(pos);
 | |
|             if (pos == curr.getEndOffset())
 | |
|               break;
 | |
| 
 | |
|             MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes();
 | |
|             ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace));
 | |
|             // If replace is true, remove all the old attributes.
 | |
|             if (replace)
 | |
|               a.removeAttributes(a);
 | |
|             // Add all the new attributes.
 | |
|             a.addAttributes(attributes);
 | |
|             // Increment pos so we can check the next CharacterElement.
 | |
|             pos = curr.getEndOffset();
 | |
|           }
 | |
|         fireChangedUpdate(ev);
 | |
|         fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | |
|       }
 | |
|     finally
 | |
|       {
 | |
|         writeUnlock();
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the logical style for the paragraph at the specified position.
 | |
|    *
 | |
|    * @param position
 | |
|    *          the position at which the logical style is added
 | |
|    * @param style
 | |
|    *          the style to set for the current paragraph
 | |
|    */
 | |
|   public void setLogicalStyle(int position, Style style)
 | |
|   {
 | |
|     Element el = getParagraphElement(position);
 | |
|     // getParagraphElement doesn't return null but subclasses might so
 | |
|     // we check for null here.
 | |
|     if (el == null)
 | |
|       return;
 | |
|     try
 | |
|       {
 | |
|         writeLock();
 | |
|         if (el instanceof AbstractElement)
 | |
|           {
 | |
|             AbstractElement ael = (AbstractElement) el;
 | |
|             ael.setResolveParent(style);
 | |
|             int start = el.getStartOffset();
 | |
|             int end = el.getEndOffset();
 | |
|             DefaultDocumentEvent ev = new DefaultDocumentEvent(start,
 | |
|                                                                end - start,
 | |
|                                                                DocumentEvent.EventType.CHANGE);
 | |
|             fireChangedUpdate(ev);
 | |
|             fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | |
|           }
 | |
|         else
 | |
|           throw new AssertionError(
 | |
|                                    "paragraph elements are expected to be"
 | |
|                                        + "instances of AbstractDocument.AbstractElement");
 | |
|       }
 | |
|     finally
 | |
|       {
 | |
|         writeUnlock();
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets text attributes for the paragraph at the specified fragment.
 | |
|    *
 | |
|    * @param offset
 | |
|    *          the beginning of the fragment
 | |
|    * @param length
 | |
|    *          the length of the fragment
 | |
|    * @param attributes
 | |
|    *          the text attributes to set
 | |
|    * @param replace
 | |
|    *          if <code>true</code>, the attributes of the current selection
 | |
|    *          are overridden, otherwise they are merged
 | |
|    */
 | |
|   public void setParagraphAttributes(int offset, int length,
 | |
|                                      AttributeSet attributes, boolean replace)
 | |
|   {
 | |
|     try
 | |
|       {
 | |
|         // Must obtain a write lock for this method. writeLock() and
 | |
|         // writeUnlock() should always be in try/finally blocks to make
 | |
|         // sure that locking occurs in a balanced manner.
 | |
|         writeLock();
 | |
| 
 | |
|         // Create a DocumentEvent to use for changedUpdate().
 | |
|         DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
 | |
|                                                            length,
 | |
|                                                            DocumentEvent.EventType.CHANGE);
 | |
| 
 | |
|         // Have to iterate through all the _paragraph_ elements that are
 | |
|         // contained or partially contained in the interval
 | |
|         // (offset, offset + length).
 | |
|         Element rootElement = getDefaultRootElement();
 | |
|         int startElement = rootElement.getElementIndex(offset);
 | |
|         int endElement = rootElement.getElementIndex(offset + length - 1);
 | |
|         if (endElement < startElement)
 | |
|           endElement = startElement;
 | |
| 
 | |
|         for (int i = startElement; i <= endElement; i++)
 | |
|           {
 | |
|             Element par = rootElement.getElement(i);
 | |
|             MutableAttributeSet a = (MutableAttributeSet) par.getAttributes();
 | |
|             // Add the change to the DocumentEvent.
 | |
|             ev.addEdit(new AttributeUndoableEdit(par, attributes, replace));
 | |
|             // If replace is true remove the old attributes.
 | |
|             if (replace)
 | |
|               a.removeAttributes(a);
 | |
|             // Add the new attributes.
 | |
|             a.addAttributes(attributes);
 | |
|           }
 | |
|         fireChangedUpdate(ev);
 | |
|         fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | |
|       }
 | |
|     finally
 | |
|       {
 | |
|         writeUnlock();
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Called in response to content insert actions. This is used to update the
 | |
|    * element structure.
 | |
|    *
 | |
|    * @param ev
 | |
|    *          the <code>DocumentEvent</code> describing the change
 | |
|    * @param attr
 | |
|    *          the attributes for the change
 | |
|    */
 | |
|   protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
 | |
|   {
 | |
|     int offs = ev.getOffset();
 | |
|     int len = ev.getLength();
 | |
|     int endOffs = offs + len;
 | |
|     if (attr == null)
 | |
|       attr = SimpleAttributeSet.EMPTY;
 | |
| 
 | |
|     // Paragraph attributes are fetched from the point _after_ the insertion.
 | |
|     Element paragraph = getParagraphElement(endOffs);
 | |
|     AttributeSet pAttr = paragraph.getAttributes();
 | |
|     // Character attributes are fetched from the actual insertion point.
 | |
|     Element paragraph2 = getParagraphElement(offs);
 | |
|     int contIndex = paragraph2.getElementIndex(offs);
 | |
|     Element content = paragraph2.getElement(contIndex);
 | |
|     AttributeSet cAttr = content.getAttributes();
 | |
| 
 | |
|     boolean insertAtBoundary = content.getEndOffset() == endOffs;
 | |
|     try
 | |
|       {
 | |
|         Segment s = new Segment();
 | |
|         ArrayList buf = new ArrayList();
 | |
|         ElementSpec lastStartTag = null;
 | |
|         boolean insertAfterNewline = false;
 | |
|         short lastStartDir = ElementSpec.OriginateDirection;
 | |
| 
 | |
|         // Special handle if we are inserting after a newline.
 | |
|         if (offs > 0)
 | |
|           {
 | |
|             getText(offs - 1, 1, s);
 | |
|             if (s.array[s.offset] == '\n')
 | |
|               {
 | |
|                 insertAfterNewline = true;
 | |
|                 lastStartDir = insertAfterNewline(paragraph, paragraph2,
 | |
|                                                   pAttr, buf, offs,
 | |
|                                                   endOffs);
 | |
|                 // Search last start tag.
 | |
|                 for (int i = buf.size() - 1; i >= 0 && lastStartTag == null;
 | |
|                      i--)
 | |
|                   {
 | |
|                     ElementSpec tag = (ElementSpec) buf.get(i);
 | |
|                     if (tag.getType() == ElementSpec.StartTagType)
 | |
|                       {
 | |
|                         lastStartTag = tag;
 | |
|                       }
 | |
|                   }
 | |
|               }
 | |
| 
 | |
|           }
 | |
| 
 | |
|         // If we are not inserting after a newline, the paragraph attributes
 | |
|         // come from the paragraph under the insertion point.
 | |
|         if (! insertAfterNewline)
 | |
|           pAttr = paragraph2.getAttributes();
 | |
| 
 | |
|         // Scan text and build up the specs.
 | |
|         getText(offs, len, s);
 | |
|         int end = s.offset + s.count;
 | |
|         int last = s.offset;
 | |
|         for (int i = s.offset; i < end; i++)
 | |
|           {
 | |
|             if (s.array[i] == '\n')
 | |
|               {
 | |
|                 int breakOffs = i + 1;
 | |
|                 buf.add(new ElementSpec(attr, ElementSpec.ContentType,
 | |
|                                         breakOffs - last));
 | |
|                 buf.add(new ElementSpec(null, ElementSpec.EndTagType));
 | |
|                 lastStartTag = new ElementSpec(pAttr,
 | |
|                                                ElementSpec.StartTagType);
 | |
|                 buf.add(lastStartTag);
 | |
|                 last = breakOffs;
 | |
|               }
 | |
|           }
 | |
| 
 | |
|         // Need to add a tailing content tag if we didn't finish at a boundary.
 | |
|         if (last < end)
 | |
|           {
 | |
|             buf.add(new ElementSpec(attr, ElementSpec.ContentType,
 | |
|                                     end - last));
 | |
|           }
 | |
| 
 | |
|         // Now we need to fix up the directions of the specs.
 | |
|         ElementSpec first = (ElementSpec) buf.get(0);
 | |
|         int doclen = getLength();
 | |
| 
 | |
|         // Maybe join-previous the first tag if it is content and has
 | |
|         // the same attributes as the previous character run.
 | |
|         if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr))
 | |
|           first.setDirection(ElementSpec.JoinPreviousDirection);
 | |
| 
 | |
|         // Join-fracture or join-next the last start tag if necessary.
 | |
|         if (lastStartTag != null)
 | |
|           {
 | |
|             if (insertAfterNewline)
 | |
|               lastStartTag.setDirection(lastStartDir);
 | |
|             else if (paragraph2.getEndOffset() != endOffs)
 | |
|               lastStartTag.setDirection(ElementSpec.JoinFractureDirection);
 | |
|             else
 | |
|               {
 | |
|                 Element par = paragraph2.getParentElement();
 | |
|                 int par2Index = par.getElementIndex(offs);
 | |
|                 if (par2Index + 1 < par.getElementCount()
 | |
|                     && ! par.getElement(par2Index + 1).isLeaf())
 | |
|                   lastStartTag.setDirection(ElementSpec.JoinNextDirection);
 | |
|               }
 | |
|           }
 | |
| 
 | |
|         // Join-next last tag if possible.
 | |
|         if (insertAtBoundary && endOffs < doclen)
 | |
|           {
 | |
|             ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
 | |
|             if (lastTag.getType() == ElementSpec.ContentType
 | |
|                 && ((lastStartTag == null
 | |
|                      && (paragraph == paragraph2 || insertAfterNewline))
 | |
|                     || (lastStartTag != null
 | |
|              && lastStartTag.getDirection() != ElementSpec.OriginateDirection)))
 | |
|               {
 | |
|                 int nextIndex = paragraph.getElementIndex(endOffs);
 | |
|                 Element nextRun = paragraph.getElement(nextIndex);
 | |
|                 if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes()))
 | |
|                   lastTag.setDirection(ElementSpec.JoinNextDirection);
 | |
|               }
 | |
|           }
 | |
| 
 | |
|         else if (! insertAtBoundary && lastStartTag != null
 | |
|            && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection)
 | |
|           {
 | |
|             ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
 | |
|             if (lastTag.getType() == ElementSpec.ContentType
 | |
|                 && lastTag.getDirection() != ElementSpec.JoinPreviousDirection
 | |
|                 && attr.isEqual(cAttr))
 | |
|               {
 | |
|                 lastTag.setDirection(ElementSpec.JoinNextDirection);
 | |
|               }
 | |
|           }
 | |
| 
 | |
|         ElementSpec[] specs = new ElementSpec[buf.size()];
 | |
|         specs = (ElementSpec[]) buf.toArray(specs);
 | |
|         buffer.insert(offs, len, specs, ev);
 | |
|       }
 | |
|     catch (BadLocationException ex)
 | |
|       {
 | |
|         // Ignore this. Comment out for debugging.
 | |
|         ex.printStackTrace();
 | |
|       }
 | |
|     super.insertUpdate(ev, attr);
 | |
|   }
 | |
| 
 | |
|   private short insertAfterNewline(Element par1, Element par2,
 | |
|                                    AttributeSet attr, ArrayList buf,
 | |
|                                    int offs, int endOffs)
 | |
|   {
 | |
|     short dir = 0;
 | |
|     if (par1.getParentElement() == par2.getParentElement())
 | |
|       {
 | |
|         ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType);
 | |
|         buf.add(tag);
 | |
|         tag = new ElementSpec(attr, ElementSpec.StartTagType);
 | |
|         buf.add(tag);
 | |
|         if (par2.getEndOffset() != endOffs)
 | |
|           dir = ElementSpec.JoinFractureDirection;
 | |
|         else
 | |
|           {
 | |
|             Element par = par2.getParentElement();
 | |
|             if (par.getElementIndex(offs) + 1 < par.getElementCount())
 | |
|               dir = ElementSpec.JoinNextDirection;
 | |
|           }
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         // For text with more than 2 levels, find the common parent of
 | |
|         // par1 and par2.
 | |
|         ArrayList parentsLeft = new ArrayList();
 | |
|         ArrayList parentsRight = new ArrayList();
 | |
|         Element e = par2;
 | |
|         while (e != null)
 | |
|           {
 | |
|             parentsLeft.add(e);
 | |
|             e = e.getParentElement();
 | |
|           }
 | |
|         e = par1;
 | |
|         int leftIndex = -1;
 | |
|         while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1)
 | |
|           {
 | |
|             parentsRight.add(e);
 | |
|             e = e.getParentElement();
 | |
|           }
 | |
| 
 | |
|         if (e != null)
 | |
| 
 | |
|           {
 | |
|             // e is now the common parent.
 | |
|             // Insert the end tags.
 | |
|             for (int c = 0; c < leftIndex; c++)
 | |
|               {
 | |
|                 buf.add(new ElementSpec(null, ElementSpec.EndTagType));
 | |
|               }
 | |
|             // Insert the start tags.
 | |
|             for (int c = parentsRight.size() - 1; c >= 0; c--)
 | |
|               {
 | |
|                 Element el = (Element) parentsRight.get(c);
 | |
|                 ElementSpec tag = new ElementSpec(el.getAttributes(),
 | |
|                                                   ElementSpec.StartTagType);
 | |
|                 if (c > 0)
 | |
|                   tag.setDirection(ElementSpec.JoinNextDirection);
 | |
|                 buf.add(tag);
 | |
|               }
 | |
|             if (parentsRight.size() > 0)
 | |
|               dir = ElementSpec.JoinNextDirection;
 | |
|             else
 | |
|               dir = ElementSpec.JoinFractureDirection;
 | |
|           }
 | |
|         else
 | |
|           assert false;
 | |
|       }
 | |
|     return dir;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * A helper method to set up the ElementSpec buffer for the special case of an
 | |
|    * insertion occurring immediately after a newline.
 | |
|    *
 | |
|    * @param specs
 | |
|    *          the ElementSpec buffer to initialize.
 | |
|    */
 | |
|   short handleInsertAfterNewline(Vector specs, int offset, int endOffset,
 | |
|                                  Element prevParagraph, Element paragraph,
 | |
|                                  AttributeSet a)
 | |
|   {
 | |
|     if (prevParagraph.getParentElement() == paragraph.getParentElement())
 | |
|       {
 | |
|         specs.add(new ElementSpec(a, ElementSpec.EndTagType));
 | |
|         specs.add(new ElementSpec(a, ElementSpec.StartTagType));
 | |
|         if (paragraph.getStartOffset() != endOffset)
 | |
|           return ElementSpec.JoinFractureDirection;
 | |
|         // If there is an Element after this one, use JoinNextDirection.
 | |
|         Element parent = paragraph.getParentElement();
 | |
|         if (parent.getElementCount() > (parent.getElementIndex(offset) + 1))
 | |
|           return ElementSpec.JoinNextDirection;
 | |
|       }
 | |
|     return ElementSpec.OriginateDirection;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Updates the document structure in response to text removal. This is
 | |
|    * forwarded to the {@link ElementBuffer} of this document. Any changes to the
 | |
|    * document structure are added to the specified document event and sent to
 | |
|    * registered listeners.
 | |
|    *
 | |
|    * @param ev
 | |
|    *          the document event that records the changes to the document
 | |
|    */
 | |
|   protected void removeUpdate(DefaultDocumentEvent ev)
 | |
|   {
 | |
|     super.removeUpdate(ev);
 | |
|     buffer.remove(ev.getOffset(), ev.getLength(), ev);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns an enumeration of all style names.
 | |
|    *
 | |
|    * @return an enumeration of all style names
 | |
|    */
 | |
|   public Enumeration<?> getStyleNames()
 | |
|   {
 | |
|     StyleContext context = (StyleContext) getAttributeContext();
 | |
|     return context.getStyleNames();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Called when any of this document's styles changes.
 | |
|    *
 | |
|    * @param style
 | |
|    *          the style that changed
 | |
|    */
 | |
|   protected void styleChanged(Style style)
 | |
|   {
 | |
|     // Nothing to do here. This is intended to be overridden by subclasses.
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Inserts a bulk of structured content at once.
 | |
|    *
 | |
|    * @param offset
 | |
|    *          the offset at which the content should be inserted
 | |
|    * @param data
 | |
|    *          the actual content spec to be inserted
 | |
|    */
 | |
|   protected void insert(int offset, ElementSpec[] data)
 | |
|       throws BadLocationException
 | |
|   {
 | |
|     if (data == null || data.length == 0)
 | |
|       return;
 | |
|     try
 | |
|       {
 | |
|         // writeLock() and writeUnlock() should always be in a try/finally
 | |
|         // block so that locking balance is guaranteed even if some
 | |
|         // exception is thrown.
 | |
|         writeLock();
 | |
| 
 | |
|         // First we collect the content to be inserted.
 | |
|         CPStringBuilder contentBuffer = new CPStringBuilder();
 | |
|         for (int i = 0; i < data.length; i++)
 | |
|           {
 | |
|             // Collect all inserts into one so we can get the correct
 | |
|             // ElementEdit
 | |
|             ElementSpec spec = data[i];
 | |
|             if (spec.getArray() != null && spec.getLength() > 0)
 | |
|               contentBuffer.append(spec.getArray(), spec.getOffset(),
 | |
|                                    spec.getLength());
 | |
|           }
 | |
| 
 | |
|         int length = contentBuffer.length();
 | |
| 
 | |
|         // If there was no content inserted then exit early.
 | |
|         if (length == 0)
 | |
|           return;
 | |
| 
 | |
|         Content c = getContent();
 | |
|         UndoableEdit edit = c.insertString(offset,
 | |
|                                            contentBuffer.toString());
 | |
| 
 | |
|         // Create the DocumentEvent with the ElementEdit added
 | |
|         DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
 | |
|                                                            length,
 | |
|                                                            DocumentEvent.EventType.INSERT);
 | |
| 
 | |
|         ev.addEdit(edit);
 | |
| 
 | |
|         // Finally we must update the document structure and fire the insert
 | |
|         // update event.
 | |
|         buffer.insert(offset, length, data, ev);
 | |
| 
 | |
|         super.insertUpdate(ev, null);
 | |
| 
 | |
|         ev.end();
 | |
|         fireInsertUpdate(ev);
 | |
|         fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | |
|       }
 | |
|     finally
 | |
|       {
 | |
|         writeUnlock();
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Initializes the <code>DefaultStyledDocument</code> with the specified
 | |
|    * data.
 | |
|    *
 | |
|    * @param data
 | |
|    *          the specification of the content with which the document is
 | |
|    *          initialized
 | |
|    */
 | |
|   protected void create(ElementSpec[] data)
 | |
|   {
 | |
|     try
 | |
|       {
 | |
| 
 | |
|         // Clear content if there is some.
 | |
|         int len = getLength();
 | |
|         if (len > 0)
 | |
|           remove(0, len);
 | |
| 
 | |
|         writeLock();
 | |
| 
 | |
|         // Now we insert the content.
 | |
|         StringBuilder b = new StringBuilder();
 | |
|         for (int i = 0; i < data.length; ++i)
 | |
|           {
 | |
|             ElementSpec el = data[i];
 | |
|             if (el.getArray() != null && el.getLength() > 0)
 | |
|               b.append(el.getArray(), el.getOffset(), el.getLength());
 | |
|           }
 | |
|         Content content = getContent();
 | |
|         UndoableEdit cEdit = content.insertString(0, b.toString());
 | |
| 
 | |
|         len = b.length();
 | |
|         DefaultDocumentEvent ev =
 | |
|           new DefaultDocumentEvent(0, b.length(),
 | |
|                                    DocumentEvent.EventType.INSERT);
 | |
|         ev.addEdit(cEdit);
 | |
| 
 | |
|         buffer.create(len, data, ev);
 | |
| 
 | |
|         // For the bidi update.
 | |
|         super.insertUpdate(ev, null);
 | |
| 
 | |
|         ev.end();
 | |
|         fireInsertUpdate(ev);
 | |
|         fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | |
|       }
 | |
|     catch (BadLocationException ex)
 | |
|       {
 | |
|         AssertionError err = new AssertionError("Unexpected bad location");
 | |
|         err.initCause(ex);
 | |
|         throw err;
 | |
|       }
 | |
|     finally
 | |
|       {
 | |
|         writeUnlock();
 | |
|       }
 | |
|   }
 | |
| }
 |