1 /* DefaultStyledDocument.java --
2 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing.text;
41 import java.awt.Color;
43 import java.io.Serializable;
44 import java.util.ArrayList;
45 import java.util.Enumeration;
46 import java.util.Iterator;
47 import java.util.Stack;
48 import java.util.Vector;
50 import javax.swing.event.ChangeEvent;
51 import javax.swing.event.ChangeListener;
52 import javax.swing.event.DocumentEvent;
53 import javax.swing.event.UndoableEditEvent;
54 import javax.swing.undo.AbstractUndoableEdit;
55 import javax.swing.undo.UndoableEdit;
58 * The default implementation of {@link StyledDocument}. The document is
59 * modeled as an {@link Element} tree, which has a {@link SectionElement} as
60 * single root, which has one or more {@link AbstractDocument.BranchElement}s
61 * as paragraph nodes and each paragraph node having one or more
62 * {@link AbstractDocument.LeafElement}s as content nodes.
64 * @author Michael Koch (konqueror@gmx.de)
65 * @author Roman Kennke (roman@kennke.org)
67 public class DefaultStyledDocument extends AbstractDocument implements
72 * An {@link UndoableEdit} that can undo attribute changes to an element.
74 * @author Roman Kennke (kennke@aicas.com)
76 public static class AttributeUndoableEdit extends AbstractUndoableEdit
79 * A copy of the old attributes.
81 protected AttributeSet copy;
86 protected AttributeSet newAttributes;
89 * If the new attributes replaced the old attributes or if they only were
92 protected boolean isReplacing;
95 * The element that has changed.
97 protected Element element;
100 * Creates a new <code>AttributeUndoableEdit</code>.
103 * the element that changes attributes
107 * if the new attributes replace the old or only append to them
109 public AttributeUndoableEdit(Element el, AttributeSet newAtts,
113 newAttributes = newAtts;
114 isReplacing = replacing;
115 copy = el.getAttributes().copyAttributes();
119 * Undos the attribute change. The <code>copy</code> field is set as
120 * attributes on <code>element</code>.
125 AttributeSet atts = element.getAttributes();
126 if (atts instanceof MutableAttributeSet)
128 MutableAttributeSet mutable = (MutableAttributeSet) atts;
129 mutable.removeAttributes(atts);
130 mutable.addAttributes(copy);
135 * Redos an attribute change. This adds <code>newAttributes</code> to the
136 * <code>element</code>'s attribute set, possibly clearing all attributes
137 * if <code>isReplacing</code> is true.
142 AttributeSet atts = element.getAttributes();
143 if (atts instanceof MutableAttributeSet)
145 MutableAttributeSet mutable = (MutableAttributeSet) atts;
147 mutable.removeAttributes(atts);
148 mutable.addAttributes(newAttributes);
154 * Carries specification information for new {@link Element}s that should be
155 * created in {@link ElementBuffer}. This allows the parsing process to be
156 * decoupled from the <code>Element</code> creation process.
158 public static class ElementSpec
161 * This indicates a start tag. This is a possible value for {@link #getType}.
163 public static final short StartTagType = 1;
166 * This indicates an end tag. This is a possible value for {@link #getType}.
168 public static final short EndTagType = 2;
171 * This indicates a content element. This is a possible value for
174 public static final short ContentType = 3;
177 * This indicates that the data associated with this spec should be joined
178 * with what precedes it. This is a possible value for {@link #getDirection}.
180 public static final short JoinPreviousDirection = 4;
183 * This indicates that the data associated with this spec should be joined
184 * with what follows it. This is a possible value for {@link #getDirection}.
186 public static final short JoinNextDirection = 5;
189 * This indicates that the data associated with this spec should be used to
190 * create a new element. This is a possible value for {@link #getDirection}.
192 public static final short OriginateDirection = 6;
195 * This indicates that the data associated with this spec should be joined
196 * to the fractured element. This is a possible value for
197 * {@link #getDirection}.
199 public static final short JoinFractureDirection = 7;
202 * The type of the tag.
207 * The direction of the tag.
212 * The offset of the content.
217 * The length of the content.
222 * The actual content.
227 * The attributes for the tag.
229 AttributeSet attributes;
232 * Creates a new <code>ElementSpec</code> with no content, length or
233 * offset. This is most useful for start and end tags.
236 * the attributes for the element to be created
238 * the type of the tag
240 public ElementSpec(AttributeSet a, short type)
246 * Creates a new <code>ElementSpec</code> that specifies the length but
247 * not the offset of an element. Such <code>ElementSpec</code>s are
248 * processed sequentially from a known starting point.
251 * the attributes for the element to be created
253 * the type of the tag
255 * the length of the element
257 public ElementSpec(AttributeSet a, short type, int len)
259 this(a, type, null, 0, len);
263 * Creates a new <code>ElementSpec</code> with document content.
266 * the attributes for the element to be created
268 * the type of the tag
272 * the offset into the <code>txt</code> array
274 * the length of the element
276 public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)
283 direction = OriginateDirection;
287 * Sets the type of the element.
290 * the type of the element to be set
292 public void setType(short type)
298 * Returns the type of the element.
300 * @return the type of the element
302 public short getType()
308 * Sets the direction of the element.
311 * the direction of the element to be set
313 public void setDirection(short dir)
319 * Returns the direction of the element.
321 * @return the direction of the element
323 public short getDirection()
329 * Returns the attributes of the element.
331 * @return the attributes of the element
333 public AttributeSet getAttributes()
339 * Returns the actual content of the element.
341 * @return the actual content of the element
343 public char[] getArray()
349 * Returns the offset of the content.
351 * @return the offset of the content
353 public int getOffset()
359 * Returns the length of the content.
361 * @return the length of the content
363 public int getLength()
369 * Returns a String representation of this <code>ElementSpec</code>
370 * describing the type, direction and length of this
371 * <code>ElementSpec</code>.
373 * @return a String representation of this <code>ElementSpec</code>
375 public String toString()
377 StringBuilder b = new StringBuilder();
381 b.append("StartTag");
398 case JoinPreviousDirection:
399 b.append("JoinPrevious");
401 case JoinNextDirection:
402 b.append("JoinNext");
404 case OriginateDirection:
405 b.append("Originate");
407 case JoinFractureDirection:
408 b.append("Fracture");
423 * Performs all <em>structural</code> changes to the <code>Element</code>
424 * hierarchy. This class was implemented with much help from the document:
425 * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html.
427 public class ElementBuffer implements Serializable
430 * Instance of all editing information for an object in the Vector. This class
431 * is used to add information to the DocumentEvent associated with an
432 * insertion/removal/change as well as to store the changes that need to be
433 * made so they can be made all at the same (appropriate) time.
437 /** The element to edit . */
440 /** The index of the change. */
443 /** The removed elements. */
444 ArrayList removed = new ArrayList();
446 /** The added elements. */
447 ArrayList added = new ArrayList();
450 * Indicates if this edit contains a fracture.
455 * Creates a new Edit for the specified element at index i.
457 * @param el the element
460 Edit(Element el, int i)
466 * Creates a new Edit for the specified element at index i.
468 * @param el the element
470 * @param frac if this is a fracture edit or not
472 Edit(Element el, int i, boolean frac)
481 /** The serialization UID (compatible with JDK1.5). */
482 private static final long serialVersionUID = 1688745877691146623L;
484 /** The root element of the hierarchy. */
485 private Element root;
487 /** Holds the offset for structural changes. */
490 /** Holds the end offset for structural changes. */
491 private int endOffset;
493 /** Holds the length of structural changes. */
496 /** Holds the position of the change. */
500 * The parent of the fracture.
502 private Element fracturedParent;
505 * The fractured child.
507 private Element fracturedChild;
510 * Indicates if a fracture has been created.
512 private boolean createdFracture;
515 * The current position in the element tree. This is used for bulk inserts
516 * using ElementSpecs.
518 private Stack elementStack;
520 private Edit[] insertPath;
522 private boolean recreateLeafs;
525 * Vector that contains all the edits. Maybe replace by a HashMap.
527 private ArrayList edits;
529 private boolean offsetLastIndex;
530 private boolean offsetLastIndexReplace;
533 * Creates a new <code>ElementBuffer</code> for the specified
534 * <code>root</code> element.
537 * the root element for this <code>ElementBuffer</code>
539 public ElementBuffer(Element root)
545 * Returns the root element of this <code>ElementBuffer</code>.
547 * @return the root element of this <code>ElementBuffer</code>
549 public Element getRootElement()
555 * Removes the content. This method sets some internal parameters and
556 * delegates the work to {@link #removeUpdate}.
559 * the offset from which content is remove
561 * the length of the removed content
563 * the document event that records the changes
565 public void remove(int offs, int len, DefaultDocumentEvent ev)
567 prepareEdit(offs, len);
573 * Updates the element structure of the document in response to removal of
574 * content. It removes the affected {@link Element}s from the document
577 protected void removeUpdate()
579 removeElements(root, offset, endOffset);
582 private boolean removeElements(Element elem, int rmOffs0, int rmOffs1)
587 // Update stack for changes.
588 int index0 = elem.getElementIndex(rmOffs0);
589 int index1 = elem.getElementIndex(rmOffs1);
590 elementStack.push(new Edit(elem, index0));
591 Edit ec = (Edit) elementStack.peek();
593 // If the range is contained by one element,
594 // we just forward the request
595 if (index0 == index1)
597 Element child0 = elem.getElement(index0);
598 if(rmOffs0 <= child0.getStartOffset()
599 && rmOffs1 >= child0.getEndOffset())
601 // Element totally removed.
602 ec.removed.add(child0);
604 else if (removeElements(child0, rmOffs0, rmOffs1))
606 ec.removed.add(child0);
611 // The removal range spans elements. If we can join
612 // the two endpoints, do it. Otherwise we remove the
613 // interior and forward to the endpoints.
614 Element child0 = elem.getElement(index0);
615 Element child1 = elem.getElement(index1);
616 boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
617 if (containsOffs1 && canJoin(child0, child1))
620 for (int i = index0; i <= index1; i++)
622 ec.removed.add(elem.getElement(i));
624 Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
629 // Remove interior and forward.
630 int rmIndex0 = index0 + 1;
631 int rmIndex1 = index1 - 1;
632 if (child0.getStartOffset() == rmOffs0
633 || (index0 == 0 && child0.getStartOffset() > rmOffs0
634 && child0.getEndOffset() <= rmOffs1))
636 // Start element completely consumed.
645 else if (child1.getStartOffset() == rmOffs1)
647 // End element not touched.
650 if (rmIndex0 <= rmIndex1)
654 for (int i = rmIndex0; i <= rmIndex1; i++)
656 ec.removed.add(elem.getElement(i));
660 if(removeElements(child0, rmOffs0, rmOffs1))
662 ec.removed.add(0, child0);
668 if(removeElements(child1, rmOffs0, rmOffs1))
670 ec.removed.add(child1);
679 // Return true if we no longer have any children.
680 if(elem.getElementCount() == (ec.removed.size() - ec.added.size()))
687 * Creates a document in response to a call to
688 * {@link DefaultStyledDocument#create(ElementSpec[])}.
690 * @param len the length of the inserted text
691 * @param data the specs for the elements
692 * @param ev the document event
694 void create(int len, ElementSpec[] data, DefaultDocumentEvent ev)
696 prepareEdit(offset, len);
698 int index = el.getElementIndex(0);
699 while (! el.isLeaf())
701 Element child = el.getElement(index);
702 Edit edit = new Edit(el, index, false);
703 elementStack.push(edit);
705 index = el.getElementIndex(0);
707 Edit ed = (Edit) elementStack.peek();
708 Element child = ed.e.getElement(ed.index);
709 ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(),
710 child.getEndOffset()));
711 ed.removed.add(child);
712 while (elementStack.size() > 1)
716 // Reset root element's attributes.
717 AttributeSet newAtts = null;
718 if (n > 0 && data[0].getType() == ElementSpec.StartTagType)
719 newAtts = data[0].getAttributes();
721 newAtts = SimpleAttributeSet.EMPTY;
722 MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes();
723 ev.addEdit(new AttributeUndoableEdit(root, newAtts, true));
724 mAtts.removeAttributes(mAtts);
725 mAtts.addAttributes(newAtts);
727 // Insert the specified elements.
728 for (int i = 1; i < n; i++)
729 insertElement(data[i]);
731 // Pop remaining stack.
732 while (elementStack.size() > 0)
738 private boolean canJoin(Element e0, Element e1)
741 if ((e0 != null) && (e1 != null))
743 // Don't join a leaf to a branch.
744 boolean isLeaf0 = e0.isLeaf();
745 boolean isLeaf1 = e1.isLeaf();
746 if(isLeaf0 == isLeaf1)
750 // Only join leaves if the attributes match, otherwise
751 // style information will be lost.
752 ret = e0.getAttributes().isEqual(e1.getAttributes());
756 // Only join non-leafs if the names are equal. This may result
757 // in loss of style information, but this is typically
758 // acceptable for non-leafs.
759 String name0 = e0.getName();
760 String name1 = e1.getName();
762 ret = name0.equals(name1);
763 else if (name1 != null)
764 ret = name1.equals(name0);
765 else // Both names null.
773 private Element join(Element p, Element left, Element right, int rmOffs0,
776 Element joined = null;
777 if (left.isLeaf() && right.isLeaf())
779 joined = createLeafElement(p, left.getAttributes(),
780 left.getStartOffset(),
781 right.getEndOffset());
783 else if ((! left.isLeaf()) && (! right.isLeaf()))
785 // Join two branch elements. This copies the children before
786 // the removal range on the left element, and after the removal
787 // range on the right element. The two elements on the edge
788 // are joined if possible and needed.
789 joined = createBranchElement(p, left.getAttributes());
790 int ljIndex = left.getElementIndex(rmOffs0);
791 int rjIndex = right.getElementIndex(rmOffs1);
792 Element lj = left.getElement(ljIndex);
793 if (lj.getStartOffset() >= rmOffs0)
797 Element rj = right.getElement(rjIndex);
798 if (rj.getStartOffset() == rmOffs1)
802 ArrayList children = new ArrayList();
803 // Transfer the left.
804 for (int i = 0; i < ljIndex; i++)
806 children.add(clone(joined, left.getElement(i)));
809 // Transfer the join/middle.
812 Element e = join(joined, lj, rj, rmOffs0, rmOffs1);
819 children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1));
823 children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1));
827 // Transfer the right.
828 int n = right.getElementCount();
829 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++)
831 children.add(clone(joined, right.getElement(i)));
834 // Install the children.
835 Element[] c = new Element[children.size()];
836 c = (Element[]) children.toArray(c);
837 ((BranchElement) joined).replace(0, 0, c);
841 assert false : "Must not happen";
847 * Performs the actual work for {@link #change}. The elements at the
848 * interval boundaries are split up (if necessary) so that the interval
849 * boundaries are located at element boundaries.
851 protected void changeUpdate()
853 boolean didEnd = split(offset, length);
856 // need to do the other end
857 while (elementStack.size() != 0)
861 split(offset + length, 0);
863 while (elementStack.size() != 0)
870 * Modifies the element structure so that the specified interval starts and
871 * ends at an element boundary. Content and paragraph elements are split and
872 * created as necessary. This also updates the
873 * <code>DefaultDocumentEvent</code> to reflect the structural changes.
874 * The bulk work is delegated to {@link #changeUpdate()}.
877 * the start index of the interval to be changed
879 * the length of the interval to be changed
881 * the <code>DefaultDocumentEvent</code> describing the change
883 public void change(int offset, int length, DefaultDocumentEvent ev)
885 prepareEdit(offset, length);
891 * Creates and returns a deep clone of the specified <code>clonee</code>
892 * with the specified parent as new parent.
894 * This method can only clone direct instances of {@link BranchElement}
895 * or {@link LeafElement}.
897 * @param parent the new parent
898 * @param clonee the element to be cloned
900 * @return the cloned element with the new parent
902 public Element clone(Element parent, Element clonee)
904 Element clone = clonee;
905 // We can only handle AbstractElements here.
906 if (clonee instanceof BranchElement)
908 BranchElement branchEl = (BranchElement) clonee;
909 BranchElement branchClone =
910 new BranchElement(parent, branchEl.getAttributes());
911 // Also clone all of the children.
912 int numChildren = branchClone.getElementCount();
913 Element[] cloneChildren = new Element[numChildren];
914 for (int i = 0; i < numChildren; ++i)
916 cloneChildren[i] = clone(branchClone,
917 branchClone.getElement(i));
919 branchClone.replace(0, 0, cloneChildren);
922 else if (clonee instanceof LeafElement)
924 clone = new LeafElement(parent, clonee.getAttributes(),
925 clonee.getStartOffset(),
926 clonee.getEndOffset());
931 private Element cloneAsNecessary(Element parent, Element clonee,
932 int rmOffs0, int rmOffs1)
937 cloned = createLeafElement(parent, clonee.getAttributes(),
938 clonee.getStartOffset(),
939 clonee.getEndOffset());
943 Element e = createBranchElement(parent, clonee.getAttributes());
944 int n = clonee.getElementCount();
945 ArrayList childrenList = new ArrayList(n);
946 for (int i = 0; i < n; i++)
948 Element elem = clonee.getElement(i);
949 if (elem.getStartOffset() < rmOffs0
950 || elem.getEndOffset() > rmOffs1)
952 childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
956 Element[] children = new Element[childrenList.size()];
957 children = (Element[]) childrenList.toArray(children);
958 ((BranchElement) e).replace(0, 0, children);
965 * Inserts new <code>Element</code> in the document at the specified
966 * position. Most of the work is done by {@link #insertUpdate}, after some
967 * fields have been prepared for it.
970 * the location in the document at which the content is inserted
972 * the length of the inserted content
974 * the element specifications for the content to be inserted
976 * the document event that is updated to reflect the structural
979 public void insert(int offset, int length, ElementSpec[] data,
980 DefaultDocumentEvent ev)
984 prepareEdit(offset, length);
991 * Prepares the state of this object for performing an insert.
993 * @param offset the offset at which is inserted
994 * @param length the length of the inserted region
996 private void prepareEdit(int offset, int length)
998 this.offset = offset;
1000 this.endOffset = offset + length;
1001 this.length = length;
1004 edits = new ArrayList();
1008 if (elementStack == null)
1009 elementStack = new Stack();
1011 elementStack.clear();
1013 fracturedParent = null;
1014 fracturedChild = null;
1015 offsetLastIndex = false;
1016 offsetLastIndexReplace = false;
1020 * Finishes an insert. This applies all changes and updates
1021 * the DocumentEvent.
1023 * @param ev the document event
1025 private void finishEdit(DefaultDocumentEvent ev)
1027 // This for loop applies all the changes that were made and updates the
1029 for (Iterator i = edits.iterator(); i.hasNext();)
1031 Edit edits = (Edit) i.next();
1032 Element[] removed = new Element[edits.removed.size()];
1033 removed = (Element[]) edits.removed.toArray(removed);
1034 Element[] added = new Element[edits.added.size()];
1035 added = (Element[]) edits.added.toArray(added);
1036 int index = edits.index;
1037 BranchElement parent = (BranchElement) edits.e;
1038 parent.replace(index, removed.length, added);
1039 ElementEdit ee = new ElementEdit(parent, index, removed, added);
1043 elementStack.clear();
1047 * Inserts new content.
1049 * @param data the element specifications for the elements to be inserted
1051 protected void insertUpdate(ElementSpec[] data)
1053 // Push the current path to the stack.
1054 Element current = root;
1055 int index = current.getElementIndex(offset);
1056 while (! current.isLeaf())
1058 Element child = current.getElement(index);
1059 int editIndex = child.isLeaf() ? index : index + 1;
1060 Edit edit = new Edit(current, editIndex);
1061 elementStack.push(edit);
1063 index = current.getElementIndex(offset);
1066 // Create a copy of the original path.
1067 insertPath = new Edit[elementStack.size()];
1068 insertPath = (Edit[]) elementStack.toArray(insertPath);
1071 createdFracture = false;
1073 // Insert first content tag.
1075 recreateLeafs = false;
1076 int type = data[0].getType();
1077 if (type == ElementSpec.ContentType)
1079 // If the first tag is content we must treat it separately to allow
1080 // for joining properly to previous Elements and to ensure that
1081 // no extra LeafElements are erroneously inserted.
1082 insertFirstContentTag(data);
1083 pos += data[0].length;
1088 createFracture(data);
1092 // Handle each ElementSpec individually.
1093 for (; i < data.length; i++)
1095 insertElement(data[i]);
1098 // Fracture if we haven't done yet.
1099 if (! createdFracture)
1102 // Pop the remaining stack.
1103 while (elementStack.size() != 0)
1106 // Offset last index if necessary.
1107 if (offsetLastIndex && offsetLastIndexReplace)
1108 insertPath[insertPath.length - 1].index++;
1110 // Make sure we havea an Edit for each path item that has a change.
1111 for (int p = insertPath.length - 1; p >= 0; p--)
1113 Edit edit = insertPath[p];
1114 if (edit.e == fracturedParent)
1115 edit.added.add(fracturedChild);
1116 if ((edit.added.size() > 0 || edit.removed.size() > 0)
1117 && ! edits.contains(edit))
1121 // Remove element that would be created by an insert at 0 with
1122 // an initial end tag.
1123 if (offset == 0 && fracturedParent != null
1124 && data[0].getType() == ElementSpec.EndTagType)
1128 p < data.length && data[p].getType() == ElementSpec.EndTagType;
1130 Edit edit = insertPath[insertPath.length - p - 1];
1132 edit.removed.add(0, edit.e.getElement(edit.index));
1138 Edit edit = (Edit) elementStack.peek();
1140 if ((edit.added.size() > 0) || (edit.removed.size() > 0))
1144 else if (! elementStack.isEmpty())
1147 if (e.getElementCount() == 0)
1149 // If we pushed a branch element that didn't get
1150 // used, make sure its not marked as having been added.
1151 edit = (Edit) elementStack.peek();
1152 edit.added.remove(e);
1157 private void insertElement(ElementSpec spec)
1159 Edit edit = (Edit) elementStack.peek();
1160 switch (spec.getType())
1162 case ElementSpec.StartTagType:
1163 switch (spec.getDirection())
1165 case ElementSpec.JoinFractureDirection:
1166 // Fracture the tree and ensure the appropriate element
1167 // is on top of the stack.
1168 if (! createdFracture)
1170 fracture(elementStack.size() - 1);
1172 if (! edit.isFracture)
1174 // If the parent isn't a fracture, then the fracture is
1175 // in fracturedChild.
1176 Edit newEdit = new Edit(fracturedChild, 0, true);
1177 elementStack.push(newEdit);
1181 // Otherwise use the parent's first child.
1182 Element el = edit.e.getElement(0);
1183 Edit newEdit = new Edit(el, 0, true);
1184 elementStack.push(newEdit);
1187 case ElementSpec.JoinNextDirection:
1188 // Push the next paragraph element onto the stack so
1189 // future insertions are added to it.
1190 Element parent = edit.e.getElement(edit.index);
1191 if (parent.isLeaf())
1193 if (edit.index + 1 < edit.e.getElementCount())
1194 parent = edit.e.getElement(edit.index + 1);
1196 assert false; // Must not happen.
1198 elementStack.push(new Edit(parent, 0, true));
1201 Element branch = createBranchElement(edit.e,
1202 spec.getAttributes());
1203 edit.added.add(branch);
1204 elementStack.push(new Edit(branch, 0));
1208 case ElementSpec.EndTagType:
1211 case ElementSpec.ContentType:
1212 insertContentTag(spec, edit);
1218 * Inserts the first tag into the document.
1221 * the data to be inserted.
1223 private void insertFirstContentTag(ElementSpec[] data)
1225 ElementSpec first = data[0];
1226 Edit edit = (Edit) elementStack.peek();
1227 Element current = edit.e.getElement(edit.index);
1228 int firstEndOffset = offset + first.length;
1229 boolean onlyContent = data.length == 1;
1230 switch (first.getDirection())
1232 case ElementSpec.JoinPreviousDirection:
1233 if (current.getEndOffset() != firstEndOffset && ! onlyContent)
1235 Element newEl1 = createLeafElement(edit.e,
1236 current.getAttributes(),
1237 current.getStartOffset(),
1239 edit.added.add(newEl1);
1240 edit.removed.add(current);
1241 if (current.getEndOffset() != endOffset)
1242 recreateLeafs = true;
1244 offsetLastIndex = true;
1248 offsetLastIndex = true;
1249 offsetLastIndexReplace = true;
1252 case ElementSpec.JoinNextDirection:
1255 Element newEl1 = createLeafElement(edit.e,
1256 current.getAttributes(),
1257 current.getStartOffset(),
1259 edit.added.add(newEl1);
1260 Element next = edit.e.getElement(edit.index + 1);
1262 newEl1 = createLeafElement(edit.e, next.getAttributes(),
1263 offset, next.getEndOffset());
1266 newEl1 = createLeafElement(edit.e, next.getAttributes(),
1267 offset, firstEndOffset);
1269 edit.added.add(newEl1);
1270 edit.removed.add(current);
1271 edit.removed.add(next);
1274 default: // OriginateDirection.
1275 if (current.getStartOffset() != offset)
1277 Element newEl = createLeafElement(edit.e,
1278 current.getAttributes(),
1279 current.getStartOffset(),
1281 edit.added.add(newEl);
1283 edit.removed.add(current);
1284 Element newEl1 = createLeafElement(edit.e, first.getAttributes(),
1285 offset, firstEndOffset);
1286 edit.added.add(newEl1);
1287 if (current.getEndOffset() != endOffset)
1288 recreateLeafs = true;
1290 offsetLastIndex = true;
1296 * Inserts a content element into the document structure.
1301 private void insertContentTag(ElementSpec tag, Edit edit)
1303 int len = tag.getLength();
1304 int dir = tag.getDirection();
1305 if (dir == ElementSpec.JoinNextDirection)
1307 if (! edit.isFracture)
1309 Element first = null;
1310 if (insertPath != null)
1312 for (int p = insertPath.length - 1; p >= 0; p--)
1314 if (insertPath[p] == edit)
1316 if (p != insertPath.length - 1)
1317 first = edit.e.getElement(edit.index);
1323 first = edit.e.getElement(edit.index + 1);
1324 Element leaf = createLeafElement(edit.e, first.getAttributes(),
1325 pos, first.getEndOffset());
1326 edit.added.add(leaf);
1327 edit.removed.add(first);
1331 Element first = edit.e.getElement(0);
1332 Element leaf = createLeafElement(edit.e, first.getAttributes(),
1333 pos, first.getEndOffset());
1334 edit.added.add(leaf);
1335 edit.removed.add(first);
1340 Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos,
1342 edit.added.add(leaf);
1350 * This method fractures bottomost leaf in the elementStack. This
1351 * happens when the first inserted tag is not content.
1354 * the ElementSpecs used for the entire insertion
1356 private void createFracture(ElementSpec[] data)
1358 Edit edit = (Edit) elementStack.peek();
1359 Element child = edit.e.getElement(edit.index);
1362 Element newChild = createLeafElement(edit.e, child.getAttributes(),
1363 child.getStartOffset(), offset);
1364 edit.added.add(newChild);
1366 edit.removed.add(child);
1367 if (child.getEndOffset() != endOffset)
1368 recreateLeafs = true;
1370 offsetLastIndex = true;
1373 private void fracture(int depth)
1375 int len = insertPath.length;
1377 boolean recreate = recreateLeafs;
1378 Edit lastEdit = insertPath[len - 1];
1379 boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount();
1380 int deepestChangedIndex = recreate ? len : - 1;
1381 int lastChangedIndex = len - 1;
1382 createdFracture = true;
1383 for (int i = len - 2; i >= 0; i--)
1385 Edit edit = insertPath[i];
1386 if (edit.added.size() > 0 || i == depth)
1389 if (! recreate && childChanged)
1392 if (deepestChangedIndex == -1)
1393 deepestChangedIndex = lastChangedIndex + 1;
1396 if (! childChanged && edit.index < edit.e.getElementCount())
1398 childChanged = true;
1399 lastChangedIndex = i;
1404 if (lastIndex == -1)
1405 lastIndex = len - 1;
1406 recreate(lastIndex, deepestChangedIndex);
1410 private void recreate(int startIndex, int endIndex)
1412 // Recreate the element representing the inserted index.
1413 Edit edit = insertPath[startIndex];
1416 int changeLength = insertPath.length;
1418 if (startIndex + 1 == changeLength)
1419 child = edit.e.getElement(edit.index);
1421 child = edit.e.getElement(edit.index - 1);
1425 newChild = createLeafElement(edit.e, child.getAttributes(),
1426 Math.max(endOffset, child.getStartOffset()),
1427 child.getEndOffset());
1431 newChild = createBranchElement(edit.e, child.getAttributes());
1433 fracturedParent = edit.e;
1434 fracturedChild = newChild;
1436 // Recreate all the elements to the right of the insertion point.
1437 Element parent = newChild;
1438 while (++startIndex < endIndex)
1440 boolean isEnd = (startIndex + 1) == endIndex;
1441 boolean isEndLeaf = (startIndex + 1) == changeLength;
1443 // Create the newChild, a duplicate of the elment at
1444 // index. This isn't done if isEnd and offsetLastIndex are true
1445 // indicating a join previous was done.
1446 edit = insertPath[startIndex];
1448 // Determine the child to duplicate, won't have to duplicate
1449 // if at end of fracture, or offseting index.
1452 if(offsetLastIndex || ! isEndLeaf)
1455 child = edit.e.getElement(edit.index);
1459 child = edit.e.getElement(edit.index - 1);
1467 newChild = createLeafElement(parent, child.getAttributes(),
1468 Math.max(endOffset, child.getStartOffset()),
1469 child.getEndOffset());
1473 newChild = createBranchElement(parent,
1474 child.getAttributes());
1480 // Recreate the remaining children (there may be none).
1481 int childrenToMove = edit.e.getElementCount() - edit.index;
1484 int childStartIndex = 1;
1486 if (newChild == null)
1488 // Last part of fracture.
1492 moveStartIndex = edit.index + 1;
1496 moveStartIndex = edit.index;
1498 childStartIndex = 0;
1499 children = new Element[childrenToMove];
1507 moveStartIndex = edit.index;
1511 // Last leaf, need to recreate part of it.
1512 moveStartIndex = edit.index + 1;
1514 children = new Element[childrenToMove];
1515 children[0] = newChild;
1518 for (int c = childStartIndex; c < childrenToMove; c++)
1520 Element toMove = edit.e.getElement(moveStartIndex++);
1521 children[c] = recreateFracturedElement(parent, toMove);
1522 edit.removed.add(toMove);
1524 ((BranchElement) parent).replace(0, 0, children);
1530 private Element recreateFracturedElement(Element parent, Element toCopy)
1535 recreated = createLeafElement(parent, toCopy.getAttributes(),
1536 Math.max(toCopy.getStartOffset(), endOffset),
1537 toCopy.getEndOffset());
1541 Element newParent = createBranchElement(parent,
1542 toCopy.getAttributes());
1543 int childCount = toCopy.getElementCount();
1544 Element[] newChildren = new Element[childCount];
1545 for (int i = 0; i < childCount; i++)
1547 newChildren[i] = recreateFracturedElement(newParent,
1548 toCopy.getElement(i));
1550 ((BranchElement) newParent).replace(0, 0, newChildren);
1551 recreated = newParent;
1556 private boolean split(int offs, int len)
1558 boolean splitEnd = false;
1559 // Push the path to the stack.
1561 int index = e.getElementIndex(offs);
1562 while (! e.isLeaf())
1564 elementStack.push(new Edit(e, index));
1565 e = e.getElement(index);
1566 index = e.getElementIndex(offs);
1569 Edit ec = (Edit) elementStack.peek();
1570 Element child = ec.e.getElement(ec.index);
1571 // Make sure there is something to do. If the
1572 // offset is already at a boundary then there is
1574 if (child.getStartOffset() < offs && offs < child.getEndOffset())
1576 // We need to split, now see if the other end is within
1578 int index0 = ec.index;
1579 int index1 = index0;
1580 if (((offs + len) < ec.e.getEndOffset()) && (len != 0))
1582 // It's a range split in the same parent.
1583 index1 = ec.e.getElementIndex(offs+len);
1584 if (index1 == index0)
1586 // It's a three-way split.
1587 ec.removed.add(child);
1588 e = createLeafElement(ec.e, child.getAttributes(),
1589 child.getStartOffset(), offs);
1591 e = createLeafElement(ec.e, child.getAttributes(),
1594 e = createLeafElement(ec.e, child.getAttributes(),
1595 offs + len, child.getEndOffset());
1601 child = ec.e.getElement(index1);
1602 if ((offs + len) == child.getStartOffset())
1604 // End is already on a boundary.
1611 // Split the first location.
1613 child = ec.e.getElement(index0);
1614 ec.removed.add(child);
1615 e = createLeafElement(ec.e, child.getAttributes(),
1616 child.getStartOffset(), pos);
1618 e = createLeafElement(ec.e, child.getAttributes(),
1619 pos, child.getEndOffset());
1622 // Pick up things in the middle.
1623 for (int i = index0 + 1; i < index1; i++)
1625 child = ec.e.getElement(i);
1626 ec.removed.add(child);
1627 ec.added.add(child);
1630 if (index1 != index0)
1632 child = ec.e.getElement(index1);
1634 ec.removed.add(child);
1635 e = createLeafElement(ec.e, child.getAttributes(),
1636 child.getStartOffset(), pos);
1638 e = createLeafElement(ec.e, child.getAttributes(),
1639 pos, child.getEndOffset());
1652 * An element type for sections. This is a simple BranchElement with a unique
1655 protected class SectionElement extends BranchElement
1658 * Creates a new SectionElement.
1660 public SectionElement()
1666 * Returns the name of the element. This method always returns
1667 * "section".
1669 * @return the name of the element
1671 public String getName()
1673 return SectionElementName;
1678 * Receives notification when any of the document's style changes and calls
1679 * {@link DefaultStyledDocument#styleChanged(Style)}.
1681 * @author Roman Kennke (kennke@aicas.com)
1683 private class StyleChangeListener implements ChangeListener
1687 * Receives notification when any of the document's style changes and calls
1688 * {@link DefaultStyledDocument#styleChanged(Style)}.
1693 public void stateChanged(ChangeEvent event)
1695 Style style = (Style) event.getSource();
1696 styleChanged(style);
1700 /** The serialization UID (compatible with JDK1.5). */
1701 private static final long serialVersionUID = 940485415728614849L;
1704 * The default size to use for new content buffers.
1706 public static final int BUFFER_SIZE_DEFAULT = 4096;
1709 * The <code>EditorBuffer</code> that is used to manage to
1710 * <code>Element</code> hierarchy.
1712 protected DefaultStyledDocument.ElementBuffer buffer;
1715 * Listens for changes on this document's styles and notifies styleChanged().
1717 private StyleChangeListener styleChangeListener;
1720 * Creates a new <code>DefaultStyledDocument</code>.
1722 public DefaultStyledDocument()
1724 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
1728 * Creates a new <code>DefaultStyledDocument</code> that uses the specified
1729 * {@link StyleContext}.
1732 * the <code>StyleContext</code> to use
1734 public DefaultStyledDocument(StyleContext context)
1736 this(new GapContent(BUFFER_SIZE_DEFAULT), context);
1740 * Creates a new <code>DefaultStyledDocument</code> that uses the specified
1741 * {@link StyleContext} and {@link Content} buffer.
1744 * the <code>Content</code> buffer to use
1746 * the <code>StyleContext</code> to use
1748 public DefaultStyledDocument(AbstractDocument.Content content,
1749 StyleContext context)
1751 super(content, context);
1752 buffer = new ElementBuffer(createDefaultRoot());
1753 setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
1757 * Adds a style into the style hierarchy. Unspecified style attributes can be
1758 * resolved in the <code>parent</code> style, if one is specified. While it
1759 * is legal to add nameless styles (<code>nm == null</code),
1760 * you must be aware that the client application is then responsible
1761 * for managing the style hierarchy, since unnamed styles cannot be
1762 * looked up by their name.
1764 * @param nm the name of the style or <code>null</code> if the style should
1766 * @param parent the parent in which unspecified style attributes are
1767 * resolved, or <code>null</code> if that is not necessary
1769 * @return the newly created <code>Style</code>
1771 public Style addStyle(String nm, Style parent)
1773 StyleContext context = (StyleContext) getAttributeContext();
1774 Style newStyle = context.addStyle(nm, parent);
1776 // Register change listener.
1777 if (styleChangeListener == null)
1778 styleChangeListener = new StyleChangeListener();
1779 newStyle.addChangeListener(styleChangeListener);
1785 * Create the default root element for this kind of <code>Document</code>.
1787 * @return the default root element for this kind of <code>Document</code>
1789 protected AbstractDocument.AbstractElement createDefaultRoot()
1792 SectionElement section = new SectionElement();
1794 BranchElement paragraph = new BranchElement(section, null);
1795 tmp = new Element[1];
1797 section.replace(0, 0, tmp);
1799 Element leaf = new LeafElement(paragraph, null, 0, 1);
1800 tmp = new Element[1];
1802 paragraph.replace(0, 0, tmp);
1808 * Returns the <code>Element</code> that corresponds to the character at the
1809 * specified position.
1812 * the position of which we query the corresponding
1813 * <code>Element</code>
1814 * @return the <code>Element</code> that corresponds to the character at the
1815 * specified position
1817 public Element getCharacterElement(int position)
1819 Element element = getDefaultRootElement();
1821 while (!element.isLeaf())
1823 int index = element.getElementIndex(position);
1824 element = element.getElement(index);
1831 * Extracts a background color from a set of attributes.
1834 * the attributes from which to get a background color
1835 * @return the background color that correspond to the attributes
1837 public Color getBackground(AttributeSet attributes)
1839 StyleContext context = (StyleContext) getAttributeContext();
1840 return context.getBackground(attributes);
1844 * Returns the default root element.
1846 * @return the default root element
1848 public Element getDefaultRootElement()
1850 return buffer.getRootElement();
1854 * Extracts a font from a set of attributes.
1857 * the attributes from which to get a font
1858 * @return the font that correspond to the attributes
1860 public Font getFont(AttributeSet attributes)
1862 StyleContext context = (StyleContext) getAttributeContext();
1863 return context.getFont(attributes);
1867 * Extracts a foreground color from a set of attributes.
1870 * the attributes from which to get a foreground color
1871 * @return the foreground color that correspond to the attributes
1873 public Color getForeground(AttributeSet attributes)
1875 StyleContext context = (StyleContext) getAttributeContext();
1876 return context.getForeground(attributes);
1880 * Returns the logical <code>Style</code> for the specified position.
1883 * the position from which to query to logical style
1884 * @return the logical <code>Style</code> for the specified position
1886 public Style getLogicalStyle(int position)
1888 Element paragraph = getParagraphElement(position);
1889 AttributeSet attributes = paragraph.getAttributes();
1890 AttributeSet a = attributes.getResolveParent();
1891 // If the resolve parent is not of type Style, we return null.
1892 if (a instanceof Style)
1898 * Returns the paragraph element for the specified position. If the position
1899 * is outside the bounds of the document's root element, then the closest
1900 * element is returned. That is the last paragraph if
1901 * <code>position >= endIndex</code> or the first paragraph if
1902 * <code>position < startIndex</code>.
1905 * the position for which to query the paragraph element
1906 * @return the paragraph element for the specified position
1908 public Element getParagraphElement(int position)
1910 Element e = getDefaultRootElement();
1912 e = e.getElement(e.getElementIndex(position));
1915 return e.getParentElement();
1920 * Looks up and returns a named <code>Style</code>.
1923 * the name of the <code>Style</code>
1924 * @return the found <code>Style</code> of <code>null</code> if no such
1925 * <code>Style</code> exists
1927 public Style getStyle(String nm)
1929 StyleContext context = (StyleContext) getAttributeContext();
1930 return context.getStyle(nm);
1934 * Removes a named <code>Style</code> from the style hierarchy.
1937 * the name of the <code>Style</code> to be removed
1939 public void removeStyle(String nm)
1941 StyleContext context = (StyleContext) getAttributeContext();
1942 context.removeStyle(nm);
1946 * Sets text attributes for the fragment specified by <code>offset</code>
1947 * and <code>length</code>.
1950 * the start offset of the fragment
1952 * the length of the fragment
1954 * the text attributes to set
1956 * if <code>true</code>, the attributes of the current selection
1957 * are overridden, otherwise they are merged
1959 public void setCharacterAttributes(int offset, int length,
1960 AttributeSet attributes, boolean replace)
1962 // Exit early if length is 0, so no DocumentEvent is created or fired.
1967 // Must obtain a write lock for this method. writeLock() and
1968 // writeUnlock() should always be in try/finally block to make
1969 // sure that locking happens in a balanced manner.
1971 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
1973 DocumentEvent.EventType.CHANGE);
1975 // Modify the element structure so that the interval begins at an
1977 // start and ends at an element end.
1978 buffer.change(offset, length, ev);
1980 // Visit all paragraph elements within the specified interval
1981 int end = offset + length;
1983 for (int pos = offset; pos < end;)
1985 // Get the CharacterElement at offset pos.
1986 curr = getCharacterElement(pos);
1987 if (pos == curr.getEndOffset())
1990 MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes();
1991 ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace));
1992 // If replace is true, remove all the old attributes.
1994 a.removeAttributes(a);
1995 // Add all the new attributes.
1996 a.addAttributes(attributes);
1997 // Increment pos so we can check the next CharacterElement.
1998 pos = curr.getEndOffset();
2000 fireChangedUpdate(ev);
2001 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2010 * Sets the logical style for the paragraph at the specified position.
2013 * the position at which the logical style is added
2015 * the style to set for the current paragraph
2017 public void setLogicalStyle(int position, Style style)
2019 Element el = getParagraphElement(position);
2020 // getParagraphElement doesn't return null but subclasses might so
2021 // we check for null here.
2027 if (el instanceof AbstractElement)
2029 AbstractElement ael = (AbstractElement) el;
2030 ael.setResolveParent(style);
2031 int start = el.getStartOffset();
2032 int end = el.getEndOffset();
2033 DefaultDocumentEvent ev = new DefaultDocumentEvent(start,
2035 DocumentEvent.EventType.CHANGE);
2036 fireChangedUpdate(ev);
2037 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2040 throw new AssertionError(
2041 "paragraph elements are expected to be"
2042 + "instances of AbstractDocument.AbstractElement");
2051 * Sets text attributes for the paragraph at the specified fragment.
2054 * the beginning of the fragment
2056 * the length of the fragment
2058 * the text attributes to set
2060 * if <code>true</code>, the attributes of the current selection
2061 * are overridden, otherwise they are merged
2063 public void setParagraphAttributes(int offset, int length,
2064 AttributeSet attributes, boolean replace)
2068 // Must obtain a write lock for this method. writeLock() and
2069 // writeUnlock() should always be in try/finally blocks to make
2070 // sure that locking occurs in a balanced manner.
2073 // Create a DocumentEvent to use for changedUpdate().
2074 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
2076 DocumentEvent.EventType.CHANGE);
2078 // Have to iterate through all the _paragraph_ elements that are
2079 // contained or partially contained in the interval
2080 // (offset, offset + length).
2081 Element rootElement = getDefaultRootElement();
2082 int startElement = rootElement.getElementIndex(offset);
2083 int endElement = rootElement.getElementIndex(offset + length - 1);
2084 if (endElement < startElement)
2085 endElement = startElement;
2087 for (int i = startElement; i <= endElement; i++)
2089 Element par = rootElement.getElement(i);
2090 MutableAttributeSet a = (MutableAttributeSet) par.getAttributes();
2091 // Add the change to the DocumentEvent.
2092 ev.addEdit(new AttributeUndoableEdit(par, attributes, replace));
2093 // If replace is true remove the old attributes.
2095 a.removeAttributes(a);
2096 // Add the new attributes.
2097 a.addAttributes(attributes);
2099 fireChangedUpdate(ev);
2100 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2109 * Called in response to content insert actions. This is used to update the
2110 * element structure.
2113 * the <code>DocumentEvent</code> describing the change
2115 * the attributes for the change
2117 protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
2119 int offs = ev.getOffset();
2120 int len = ev.getLength();
2121 int endOffs = offs + len;
2123 attr = SimpleAttributeSet.EMPTY;
2125 // Paragraph attributes are fetched from the point _after_ the insertion.
2126 Element paragraph = getParagraphElement(endOffs);
2127 AttributeSet pAttr = paragraph.getAttributes();
2128 // Character attributes are fetched from the actual insertion point.
2129 Element paragraph2 = getParagraphElement(offs);
2130 int contIndex = paragraph2.getElementIndex(offs);
2131 Element content = paragraph2.getElement(contIndex);
2132 AttributeSet cAttr = content.getAttributes();
2134 boolean insertAtBoundary = content.getEndOffset() == endOffs;
2137 Segment s = new Segment();
2138 ArrayList buf = new ArrayList();
2139 ElementSpec lastStartTag = null;
2140 boolean insertAfterNewline = false;
2141 short lastStartDir = ElementSpec.OriginateDirection;
2143 // Special handle if we are inserting after a newline.
2146 getText(offs - 1, 1, s);
2147 if (s.array[s.offset] == '\n')
2149 insertAfterNewline = true;
2150 lastStartDir = insertAfterNewline(paragraph, paragraph2,
2153 // Search last start tag.
2154 for (int i = buf.size() - 1; i >= 0 && lastStartTag == null;
2157 ElementSpec tag = (ElementSpec) buf.get(i);
2158 if (tag.getType() == ElementSpec.StartTagType)
2167 // If we are not inserting after a newline, the paragraph attributes
2168 // come from the paragraph under the insertion point.
2169 if (! insertAfterNewline)
2170 pAttr = paragraph2.getAttributes();
2172 // Scan text and build up the specs.
2173 getText(offs, len, s);
2174 int end = s.offset + s.count;
2175 int last = s.offset;
2176 for (int i = s.offset; i < end; i++)
2178 if (s.array[i] == '\n')
2180 int breakOffs = i + 1;
2181 buf.add(new ElementSpec(attr, ElementSpec.ContentType,
2183 buf.add(new ElementSpec(null, ElementSpec.EndTagType));
2184 lastStartTag = new ElementSpec(pAttr,
2185 ElementSpec.StartTagType);
2186 buf.add(lastStartTag);
2191 // Need to add a tailing content tag if we didn't finish at a boundary.
2194 buf.add(new ElementSpec(attr, ElementSpec.ContentType,
2198 // Now we need to fix up the directions of the specs.
2199 ElementSpec first = (ElementSpec) buf.get(0);
2200 int doclen = getLength();
2202 // Maybe join-previous the first tag if it is content and has
2203 // the same attributes as the previous character run.
2204 if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr))
2205 first.setDirection(ElementSpec.JoinPreviousDirection);
2207 // Join-fracture or join-next the last start tag if necessary.
2208 if (lastStartTag != null)
2210 if (insertAfterNewline)
2211 lastStartTag.setDirection(lastStartDir);
2212 else if (paragraph2.getEndOffset() != endOffs)
2213 lastStartTag.setDirection(ElementSpec.JoinFractureDirection);
2216 Element par = paragraph2.getParentElement();
2217 int par2Index = par.getElementIndex(offs);
2218 if (par2Index + 1 < par.getElementCount()
2219 && ! par.getElement(par2Index + 1).isLeaf())
2220 lastStartTag.setDirection(ElementSpec.JoinNextDirection);
2224 // Join-next last tag if possible.
2225 if (insertAtBoundary && endOffs < doclen)
2227 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
2228 if (lastTag.getType() == ElementSpec.ContentType
2229 && ((lastStartTag == null
2230 && (paragraph == paragraph2 || insertAfterNewline))
2231 || (lastStartTag != null
2232 && lastStartTag.getDirection() != ElementSpec.OriginateDirection)))
2234 int nextIndex = paragraph.getElementIndex(endOffs);
2235 Element nextRun = paragraph.getElement(nextIndex);
2236 if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes()))
2237 lastTag.setDirection(ElementSpec.JoinNextDirection);
2241 else if (! insertAtBoundary && lastStartTag != null
2242 && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection)
2244 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
2245 if (lastTag.getType() == ElementSpec.ContentType
2246 && lastTag.getDirection() != ElementSpec.JoinPreviousDirection
2247 && attr.isEqual(cAttr))
2249 lastTag.setDirection(ElementSpec.JoinNextDirection);
2253 ElementSpec[] specs = new ElementSpec[buf.size()];
2254 specs = (ElementSpec[]) buf.toArray(specs);
2255 buffer.insert(offs, len, specs, ev);
2257 catch (BadLocationException ex)
2259 // Ignore this. Comment out for debugging.
2260 ex.printStackTrace();
2262 super.insertUpdate(ev, attr);
2265 private short insertAfterNewline(Element par1, Element par2,
2266 AttributeSet attr, ArrayList buf,
2267 int offs, int endOffs)
2270 if (par1.getParentElement() == par2.getParentElement())
2272 ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType);
2274 tag = new ElementSpec(attr, ElementSpec.StartTagType);
2276 if (par2.getEndOffset() != endOffs)
2277 dir = ElementSpec.JoinFractureDirection;
2280 Element par = par2.getParentElement();
2281 if (par.getElementIndex(offs) + 1 < par.getElementCount())
2282 dir = ElementSpec.JoinNextDirection;
2287 // For text with more than 2 levels, find the common parent of
2289 ArrayList parentsLeft = new ArrayList();
2290 ArrayList parentsRight = new ArrayList();
2295 e = e.getParentElement();
2299 while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1)
2301 parentsRight.add(e);
2302 e = e.getParentElement();
2308 // e is now the common parent.
2309 // Insert the end tags.
2310 for (int c = 0; c < leftIndex; c++)
2312 buf.add(new ElementSpec(null, ElementSpec.EndTagType));
2314 // Insert the start tags.
2315 for (int c = parentsRight.size() - 1; c >= 0; c--)
2317 Element el = (Element) parentsRight.get(c);
2318 ElementSpec tag = new ElementSpec(el.getAttributes(),
2319 ElementSpec.StartTagType);
2321 tag.setDirection(ElementSpec.JoinNextDirection);
2324 if (parentsRight.size() > 0)
2325 dir = ElementSpec.JoinNextDirection;
2327 dir = ElementSpec.JoinFractureDirection;
2336 * A helper method to set up the ElementSpec buffer for the special case of an
2337 * insertion occurring immediately after a newline.
2340 * the ElementSpec buffer to initialize.
2342 short handleInsertAfterNewline(Vector specs, int offset, int endOffset,
2343 Element prevParagraph, Element paragraph,
2346 if (prevParagraph.getParentElement() == paragraph.getParentElement())
2348 specs.add(new ElementSpec(a, ElementSpec.EndTagType));
2349 specs.add(new ElementSpec(a, ElementSpec.StartTagType));
2350 if (paragraph.getStartOffset() != endOffset)
2351 return ElementSpec.JoinFractureDirection;
2352 // If there is an Element after this one, use JoinNextDirection.
2353 Element parent = paragraph.getParentElement();
2354 if (parent.getElementCount() > (parent.getElementIndex(offset) + 1))
2355 return ElementSpec.JoinNextDirection;
2357 return ElementSpec.OriginateDirection;
2361 * Updates the document structure in response to text removal. This is
2362 * forwarded to the {@link ElementBuffer} of this document. Any changes to the
2363 * document structure are added to the specified document event and sent to
2364 * registered listeners.
2367 * the document event that records the changes to the document
2369 protected void removeUpdate(DefaultDocumentEvent ev)
2371 super.removeUpdate(ev);
2372 buffer.remove(ev.getOffset(), ev.getLength(), ev);
2376 * Returns an enumeration of all style names.
2378 * @return an enumeration of all style names
2380 public Enumeration<?> getStyleNames()
2382 StyleContext context = (StyleContext) getAttributeContext();
2383 return context.getStyleNames();
2387 * Called when any of this document's styles changes.
2390 * the style that changed
2392 protected void styleChanged(Style style)
2394 // Nothing to do here. This is intended to be overridden by subclasses.
2398 * Inserts a bulk of structured content at once.
2401 * the offset at which the content should be inserted
2403 * the actual content spec to be inserted
2405 protected void insert(int offset, ElementSpec[] data)
2406 throws BadLocationException
2408 if (data == null || data.length == 0)
2412 // writeLock() and writeUnlock() should always be in a try/finally
2413 // block so that locking balance is guaranteed even if some
2414 // exception is thrown.
2417 // First we collect the content to be inserted.
2418 StringBuffer contentBuffer = new StringBuffer();
2419 for (int i = 0; i < data.length; i++)
2421 // Collect all inserts into one so we can get the correct
2423 ElementSpec spec = data[i];
2424 if (spec.getArray() != null && spec.getLength() > 0)
2425 contentBuffer.append(spec.getArray(), spec.getOffset(),
2429 int length = contentBuffer.length();
2431 // If there was no content inserted then exit early.
2435 Content c = getContent();
2436 UndoableEdit edit = c.insertString(offset,
2437 contentBuffer.toString());
2439 // Create the DocumentEvent with the ElementEdit added
2440 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
2442 DocumentEvent.EventType.INSERT);
2446 // Finally we must update the document structure and fire the insert
2448 buffer.insert(offset, length, data, ev);
2450 super.insertUpdate(ev, null);
2453 fireInsertUpdate(ev);
2454 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2463 * Initializes the <code>DefaultStyledDocument</code> with the specified
2467 * the specification of the content with which the document is
2470 protected void create(ElementSpec[] data)
2475 // Clear content if there is some.
2476 int len = getLength();
2482 // Now we insert the content.
2483 StringBuilder b = new StringBuilder();
2484 for (int i = 0; i < data.length; ++i)
2486 ElementSpec el = data[i];
2487 if (el.getArray() != null && el.getLength() > 0)
2488 b.append(el.getArray(), el.getOffset(), el.getLength());
2490 Content content = getContent();
2491 UndoableEdit cEdit = content.insertString(0, b.toString());
2494 DefaultDocumentEvent ev =
2495 new DefaultDocumentEvent(0, b.length(),
2496 DocumentEvent.EventType.INSERT);
2499 buffer.create(len, data, ev);
2501 // For the bidi update.
2502 super.insertUpdate(ev, null);
2505 fireInsertUpdate(ev);
2506 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2508 catch (BadLocationException ex)
2510 AssertionError err = new AssertionError("Unexpected bad location");