summaryrefslogtreecommitdiffstats
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.java2091
1 files changed, 1239 insertions, 852 deletions
diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
index 46b82259b65..625ba4c3dcc 100644
--- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
+++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
@@ -53,27 +53,25 @@ 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
+ * 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
+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
+ public static class AttributeUndoableEdit extends AbstractUndoableEdit
{
/**
* A copy of the old attributes.
@@ -98,11 +96,13 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* 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
+ *
+ * @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)
@@ -149,21 +149,19 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * 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.
+ * 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}.
+ * 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}.
+ * This indicates an end tag. This is a possible value for {@link #getType}.
*/
public static final short EndTagType = 2;
@@ -175,22 +173,19 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* This indicates that the data associated with this spec should be joined
- * with what precedes it. This is a possible value for
- * {@link #getDirection}.
+ * 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}.
+ * 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}.
+ * 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;
@@ -234,9 +229,11 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* 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
+ *
+ * @param a
+ * the attributes for the element to be created
+ * @param type
+ * the type of the tag
*/
public ElementSpec(AttributeSet a, short type)
{
@@ -247,27 +244,34 @@ public class DefaultStyledDocument extends AbstractDocument
* 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
+ *
+ * @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
+ *
+ * @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)
+ public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)
{
attributes = a;
this.type = type;
@@ -279,8 +283,9 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Sets the type of the element.
- *
- * @param type the type of the element to be set
+ *
+ * @param type
+ * the type of the element to be set
*/
public void setType(short type)
{
@@ -289,7 +294,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the type of the element.
- *
+ *
* @return the type of the element
*/
public short getType()
@@ -299,8 +304,9 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Sets the direction of the element.
- *
- * @param dir the direction of the element to be set
+ *
+ * @param dir
+ * the direction of the element to be set
*/
public void setDirection(short dir)
{
@@ -309,7 +315,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the direction of the element.
- *
+ *
* @return the direction of the element
*/
public short getDirection()
@@ -319,7 +325,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the attributes of the element.
- *
+ *
* @return the attributes of the element
*/
public AttributeSet getAttributes()
@@ -329,7 +335,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the actual content of the element.
- *
+ *
* @return the actual content of the element
*/
public char[] getArray()
@@ -339,7 +345,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the offset of the content.
- *
+ *
* @return the offset of the content
*/
public int getOffset()
@@ -349,7 +355,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the length of the content.
- *
+ *
* @return the length of the content
*/
public int getLength()
@@ -361,7 +367,7 @@ public class DefaultStyledDocument extends AbstractDocument
* 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()
@@ -413,7 +419,8 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Performs all <em>structural</code> changes to the <code>Element</code>
- * hierarchy.
+ * 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
{
@@ -426,25 +433,20 @@ public class DefaultStyledDocument extends AbstractDocument
/** 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 end offset for structural changes. **/
- private int endOffset;
- /**
- * The number of inserted end tags. This is a counter which always gets
- * incremented when an end tag is inserted. This is evaluated before
- * content insertion to go up the element stack.
- */
- private int numEndTags;
+ /** Holds the position of the change. */
+ private int pos;
- /**
- * The number of inserted start tags. This is a counter which always gets
- * incremented when an end tag is inserted. This is evaluated before
- * content insertion to go up the element stack.
- */
- private int numStartTags;
+ /** Holds the element that was last fractured. */
+ private Element lastFractured;
+
+ /** True if a fracture was not created during a insertFracture call. */
+ private boolean fracNotCreated;
/**
* The current position in the element tree. This is used for bulk inserts
@@ -453,14 +455,6 @@ public class DefaultStyledDocument extends AbstractDocument
private Stack elementStack;
/**
- * Holds fractured elements during insertion of end and start tags.
- * Inserting an end tag may lead to fracturing of the current paragraph
- * element. The elements that have been cut off may be added to the
- * next paragraph that is created in the next start tag.
- */
- Element[] fracture;
-
- /**
* The ElementChange that describes the latest changes.
*/
DefaultDocumentEvent documentEvent;
@@ -468,8 +462,9 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Creates a new <code>ElementBuffer</code> for the specified
* <code>root</code> element.
- *
- * @param root the root element for this <code>ElementBuffer</code>
+ *
+ * @param root
+ * the root element for this <code>ElementBuffer</code>
*/
public ElementBuffer(Element root)
{
@@ -479,7 +474,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the root element of this <code>ElementBuffer</code>.
- *
+ *
* @return the root element of this <code>ElementBuffer</code>
*/
public Element getRootElement()
@@ -488,21 +483,23 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * Updates the element structure of the document in response to removal of
- * content. It removes the affected {@link Element}s from the document
- * structure.
- *
- * 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
+ * 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)
{
+ if (len == 0)
+ return;
offset = offs;
length = len;
+ pos = offset;
documentEvent = ev;
removeUpdate();
}
@@ -519,9 +516,9 @@ public class DefaultStyledDocument extends AbstractDocument
Element[] empty = new Element[0];
int removeStart = -1;
int removeEnd = -1;
- for (int i = startParagraph; i < endParagraph; i++)
+ for (int i = startParagraph; i < endParagraph; i++)
{
- Element paragraph = root.getElement(i);
+ BranchElement paragraph = (BranchElement) root.getElement(i);
int contentStart = paragraph.getElementIndex(offset);
int contentEnd = paragraph.getElementIndex(offset + length);
if (contentStart == paragraph.getStartOffset()
@@ -546,10 +543,8 @@ public class DefaultStyledDocument extends AbstractDocument
Element[] removed = new Element[removeLen];
for (int j = contentStart; j < contentEnd; j++)
removed[j] = paragraph.getElement(j);
- ((BranchElement) paragraph).replace(contentStart, removeLen,
- empty);
- documentEvent.addEdit(new ElementEdit(paragraph, contentStart,
- removed, empty));
+ Edit edit = getEditForParagraphAndIndex(paragraph, contentStart);
+ edit.addRemovedElements(removed);
}
}
// Now we remove paragraphs from the root that have been tagged for
@@ -560,265 +555,234 @@ public class DefaultStyledDocument extends AbstractDocument
Element[] removed = new Element[removeLen];
for (int i = removeStart; i < removeEnd; i++)
removed[i] = root.getElement(i);
- ((BranchElement) root).replace(removeStart, removeLen, empty);
- documentEvent.addEdit(new ElementEdit(root, removeStart, removed,
- empty));
+ Edit edit = getEditForParagraphAndIndex((BranchElement) root,
+ removeStart);
+ edit.addRemovedElements(removed);
}
}
/**
- * 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)
- {
- this.offset = offset;
- this.length = length;
- documentEvent = ev;
- changeUpdate();
- }
-
- /**
- * 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.
+ * 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()
{
// Split up the element at the start offset if necessary.
Element el = getCharacterElement(offset);
- Element[] res = split(el, offset, 0);
+ Element[] res = split(el, offset, 0, el.getElementIndex(offset));
BranchElement par = (BranchElement) el.getParentElement();
+ int index = par.getElementIndex(offset);
+ Edit edit = getEditForParagraphAndIndex(par, index);
if (res[1] != null)
{
- int index = par.getElementIndex(offset);
Element[] removed;
Element[] added;
if (res[0] == null)
{
removed = new Element[0];
- added = new Element[]{ res[1] };
+ added = new Element[] { res[1] };
index++;
}
else
{
- removed = new Element[]{ el };
- added = new Element[]{ res[0], res[1] };
+ removed = new Element[] { el };
+ added = new Element[] { res[0], res[1] };
}
- par.replace(index, removed.length, added);
- addEdit(par, index, removed, added);
+ edit.addRemovedElements(removed);
+
+ edit.addAddedElements(added);
}
int endOffset = offset + length;
el = getCharacterElement(endOffset);
- res = split(el, endOffset, 0);
+ res = split(el, endOffset, 0, el.getElementIndex(endOffset));
par = (BranchElement) el.getParentElement();
- if (res[1] != null)
+ if (res[0] != null)
{
- int index = par.getElementIndex(offset);
Element[] removed;
Element[] added;
if (res[1] == null)
{
removed = new Element[0];
- added = new Element[]{ res[1] };
+ added = new Element[] { res[1] };
}
else
{
- removed = new Element[]{ el };
- added = new Element[]{ res[0], res[1] };
+ removed = new Element[] { el };
+ added = new Element[] { res[0], res[1] };
}
- par.replace(index, removed.length, added);
- addEdit(par, index, removed, added);
+ edit.addRemovedElements(removed);
+ edit.addAddedElements(added);
}
}
/**
- * Splits an element if <code>offset</code> is not alread at its boundary.
+ * 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)
+ {
+ if (length == 0)
+ return;
+ this.offset = offset;
+ this.pos = offset;
+ this.length = length;
+ documentEvent = ev;
+ changeUpdate();
+ }
+
+ /**
+ * Creates and returns a deep clone of the specified <code>clonee</code>
+ * with the specified parent as new parent.
*
- * @param el the Element to possibly split
- * @param offset the offset at which to possibly split
- * @param space the amount of space to create between the splitted parts
+ * This method can only clone direct instances of {@link BranchElement}
+ * or {@link LeafElement}.
*
- * @return An array of elements which represent the split result. This
- * array has two elements, the two parts of the split. The first
- * element might be null, which means that the element which should
- * be splitted can remain in place. The second element might also
- * be null, which means that the offset is already at an element
- * boundary and the element doesn't need to be splitted.
- *
+ * @param parent the new parent
+ * @param clonee the element to be cloned
+ *
+ * @return the cloned element with the new parent
*/
- private Element[] split(Element el, int offset, int space)
+ public Element clone(Element parent, Element clonee)
{
- // If we are at an element boundary, then return an empty array.
- if ((offset == el.getStartOffset() || offset == el.getEndOffset())
- && space == 0 && el.isLeaf())
- return new Element[2];
-
- // If the element is an instance of BranchElement, then we recursivly
- // call this method to perform the split.
- Element[] res = new Element[2];
- if (el instanceof BranchElement)
+ Element clone = clonee;
+ // We can only handle AbstractElements here.
+ if (clonee instanceof BranchElement)
{
- int index = el.getElementIndex(offset);
- Element child = el.getElement(index);
- Element[] result = split(child, offset, space);
- Element[] removed;
- Element[] added;
- Element[] newAdded;
-
- int count = el.getElementCount();
- if (!(result[1] == null))
- {
- // This is the case when we can keep the first element.
- if (result[0] == null)
- {
- removed = new Element[count - index - 1];
- newAdded = new Element[count - index - 1];
- added = new Element[]{};
- }
- // This is the case when we may not keep the first element.
- else
- {
- removed = new Element[count - index];
- newAdded = new Element[count - index];
- added = new Element[]{result[0]};
- }
- newAdded[0] = result[1];
- for (int i = index; i < count; i++)
- {
- Element el2 = el.getElement(i);
- int ind = i - count + removed.length;
- removed[ind] = el2;
- if (ind != 0)
- newAdded[ind] = el2;
- }
-
- ((BranchElement) el).replace(index, removed.length, added);
- addEdit(el, index, removed, added);
- BranchElement newPar =
- (BranchElement) createBranchElement(el.getParentElement(),
- el.getAttributes());
- newPar.replace(0, 0, newAdded);
- res = new Element[]{ null, newPar };
- }
- else
+ 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)
{
- removed = new Element[count - index];
- for (int i = index; i < count; ++i)
- removed[i - index] = el.getElement(i);
- added = new Element[0];
- ((BranchElement) el).replace(index, removed.length,
- added);
- addEdit(el, index, removed, added);
- BranchElement newPar =
- (BranchElement) createBranchElement(el.getParentElement(),
- el.getAttributes());
- newPar.replace(0, 0, removed);
- res = new Element[]{ null, newPar };
+ cloneChildren[i] = clone(branchClone,
+ branchClone.getElement(i));
}
+ branchClone.replace(0, 0, cloneChildren);
+ clone = branchClone;
}
- else if (el instanceof LeafElement)
+ else if (clonee instanceof LeafElement)
{
- BranchElement par = (BranchElement) el.getParentElement();
- Element el1 = createLeafElement(par, el.getAttributes(),
- el.getStartOffset(), offset);
- Element el2 = createLeafElement(par, el.getAttributes(),
- offset + space, el.getEndOffset());
- res = new Element[]{ el1, el2 };
+ clone = new LeafElement(parent, clonee.getAttributes(),
+ clonee.getStartOffset(),
+ clonee.getEndOffset());
}
- return res;
+ return clone;
}
/**
* 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
+ * 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)
return;
+
this.offset = offset;
- this.length = length;
+ this.pos = offset;
this.endOffset = offset + length;
+ this.length = length;
documentEvent = ev;
- // Push the root and the paragraph at offset onto the element stack.
- elementStack.clear();
- elementStack.push(root);
- elementStack.push(root.getElement(root.getElementIndex(offset)));
- numEndTags = 0;
- numStartTags = 0;
+
+ edits.removeAllElements();
+ elementStack.removeAllElements();
+ lastFractured = null;
+ fracNotCreated = false;
insertUpdate(data);
+ // This for loop applies all the changes that were made and updates the
+ // DocumentEvent.
+ int size = edits.size();
+ for (int i = 0; i < size; i++)
+ {
+ Edit curr = (Edit) edits.get(i);
+ BranchElement e = (BranchElement) curr.e;
+ Element[] removed = curr.getRemovedElements();
+ Element[] added = curr.getAddedElements();
+ // FIXME: We probably shouldn't create the empty Element[] in the
+ // first place.
+ if (removed.length > 0 || added.length > 0)
+ {
+ if (curr.index + removed.length <= e.getElementCount())
+ {
+ e.replace(curr.index, removed.length, added);
+ ElementEdit ee = new ElementEdit(e, curr.index, removed, added);
+ ev.addEdit(ee);
+ }
+ else
+ {
+ System.err.println("WARNING: Tried to replace elements ");
+ System.err.print("beyond boundaries: elementCount: ");
+ System.err.println(e.getElementCount());
+ System.err.print("index: " + curr.index);
+ System.err.println(", removed.length: " + removed.length);
+ }
+ }
+ }
}
/**
- * Performs the actual structural change for {@link #insert}. This
- * creates a bunch of {@link Element}s as specified by <code>data</code>
- * and inserts it into the document as specified in the arguments to
- * {@link #insert}.
- *
- * @param data the element specifications for the elements to be inserte
- */
+ * Inserts new content
+ *
+ * @param data
+ * the element specifications for the elements to be inserted
+ */
protected void insertUpdate(ElementSpec[] data)
{
- if (data[0].getType() == ElementSpec.EndTagType)
+ // Push the root and the paragraph at offset onto the element stack.
+ Element current = root;
+ int index;
+ while (!current.isLeaf())
{
- // fracture deepest child here
- BranchElement paragraph = (BranchElement) elementStack.peek();
- Element curr = paragraph.getParentElement();
- int index = curr.getElementIndex(offset);
- while (!curr.isLeaf())
- {
- index = curr.getElementIndex(offset);
- curr = curr.getElement(index);
- }
- Element parent = curr.getParentElement();
- Element newEl1 = createLeafElement(parent,
- curr.getAttributes(),
- curr.getStartOffset(), offset);
- Element grandParent = parent.getParentElement();
- BranchElement nextBranch =
- (BranchElement) grandParent.getElement
- (grandParent.getElementIndex(parent.getEndOffset()));
- Element firstLeaf = nextBranch.getElement(0);
- while (!firstLeaf.isLeaf())
- {
- firstLeaf = firstLeaf.getElement(0);
- }
- BranchElement parent2 = (BranchElement) firstLeaf.getParentElement();
- Element newEl2 =
- createLeafElement(parent2,
- firstLeaf.getAttributes(),
- offset, firstLeaf.getEndOffset());
- parent2.replace(0, 1, new Element[] { newEl2 });
-
-
- ((BranchElement) parent).
- replace(index, 1, new Element[] { newEl1 });
+ index = current.getElementIndex(offset);
+ elementStack.push(current);
+ current = current.getElement(index);
}
- for (int i = 0; i < data.length; i++)
+ int i = 0;
+ 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++)
{
BranchElement paragraph = (BranchElement) elementStack.peek();
switch (data[i].getType())
@@ -827,19 +791,41 @@ public class DefaultStyledDocument extends AbstractDocument
switch (data[i].getDirection())
{
case ElementSpec.JoinFractureDirection:
+ // Fracture the tree and ensure the appropriate element
+ // is on top of the stack.
+ fracNotCreated = false;
insertFracture(data[i]);
+ if (fracNotCreated)
+ {
+ if (lastFractured != null)
+ elementStack.push(lastFractured.getParentElement());
+ else
+ elementStack.push(paragraph.getElement(0));
+ }
break;
case ElementSpec.JoinNextDirection:
- int index = paragraph.getElementIndex(offset);
- elementStack.push(paragraph.getElement(index));
- break;
- case ElementSpec.OriginateDirection:
- Element current = (Element) elementStack.peek();
- Element newParagraph =
- insertParagraph((BranchElement) current, offset);
- elementStack.push(newParagraph);
+ // Push the next paragraph element onto the stack so
+ // future insertions are added to it.
+ int ix = paragraph.getElementIndex(pos) + 1;
+ elementStack.push(paragraph.getElement(ix));
break;
default:
+ Element br = null;
+ if (data.length > i + 1)
+ {
+ // leaves will be added to paragraph later
+ int x = 0;
+ if (paragraph.getElementCount() > 0)
+ x = paragraph.getElementIndex(pos) + 1;
+ Edit e = getEditForParagraphAndIndex(paragraph, x);
+ br = (BranchElement) createBranchElement(paragraph,
+ data[i].getAttributes());
+ e.added.add(br);
+ elementStack.push(br);
+ }
+ else
+ // need to add leaves to paragraph now
+ br = insertParagraph(paragraph, pos);
break;
}
break;
@@ -848,50 +834,27 @@ public class DefaultStyledDocument extends AbstractDocument
break;
case ElementSpec.ContentType:
insertContentTag(data[i]);
+ offset = pos;
break;
}
}
- endEdit();
}
-
- /**
- * Finishes an insertion by possibly evaluating the outstanding start and
- * end tags. However, this is only performed if the event has received any
- * modifications.
- */
- private void endEdit()
- {
- if (documentEvent.modified)
- prepareContentInsertion();
- }
-
+
/**
- * Evaluates the number of inserted end tags and performs the corresponding
- * structural changes.
+ * Inserts a new paragraph.
+ *
+ * @param par -
+ * the parent
+ * @param offset -
+ * the offset
+ * @return the new paragraph
*/
- private void prepareContentInsertion()
- {
- while (numEndTags > 0)
- {
- elementStack.pop();
- numEndTags--;
- }
-
- while (numStartTags > 0)
- {
- Element current = (Element) elementStack.peek();
- Element newParagraph =
- insertParagraph((BranchElement) current, offset);
- elementStack.push(newParagraph);
- numStartTags--;
- }
- }
-
private Element insertParagraph(BranchElement par, int offset)
{
- Element current = par.getElement(par.getElementIndex(offset));
- Element[] res = split(current, offset, 0);
int index = par.getElementIndex(offset);
+ Element current = par.getElement(index);
+ Element[] res = split(current, offset, 0, 0);
+ Edit e = getEditForParagraphAndIndex(par, index + 1);
Element ret;
if (res[1] != null)
{
@@ -902,334 +865,757 @@ public class DefaultStyledDocument extends AbstractDocument
removed = new Element[0];
if (res[1] instanceof BranchElement)
{
- added = new Element[]{ res[1] };
+ added = new Element[] { res[1] };
ret = res[1];
}
else
{
ret = createBranchElement(par, null);
- added = new Element[]{ ret, res[1] };
+ added = new Element[] { ret, res[1] };
}
index++;
}
else
{
- removed = new Element[]{ current };
+ removed = new Element[] { current };
if (res[1] instanceof BranchElement)
{
ret = res[1];
- added = new Element[]{ res[0], res[1] };
+ added = new Element[] { res[0], res[1] };
}
else
{
ret = createBranchElement(par, null);
- added = new Element[]{ res[0], ret, res[1] };
+ added = new Element[] { res[0], ret, res[1] };
}
}
- par.replace(index, removed.length, added);
- addEdit(par, index, removed, added);
+
+ e.addAddedElements(added);
+ e.addRemovedElements(removed);
}
else
{
ret = createBranchElement(par, null);
- Element[] added = new Element[]{ ret };
- par.replace(index, 0, added);
- addEdit(par, index, new Element[0], added);
+ e.addAddedElement(ret);
}
return ret;
}
/**
- * Inserts a fracture into the document structure.
+ * Inserts the first tag into the document.
*
- * @param tag - the element spec.
+ * @param data -
+ * the data to be inserted.
*/
- private void insertFracture(ElementSpec tag)
+ private void insertFirstContentTag(ElementSpec[] data)
{
- // This is the parent of the paragraph about to be fractured. We will
- // create a new child of this parent.
- BranchElement parent = (BranchElement) elementStack.peek();
- int parentIndex = parent.getElementIndex(offset);
-
- // This is the old paragraph. We must remove all its children that
- // occur after offset and move them to a new paragraph. We must
- // also recreate its child that occurs at offset to have the proper
- // end offset. The remainder of this child will also go in the new
- // paragraph.
- BranchElement previous = (BranchElement) parent.getElement(parentIndex);
-
- // This is the new paragraph.
- BranchElement newBranch =
- (BranchElement) createBranchElement(parent, previous.getAttributes());
-
-
- // The steps we must take to properly fracture are:
- // 1. Recreate the LeafElement at offset to have the correct end offset.
- // 2. Create a new LeafElement with the remainder of the LeafElement in
- // #1 ==> this is whatever was in that LeafElement to the right of the
- // inserted newline.
- // 3. Find the paragraph at offset and remove all its children that
- // occur _after_ offset. These will be moved to the newly created
- // paragraph.
- // 4. Move the LeafElement created in #2 and all the LeafElements removed
- // in #3 to the newly created paragraph.
- // 5. Add the new paragraph to the parent.
- int previousIndex = previous.getElementIndex(offset);
- int numReplaced = previous.getElementCount() - previousIndex;
- Element previousLeaf = previous.getElement(previousIndex);
- AttributeSet prevLeafAtts = previous.getAttributes();
-
- // This recreates the child at offset to have the proper end offset.
- // (Step 1).
- Element newPreviousLeaf =
- createLeafElement(previous,
- prevLeafAtts, previousLeaf.getStartOffset(),
- offset);
- // This creates the new child, which is the remainder of the old child.
- // (Step 2).
-
- Element firstLeafInNewBranch =
- createLeafElement(newBranch, prevLeafAtts,
- offset, previousLeaf.getEndOffset());
-
- // Now we move the new LeafElement and all the old children that occurred
- // after the offset to the new paragraph. (Step 4).
- Element[] newLeaves = new Element[numReplaced];
- newLeaves[0] = firstLeafInNewBranch;
- for (int i = 1; i < numReplaced; i++)
- newLeaves[i] = previous.getElement(previousIndex + i);
- newBranch.replace(0, 0, newLeaves);
- addEdit(newBranch, 0, null, newLeaves);
-
- // Now we remove the children after the offset from the previous
- // paragraph. (Step 3).
- int removeSize = previous.getElementCount() - previousIndex;
- Element[] add = new Element[] { newPreviousLeaf };
- Element[] remove = new Element[removeSize];
- for (int j = 0; j < removeSize; j++)
- remove[j] = previous.getElement(previousIndex + j);
- previous.replace(previousIndex, removeSize, add);
- addEdit(previous, previousIndex, remove, add);
-
- // Finally we add the new paragraph to the parent. (Step 5).
- Element[] nb = new Element[] { newBranch };
- int index = parentIndex + 1;
- parent.replace(index, 0, nb);
- addEdit(parent, index, null, nb);
+ ElementSpec first = data[0];
+ BranchElement paragraph = (BranchElement) elementStack.peek();
+ int index = paragraph.getElementIndex(pos);
+ Element current = paragraph.getElement(index);
+ int newEndOffset = pos + first.length;
+ boolean onlyContent = data.length == 1;
+ Edit edit = getEditForParagraphAndIndex(paragraph, index);
+ switch (first.getDirection())
+ {
+ case ElementSpec.JoinPreviousDirection:
+ if (current.getEndOffset() != newEndOffset && !onlyContent)
+ {
+ Element newEl1 = createLeafElement(paragraph,
+ current.getAttributes(),
+ current.getStartOffset(),
+ newEndOffset);
+ edit.addAddedElement(newEl1);
+ edit.addRemovedElement(current);
+ offset = newEndOffset;
+ }
+ break;
+ case ElementSpec.JoinNextDirection:
+ if (pos != 0)
+ {
+ Element newEl1 = createLeafElement(paragraph,
+ current.getAttributes(),
+ current.getStartOffset(),
+ pos);
+ edit.addAddedElement(newEl1);
+ Element next = paragraph.getElement(index + 1);
+
+ if (onlyContent)
+ newEl1 = createLeafElement(paragraph, next.getAttributes(),
+ pos, next.getEndOffset());
+ else
+ {
+ newEl1 = createLeafElement(paragraph, next.getAttributes(),
+ pos, newEndOffset);
+ pos = newEndOffset;
+ }
+ edit.addAddedElement(newEl1);
+ edit.addRemovedElement(current);
+ edit.addRemovedElement(next);
+ }
+ break;
+ default:
+ if (current.getStartOffset() != pos)
+ {
+ Element newEl = createLeafElement(paragraph,
+ current.getAttributes(),
+ current.getStartOffset(),
+ pos);
+ edit.addAddedElement(newEl);
+ }
+ edit.addRemovedElement(current);
+ Element newEl1 = createLeafElement(paragraph, first.getAttributes(),
+ pos, newEndOffset);
+ edit.addAddedElement(newEl1);
+ if (current.getEndOffset() != endOffset)
+ recreateLeaves(newEndOffset, paragraph, onlyContent);
+ else
+ offset = newEndOffset;
+ break;
+ }
}
-
+
/**
* Inserts a content element into the document structure.
*
- * @param tag the element spec
+ * @param tag -
+ * the element spec
*/
private void insertContentTag(ElementSpec tag)
{
- prepareContentInsertion();
+ BranchElement paragraph = (BranchElement) elementStack.peek();
int len = tag.getLength();
int dir = tag.getDirection();
AttributeSet tagAtts = tag.getAttributes();
- if (dir == ElementSpec.JoinPreviousDirection)
- {
- // The mauve tests to this class show that a JoinPrevious insertion
- // does not add any edits to the document event. To me this means
- // that nothing is done here. The previous element naturally should
- // expand so that it covers the new characters.
- }
- else if (dir == ElementSpec.JoinNextDirection)
+
+ if (dir == ElementSpec.JoinNextDirection)
{
- // FIXME:
- // Have to handle JoinNext differently depending on whether
- // or not it comes after a fracture. If comes after a fracture,
- // the insertFracture method takes care of everything and nothing
- // needs to be done here. Otherwise, we need to adjust the
- // Element structure. For now, I check if the elementStack's
- // top Element is the immediate parent of the LeafElement at
- // offset - if so, we did not come immediately after a
- // fracture. This seems awkward and should probably be improved.
- // We may be doing too much in insertFracture because we are
- // adjusting the offsets, the correct thing to do may be to
- // create a new branch element and push it on to element stack
- // and then this method here can be more general.
-
- BranchElement paragraph = (BranchElement) elementStack.peek();
- int index = paragraph.getElementIndex(offset);
+ int index = paragraph.getElementIndex(pos);
Element target = paragraph.getElement(index);
- if (target.isLeaf() && paragraph.getElementCount() > (index + 1))
+ Edit edit = getEditForParagraphAndIndex(paragraph, index);
+
+ if (paragraph.getStartOffset() > pos)
+ {
+ Element first = paragraph.getElement(0);
+ Element newEl = createLeafElement(paragraph,
+ first.getAttributes(), pos,
+ first.getEndOffset());
+ edit.addAddedElement(newEl);
+ edit.addRemovedElement(first);
+ }
+ else if (paragraph.getElementCount() > (index + 1)
+ && (pos == target.getStartOffset() && !target.equals(lastFractured)))
{
Element next = paragraph.getElement(index + 1);
- Element newEl1 = createLeafElement(paragraph,
- target.getAttributes(),
- target.getStartOffset(),
- offset);
- Element newEl2 = createLeafElement(paragraph,
- next.getAttributes(), offset,
- next.getEndOffset());
- Element[] add = new Element[] { newEl1, newEl2 };
- paragraph.replace (index, 2, add);
- addEdit(paragraph, index, new Element[] { target, next }, add);
+ Element newEl = createLeafElement(paragraph,
+ next.getAttributes(), pos,
+ next.getEndOffset());
+ edit.addAddedElement(newEl);
+ edit.addRemovedElement(next);
+ edit.addRemovedElement(target);
+ }
+ else
+ {
+ BranchElement parent = (BranchElement) paragraph.getParentElement();
+ int i = parent.getElementIndex(pos);
+ BranchElement next = (BranchElement) parent.getElement(i + 1);
+ AttributeSet atts = tag.getAttributes();
+
+ if (next != null)
+ {
+ Element nextLeaf = next.getElement(0);
+ Edit e = getEditForParagraphAndIndex(next, 0);
+ Element newEl2 = createLeafElement(next, atts, pos, nextLeaf.getEndOffset());
+ e.addAddedElement(newEl2);
+ e.addRemovedElement(nextLeaf);
+ }
}
}
- else if (dir == ElementSpec.OriginateDirection)
+ else
{
- BranchElement paragraph = (BranchElement) elementStack.peek();
- int index = paragraph.getElementIndex(offset);
- Element current = paragraph.getElement(index);
+ int end = pos + len;
+ Element leaf = createLeafElement(paragraph, tag.getAttributes(), pos, end);
- Element[] added;
- Element[] removed = new Element[] {current};
- Element[] splitRes = split(current, offset, length);
- if (splitRes[0] == null)
+ // Check for overlap with other leaves/branches
+ if (paragraph.getElementCount() > 0)
{
- added = new Element[2];
- added[0] = createLeafElement(paragraph, tagAtts,
- offset, endOffset);
- added[1] = splitRes[1];
- removed = new Element[0];
- index++;
- }
- else if (current.getStartOffset() == offset)
- {
- // This is if the new insertion happens immediately before
- // the <code>current</code> Element. In this case there are 2
- // resulting Elements.
- added = new Element[2];
- added[0] = createLeafElement(paragraph, tagAtts, offset,
- endOffset);
- added[1] = splitRes[1];
- }
- else if (current.getEndOffset() == endOffset)
- {
- // This is if the new insertion happens right at the end of
- // the <code>current</code> Element. In this case there are
- // 2 resulting Elements.
- added = new Element[2];
- added[0] = splitRes[0];
- added[1] = createLeafElement(paragraph, tagAtts, offset,
- endOffset);
+ int index = paragraph.getElementIndex(pos);
+ Element target = paragraph.getElement(index);
+ boolean onlyContent = target.isLeaf();
+
+ BranchElement toRec = paragraph;
+ if (!onlyContent)
+ toRec = (BranchElement) target;
+
+ // Check if we should place the leaf before or after target
+ if (pos > target.getStartOffset())
+ index++;
+
+ Edit edit = getEditForParagraphAndIndex(paragraph, index);
+ edit.addAddedElement(leaf);
+
+ if (end != toRec.getEndOffset())
+ {
+ recreateLeaves(end, toRec, onlyContent);
+
+ if (onlyContent)
+ edit.addRemovedElement(target);
+ }
}
else
- {
- // This is if the new insertion is in the middle of the
- // <code>current</code> Element. In this case
- // there will be 3 resulting Elements.
- added = new Element[3];
- added[0] = splitRes[0];
- added[1] = createLeafElement(paragraph, tagAtts, offset,
- endOffset);
- added[2] = splitRes[1];
- }
- paragraph.replace(index, removed.length, added);
- addEdit(paragraph, index, removed, added);
+ paragraph.replace(0, 0, new Element[] { leaf });
}
- offset += len;
+
+ pos += len;
}
-
+
/**
- * Creates a copy of the element <code>clonee</code> that has the parent
- * <code>parent</code>.
- * @param parent the parent of the newly created Element
- * @param clonee the Element to clone
- * @return the cloned Element
+ * This method fractures the child at offset.
+ *
+ * @param data
+ * the ElementSpecs used for the entire insertion
*/
- public Element clone (Element parent, Element clonee)
+ private void createFracture(ElementSpec[] data)
{
- // If the Element we want to clone is a leaf, then simply copy it
- if (clonee.isLeaf())
- return createLeafElement(parent, clonee.getAttributes(),
- clonee.getStartOffset(), clonee.getEndOffset());
+ BranchElement paragraph = (BranchElement) elementStack.peek();
+ int index = paragraph.getElementIndex(offset);
+ Element child = paragraph.getElement(index);
+ Edit edit = getEditForParagraphAndIndex(paragraph, index);
+ AttributeSet atts = child.getAttributes();
- // Otherwise create a new BranchElement with the desired parent and
- // the clonee's attributes
- BranchElement result = (BranchElement) createBranchElement(parent, clonee.getAttributes());
-
- // And clone all the of clonee's children
- Element[] children = new Element[clonee.getElementCount()];
- for (int i = 0; i < children.length; i++)
- children[i] = clone(result, clonee.getElement(i));
-
- // Make the cloned children the children of the BranchElement
- result.replace(0, 0, children);
- return result;
+ if (offset != 0)
+ {
+ Element newEl1 = createLeafElement(paragraph, atts,
+ child.getStartOffset(), offset);
+ edit.addAddedElement(newEl1);
+ edit.addRemovedElement(child);
+ }
}
/**
- * Adds an ElementChange for a given element modification to the document
- * event. If there already is an ElementChange registered for this element,
- * this method tries to merge the ElementChanges together. However, this
- * is only possible if the indices of the new and old ElementChange are
- * equal.
- *
- * @param e the element
- * @param i the index of the change
- * @param removed the removed elements, or <code>null</code>
- * @param added the added elements, or <code>null</code>
+ * Recreates a specified part of a the tree after a new leaf
+ * has been inserted.
+ *
+ * @param start - where to start recreating from
+ * @param paragraph - the paragraph to recreate
+ * @param onlyContent - true if this is the only content
+ */
+ private void recreateLeaves(int start, BranchElement paragraph, boolean onlyContent)
+ {
+ int index = paragraph.getElementIndex(start);
+ Element child = paragraph.getElement(index);
+ AttributeSet atts = child.getAttributes();
+
+ if (!onlyContent)
+ {
+ BranchElement newBranch = (BranchElement) createBranchElement(paragraph,
+ atts);
+ Element newLeaf = createLeafElement(newBranch, atts, start,
+ child.getEndOffset());
+ newBranch.replace(0, 0, new Element[] { newLeaf });
+
+ BranchElement parent = (BranchElement) paragraph.getParentElement();
+ int parSize = parent.getElementCount();
+ Edit edit = getEditForParagraphAndIndex(parent, parSize);
+ edit.addAddedElement(newBranch);
+
+ int paragraphSize = paragraph.getElementCount();
+ Element[] removed = new Element[paragraphSize - (index + 1)];
+ int s = 0;
+ for (int j = index + 1; j < paragraphSize; j++)
+ removed[s++] = paragraph.getElement(j);
+
+ edit = getEditForParagraphAndIndex(paragraph, index);
+ edit.addRemovedElements(removed);
+ Element[] added = recreateAfterFracture(removed, newBranch, 0, child.getEndOffset());
+ newBranch.replace(1, 0, added);
+
+ lastFractured = newLeaf;
+ pos = newBranch.getEndOffset();
+ }
+ else
+ {
+ Element newLeaf = createLeafElement(paragraph, atts, start,
+ child.getEndOffset());
+ Edit edit = getEditForParagraphAndIndex(paragraph, index);
+ edit.addAddedElement(newLeaf);
+ }
+ }
+
+ /**
+ * Splits an element if <code>offset</code> is not already at its
+ * boundary.
+ *
+ * @param el
+ * the Element to possibly split
+ * @param offset
+ * the offset at which to possibly split
+ * @param space
+ * the amount of space to create between the splitted parts
+ * @param editIndex
+ * the index of the edit to use
+ * @return An array of elements which represent the split result. This array
+ * has two elements, the two parts of the split. The first element
+ * might be null, which means that the element which should be
+ * splitted can remain in place. The second element might also be
+ * null, which means that the offset is already at an element
+ * boundary and the element doesn't need to be splitted.
*/
- private void addEdit(Element e, int i, Element[] removed, Element[] added)
+ private Element[] split(Element el, int offset, int space, int editIndex)
{
- // Perform sanity check first.
- DocumentEvent.ElementChange ec = documentEvent.getChange(e);
+ // If we are at an element boundary, then return an empty array.
+ if ((offset == el.getStartOffset() || offset == el.getEndOffset())
+ && space == 0 && el.isLeaf())
+ return new Element[2];
- // Merge the existing stuff with the new stuff.
- Element[] oldAdded = ec == null ? null: ec.getChildrenAdded();
- Element[] newAdded;
- if (oldAdded != null && added != null)
+ // If the element is an instance of BranchElement, then we
+ // recursivly
+ // call this method to perform the split.
+ Element[] res = new Element[2];
+ if (el instanceof BranchElement)
{
- if (ec.getIndex() <= i)
+ int index = el.getElementIndex(offset);
+ Element child = el.getElement(index);
+ Element[] result = split(child, offset, space, editIndex);
+ Element[] removed;
+ Element[] added;
+ Element[] newAdded;
+
+ int count = el.getElementCount();
+ if (result[1] != null)
{
- int index = i - ec.getIndex();
- // Merge adds together.
- newAdded = new Element[oldAdded.length + added.length];
- System.arraycopy(oldAdded, 0, newAdded, 0, index);
- System.arraycopy(added, 0, newAdded, index, added.length);
- System.arraycopy(oldAdded, index, newAdded, index + added.length,
- oldAdded.length - index);
- i = ec.getIndex();
+ // This is the case when we can keep the first element.
+ if (result[0] == null)
+ {
+ removed = new Element[count - index - 1];
+ newAdded = new Element[count - index - 1];
+ added = new Element[] {};
+
+ }
+ // This is the case when we may not keep the first
+ // element.
+ else
+ {
+ removed = new Element[count - index];
+ newAdded = new Element[count - index];
+ added = new Element[] { result[0] };
+ }
+ newAdded[0] = result[1];
+ for (int i = index; i < count; i++)
+ {
+ Element el2 = el.getElement(i);
+ int ind = i - count + removed.length;
+ removed[ind] = el2;
+ if (ind != 0)
+ newAdded[ind] = el2;
+ }
+
+ Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex);
+ edit.addRemovedElements(removed);
+ edit.addAddedElements(added);
+
+ BranchElement newPar =
+ (BranchElement) createBranchElement(el.getParentElement(),
+ el.getAttributes());
+ newPar.replace(0, 0, newAdded);
+ res = new Element[] { null, newPar };
}
else
- throw new AssertionError("Not yet implemented case.");
+ {
+ removed = new Element[count - index];
+ for (int i = index; i < count; ++i)
+ removed[i - index] = el.getElement(i);
+
+ Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex);
+ edit.addRemovedElements(removed);
+
+ BranchElement newPar = (BranchElement) createBranchElement(el.getParentElement(),
+ el.getAttributes());
+ newPar.replace(0, 0, removed);
+ res = new Element[] { null, newPar };
+ }
}
- else if (added != null)
- newAdded = added;
- else if (oldAdded != null)
- newAdded = oldAdded;
- else
- newAdded = new Element[0];
+ else if (el instanceof LeafElement)
+ {
+ BranchElement par = (BranchElement) el.getParentElement();
+ Element el1 = createLeafElement(par, el.getAttributes(),
+ el.getStartOffset(), offset);
+
+ Element el2 = createLeafElement(par, el.getAttributes(),
+ offset + space,
+ el.getEndOffset());
+ res = new Element[] { el1, el2 };
+ }
+ return res;
+ }
- Element[] oldRemoved = ec == null ? null: ec.getChildrenRemoved();
- Element[] newRemoved;
- if (oldRemoved != null && removed != null)
+ /**
+ * Inserts a fracture into the document structure.
+ *
+ * @param tag -
+ * the element spec.
+ */
+ private void insertFracture(ElementSpec tag)
+ {
+ // insert the fracture at offset.
+ BranchElement parent = (BranchElement) elementStack.peek();
+ int parentIndex = parent.getElementIndex(pos);
+ AttributeSet parentAtts = parent.getAttributes();
+ Element toFracture = parent.getElement(parentIndex);
+ int parSize = parent.getElementCount();
+ Edit edit = getEditForParagraphAndIndex(parent, parentIndex);
+ Element frac = toFracture;
+ int leftIns = 0;
+ int indexOfFrac = toFracture.getElementIndex(pos);
+ int size = toFracture.getElementCount();
+
+ // gets the leaf that falls along the fracture
+ frac = toFracture.getElement(indexOfFrac);
+ while (!frac.isLeaf())
+ frac = frac.getElement(frac.getElementIndex(pos));
+
+ AttributeSet atts = frac.getAttributes();
+ int fracStart = frac.getStartOffset();
+ int fracEnd = frac.getEndOffset();
+ if (pos >= fracStart && pos < fracEnd)
{
- if (ec.getIndex() <= i)
+ // recreate left-side of branch and all its children before offset
+ // add the fractured leaves to the right branch
+ BranchElement rightBranch =
+ (BranchElement) createBranchElement(parent, parentAtts);
+
+ // Check if left branch has already been edited. If so, we only
+ // need to create the right branch.
+ BranchElement leftBranch = null;
+ Element[] added = null;
+ if (edit.added.size() > 0 || edit.removed.size() > 0)
{
- int index = i - ec.getIndex();
- // Merge removes together.
- newRemoved = new Element[oldRemoved.length + removed.length];
- System.arraycopy(oldAdded, 0, newRemoved, 0, index);
- System.arraycopy(removed, 0, newRemoved, index, removed.length);
- System.arraycopy(oldRemoved, index, newRemoved,
- index + removed.length,
- oldRemoved.length - index);
- i = ec.getIndex();
+ added = new Element[] { rightBranch };
+
+ // don't try to remove left part of tree
+ parentIndex++;
}
else
- throw new AssertionError("Not yet implemented case.");
+ {
+ leftBranch =
+ (BranchElement) createBranchElement(parent, parentAtts);
+ added = new Element[] { leftBranch, rightBranch };
+
+ // add fracture to leftBranch
+ if (fracStart != pos)
+ {
+ Element leftFracturedLeaf =
+ createLeafElement(leftBranch, atts, fracStart, pos);
+ leftBranch.replace(leftIns, 0,
+ new Element[] { leftFracturedLeaf });
+ }
+ }
+
+ if (!toFracture.isLeaf())
+ {
+ // add all non-fracture elements to the branches
+ if (indexOfFrac > 0 && leftBranch != null)
+ {
+ Element[] add = new Element[indexOfFrac];
+ for (int i = 0; i < indexOfFrac; i++)
+ add[i] = toFracture.getElement(i);
+ leftIns = add.length;
+ leftBranch.replace(0, 0, add);
+ }
+
+ int count = size - indexOfFrac - 1;
+ if (count > 0)
+ {
+ Element[] add = new Element[count];
+ int j = 0;
+ int i = indexOfFrac + 1;
+ while (j < count)
+ add[j++] = toFracture.getElement(i++);
+ rightBranch.replace(0, 0, add);
+ }
+ }
+
+ // add to fracture to rightBranch
+ // Check if we can join the right frac leaf with the next leaf
+ int rm = 0;
+ int end = fracEnd;
+ Element next = rightBranch.getElement(0);
+ if (next != null && next.isLeaf()
+ && next.getAttributes().isEqual(atts))
+ {
+ end = next.getEndOffset();
+ rm = 1;
+ }
+
+ Element rightFracturedLeaf = createLeafElement(rightBranch, atts,
+ pos, end);
+ rightBranch.replace(0, rm, new Element[] { rightFracturedLeaf });
+
+ // recreate those elements after parentIndex and add/remove all
+ // new/old elements to parent
+ int remove = parSize - parentIndex;
+ Element[] removed = new Element[0];
+ Element[] added2 = new Element[0];
+ if (remove > 0)
+ {
+ removed = new Element[remove];
+ int s = 0;
+ for (int j = parentIndex; j < parSize; j++)
+ removed[s++] = parent.getElement(j);
+ edit.addRemovedElements(removed);
+ added2 = recreateAfterFracture(removed, parent, 1,
+ rightBranch.getEndOffset());
+ }
+
+ edit.addAddedElements(added);
+ edit.addAddedElements(added2);
+ elementStack.push(rightBranch);
+ lastFractured = rightFracturedLeaf;
}
- else if (removed != null)
- newRemoved = removed;
- else if (oldRemoved != null)
- newRemoved = oldRemoved;
else
- newRemoved = new Element[0];
+ fracNotCreated = true;
+ }
+
+ /**
+ * Recreates all the elements from the parent to the element on the top of
+ * the stack, starting from startFrom with the starting offset of
+ * startOffset.
+ *
+ * @param recreate -
+ * the elements to recreate
+ * @param parent -
+ * the element to add the new elements to
+ * @param startFrom -
+ * where to start recreating from
+ * @param startOffset -
+ * the offset of the first element
+ * @return the array of added elements
+ */
+ private Element[] recreateAfterFracture(Element[] recreate,
+ BranchElement parent, int startFrom,
+ int startOffset)
+ {
+ Element[] added = new Element[recreate.length - startFrom];
+ int j = 0;
+ for (int i = startFrom; i < recreate.length; i++)
+ {
+ Element curr = recreate[i];
+ int len = curr.getEndOffset() - curr.getStartOffset();
+ if (curr instanceof LeafElement)
+ added[j] = createLeafElement(parent, curr.getAttributes(),
+ startOffset, startOffset + len);
+ else
+ {
+ BranchElement br =
+ (BranchElement) createBranchElement(parent,
+ curr.getAttributes());
+ int bSize = curr.getElementCount();
+ for (int k = 0; k < bSize; k++)
+ {
+ Element bCurr = curr.getElement(k);
+ Element[] add = recreateAfterFracture(new Element[] { bCurr }, br, 0,
+ startOffset);
+ br.replace(0, 0, add);
+
+ }
+ added[j] = br;
+ }
+ startOffset += len;
+ j++;
+ }
+
+ return added;
+ }
+ }
+
+ /**
+ * This method looks through the Vector of Edits to see if there is already an
+ * Edit object associated with the given paragraph. If there is, then we
+ * return it. Otherwise we create a new Edit object, add it to the vector, and
+ * return it. Note: this method is package private to avoid accessors.
+ *
+ * @param index
+ * the index associated with the Edit we want to create
+ * @param para
+ * the paragraph associated with the Edit we want
+ * @return the found or created Edit object
+ */
+ Edit getEditForParagraphAndIndex(BranchElement para, int index)
+ {
+ Edit curr;
+ int size = edits.size();
+ for (int i = 0; i < size; i++)
+ {
+ curr = (Edit) edits.elementAt(i);
+ if (curr.e.equals(para))
+ return curr;
+ }
+ curr = new Edit(para, index, null, null);
+ edits.add(curr);
+
+ return curr;
+ }
+ /**
+ * 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. */
+ Vector removed = new Vector();
+
+ /** The added elements. */
+ Vector added = new Vector();
+
+ /**
+ * Return an array containing the Elements that have been removed from the
+ * paragraph associated with this Edit.
+ *
+ * @return an array of removed Elements
+ */
+ public Element[] getRemovedElements()
+ {
+ int size = removed.size();
+ Element[] removedElements = new Element[size];
+ for (int i = 0; i < size; i++)
+ removedElements[i] = (Element) removed.elementAt(i);
+ return removedElements;
+ }
+
+ /**
+ * Return an array containing the Elements that have been added to the
+ * paragraph associated with this Edit.
+ *
+ * @return an array of added Elements
+ */
+ public Element[] getAddedElements()
+ {
+ int size = added.size();
+ Element[] addedElements = new Element[size];
+ for (int i = 0; i < size; i++)
+ addedElements[i] = (Element) added.elementAt(i);
+ return addedElements;
+ }
+
+ /**
+ * Checks if e is already in the vector.
+ *
+ * @param e - the Element to look for
+ * @param v - the vector to search
+ * @return true if e is in v.
+ */
+ private boolean contains(Vector v, Element e)
+ {
+ if (e == null)
+ return false;
+
+ int i = v.size();
+ for (int j = 0; j < i; j++)
+ {
+ Element e1 = (Element) v.get(j);
+ if ((e1 != null) && (e1.getAttributes().isEqual(e.getAttributes()))
+ && (e1.getName().equals(e.getName()))
+ && (e1.getStartOffset() == e.getStartOffset())
+ && (e1.getEndOffset() == e.getEndOffset())
+ && (e1.getParentElement().equals(e.getParentElement()))
+ && (e1.getElementCount() == e.getElementCount()))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds one Element to the vector of removed Elements.
+ *
+ * @param e
+ * the Element to add
+ */
+ public void addRemovedElement(Element e)
+ {
+ if (!contains(removed, e))
+ removed.add(e);
+ }
+
+ /**
+ * Adds each Element in the given array to the vector of removed Elements
+ *
+ * @param e
+ * the array containing the Elements to be added
+ */
+ public void addRemovedElements(Element[] e)
+ {
+ if (e == null || e.length == 0)
+ return;
+ for (int i = 0; i < e.length; i++)
+ {
+ if (!contains(removed, e[i]))
+ removed.add(e[i]);
+ }
+ }
- // Replace the existing edit for the element with the merged.
- documentEvent.addEdit(new ElementEdit(e, i, newRemoved, newAdded));
+ /**
+ * Adds one Element to the vector of added Elements.
+ *
+ * @param e
+ * the Element to add
+ */
+ public void addAddedElement(Element e)
+ {
+ if (!contains(added, e))
+ added.add(e);
+ }
+
+ /**
+ * Adds each Element in the given array to the vector of added Elements.
+ *
+ * @param e
+ * the array containing the Elements to be added
+ */
+ public void addAddedElements(Element[] e)
+ {
+ if (e == null || e.length == 0)
+ return;
+ for (int i = 0; i < e.length; i++)
+ {
+ if (!contains(added, e[i]))
+ added.add(e[i]);
+ }
+ }
+
+ /**
+ * Creates a new Edit object with the given parameters
+ *
+ * @param e
+ * the paragraph Element associated with this Edit
+ * @param i
+ * the index within the paragraph where changes are started
+ * @param removed
+ * an array containing Elements that should be removed from the
+ * paragraph Element
+ * @param added
+ * an array containing Elements that should be added to the
+ * paragraph Element
+ */
+ public Edit(Element e, int i, Element[] removed, Element[] added)
+ {
+ this.e = e;
+ this.index = i;
+ addRemovedElements(removed);
+ addAddedElements(added);
}
}
/**
- * An element type for sections. This is a simple BranchElement with
- * a unique name.
+ * An element type for sections. This is a simple BranchElement with a unique
+ * name.
*/
protected class SectionElement extends BranchElement
{
@@ -1244,7 +1630,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the name of the element. This method always returns
* &quot;section&quot;.
- *
+ *
* @return the name of the element
*/
public String getName()
@@ -1256,18 +1642,18 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* 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
+ 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
+ *
+ * @param event
+ * the change event
*/
public void stateChanged(ChangeEvent event)
{
@@ -1296,6 +1682,11 @@ public class DefaultStyledDocument extends AbstractDocument
private StyleChangeListener styleChangeListener;
/**
+ * Vector that contains all the edits. Maybe replace by a HashMap.
+ */
+ Vector edits = new Vector();
+
+ /**
* Creates a new <code>DefaultStyledDocument</code>.
*/
public DefaultStyledDocument()
@@ -1304,10 +1695,11 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * Creates a new <code>DefaultStyledDocument</code> that uses the
- * specified {@link StyleContext}.
- *
- * @param context the <code>StyleContext</code> to use
+ * Creates a new <code>DefaultStyledDocument</code> that uses the specified
+ * {@link StyleContext}.
+ *
+ * @param context
+ * the <code>StyleContext</code> to use
*/
public DefaultStyledDocument(StyleContext context)
{
@@ -1315,14 +1707,16 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * 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
+ * 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)
+ StyleContext context)
{
super(content, context);
buffer = new ElementBuffer(createDefaultRoot());
@@ -1330,10 +1724,9 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * 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),
+ * 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.
@@ -1360,14 +1753,12 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* 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;
- // FIXME: Create a SecionElement here instead of a BranchElement.
- // Use createBranchElement() and createLeafElement instead.
SectionElement section = new SectionElement();
BranchElement paragraph = new BranchElement(section, null);
@@ -1375,7 +1766,7 @@ public class DefaultStyledDocument extends AbstractDocument
tmp[0] = paragraph;
section.replace(0, 0, tmp);
- LeafElement leaf = new LeafElement(paragraph, null, 0, 1);
+ Element leaf = new LeafElement(paragraph, null, 0, 1);
tmp = new Element[1];
tmp[0] = leaf;
paragraph.replace(0, 0, tmp);
@@ -1384,14 +1775,14 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * 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
+ * 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)
{
@@ -1402,15 +1793,15 @@ public class DefaultStyledDocument extends AbstractDocument
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
- *
+ *
+ * @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)
@@ -1421,7 +1812,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the default root element.
- *
+ *
* @return the default root element
*/
public Element getDefaultRootElement()
@@ -1431,9 +1822,9 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Extracts a font from a set of attributes.
- *
- * @param attributes the attributes from which to get a font
- *
+ *
+ * @param attributes
+ * the attributes from which to get a font
* @return the font that correspond to the attributes
*/
public Font getFont(AttributeSet attributes)
@@ -1441,12 +1832,12 @@ public class DefaultStyledDocument extends AbstractDocument
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
- *
+ *
+ * @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)
@@ -1457,9 +1848,9 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns the logical <code>Style</code> for the specified position.
- *
- * @param position the position from which to query to logical style
- *
+ *
+ * @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)
@@ -1474,37 +1865,32 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * 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
+ * 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
- *
+ *
+ * @param position
+ * the position for which to query the paragraph element
* @return the paragraph element for the specified position
*/
public Element getParagraphElement(int position)
{
- BranchElement root = (BranchElement) getDefaultRootElement();
- int start = root.getStartOffset();
- int end = root.getEndOffset();
- if (position >= end)
- position = end - 1;
- else if (position < start)
- position = start;
-
- Element par = root.positionToElement(position);
-
- assert par != null : "The paragraph element must not be null";
- return par;
+ 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>
- *
+ *
+ * @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
*/
@@ -1516,8 +1902,9 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Removes a named <code>Style</code> from the style hierarchy.
- *
- * @param nm the name of the <code>Style</code> to be removed
+ *
+ * @param nm
+ * the name of the <code>Style</code> to be removed
*/
public void removeStyle(String nm)
{
@@ -1528,31 +1915,32 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* 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
+ *
+ * @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)
+ 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
+ // 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);
+ DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
+ length,
+ DocumentEvent.EventType.CHANGE);
// Modify the element structure so that the interval begins at an
// element
@@ -1563,13 +1951,13 @@ public class DefaultStyledDocument extends AbstractDocument
// Visit all paragraph elements within the specified interval
int end = offset + length;
Element curr;
- for (int pos = offset; pos < end; )
+ 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.
@@ -1588,12 +1976,14 @@ public class DefaultStyledDocument extends AbstractDocument
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
+ *
+ * @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)
{
@@ -1603,60 +1993,59 @@ public class DefaultStyledDocument extends AbstractDocument
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);
- // FIXME: Add an UndoableEdit to this event and fire it.
- fireChangedUpdate(ev);
- }
- else
- throw new
- AssertionError("paragraph elements are expected to be"
- + "instances of AbstractDocument.AbstractElement");
- }
+ {
+ 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();
- }
+ {
+ 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
+ *
+ * @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)
+ AttributeSet attributes, boolean replace)
{
try
{
- // Must obtain a write lock for this method. writeLock() and
+ // 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);
-
+ 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).
@@ -1665,7 +2054,7 @@ public class DefaultStyledDocument extends AbstractDocument
int endElement = rootElement.getElementIndex(offset + length - 1);
if (endElement < startElement)
endElement = startElement;
-
+
for (int i = startElement; i <= endElement; i++)
{
Element par = rootElement.getElement(i);
@@ -1688,11 +2077,13 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
- * 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
+ * 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)
{
@@ -1703,8 +2094,7 @@ public class DefaultStyledDocument extends AbstractDocument
int offset = ev.getOffset();
int length = ev.getLength();
int endOffset = offset + length;
- AttributeSet paragraphAttributes =
- getParagraphElement(endOffset).getAttributes();
+ AttributeSet paragraphAttributes = getParagraphElement(endOffset).getAttributes();
Segment txt = new Segment();
try
{
@@ -1723,91 +2113,75 @@ public class DefaultStyledDocument extends AbstractDocument
short finalStartDirection = ElementSpec.OriginateDirection;
boolean prevCharWasNewline = false;
Element prev = getCharacterElement(offset);
- Element next = getCharacterElement(endOffset);
+ Element next = getCharacterElement(endOffset);
Element prevParagraph = getParagraphElement(offset);
Element paragraph = getParagraphElement(endOffset);
-
+
int segmentEnd = txt.offset + txt.count;
-
+
// Check to see if we're inserting immediately after a newline.
if (offset > 0)
{
try
- {
- String s = getText(offset - 1, 1);
- if (s.equals("\n"))
- {
- finalStartDirection =
- handleInsertAfterNewline(specs, offset, endOffset,
- prevParagraph,
- paragraph,
- paragraphAttributes);
-
- prevCharWasNewline = true;
- // Find the final start tag from the ones just created.
- for (int i = 0; i < specs.size(); i++)
- if (((ElementSpec) specs.get(i)).getType()
- == ElementSpec.StartTagType)
- finalStartTag = (ElementSpec)specs.get(i);
- }
- }
+ {
+ String s = getText(offset - 1, 1);
+ if (s.equals("\n"))
+ {
+ finalStartDirection = handleInsertAfterNewline(specs, offset,
+ endOffset,
+ prevParagraph,
+ paragraph,
+ paragraphAttributes);
+
+ prevCharWasNewline = true;
+ // Find the final start tag from the ones just created.
+ for (int i = 0; i < specs.size(); i++)
+ if (((ElementSpec) specs.get(i)).getType() == ElementSpec.StartTagType)
+ finalStartTag = (ElementSpec) specs.get(i);
+ }
+ }
catch (BadLocationException ble)
- {
- // This shouldn't happen.
- AssertionError ae = new AssertionError();
- ae.initCause(ble);
- throw ae;
- }
+ {
+ // This shouldn't happen.
+ AssertionError ae = new AssertionError();
+ ae.initCause(ble);
+ throw ae;
+ }
}
-
for (int i = txt.offset; i < segmentEnd; ++i)
{
len++;
if (txt.array[i] == '\n')
{
// Add the ElementSpec for the content.
- specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
+ specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
// Add ElementSpecs for the newline.
specs.add(new ElementSpec(null, ElementSpec.EndTagType));
finalStartTag = new ElementSpec(paragraphAttributes,
- ElementSpec.StartTagType);
+ ElementSpec.StartTagType);
specs.add(finalStartTag);
len = 0;
}
}
// Create last element if last character hasn't been a newline.
- if (len > 0)
+ if (len > 0)
specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
- // Set the direction of the last spec of type StartTagType.
- // If we are inserting after a newline then this value comes from
+ // Set the direction of the last spec of type StartTagType.
+ // If we are inserting after a newline then this value comes from
// handleInsertAfterNewline.
if (finalStartTag != null)
- {
+ {
if (prevCharWasNewline)
finalStartTag.setDirection(finalStartDirection);
else if (prevParagraph.getEndOffset() != endOffset)
- {
- try
- {
- String last = getText(endOffset - 1, 1);
- if (!last.equals("\n"))
- finalStartTag.setDirection(ElementSpec.JoinFractureDirection);
- }
- catch (BadLocationException ble)
- {
- // This shouldn't happen.
- AssertionError ae = new AssertionError();
- ae.initCause(ble);
- throw ae;
- }
- }
+ finalStartTag.setDirection(ElementSpec.JoinFractureDirection);
else
{
- // If there is an element AFTER this one, then set the
+ // If there is an element AFTER this one, then set the
// direction to JoinNextDirection.
Element parent = prevParagraph.getParentElement();
int index = parent.getElementIndex(offset);
@@ -1816,19 +2190,18 @@ public class DefaultStyledDocument extends AbstractDocument
finalStartTag.setDirection(ElementSpec.JoinNextDirection);
}
}
-
+
// If we are at the last index, then check if we could probably be
// joined with the next element.
// This means:
- // - we must be a ContentTag
- // - if there is a next Element, we must have the same attributes
- // - if there is no next Element, but one will be created,
- // we must have the same attributes as the higher-level run.
+ // - we must be a ContentTag
+ // - if there is a next Element, we must have the same attributes
+ // - if there is no next Element, but one will be created,
+ // we must have the same attributes as the higher-level run.
ElementSpec last = (ElementSpec) specs.lastElement();
if (last.getType() == ElementSpec.ContentType)
{
- Element currentRun =
- prevParagraph.getElement(prevParagraph.getElementIndex(offset));
+ Element currentRun = prevParagraph.getElement(prevParagraph.getElementIndex(offset));
if (currentRun.getEndOffset() == endOffset)
{
if (endOffset < getLength() && next.getAttributes().isEqual(attr)
@@ -1838,62 +2211,58 @@ public class DefaultStyledDocument extends AbstractDocument
else
{
if (finalStartTag != null
- && finalStartTag.getDirection() ==
- ElementSpec.JoinFractureDirection
+ && finalStartTag.getDirection() == ElementSpec.JoinFractureDirection
&& currentRun.getAttributes().isEqual(attr))
{
last.setDirection(ElementSpec.JoinNextDirection);
}
}
}
-
+
// If we are at the first new element, then check if it could be
// joined with the previous element.
ElementSpec first = (ElementSpec) specs.firstElement();
if (prev.getAttributes().isEqual(attr)
&& first.getType() == ElementSpec.ContentType)
first.setDirection(ElementSpec.JoinPreviousDirection);
-
- ElementSpec[] elSpecs =
- (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]);
+ ElementSpec[] elSpecs = (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]);
buffer.insert(offset, length, elSpecs, ev);
}
/**
- * 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.
+ * 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)
+ 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 (prevParagraph.getEndOffset() != endOffset)
+ 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)
+ if (parent.getElementCount() > (parent.getElementIndex(offset) + 1))
return ElementSpec.JoinNextDirection;
}
- else
- {
- // TODO: What to do here?
- }
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
+ * 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)
{
@@ -1903,7 +2272,7 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Returns an enumeration of all style names.
- *
+ *
* @return an enumeration of all style names
*/
public Enumeration getStyleNames()
@@ -1914,61 +2283,35 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Called when any of this document's styles changes.
- *
- * @param style the style that changed
+ *
+ * @param style
+ * the style that changed
*/
protected void styleChanged(Style style)
{
// Nothing to do here. This is intended to be overridden by subclasses.
}
- void printElements (Element start, int pad)
- {
- for (int i = 0; i < pad; i++)
- System.out.print(" ");
- if (pad == 0)
- System.out.println ("ROOT ELEMENT ("+start.getStartOffset()+", "+start.getEndOffset()+")");
- else if (start instanceof AbstractDocument.BranchElement)
- System.out.println ("BranchElement ("+start.getStartOffset()+", "+start.getEndOffset()+")");
- else
- {
- {
- try
- {
- System.out.println ("LeafElement ("+start.getStartOffset()+", "
- + start.getEndOffset()+"): "+
- start.getDocument().
- getText(start.getStartOffset(),
- start.getEndOffset() -
- start.getStartOffset()));
- }
- catch (BadLocationException ble)
- {
- }
- }
- }
- for (int i = 0; i < start.getElementCount(); i ++)
- printElements (start.getElement(i), pad+3);
- }
-
/**
* 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
+ *
+ * @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
+ 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
+ // block so that locking balance is guaranteed even if some
// exception is thrown.
writeLock();
-
+
// First we collect the content to be inserted.
StringBuffer contentBuffer = new StringBuffer();
for (int i = 0; i < data.length; i++)
@@ -1986,15 +2329,14 @@ public class DefaultStyledDocument extends AbstractDocument
// If there was no content inserted then exit early.
if (length == 0)
return;
-
+
UndoableEdit edit = content.insertString(offset,
contentBuffer.toString());
// Create the DocumentEvent with the ElementEdit added
- DefaultDocumentEvent ev =
- new DefaultDocumentEvent(offset,
- length,
- DocumentEvent.EventType.INSERT);
+ DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
+ length,
+ DocumentEvent.EventType.INSERT);
ev.addEdit(edit);
// Finally we must update the document structure and fire the insert
@@ -2012,20 +2354,66 @@ public class DefaultStyledDocument extends AbstractDocument
/**
* Initializes the <code>DefaultStyledDocument</code> with the specified
* data.
- *
- * @param data the specification of the content with which the document is
- * initialized
+ *
+ * @param data
+ * the specification of the content with which the document is
+ * initialized
*/
protected void create(ElementSpec[] data)
{
+ writeLock();
try
{
- // Clear content.
- content.remove(0, content.length());
- // Clear buffer and root element.
- buffer = new ElementBuffer(createDefaultRoot());
- // Insert the data.
- insert(0, data);
+ // Clear content if there is some.
+ int len = getLength();
+ if (len > 0)
+ remove(0, len);
+
+ // 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());
+
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(0, b.length(),
+ DocumentEvent.EventType.INSERT);
+ ev.addEdit(cEdit);
+
+ // We do a little trick here to get the new structure: We instantiate
+ // a new ElementBuffer with a new root element, insert into that root
+ // and then reparent the newly created elements to the old root
+ // element.
+ BranchElement createRoot =
+ (BranchElement) createBranchElement(null, null);
+ Element dummyLeaf = createLeafElement(createRoot, null, 0, 1);
+ createRoot.replace(0, 0, new Element[]{ dummyLeaf });
+ ElementBuffer createBuffer = new ElementBuffer(createRoot);
+ createBuffer.insert(0, b.length(), data, new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT));
+ // Now the new root is the first child of the createRoot.
+ Element newRoot = createRoot.getElement(0);
+ BranchElement root = (BranchElement) getDefaultRootElement();
+ Element[] added = new Element[newRoot.getElementCount()];
+ for (int i = 0; i < added.length; ++i)
+ {
+ added[i] = newRoot.getElement(i);
+ ((AbstractElement) added[i]).element_parent = root;
+ }
+ Element[] removed = new Element[root.getElementCount()];
+ for (int i = 0; i < removed.length; ++i)
+ removed[i] = root.getElement(i);
+
+ // Replace the old elements in root with the new and update the event.
+ root.replace(0, removed.length, added);
+ ev.addEdit(new ElementEdit(root, 0, removed, added));
+
+ fireInsertUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
}
catch (BadLocationException ex)
{
@@ -2033,10 +2421,9 @@ public class DefaultStyledDocument extends AbstractDocument
err.initCause(ex);
throw err;
}
- }
-
- static boolean attributeSetsAreSame (AttributeSet a, AttributeSet b)
- {
- return (a == null && b == null) || (a != null && a.isEqual(b));
+ finally
+ {
+ writeUnlock();
+ }
}
}
OpenPOWER on IntegriCloud