aboutsummaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
diff options
context:
space:
mode:
authorMark Wielaard <mark@gcc.gnu.org>2006-01-17 18:09:40 +0000
committerMark Wielaard <mark@gcc.gnu.org>2006-01-17 18:09:40 +0000
commit2127637945ea6b763966398130e0770fa993c860 (patch)
treec976ca91e3ef0bda3b34b37c0195145638d8d08e /libjava/classpath/javax/swing/text/DefaultStyledDocument.java
parentbcb36c3e02e3bd2843aad1b9888513dfb5d6e337 (diff)
downloadgcc-2127637945ea6b763966398130e0770fa993c860.zip
gcc-2127637945ea6b763966398130e0770fa993c860.tar.gz
gcc-2127637945ea6b763966398130e0770fa993c860.tar.bz2
Imported GNU Classpath 0.20
Imported GNU Classpath 0.20 * Makefile.am (AM_CPPFLAGS): Add classpath/include. * java/nio/charset/spi/CharsetProvider.java: New override file. * java/security/Security.java: Likewise. * sources.am: Regenerated. * Makefile.in: Likewise. From-SVN: r109831
Diffstat (limited to 'libjava/classpath/javax/swing/text/DefaultStyledDocument.java')
-rw-r--r--libjava/classpath/javax/swing/text/DefaultStyledDocument.java1271
1 files changed, 964 insertions, 307 deletions
diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
index eb56bb0..46b8225 100644
--- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
+++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java
@@ -42,11 +42,13 @@ import java.awt.Color;
import java.awt.Font;
import java.io.Serializable;
import java.util.Enumeration;
+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;
@@ -365,7 +367,6 @@ public class DefaultStyledDocument extends AbstractDocument
public String toString()
{
StringBuilder b = new StringBuilder();
- b.append('<');
switch (type)
{
case StartTagType:
@@ -427,6 +428,29 @@ public class DefaultStyledDocument extends AbstractDocument
/** 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;
+
+ /**
+ * 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;
+
+ /**
+ * The current position in the element tree. This is used for bulk inserts
+ * using ElementSpecs.
+ */
+ private Stack elementStack;
/**
* Holds fractured elements during insertion of end and start tags.
@@ -450,6 +474,7 @@ public class DefaultStyledDocument extends AbstractDocument
public ElementBuffer(Element root)
{
this.root = root;
+ elementStack = new Stack();
}
/**
@@ -463,6 +488,85 @@ 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
+ */
+ public void remove(int offs, int len, DefaultDocumentEvent ev)
+ {
+ offset = offs;
+ length = len;
+ documentEvent = ev;
+ removeUpdate();
+ }
+
+ /**
+ * 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()
+ {
+ int startParagraph = root.getElementIndex(offset);
+ int endParagraph = root.getElementIndex(offset + length);
+ Element[] empty = new Element[0];
+ int removeStart = -1;
+ int removeEnd = -1;
+ for (int i = startParagraph; i < endParagraph; i++)
+ {
+ Element paragraph = root.getElement(i);
+ int contentStart = paragraph.getElementIndex(offset);
+ int contentEnd = paragraph.getElementIndex(offset + length);
+ if (contentStart == paragraph.getStartOffset()
+ && contentEnd == paragraph.getEndOffset())
+ {
+ // In this case we only need to remove the whole paragraph. We
+ // do this in one go after this loop and only record the indices
+ // here.
+ if (removeStart == -1)
+ {
+ removeStart = i;
+ removeEnd = i;
+ }
+ else
+ removeEnd = i;
+ }
+ else
+ {
+ // In this case we remove a couple of child elements from this
+ // paragraph.
+ int removeLen = contentEnd - contentStart;
+ 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));
+ }
+ }
+ // Now we remove paragraphs from the root that have been tagged for
+ // removal.
+ if (removeStart != -1)
+ {
+ int removeLen = removeEnd - removeStart;
+ 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));
+ }
+ }
+
+ /**
* 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.
@@ -493,11 +597,50 @@ public class DefaultStyledDocument extends AbstractDocument
{
// Split up the element at the start offset if necessary.
Element el = getCharacterElement(offset);
- split(el, offset);
+ Element[] res = split(el, offset, 0);
+ BranchElement par = (BranchElement) el.getParentElement();
+ 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] };
+ index++;
+ }
+ else
+ {
+ removed = new Element[]{ el };
+ added = new Element[]{ res[0], res[1] };
+ }
+ par.replace(index, removed.length, added);
+ addEdit(par, index, removed, added);
+ }
int endOffset = offset + length;
el = getCharacterElement(endOffset);
- split(el, endOffset);
+ res = split(el, endOffset, 0);
+ par = (BranchElement) el.getParentElement();
+ if (res[1] != null)
+ {
+ int index = par.getElementIndex(offset);
+ Element[] removed;
+ Element[] added;
+ if (res[1] == null)
+ {
+ removed = new Element[0];
+ added = new Element[]{ res[1] };
+ }
+ else
+ {
+ removed = new Element[]{ el };
+ added = new Element[]{ res[0], res[1] };
+ }
+ par.replace(index, removed.length, added);
+ addEdit(par, index, removed, added);
+ }
}
/**
@@ -505,42 +648,96 @@ public class DefaultStyledDocument extends AbstractDocument
*
* @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
+ *
+ * @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.
+ *
*/
- void split(Element el, int offset)
+ private Element[] split(Element el, int offset, int space)
{
- if (el instanceof AbstractElement)
+ // 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)
{
- AbstractElement ael = (AbstractElement) el;
- int startOffset = ael.getStartOffset();
- int endOffset = ael.getEndOffset();
- int len = endOffset - startOffset;
- if (startOffset != offset && endOffset != offset)
+ 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))
{
- Element paragraph = ael.getParentElement();
- if (paragraph instanceof BranchElement)
+ // This is the case when we can keep the first element.
+ if (result[0] == null)
{
- BranchElement par = (BranchElement) paragraph;
- Element child1 = createLeafElement(par, ael, startOffset,
- offset);
- Element child2 = createLeafElement(par, ael, offset,
- endOffset);
- int index = par.getElementIndex(startOffset);
- Element[] add = new Element[]{ child1, child2 };
- par.replace(index, 1, add);
- documentEvent.addEdit(new ElementEdit(par, index,
- new Element[]{ el },
- add));
+ 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
- throw new AssertionError("paragraph elements are expected to "
- + "be instances of "
- + "javax.swing.text.AbstractDocument.BranchElement");
+ {
+ 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
+ {
+ 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 };
}
}
- else
- throw new AssertionError("content elements are expected to be "
- + "instances of "
- + "javax.swing.text.AbstractDocument.AbstractElement");
+ 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;
}
/**
@@ -560,9 +757,18 @@ public class DefaultStyledDocument extends AbstractDocument
public void insert(int offset, int length, ElementSpec[] data,
DefaultDocumentEvent ev)
{
+ if (length == 0)
+ return;
this.offset = offset;
this.length = length;
+ this.endOffset = offset + 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;
insertUpdate(data);
}
@@ -573,202 +779,348 @@ public class DefaultStyledDocument extends AbstractDocument
* {@link #insert}.
*
* @param data the element specifications for the elements to be inserte
- */
+ */
protected void insertUpdate(ElementSpec[] data)
{
+ if (data[0].getType() == ElementSpec.EndTagType)
+ {
+ // 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 });
+ }
+
for (int i = 0; i < data.length; i++)
{
+ BranchElement paragraph = (BranchElement) elementStack.peek();
switch (data[i].getType())
{
case ElementSpec.StartTagType:
- insertStartTag(data[i]);
+ switch (data[i].getDirection())
+ {
+ case ElementSpec.JoinFractureDirection:
+ insertFracture(data[i]);
+ 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);
+ break;
+ default:
+ break;
+ }
break;
case ElementSpec.EndTagType:
- insertEndTag(data[i]);
+ elementStack.pop();
break;
- default:
+ case ElementSpec.ContentType:
insertContentTag(data[i]);
break;
}
}
+ endEdit();
}
/**
- * Insert a new paragraph after the paragraph at the current position.
- *
- * @param tag the element spec that describes the element to be inserted
+ * 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.
*/
- void insertStartTag(ElementSpec tag)
+ private void prepareContentInsertion()
{
- BranchElement root = (BranchElement) getDefaultRootElement();
- int index = root.getElementIndex(offset);
- if (index == -1)
- index = 0;
-
- BranchElement newParagraph =
- (BranchElement) createBranchElement(root, tag.getAttributes());
- newParagraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE));
-
- // Add new paragraph into document structure.
- Element[] added = new Element[]{newParagraph};
- root.replace(index + 1, 0, added);
- ElementEdit edit = new ElementEdit(root, index + 1, new Element[0],
- added);
- documentEvent.addEdit(edit);
-
- // Maybe add fractured elements.
- if (tag.getDirection() == ElementSpec.JoinFractureDirection)
+ while (numEndTags > 0)
+ {
+ elementStack.pop();
+ numEndTags--;
+ }
+
+ while (numStartTags > 0)
{
- Element[] newFracture = new Element[fracture.length];
- for (int i = 0; i < fracture.length; i++)
+ 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 ret;
+ if (res[1] != null)
+ {
+ Element[] removed;
+ Element[] added;
+ if (res[0] == null)
{
- Element oldLeaf = fracture[i];
- Element newLeaf = createLeafElement(newParagraph,
- oldLeaf.getAttributes(),
- oldLeaf.getStartOffset(),
- oldLeaf.getEndOffset());
- newFracture[i] = newLeaf;
+ removed = new Element[0];
+ if (res[1] instanceof BranchElement)
+ {
+ added = new Element[]{ res[1] };
+ ret = res[1];
+ }
+ else
+ {
+ ret = createBranchElement(par, null);
+ added = new Element[]{ ret, res[1] };
+ }
+ index++;
+ }
+ else
+ {
+ removed = new Element[]{ current };
+ if (res[1] instanceof BranchElement)
+ {
+ ret = res[1];
+ added = new Element[]{ res[0], res[1] };
+ }
+ else
+ {
+ ret = createBranchElement(par, null);
+ added = new Element[]{ res[0], ret, res[1] };
+ }
}
- newParagraph.replace(0, 0, newFracture);
- edit = new ElementEdit(newParagraph, 0, new Element[0],
- fracture);
- documentEvent.addEdit(edit);
- fracture = new Element[0];
+ par.replace(index, removed.length, added);
+ addEdit(par, index, removed, added);
}
+ else
+ {
+ ret = createBranchElement(par, null);
+ Element[] added = new Element[]{ ret };
+ par.replace(index, 0, added);
+ addEdit(par, index, new Element[0], added);
+ }
+ return ret;
}
-
+
/**
- * Inserts an end tag into the document structure. This cuts of the
- * current paragraph element, possibly fracturing it's child elements.
- * The fractured elements are saved so that they can be joined later
- * with a new paragraph element.
+ * Inserts a fracture into the document structure.
+ *
+ * @param tag - the element spec.
*/
- void insertEndTag(ElementSpec tag)
+ private void insertFracture(ElementSpec tag)
{
- BranchElement root = (BranchElement) getDefaultRootElement();
- int parIndex = root.getElementIndex(offset);
- BranchElement paragraph = (BranchElement) root.getElement(parIndex);
-
- int index = paragraph.getElementIndex(offset);
- LeafElement content = (LeafElement) paragraph.getElement(index);
- // We might have to split the element at offset.
- split(content, offset);
- index = paragraph.getElementIndex(offset);
-
- int count = paragraph.getElementCount();
- // Store fractured elements.
- fracture = new Element[count - index];
- for (int i = index; i < count; ++i)
- fracture[i - index] = paragraph.getElement(i);
-
- // Delete fractured elements.
- paragraph.replace(index, count - index, new Element[0]);
-
- // Add this action to the document event.
- ElementEdit edit = new ElementEdit(paragraph, index, fracture,
- new Element[0]);
- documentEvent.addEdit(edit);
+ // 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);
}
-
+
/**
* Inserts a content element into the document structure.
- *
+ *
* @param tag the element spec
*/
- void insertContentTag(ElementSpec tag)
+ private void insertContentTag(ElementSpec tag)
{
+ prepareContentInsertion();
int len = tag.getLength();
int dir = tag.getDirection();
+ AttributeSet tagAtts = tag.getAttributes();
if (dir == ElementSpec.JoinPreviousDirection)
{
- Element prev = getCharacterElement(offset);
- BranchElement prevParent = (BranchElement) prev.getParentElement();
- Element join = createLeafElement(prevParent, tag.getAttributes(),
- prev.getStartOffset(),
- Math.max(prev.getEndOffset(),
- offset + len));
- int ind = prevParent.getElementIndex(offset);
- if (ind == -1)
- ind = 0;
- Element[] add = new Element[]{join};
- prevParent.replace(ind, 1, add);
-
- // Add this action to the document event.
- ElementEdit edit = new ElementEdit(prevParent, ind,
- new Element[]{prev}, add);
- documentEvent.addEdit(edit);
+ // 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)
{
- Element next = getCharacterElement(offset + len);
- BranchElement nextParent = (BranchElement) next.getParentElement();
- Element join = createLeafElement(nextParent, tag.getAttributes(),
- offset,
- next.getEndOffset());
- int ind = nextParent.getElementIndex(offset + len);
- if (ind == -1)
- ind = 0;
- Element[] add = new Element[]{join};
- nextParent.replace(ind, 1, add);
-
- // Add this action to the document event.
- ElementEdit edit = new ElementEdit(nextParent, ind,
- new Element[]{next}, add);
- documentEvent.addEdit(edit);
+ // 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);
+ Element target = paragraph.getElement(index);
+ if (target.isLeaf() && paragraph.getElementCount() > (index + 1))
+ {
+ 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);
+ }
}
- else
+ else if (dir == ElementSpec.OriginateDirection)
{
- BranchElement par = (BranchElement) getParagraphElement(offset);
-
- int ind = par.getElementIndex(offset);
-
- // Make room for the element.
- // Cut previous element.
- Element prev = par.getElement(ind);
- if (prev != null && prev.getStartOffset() < offset)
+ BranchElement paragraph = (BranchElement) elementStack.peek();
+ int index = paragraph.getElementIndex(offset);
+ Element current = paragraph.getElement(index);
+
+ Element[] added;
+ Element[] removed = new Element[] {current};
+ Element[] splitRes = split(current, offset, length);
+ if (splitRes[0] == null)
{
- Element cutPrev = createLeafElement(par, prev.getAttributes(),
- prev.getStartOffset(),
- offset);
- Element[] remove = new Element[]{prev};
- Element[] add = new Element[]{cutPrev};
- if (prev.getEndOffset() > offset + len)
- {
- Element rem = createLeafElement(par, prev.getAttributes(),
- offset + len,
- prev.getEndOffset());
- add = new Element[]{cutPrev, rem};
- }
-
- par.replace(ind, 1, add);
- documentEvent.addEdit(new ElementEdit(par, ind, remove, add));
- ind++;
+ added = new Element[2];
+ added[0] = createLeafElement(paragraph, tagAtts,
+ offset, endOffset);
+ added[1] = splitRes[1];
+ removed = new Element[0];
+ index++;
}
- // ind now points to the next element.
-
- // Cut next element if necessary.
- Element next = par.getElement(ind);
- if (next != null && next.getStartOffset() < offset + len)
+ 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)
{
- Element cutNext = createLeafElement(par, next.getAttributes(),
- offset + len,
- next.getEndOffset());
- Element[] remove = new Element[]{next};
- Element[] add = new Element[]{cutNext};
- par.replace(ind, 1, add);
- documentEvent.addEdit(new ElementEdit(par, ind, remove,
- add));
+ // 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);
}
-
- // Insert new element.
- Element newEl = createLeafElement(par, tag.getAttributes(),
- offset, offset + len);
- Element[] added = new Element[]{newEl};
- par.replace(ind, 0, added);
- // Add this action to the document event.
- ElementEdit edit = new ElementEdit(par, ind, new Element[0],
- added);
- documentEvent.addEdit(edit);
+ 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);
}
offset += len;
}
@@ -800,6 +1152,79 @@ public class DefaultStyledDocument extends AbstractDocument
result.replace(0, 0, children);
return result;
}
+
+ /**
+ * 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>
+ */
+ private void addEdit(Element e, int i, Element[] removed, Element[] added)
+ {
+ // Perform sanity check first.
+ DocumentEvent.ElementChange ec = documentEvent.getChange(e);
+
+ // Merge the existing stuff with the new stuff.
+ Element[] oldAdded = ec == null ? null: ec.getChildrenAdded();
+ Element[] newAdded;
+ if (oldAdded != null && added != null)
+ {
+ if (ec.getIndex() <= i)
+ {
+ 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();
+ }
+ else
+ throw new AssertionError("Not yet implemented case.");
+ }
+ else if (added != null)
+ newAdded = added;
+ else if (oldAdded != null)
+ newAdded = oldAdded;
+ else
+ newAdded = new Element[0];
+
+ Element[] oldRemoved = ec == null ? null: ec.getChildrenRemoved();
+ Element[] newRemoved;
+ if (oldRemoved != null && removed != null)
+ {
+ if (ec.getIndex() <= i)
+ {
+ 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();
+ }
+ else
+ throw new AssertionError("Not yet implemented case.");
+ }
+ else if (removed != null)
+ newRemoved = removed;
+ else if (oldRemoved != null)
+ newRemoved = oldRemoved;
+ else
+ newRemoved = new Element[0];
+
+ // Replace the existing edit for the element with the merged.
+ documentEvent.addEdit(new ElementEdit(e, i, newRemoved, newAdded));
+ }
}
/**
@@ -824,7 +1249,7 @@ public class DefaultStyledDocument extends AbstractDocument
*/
public String getName()
{
- return "section";
+ return SectionElementName;
}
}
@@ -945,9 +1370,7 @@ public class DefaultStyledDocument extends AbstractDocument
// Use createBranchElement() and createLeafElement instead.
SectionElement section = new SectionElement();
- BranchElement paragraph =
- (BranchElement) createBranchElement(section, null);
- paragraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE));
+ BranchElement paragraph = new BranchElement(section, null);
tmp = new Element[1];
tmp[0] = paragraph;
section.replace(0, 0, tmp);
@@ -1043,7 +1466,11 @@ public class DefaultStyledDocument extends AbstractDocument
{
Element paragraph = getParagraphElement(position);
AttributeSet attributes = paragraph.getAttributes();
- return (Style) attributes.getResolveParent();
+ 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;
}
/**
@@ -1112,50 +1539,54 @@ public class DefaultStyledDocument extends AbstractDocument
AttributeSet attributes,
boolean replace)
{
- 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);
-
- Element root = getDefaultRootElement();
- // Visit all paragraph elements within the specified interval
- int paragraphCount = root.getElementCount();
- for (int pindex = 0; pindex < paragraphCount; pindex++)
+ // Exit early if length is 0, so no DocumentEvent is created or fired.
+ if (length == 0)
+ return;
+ try
{
- Element paragraph = root.getElement(pindex);
- // Skip paragraphs that lie outside the interval.
- if ((paragraph.getStartOffset() > offset + length)
- || (paragraph.getEndOffset() < offset))
- continue;
-
- // Visit content elements within this paragraph
- int contentCount = paragraph.getElementCount();
- for (int cindex = 0; cindex < contentCount; cindex++)
+ // 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);
+
+ Element root = getDefaultRootElement();
+ // Visit all paragraph elements within the specified interval
+ int end = offset + length;
+ Element curr;
+ for (int pos = offset; pos < end; )
{
- Element content = paragraph.getElement(cindex);
- // Skip content that lies outside the interval.
- if ((content.getStartOffset() > offset + length)
- || (content.getEndOffset() < offset))
- continue;
-
- if (content instanceof AbstractElement)
- {
- AbstractElement el = (AbstractElement) content;
- if (replace)
- el.removeAttributes(el);
- el.addAttributes(attributes);
- }
- else
- throw new AssertionError("content elements are expected to be"
- + "instances of "
- + "javax.swing.text.AbstractDocument.AbstractElement");
+ // 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();
}
-
- fireChangedUpdate(ev);
}
/**
@@ -1167,14 +1598,36 @@ public class DefaultStyledDocument extends AbstractDocument
public void setLogicalStyle(int position, Style style)
{
Element el = getParagraphElement(position);
- if (el instanceof AbstractElement)
- {
- AbstractElement ael = (AbstractElement) el;
- ael.setResolveParent(style);
- }
- else
- throw new AssertionError("paragraph elements are expected to be"
- + "instances of javax.swing.text.AbstractDocument.AbstractElement");
+ // 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);
+ // 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");
+ }
+ finally
+ {
+ writeUnlock();
+ }
}
/**
@@ -1190,15 +1643,47 @@ public class DefaultStyledDocument extends AbstractDocument
AttributeSet attributes,
boolean replace)
{
- int index = offset;
- while (index < offset + length)
+ 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
{
- AbstractElement par = (AbstractElement) getParagraphElement(index);
- AttributeContext ctx = getAttributeContext();
- if (replace)
- par.removeAttributes(par);
- par.addAttributes(attributes);
- index = par.getElementCount();
+ writeUnlock();
}
}
@@ -1212,9 +1697,14 @@ public class DefaultStyledDocument extends AbstractDocument
protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
{
super.insertUpdate(ev, attr);
+ // If the attribute set is null, use an empty attribute set.
+ if (attr == null)
+ attr = SimpleAttributeSet.EMPTY;
int offset = ev.getOffset();
int length = ev.getLength();
int endOffset = offset + length;
+ AttributeSet paragraphAttributes =
+ getParagraphElement(endOffset).getAttributes();
Segment txt = new Segment();
try
{
@@ -1229,66 +1719,141 @@ public class DefaultStyledDocument extends AbstractDocument
int len = 0;
Vector specs = new Vector();
-
+ ElementSpec finalStartTag = null;
+ 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);
+ }
+ }
+ catch (BadLocationException ble)
+ {
+ // This shouldn't happen.
+ AssertionError ae = new AssertionError();
+ ae.initCause(ble);
+ throw ae;
+ }
+ }
- for (int i = offset; i < endOffset; ++i)
+
+ for (int i = txt.offset; i < segmentEnd; ++i)
{
len++;
if (txt.array[i] == '\n')
{
- ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType,
- len);
-
- // If we are at the last index, then check if we could probably be
- // joined with the next element.
- if (i == endOffset - 1)
- {
- if (next.getAttributes().isEqual(attr))
- spec.setDirection(ElementSpec.JoinNextDirection);
- }
- // If we are at the first new element, then check if it could be
- // joined with the previous element.
- else if (specs.size() == 0)
- {
- if (prev.getAttributes().isEqual(attr))
- spec.setDirection(ElementSpec.JoinPreviousDirection);
- }
-
- specs.add(spec);
+ // Add the ElementSpec for the content.
+ specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
// Add ElementSpecs for the newline.
- ElementSpec endTag = new ElementSpec(null, ElementSpec.EndTagType);
- specs.add(endTag);
- ElementSpec startTag = new ElementSpec(null,
+ specs.add(new ElementSpec(null, ElementSpec.EndTagType));
+ finalStartTag = new ElementSpec(paragraphAttributes,
ElementSpec.StartTagType);
- startTag.setDirection(ElementSpec.JoinFractureDirection);
- specs.add(startTag);
-
+ specs.add(finalStartTag);
len = 0;
- offset += len;
}
}
// 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
+ // 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;
+ }
+ }
+ else
+ {
+ // If there is an element AFTER this one, then set the
+ // direction to JoinNextDirection.
+ Element parent = prevParagraph.getParentElement();
+ int index = parent.getElementIndex(offset);
+ if (index + 1 < parent.getElementCount()
+ && !parent.getElement(index + 1).isLeaf())
+ 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.
+ ElementSpec last = (ElementSpec) specs.lastElement();
+ if (last.getType() == ElementSpec.ContentType)
{
- ElementSpec spec = new ElementSpec(attr, ElementSpec.ContentType, len);
- // If we are at the first new element, then check if it could be
- // joined with the previous element.
- if (specs.size() == 0)
+ Element currentRun =
+ prevParagraph.getElement(prevParagraph.getElementIndex(offset));
+ if (currentRun.getEndOffset() == endOffset)
{
- if (prev.getAttributes().isEqual(attr))
- spec.setDirection(ElementSpec.JoinPreviousDirection);
+ if (endOffset < getLength() && next.getAttributes().isEqual(attr)
+ && last.getType() == ElementSpec.ContentType)
+ last.setDirection(ElementSpec.JoinNextDirection);
+ }
+ else
+ {
+ if (finalStartTag != null
+ && finalStartTag.getDirection() ==
+ ElementSpec.JoinFractureDirection
+ && currentRun.getAttributes().isEqual(attr))
+ {
+ last.setDirection(ElementSpec.JoinNextDirection);
+ }
}
- // Check if we could probably be joined with the next element.
- else if (next.getAttributes().isEqual(attr))
- spec.setDirection(ElementSpec.JoinNextDirection);
-
- specs.add(spec);
}
-
+
+ // 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()]);
@@ -1296,6 +1861,47 @@ public class DefaultStyledDocument extends AbstractDocument
}
/**
+ * 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 (prevParagraph.getEndOffset() != 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;
+ }
+ 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
+ */
+ 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
@@ -1316,6 +1922,35 @@ public class DefaultStyledDocument extends AbstractDocument
// 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.
*
@@ -1325,36 +1960,53 @@ public class DefaultStyledDocument extends AbstractDocument
protected void insert(int offset, ElementSpec[] data)
throws BadLocationException
{
- writeLock();
- // First we insert the content.
- int index = offset;
- for (int i = 0; i < data.length; i++)
+ if (data == null || data.length == 0)
+ return;
+ try
{
- ElementSpec spec = data[i];
- if (spec.getArray() != null && spec.getLength() > 0)
+ // 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.
+ StringBuffer contentBuffer = new StringBuffer();
+ for (int i = 0; i < data.length; i++)
{
- String insertString = new String(spec.getArray(), spec.getOffset(),
- spec.getLength());
- content.insertString(index, insertString);
+ // 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());
}
- index += spec.getLength();
+
+ int length = contentBuffer.length();
+
+ // 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);
+ ev.addEdit(edit);
+
+ // Finally we must update the document structure and fire the insert
+ // update event.
+ buffer.insert(offset, length, data, ev);
+ fireInsertUpdate(ev);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
}
- // Update the view structure.
- DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, index - offset,
- DocumentEvent.EventType.INSERT);
- for (int i = 0; i < data.length; i++)
+ finally
{
- ElementSpec spec = data[i];
- AttributeSet atts = spec.getAttributes();
- if (atts != null)
- insertUpdate(ev, atts);
+ writeUnlock();
}
-
- // Finally we must update the document structure and fire the insert update
- // event.
- buffer.insert(offset, index - offset, data, ev);
- fireInsertUpdate(ev);
- writeUnlock();
}
/**
@@ -1382,4 +2034,9 @@ public class DefaultStyledDocument extends AbstractDocument
throw err;
}
}
+
+ static boolean attributeSetsAreSame (AttributeSet a, AttributeSet b)
+ {
+ return (a == null && b == null) || (a != null && a.isEqual(b));
+ }
}