This is the first part of the work I'm doing on DefaultStyledDocument. This fixes a couple issues associated with PR 24744 but the bug is not completely fixed yet, so the testcase for that PR still crashes. I will continue to work on this but these changes are ready to go in and may help get some text/html test apps up and running so I'm committing this now.
2005-12-19 Anthony Balkissoon <[EMAIL PROTECTED]> * javax/swing/text/DefaultStyledDocument.java: (endOffset): New field. (ElementBuffer.insert): If length is 0 return early. Set the endOffset variable here. (ElementBuffer.insertContentTag): Added special case for when insertion covers entire range of the current Element. Replaced (offset + length) with endOffset everywhere. Added checks to see if the insertion has the same attributes as the surrounding Element(s), in which case we don't split them, we join them. (SectionElement.getName): Return the field SectionElementName instead of hardcoding a return value. (getLogicalStyle): As the specs require, if the attributes from the resolve parent aren't of type Style, return null. (setCharacterAttributes): Exit early if length is 0. Obtain a write lock for the body of this method. Changed the way we iterate through the paragraphs to be more efficient and more intuitive. Added AttributeUndoableEdits to the DocumentEvent and fired the UndoableEditUpdate. (setLogicalStyle): Return early if the Element is null. Obtain a write lock for the body of this method. Fire the appropriate DocumentEvent. (setParagraphAttributes): Changed the way we iterated through the paragraphs because it was incorrect and unintuitive. Obtained a write lock within a try/finally block. Added AttributeUndoableEdits to the DocumentEvent and fired the event. (insertUpdate): Changed the way we iterate because we cannot assume the Segment holds the characters starting from index 0. Give the startTag the same attributes as the paragraph. Removed the useless offset += len line because len is 0. (printElements): New temporary debugging method. (attributeSetsAreSame): New method. --Tony
Index: javax/swing/text/DefaultStyledDocument.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/text/DefaultStyledDocument.java,v retrieving revision 1.20 diff -u -r1.20 DefaultStyledDocument.java --- javax/swing/text/DefaultStyledDocument.java 19 Dec 2005 10:21:39 -0000 1.20 +++ javax/swing/text/DefaultStyledDocument.java 19 Dec 2005 22:36:04 -0000 @@ -48,6 +48,7 @@ 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; @@ -428,6 +429,9 @@ /** 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 @@ -675,8 +679,11 @@ 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(); @@ -799,7 +806,7 @@ } return ret; } - + /** * Inserts a content element into the document structure. * @@ -810,6 +817,7 @@ prepareContentInsertion(); int len = tag.getLength(); int dir = tag.getDirection(); + AttributeSet tagAtts = createLeafElement(null, null, 0, 0).getAttributes(); if (dir == ElementSpec.JoinPreviousDirection) { // The mauve tests to this class show that a JoinPrevious insertion @@ -845,45 +853,103 @@ BranchElement paragraph = (BranchElement) elementStack.peek(); int index = paragraph.getElementIndex(offset); Element current = paragraph.getElement(index); - + Element[] added; - Element[] removed; + Element[] removed = new Element[] {current}; Element[] splitRes = split(current, offset, length); - // Special case for when offset == startOffset or offset == endOffset. if (splitRes[0] == null) { added = new Element[2]; - added[0] = createLeafElement(paragraph, tag.getAttributes(), - offset, offset + length); + added[0] = createLeafElement(paragraph, tagAtts, + offset, endOffset); added[1] = splitRes[1]; removed = new Element[0]; index++; } - else if (current.getStartOffset() == offset) + else if (current.getStartOffset() == offset + && current.getEndOffset() - 1 == endOffset) { - added = new Element[2]; - added[0] = createLeafElement(paragraph, tag.getAttributes(), - offset, offset + length); - added[1] = splitRes[1]; - removed = new Element[] { current }; + // This is if the new insertion covers the entire range of + // <code>current</code>. This will generally happen for the + // first insertion into a new paragraph. + added = new Element[1]; + added[0] = createLeafElement(paragraph, tagAtts, + offset, endOffset + 1); } - else if (current.getEndOffset() - length == offset) + else if (current.getStartOffset() == offset) + { + // This is if the new insertion happens immediately before + // the <code>current</code> Element. In this case, if there is + // a split, there are 2 resulting Elements. + + AttributeSet splitAtts = splitRes[1].getAttributes(); + if (attributeSetsAreSame(tagAtts, splitAtts)) + { + // If the attributes of the adjacent Elements are the same + // then we don't split them, we join them into one. + added = new Element [1]; + added[0] = createLeafElement(paragraph, tagAtts, offset, + splitRes[1].getEndOffset()); + } + else + { + // Otherwise we have 2 resulting Elements. + added = new Element[2]; + added[0] = createLeafElement(paragraph, tagAtts, + offset, endOffset); + added[1] = splitRes[1]; + } + } + else if (current.getEndOffset() == endOffset + 1) { - added = new Element[2]; - added[0] = splitRes[0]; - added[1] = createLeafElement(paragraph, tag.getAttributes(), - offset, offset + length); - removed = new Element[] { current }; + // This is if the new insertion happens right at the end of + // the <code>current</code> Element. In this case, if there is + // a split, there are 2 resulting Elements. + AttributeSet splitAtts = splitRes[0].getAttributes(); + if (attributeSetsAreSame(tagAtts, splitAtts)) + { + // If the attributes are the same, no need to split. + added = new Element [1]; + added[0] = createLeafElement(paragraph, tagAtts, + splitRes[0].getStartOffset(), + endOffset + 1); + } + else + { + // Otherwise there are 2 resulting Elements. + added = new Element[2]; + added[0] = splitRes[0]; + added[1] = createLeafElement(paragraph, tagAtts, offset, + endOffset + 1); + } } else { - added = new Element[3]; - added[0] = splitRes[0]; - added[1] = createLeafElement(paragraph, tag.getAttributes(), - offset, offset + length); - added[2] = splitRes[1]; - removed = new Element[] { current }; - } + // This is if the new insertion is in the middle of the + // <code>current</code> Element. In this case, if there is a + // split, there will be 3 resulting Elements. Note, since + // <code>splitRes[0]</code> and <code>splitRes[1]</code> were + // once the same Element, they have the same attributes. + AttributeSet split1Atts = splitRes[0].getAttributes(); + + if (attributeSetsAreSame(tagAtts, split1Atts)) + { + // If the attributes are the same, no need to split. + added = new Element [1]; + added[0] = createLeafElement(paragraph, tagAtts, + splitRes[0].getStartOffset(), + splitRes[1].getEndOffset()); + } + else + { + // Otherwise there are 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); } @@ -1014,7 +1080,7 @@ */ public String getName() { - return "section"; + return SectionElementName; } } @@ -1233,7 +1299,11 @@ { 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; } /** @@ -1302,50 +1372,54 @@ 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); } /** @@ -1357,14 +1431,36 @@ 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(); + } } /** @@ -1380,22 +1476,48 @@ AttributeSet attributes, boolean replace) { - writeLock(); - int index = offset; - while (index < offset + length) + try { - AbstractElement par = (AbstractElement) getParagraphElement(index); - // If we have already seen this paragraph element, then exit the loop. - if (par.getEndOffset() + 1 == index) - break; - - AttributeContext ctx = getAttributeContext(); - if (replace) - par.removeAttributes(par); - par.addAttributes(attributes); - index = par.getEndOffset() + 1; + // Must obtain a write lock for this method. writeLock() and + // writeUnlock() should always be in try/finally blocks to make + // sure that locking occurs in a balanced manner. + writeLock(); + + // Create a DocumentEvent to use for changedUpdate(). + DefaultDocumentEvent ev = + new DefaultDocumentEvent ( + offset, + length, + DocumentEvent.EventType.CHANGE); + + // Have to iterate through all the _paragraph_ elements that are + // contained or partially contained in the interval + // (offset, offset + length). + Element rootElement = getDefaultRootElement(); + int startElement = rootElement.getElementIndex(offset); + int endElement = rootElement.getElementIndex(offset + length - 1); + if (endElement < startElement) + endElement = startElement; + + for (int i = startElement; i <= endElement; i++) + { + Element par = rootElement.getElement(i); + MutableAttributeSet a = (MutableAttributeSet) par.getAttributes(); + // Add the change to the DocumentEvent. + ev.addEdit(new AttributeUndoableEdit(par, attributes, replace)); + // If replace is true remove the old attributes. + if (replace) + a.removeAttributes(a); + // Add the new attributes. + a.addAttributes(attributes); + } + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + finally + { + writeUnlock(); } - writeUnlock(); } /** @@ -1407,10 +1529,12 @@ */ protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr) { - super.insertUpdate(ev, attr); + super.insertUpdate(ev, attr); int offset = ev.getOffset(); int length = ev.getLength(); int endOffset = offset + length; + AttributeSet paragraphAttributes = + getParagraphElement(endOffset).getAttributes(); Segment txt = new Segment(); try { @@ -1429,7 +1553,8 @@ Element prev = getCharacterElement(offset); Element next = getCharacterElement(endOffset); - for (int i = offset; i < endOffset; ++i) + int segmentEnd = txt.offset + txt.count; + for (int i = txt.offset; i < segmentEnd; ++i) { len++; if (txt.array[i] == '\n') @@ -1439,7 +1564,7 @@ // If we are at the last index, then check if we could probably be // joined with the next element. - if (i == endOffset - 1) + if (i == segmentEnd - 1) { if (next.getAttributes().isEqual(attr)) spec.setDirection(ElementSpec.JoinNextDirection); @@ -1457,13 +1582,12 @@ // Add ElementSpecs for the newline. ElementSpec endTag = new ElementSpec(null, ElementSpec.EndTagType); specs.add(endTag); - ElementSpec startTag = new ElementSpec(null, + ElementSpec startTag = new ElementSpec(paragraphAttributes, ElementSpec.StartTagType); startTag.setDirection(ElementSpec.JoinFractureDirection); specs.add(startTag); len = 0; - offset += len; } } @@ -1484,7 +1608,7 @@ specs.add(spec); } - + ElementSpec[] elSpecs = (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]); @@ -1512,6 +1636,20 @@ // 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 + System.out.println ("LeafElement ("+start.getStartOffset()+", "+start.getEndOffset()+")"); + for (int i = 0; i < start.getElementCount(); i ++) + printElements (start.getElement(i), pad+3); + } + /** * Inserts a bulk of structured content at once. * @@ -1545,7 +1683,7 @@ // If there was no content inserted then exit early. if (length == 0) return; - + UndoableEdit edit = content.insertString(offset, contentBuffer.toString()); @@ -1600,4 +1738,9 @@ throw err; } } + + static boolean attributeSetsAreSame (AttributeSet a, AttributeSet b) + { + return (a == null && b == null) || (a != null && a.isEqual(b)); + } }
_______________________________________________ Classpath-patches mailing list Classpath-patches@gnu.org http://lists.gnu.org/mailman/listinfo/classpath-patches