diff options
Diffstat (limited to 'libjava/classpath/javax/swing/text/DefaultStyledDocument.java')
-rw-r--r-- | libjava/classpath/javax/swing/text/DefaultStyledDocument.java | 2091 |
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 * "section". - * + * * @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(); + } } } |