diff options
Diffstat (limited to 'libjava/classpath/javax/swing/text/DefaultStyledDocument.java')
-rw-r--r-- | libjava/classpath/javax/swing/text/DefaultStyledDocument.java | 1032 |
1 files changed, 966 insertions, 66 deletions
diff --git a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java index 3545e52c453..eb56bb0f8e5 100644 --- a/libjava/classpath/javax/swing/text/DefaultStyledDocument.java +++ b/libjava/classpath/javax/swing/text/DefaultStyledDocument.java @@ -1,5 +1,5 @@ /* DefaultStyledDocument.java -- - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -41,8 +41,14 @@ package javax.swing.text; import java.awt.Color; import java.awt.Font; import java.io.Serializable; +import java.util.Enumeration; +import java.util.Vector; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.UndoableEdit; /** * The default implementation of {@link StyledDocument}. @@ -60,12 +66,359 @@ 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 + { + /** + * A copy of the old attributes. + */ + protected AttributeSet copy; + + /** + * The new attributes. + */ + protected AttributeSet newAttributes; + + /** + * If the new attributes replaced the old attributes or if they only were + * added to them. + */ + protected boolean isReplacing; + + /** + * The element that has changed. + */ + protected Element element; + + /** + * 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 + */ + public AttributeUndoableEdit(Element el, AttributeSet newAtts, + boolean replacing) + { + element = el; + newAttributes = newAtts; + isReplacing = replacing; + copy = el.getAttributes().copyAttributes(); + } + + /** + * Undos the attribute change. The <code>copy</code> field is set as + * attributes on <code>element</code>. + */ + public void undo() + { + super.undo(); + AttributeSet atts = element.getAttributes(); + if (atts instanceof MutableAttributeSet) + { + MutableAttributeSet mutable = (MutableAttributeSet) atts; + mutable.removeAttributes(atts); + mutable.addAttributes(copy); + } + } + + /** + * Redos an attribute change. This adds <code>newAttributes</code> to the + * <code>element</code>'s attribute set, possibly clearing all attributes + * if <code>isReplacing</code> is true. + */ + public void redo() + { + super.undo(); + AttributeSet atts = element.getAttributes(); + if (atts instanceof MutableAttributeSet) + { + MutableAttributeSet mutable = (MutableAttributeSet) atts; + if (isReplacing) + mutable.removeAttributes(atts); + mutable.addAttributes(newAttributes); + } + } + } + + /** + * 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}. + */ + public static final short StartTagType = 1; + + /** + * This indicates an end tag. This is a possible value for + * {@link #getType}. + */ + public static final short EndTagType = 2; + + /** + * This indicates a content element. This is a possible value for + * {@link #getType}. + */ + public static final short ContentType = 3; + + /** + * This indicates that the data associated with this spec should be joined + * 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}. + */ + 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}. + */ + public static final short OriginateDirection = 6; + + /** + * This indicates that the data associated with this spec should be joined + * to the fractured element. This is a possible value for + * {@link #getDirection}. + */ + public static final short JoinFractureDirection = 7; + + /** + * The type of the tag. + */ + short type; + + /** + * The direction of the tag. + */ + short direction; + + /** + * The offset of the content. + */ + int offset; + + /** + * The length of the content. + */ + int length; + + /** + * The actual content. + */ + char[] content; + + /** + * The attributes for the tag. + */ + AttributeSet attributes; + + /** + * 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 + */ + public ElementSpec(AttributeSet a, short type) + { + this(a, type, 0); + } + + /** + * 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 + */ + 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 + */ + public ElementSpec(AttributeSet a, short type, char[] txt, int offs, + int len) + { + attributes = a; + this.type = type; + offset = offs; + length = len; + content = txt; + direction = OriginateDirection; + } + + /** + * Sets the type of the element. + * + * @param type the type of the element to be set + */ + public void setType(short type) + { + this.type = type; + } + + /** + * Returns the type of the element. + * + * @return the type of the element + */ + public short getType() + { + return type; + } + + /** + * Sets the direction of the element. + * + * @param dir the direction of the element to be set + */ + public void setDirection(short dir) + { + direction = dir; + } + + /** + * Returns the direction of the element. + * + * @return the direction of the element + */ + public short getDirection() + { + return direction; + } + + /** + * Returns the attributes of the element. + * + * @return the attributes of the element + */ + public AttributeSet getAttributes() + { + return attributes; + } + + /** + * Returns the actual content of the element. + * + * @return the actual content of the element + */ + public char[] getArray() + { + return content; + } + + /** + * Returns the offset of the content. + * + * @return the offset of the content + */ + public int getOffset() + { + return offset; + } + + /** + * Returns the length of the content. + * + * @return the length of the content + */ + public int getLength() + { + return length; + } + + /** + * 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() + { + StringBuilder b = new StringBuilder(); + b.append('<'); + switch (type) + { + case StartTagType: + b.append("StartTag"); + break; + case EndTagType: + b.append("EndTag"); + break; + case ContentType: + b.append("Content"); + break; + default: + b.append("??"); + break; + } + + b.append(':'); + + switch (direction) + { + case JoinPreviousDirection: + b.append("JoinPrevious"); + break; + case JoinNextDirection: + b.append("JoinNext"); + break; + case OriginateDirection: + b.append("Originate"); + break; + case JoinFractureDirection: + b.append("Fracture"); + break; + default: + b.append("??"); + break; + } + + b.append(':'); + b.append(length); + + return b.toString(); + } + } + + /** * Performs all <em>structural</code> changes to the <code>Element</code> * hierarchy. */ - public class ElementBuffer - implements Serializable + public class ElementBuffer implements Serializable { + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 1688745877691146623L; + /** The root element of the hierarchy. */ private Element root; @@ -76,6 +429,19 @@ public class DefaultStyledDocument extends AbstractDocument private int length; /** + * 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; + + /** * Creates a new <code>ElementBuffer</code> for the specified * <code>root</code> element. * @@ -114,6 +480,7 @@ public class DefaultStyledDocument extends AbstractDocument { this.offset = offset; this.length = length; + documentEvent = ev; changeUpdate(); } @@ -142,38 +509,352 @@ public class DefaultStyledDocument extends AbstractDocument void split(Element el, int offset) { if (el instanceof AbstractElement) - { - AbstractElement ael = (AbstractElement) el; - int startOffset = ael.getStartOffset(); - int endOffset = ael.getEndOffset(); - int len = endOffset - startOffset; - if (startOffset != offset && endOffset != offset) - { - Element paragraph = ael.getParentElement(); - if (paragraph instanceof BranchElement) - { - BranchElement par = (BranchElement) paragraph; - Element child1 = createLeafElement(par, ael, startOffset, - offset); - Element child2 = createLeafElement(par, ael, offset, - endOffset); - int index = par.getElementIndex(startOffset); - par.replace(index, 1, new Element[]{ child1, child2 }); - } + { + AbstractElement ael = (AbstractElement) el; + int startOffset = ael.getStartOffset(); + int endOffset = ael.getEndOffset(); + int len = endOffset - startOffset; + if (startOffset != offset && endOffset != offset) + { + Element paragraph = ael.getParentElement(); + if (paragraph instanceof BranchElement) + { + 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)); + } else throw new AssertionError("paragraph elements are expected to " + "be instances of " + "javax.swing.text.AbstractDocument.BranchElement"); - } - } + } + } else - throw new AssertionError("content elements are expected to be " - + "instances of " + throw new AssertionError("content elements are expected to be " + + "instances of " + "javax.swing.text.AbstractDocument.AbstractElement"); } + + /** + * 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 + */ + public void insert(int offset, int length, ElementSpec[] data, + DefaultDocumentEvent ev) + { + this.offset = offset; + this.length = length; + documentEvent = ev; + insertUpdate(data); + } + + /** + * 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 + */ + protected void insertUpdate(ElementSpec[] data) + { + for (int i = 0; i < data.length; i++) + { + switch (data[i].getType()) + { + case ElementSpec.StartTagType: + insertStartTag(data[i]); + break; + case ElementSpec.EndTagType: + insertEndTag(data[i]); + break; + default: + insertContentTag(data[i]); + break; + } + } + } + + /** + * Insert a new paragraph after the paragraph at the current position. + * + * @param tag the element spec that describes the element to be inserted + */ + void insertStartTag(ElementSpec tag) + { + 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) + { + Element[] newFracture = new Element[fracture.length]; + for (int i = 0; i < fracture.length; i++) + { + Element oldLeaf = fracture[i]; + Element newLeaf = createLeafElement(newParagraph, + oldLeaf.getAttributes(), + oldLeaf.getStartOffset(), + oldLeaf.getEndOffset()); + newFracture[i] = newLeaf; + } + newParagraph.replace(0, 0, newFracture); + edit = new ElementEdit(newParagraph, 0, new Element[0], + fracture); + documentEvent.addEdit(edit); + fracture = new Element[0]; + } + } + + /** + * 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. + */ + void insertEndTag(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); + } + + /** + * Inserts a content element into the document structure. + * + * @param tag the element spec + */ + void insertContentTag(ElementSpec tag) + { + int len = tag.getLength(); + int dir = tag.getDirection(); + 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); + } + 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); + } + else + { + 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) + { + 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++; + } + // ind now points to the next element. + + // Cut next element if necessary. + Element next = par.getElement(ind); + if (next != null && next.getStartOffset() < offset + len) + { + 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)); + } + + // 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); + } + offset += 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 + */ + public Element clone (Element parent, Element clonee) + { + // 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()); + + // 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; + } } /** + * An element type for sections. This is a simple BranchElement with + * a unique name. + */ + protected class SectionElement extends BranchElement + { + /** + * Creates a new SectionElement. + */ + public SectionElement() + { + super(null, null); + } + + /** + * Returns the name of the element. This method always returns + * "section". + * + * @return the name of the element + */ + public String getName() + { + return "section"; + } + } + + /** + * 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 + { + + /** + * Receives notification when any of the document's style changes and calls + * {@link DefaultStyledDocument#styleChanged(Style)}. + * + * @param event the change event + */ + public void stateChanged(ChangeEvent event) + { + Style style = (Style) event.getSource(); + styleChanged(style); + } + } + + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 940485415728614849L; + + /** * The default size to use for new content buffers. */ public static final int BUFFER_SIZE_DEFAULT = 4096; @@ -185,6 +866,11 @@ public class DefaultStyledDocument extends AbstractDocument protected DefaultStyledDocument.ElementBuffer buffer; /** + * Listens for changes on this document's styles and notifies styleChanged(). + */ + private StyleChangeListener styleChangeListener; + + /** * Creates a new <code>DefaultStyledDocument</code>. */ public DefaultStyledDocument() @@ -237,7 +923,14 @@ public class DefaultStyledDocument extends AbstractDocument public Style addStyle(String nm, Style parent) { StyleContext context = (StyleContext) getAttributeContext(); - return context.addStyle(nm, parent); + Style newStyle = context.addStyle(nm, parent); + + // Register change listener. + if (styleChangeListener == null) + styleChangeListener = new StyleChangeListener(); + newStyle.addChangeListener(styleChangeListener); + + return newStyle; } /** @@ -250,9 +943,11 @@ public class DefaultStyledDocument extends AbstractDocument Element[] tmp; // FIXME: Create a SecionElement here instead of a BranchElement. // Use createBranchElement() and createLeafElement instead. - BranchElement section = new BranchElement(null, null); - - BranchElement paragraph = new BranchElement(section, null); + SectionElement section = new SectionElement(); + + BranchElement paragraph = + (BranchElement) createBranchElement(section, null); + paragraph.setResolveParent(getStyle(StyleContext.DEFAULT_STYLE)); tmp = new Element[1]; tmp[0] = paragraph; section.replace(0, 0, tmp); @@ -261,7 +956,7 @@ public class DefaultStyledDocument extends AbstractDocument tmp = new Element[1]; tmp[0] = leaf; paragraph.replace(0, 0, tmp); - + return section; } @@ -279,10 +974,10 @@ public class DefaultStyledDocument extends AbstractDocument { Element element = getDefaultRootElement(); - while (! element.isLeaf()) + while (!element.isLeaf()) { - int index = element.getElementIndex(position); - element = element.getElement(index); + int index = element.getElementIndex(position); + element = element.getElement(index); } return element; @@ -353,6 +1048,10 @@ 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 + * <code>position >= endIndex</code> or the first paragraph if + * <code>position < startIndex</code>. * * @param position the position for which to query the paragraph element * @@ -360,8 +1059,18 @@ public class DefaultStyledDocument extends AbstractDocument */ public Element getParagraphElement(int position) { - Element element = getCharacterElement(position); - return element.getParentElement(); + 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; } /** @@ -416,35 +1125,37 @@ public class DefaultStyledDocument extends AbstractDocument int paragraphCount = root.getElementCount(); for (int pindex = 0; pindex < paragraphCount; pindex++) { - 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++) - { - 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 " + 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++) + { + 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"); - } + } } + + fireChangedUpdate(ev); } /** @@ -476,10 +1187,199 @@ public class DefaultStyledDocument extends AbstractDocument * selection are overridden, otherwise they are merged */ public void setParagraphAttributes(int offset, int length, - AttributeSet attributes, - boolean replace) + AttributeSet attributes, + boolean replace) + { + int index = offset; + while (index < offset + length) + { + AbstractElement par = (AbstractElement) getParagraphElement(index); + AttributeContext ctx = getAttributeContext(); + if (replace) + par.removeAttributes(par); + par.addAttributes(attributes); + index = par.getElementCount(); + } + } + + /** + * 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) + { + super.insertUpdate(ev, attr); + int offset = ev.getOffset(); + int length = ev.getLength(); + int endOffset = offset + length; + Segment txt = new Segment(); + try + { + getText(offset, length, txt); + } + catch (BadLocationException ex) + { + AssertionError ae = new AssertionError("Unexpected bad location"); + ae.initCause(ex); + throw ae; + } + + int len = 0; + Vector specs = new Vector(); + + Element prev = getCharacterElement(offset); + Element next = getCharacterElement(endOffset); + + for (int i = offset; i < endOffset; ++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 ElementSpecs for the newline. + ElementSpec endTag = new ElementSpec(null, ElementSpec.EndTagType); + specs.add(endTag); + ElementSpec startTag = new ElementSpec(null, + ElementSpec.StartTagType); + startTag.setDirection(ElementSpec.JoinFractureDirection); + specs.add(startTag); + + len = 0; + offset += len; + } + } + + // Create last element if last character hasn't been a newline. + if (len > 0) + { + 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) + { + if (prev.getAttributes().isEqual(attr)) + spec.setDirection(ElementSpec.JoinPreviousDirection); + } + // Check if we could probably be joined with the next element. + else if (next.getAttributes().isEqual(attr)) + spec.setDirection(ElementSpec.JoinNextDirection); + + specs.add(spec); + } + + ElementSpec[] elSpecs = + (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]); + + buffer.insert(offset, length, elSpecs, ev); + } + + /** + * Returns an enumeration of all style names. + * + * @return an enumeration of all style names + */ + public Enumeration getStyleNames() + { + StyleContext context = (StyleContext) getAttributeContext(); + return context.getStyleNames(); + } + + /** + * Called when any of this document's styles changes. + * + * @param style the style that changed + */ + protected void styleChanged(Style style) { - // FIXME: Implement me. - throw new Error("not implemented"); + // Nothing to do here. This is intended to be overridden by subclasses. + } + + /** + * 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 + */ + 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++) + { + ElementSpec spec = data[i]; + if (spec.getArray() != null && spec.getLength() > 0) + { + String insertString = new String(spec.getArray(), spec.getOffset(), + spec.getLength()); + content.insertString(index, insertString); + } + index += spec.getLength(); + } + // Update the view structure. + DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, index - offset, + DocumentEvent.EventType.INSERT); + for (int i = 0; i < data.length; i++) + { + ElementSpec spec = data[i]; + AttributeSet atts = spec.getAttributes(); + if (atts != null) + insertUpdate(ev, atts); + } + + // Finally we must update the document structure and fire the insert update + // event. + buffer.insert(offset, index - offset, data, ev); + fireInsertUpdate(ev); + writeUnlock(); + } + + /** + * Initializes the <code>DefaultStyledDocument</code> with the specified + * data. + * + * @param data the specification of the content with which the document is + * initialized + */ + protected void create(ElementSpec[] data) + { + try + { + // Clear content. + content.remove(0, content.length()); + // Clear buffer and root element. + buffer = new ElementBuffer(createDefaultRoot()); + // Insert the data. + insert(0, data); + } + catch (BadLocationException ex) + { + AssertionError err = new AssertionError("Unexpected bad location"); + err.initCause(ex); + throw err; + } } } |