aboutsummaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/text/DefaultStyledDocument.java')
-rw-r--r--libjava/classpath/javax/swing/text/DefaultStyledDocument.java2526
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
- * &quot;section&quot;.
- *
- * @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();
- }
- }
-}