diff options
Diffstat (limited to 'libjava/classpath/javax/swing/text/DefaultStyledDocument.java')
-rw-r--r-- | libjava/classpath/javax/swing/text/DefaultStyledDocument.java | 2526 |
1 files changed, 0 insertions, 2526 deletions
diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java deleted file mode 100644 index 9021a19..0000000 --- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java +++ /dev/null @@ -1,2526 +0,0 @@ -/* 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(); - } - } -} |