diff options
author | Mark Wielaard <mark@gcc.gnu.org> | 2006-01-17 18:09:40 +0000 |
---|---|---|
committer | Mark Wielaard <mark@gcc.gnu.org> | 2006-01-17 18:09:40 +0000 |
commit | 2127637945ea6b763966398130e0770fa993c860 (patch) | |
tree | c976ca91e3ef0bda3b34b37c0195145638d8d08e /libjava/classpath/javax/swing/text/DefaultStyledDocument.java | |
parent | bcb36c3e02e3bd2843aad1b9888513dfb5d6e337 (diff) | |
download | gcc-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.java | 1271 |
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)); + } } |