libjava/ChangeLog:
[official-gcc.git] / libjava / classpath / javax / swing / text / DefaultStyledDocument.java
blob8c70a8a3bfd75d5aec4a2c31c5d20f93ee0a2ae3
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)
9 any later version.
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
19 02110-1301 USA.
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
24 combination.
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 gnu.java.lang.CPStringBuilder;
43 import java.awt.Color;
44 import java.awt.Font;
45 import java.io.Serializable;
46 import java.util.ArrayList;
47 import java.util.Enumeration;
48 import java.util.Iterator;
49 import java.util.Stack;
50 import java.util.Vector;
52 import javax.swing.event.ChangeEvent;
53 import javax.swing.event.ChangeListener;
54 import javax.swing.event.DocumentEvent;
55 import javax.swing.event.UndoableEditEvent;
56 import javax.swing.undo.AbstractUndoableEdit;
57 import javax.swing.undo.UndoableEdit;
59 /**
60 * The default implementation of {@link StyledDocument}. The document is
61 * modeled as an {@link Element} tree, which has a {@link SectionElement} as
62 * single root, which has one or more {@link AbstractDocument.BranchElement}s
63 * as paragraph nodes and each paragraph node having one or more
64 * {@link AbstractDocument.LeafElement}s as content nodes.
66 * @author Michael Koch (konqueror@gmx.de)
67 * @author Roman Kennke (roman@kennke.org)
69 public class DefaultStyledDocument extends AbstractDocument implements
70 StyledDocument
73 /**
74 * An {@link UndoableEdit} that can undo attribute changes to an element.
76 * @author Roman Kennke (kennke@aicas.com)
78 public static class AttributeUndoableEdit extends AbstractUndoableEdit
80 /**
81 * A copy of the old attributes.
83 protected AttributeSet copy;
85 /**
86 * The new attributes.
88 protected AttributeSet newAttributes;
90 /**
91 * If the new attributes replaced the old attributes or if they only were
92 * added to them.
94 protected boolean isReplacing;
96 /**
97 * The element that has changed.
99 protected Element element;
102 * Creates a new <code>AttributeUndoableEdit</code>.
104 * @param el
105 * the element that changes attributes
106 * @param newAtts
107 * the new attributes
108 * @param replacing
109 * if the new attributes replace the old or only append to them
111 public AttributeUndoableEdit(Element el, AttributeSet newAtts,
112 boolean replacing)
114 element = el;
115 newAttributes = newAtts;
116 isReplacing = replacing;
117 copy = el.getAttributes().copyAttributes();
121 * Undos the attribute change. The <code>copy</code> field is set as
122 * attributes on <code>element</code>.
124 public void undo()
126 super.undo();
127 AttributeSet atts = element.getAttributes();
128 if (atts instanceof MutableAttributeSet)
130 MutableAttributeSet mutable = (MutableAttributeSet) atts;
131 mutable.removeAttributes(atts);
132 mutable.addAttributes(copy);
137 * Redos an attribute change. This adds <code>newAttributes</code> to the
138 * <code>element</code>'s attribute set, possibly clearing all attributes
139 * if <code>isReplacing</code> is true.
141 public void redo()
143 super.undo();
144 AttributeSet atts = element.getAttributes();
145 if (atts instanceof MutableAttributeSet)
147 MutableAttributeSet mutable = (MutableAttributeSet) atts;
148 if (isReplacing)
149 mutable.removeAttributes(atts);
150 mutable.addAttributes(newAttributes);
156 * Carries specification information for new {@link Element}s that should be
157 * created in {@link ElementBuffer}. This allows the parsing process to be
158 * decoupled from the <code>Element</code> creation process.
160 public static class ElementSpec
163 * This indicates a start tag. This is a possible value for {@link #getType}.
165 public static final short StartTagType = 1;
168 * This indicates an end tag. This is a possible value for {@link #getType}.
170 public static final short EndTagType = 2;
173 * This indicates a content element. This is a possible value for
174 * {@link #getType}.
176 public static final short ContentType = 3;
179 * This indicates that the data associated with this spec should be joined
180 * with what precedes it. This is a possible value for {@link #getDirection}.
182 public static final short JoinPreviousDirection = 4;
185 * This indicates that the data associated with this spec should be joined
186 * with what follows it. This is a possible value for {@link #getDirection}.
188 public static final short JoinNextDirection = 5;
191 * This indicates that the data associated with this spec should be used to
192 * create a new element. This is a possible value for {@link #getDirection}.
194 public static final short OriginateDirection = 6;
197 * This indicates that the data associated with this spec should be joined
198 * to the fractured element. This is a possible value for
199 * {@link #getDirection}.
201 public static final short JoinFractureDirection = 7;
204 * The type of the tag.
206 short type;
209 * The direction of the tag.
211 short direction;
214 * The offset of the content.
216 int offset;
219 * The length of the content.
221 int length;
224 * The actual content.
226 char[] content;
229 * The attributes for the tag.
231 AttributeSet attributes;
234 * Creates a new <code>ElementSpec</code> with no content, length or
235 * offset. This is most useful for start and end tags.
237 * @param a
238 * the attributes for the element to be created
239 * @param type
240 * the type of the tag
242 public ElementSpec(AttributeSet a, short type)
244 this(a, type, 0);
248 * Creates a new <code>ElementSpec</code> that specifies the length but
249 * not the offset of an element. Such <code>ElementSpec</code>s are
250 * processed sequentially from a known starting point.
252 * @param a
253 * the attributes for the element to be created
254 * @param type
255 * the type of the tag
256 * @param len
257 * the length of the element
259 public ElementSpec(AttributeSet a, short type, int len)
261 this(a, type, null, 0, len);
265 * Creates a new <code>ElementSpec</code> with document content.
267 * @param a
268 * the attributes for the element to be created
269 * @param type
270 * the type of the tag
271 * @param txt
272 * the actual content
273 * @param offs
274 * the offset into the <code>txt</code> array
275 * @param len
276 * the length of the element
278 public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)
280 attributes = a;
281 this.type = type;
282 offset = offs;
283 length = len;
284 content = txt;
285 direction = OriginateDirection;
289 * Sets the type of the element.
291 * @param type
292 * the type of the element to be set
294 public void setType(short type)
296 this.type = type;
300 * Returns the type of the element.
302 * @return the type of the element
304 public short getType()
306 return type;
310 * Sets the direction of the element.
312 * @param dir
313 * the direction of the element to be set
315 public void setDirection(short dir)
317 direction = dir;
321 * Returns the direction of the element.
323 * @return the direction of the element
325 public short getDirection()
327 return direction;
331 * Returns the attributes of the element.
333 * @return the attributes of the element
335 public AttributeSet getAttributes()
337 return attributes;
341 * Returns the actual content of the element.
343 * @return the actual content of the element
345 public char[] getArray()
347 return content;
351 * Returns the offset of the content.
353 * @return the offset of the content
355 public int getOffset()
357 return offset;
361 * Returns the length of the content.
363 * @return the length of the content
365 public int getLength()
367 return length;
371 * Returns a String representation of this <code>ElementSpec</code>
372 * describing the type, direction and length of this
373 * <code>ElementSpec</code>.
375 * @return a String representation of this <code>ElementSpec</code>
377 public String toString()
379 CPStringBuilder b = new CPStringBuilder();
380 switch (type)
382 case StartTagType:
383 b.append("StartTag");
384 break;
385 case EndTagType:
386 b.append("EndTag");
387 break;
388 case ContentType:
389 b.append("Content");
390 break;
391 default:
392 b.append("??");
393 break;
396 b.append(':');
398 switch (direction)
400 case JoinPreviousDirection:
401 b.append("JoinPrevious");
402 break;
403 case JoinNextDirection:
404 b.append("JoinNext");
405 break;
406 case OriginateDirection:
407 b.append("Originate");
408 break;
409 case JoinFractureDirection:
410 b.append("Fracture");
411 break;
412 default:
413 b.append("??");
414 break;
417 b.append(':');
418 b.append(length);
420 return b.toString();
425 * Performs all <em>structural</code> changes to the <code>Element</code>
426 * hierarchy. This class was implemented with much help from the document:
427 * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html.
429 public class ElementBuffer implements Serializable
432 * Instance of all editing information for an object in the Vector. This class
433 * is used to add information to the DocumentEvent associated with an
434 * insertion/removal/change as well as to store the changes that need to be
435 * made so they can be made all at the same (appropriate) time.
437 class Edit
439 /** The element to edit . */
440 Element e;
442 /** The index of the change. */
443 int index;
445 /** The removed elements. */
446 ArrayList removed = new ArrayList();
448 /** The added elements. */
449 ArrayList added = new ArrayList();
452 * Indicates if this edit contains a fracture.
454 boolean isFracture;
457 * Creates a new Edit for the specified element at index i.
459 * @param el the element
460 * @param i the index
462 Edit(Element el, int i)
464 this(el, i, false);
468 * Creates a new Edit for the specified element at index i.
470 * @param el the element
471 * @param i the index
472 * @param frac if this is a fracture edit or not
474 Edit(Element el, int i, boolean frac)
476 e = el;
477 index = i;
478 isFracture = frac;
483 /** The serialization UID (compatible with JDK1.5). */
484 private static final long serialVersionUID = 1688745877691146623L;
486 /** The root element of the hierarchy. */
487 private Element root;
489 /** Holds the offset for structural changes. */
490 private int offset;
492 /** Holds the end offset for structural changes. */
493 private int endOffset;
495 /** Holds the length of structural changes. */
496 private int length;
498 /** Holds the position of the change. */
499 private int pos;
502 * The parent of the fracture.
504 private Element fracturedParent;
507 * The fractured child.
509 private Element fracturedChild;
512 * Indicates if a fracture has been created.
514 private boolean createdFracture;
517 * The current position in the element tree. This is used for bulk inserts
518 * using ElementSpecs.
520 private Stack elementStack;
522 private Edit[] insertPath;
524 private boolean recreateLeafs;
527 * Vector that contains all the edits. Maybe replace by a HashMap.
529 private ArrayList edits;
531 private boolean offsetLastIndex;
532 private boolean offsetLastIndexReplace;
535 * Creates a new <code>ElementBuffer</code> for the specified
536 * <code>root</code> element.
538 * @param root
539 * the root element for this <code>ElementBuffer</code>
541 public ElementBuffer(Element root)
543 this.root = root;
547 * Returns the root element of this <code>ElementBuffer</code>.
549 * @return the root element of this <code>ElementBuffer</code>
551 public Element getRootElement()
553 return root;
557 * Removes the content. This method sets some internal parameters and
558 * delegates the work to {@link #removeUpdate}.
560 * @param offs
561 * the offset from which content is remove
562 * @param len
563 * the length of the removed content
564 * @param ev
565 * the document event that records the changes
567 public void remove(int offs, int len, DefaultDocumentEvent ev)
569 prepareEdit(offs, len);
570 removeUpdate();
571 finishEdit(ev);
575 * Updates the element structure of the document in response to removal of
576 * content. It removes the affected {@link Element}s from the document
577 * structure.
579 protected void removeUpdate()
581 removeElements(root, offset, endOffset);
584 private boolean removeElements(Element elem, int rmOffs0, int rmOffs1)
586 boolean ret = false;
587 if (! elem.isLeaf())
589 // Update stack for changes.
590 int index0 = elem.getElementIndex(rmOffs0);
591 int index1 = elem.getElementIndex(rmOffs1);
592 elementStack.push(new Edit(elem, index0));
593 Edit ec = (Edit) elementStack.peek();
595 // If the range is contained by one element,
596 // we just forward the request
597 if (index0 == index1)
599 Element child0 = elem.getElement(index0);
600 if(rmOffs0 <= child0.getStartOffset()
601 && rmOffs1 >= child0.getEndOffset())
603 // Element totally removed.
604 ec.removed.add(child0);
606 else if (removeElements(child0, rmOffs0, rmOffs1))
608 ec.removed.add(child0);
611 else
613 // The removal range spans elements. If we can join
614 // the two endpoints, do it. Otherwise we remove the
615 // interior and forward to the endpoints.
616 Element child0 = elem.getElement(index0);
617 Element child1 = elem.getElement(index1);
618 boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
619 if (containsOffs1 && canJoin(child0, child1))
621 // Remove and join.
622 for (int i = index0; i <= index1; i++)
624 ec.removed.add(elem.getElement(i));
626 Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
627 ec.added.add(e);
629 else
631 // Remove interior and forward.
632 int rmIndex0 = index0 + 1;
633 int rmIndex1 = index1 - 1;
634 if (child0.getStartOffset() == rmOffs0
635 || (index0 == 0 && child0.getStartOffset() > rmOffs0
636 && child0.getEndOffset() <= rmOffs1))
638 // Start element completely consumed.
639 child0 = null;
640 rmIndex0 = index0;
642 if (! containsOffs1)
644 child1 = null;
645 rmIndex1++;
647 else if (child1.getStartOffset() == rmOffs1)
649 // End element not touched.
650 child1 = null;
652 if (rmIndex0 <= rmIndex1)
654 ec.index = rmIndex0;
656 for (int i = rmIndex0; i <= rmIndex1; i++)
658 ec.removed.add(elem.getElement(i));
660 if (child0 != null)
662 if(removeElements(child0, rmOffs0, rmOffs1))
664 ec.removed.add(0, child0);
665 ec.index = index0;
668 if (child1 != null)
670 if(removeElements(child1, rmOffs0, rmOffs1))
672 ec.removed.add(child1);
678 // Perform changes.
679 pop();
681 // Return true if we no longer have any children.
682 if(elem.getElementCount() == (ec.removed.size() - ec.added.size()))
683 ret = true;
685 return ret;
689 * Creates a document in response to a call to
690 * {@link DefaultStyledDocument#create(ElementSpec[])}.
692 * @param len the length of the inserted text
693 * @param data the specs for the elements
694 * @param ev the document event
696 void create(int len, ElementSpec[] data, DefaultDocumentEvent ev)
698 prepareEdit(offset, len);
699 Element el = root;
700 int index = el.getElementIndex(0);
701 while (! el.isLeaf())
703 Element child = el.getElement(index);
704 Edit edit = new Edit(el, index, false);
705 elementStack.push(edit);
706 el = child;
707 index = el.getElementIndex(0);
709 Edit ed = (Edit) elementStack.peek();
710 Element child = ed.e.getElement(ed.index);
711 ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(),
712 child.getEndOffset()));
713 ed.removed.add(child);
714 while (elementStack.size() > 1)
715 pop();
716 int n = data.length;
718 // Reset root element's attributes.
719 AttributeSet newAtts = null;
720 if (n > 0 && data[0].getType() == ElementSpec.StartTagType)
721 newAtts = data[0].getAttributes();
722 if (newAtts == null)
723 newAtts = SimpleAttributeSet.EMPTY;
724 MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes();
725 ev.addEdit(new AttributeUndoableEdit(root, newAtts, true));
726 mAtts.removeAttributes(mAtts);
727 mAtts.addAttributes(newAtts);
729 // Insert the specified elements.
730 for (int i = 1; i < n; i++)
731 insertElement(data[i]);
733 // Pop remaining stack.
734 while (elementStack.size() > 0)
735 pop();
737 finishEdit(ev);
740 private boolean canJoin(Element e0, Element e1)
742 boolean ret = false;
743 if ((e0 != null) && (e1 != null))
745 // Don't join a leaf to a branch.
746 boolean isLeaf0 = e0.isLeaf();
747 boolean isLeaf1 = e1.isLeaf();
748 if(isLeaf0 == isLeaf1)
750 if (isLeaf0)
752 // Only join leaves if the attributes match, otherwise
753 // style information will be lost.
754 ret = e0.getAttributes().isEqual(e1.getAttributes());
756 else
758 // Only join non-leafs if the names are equal. This may result
759 // in loss of style information, but this is typically
760 // acceptable for non-leafs.
761 String name0 = e0.getName();
762 String name1 = e1.getName();
763 if (name0 != null)
764 ret = name0.equals(name1);
765 else if (name1 != null)
766 ret = name1.equals(name0);
767 else // Both names null.
768 ret = true;
772 return ret;
775 private Element join(Element p, Element left, Element right, int rmOffs0,
776 int rmOffs1)
778 Element joined = null;
779 if (left.isLeaf() && right.isLeaf())
781 joined = createLeafElement(p, left.getAttributes(),
782 left.getStartOffset(),
783 right.getEndOffset());
785 else if ((! left.isLeaf()) && (! right.isLeaf()))
787 // Join two branch elements. This copies the children before
788 // the removal range on the left element, and after the removal
789 // range on the right element. The two elements on the edge
790 // are joined if possible and needed.
791 joined = createBranchElement(p, left.getAttributes());
792 int ljIndex = left.getElementIndex(rmOffs0);
793 int rjIndex = right.getElementIndex(rmOffs1);
794 Element lj = left.getElement(ljIndex);
795 if (lj.getStartOffset() >= rmOffs0)
797 lj = null;
799 Element rj = right.getElement(rjIndex);
800 if (rj.getStartOffset() == rmOffs1)
802 rj = null;
804 ArrayList children = new ArrayList();
805 // Transfer the left.
806 for (int i = 0; i < ljIndex; i++)
808 children.add(clone(joined, left.getElement(i)));
811 // Transfer the join/middle.
812 if (canJoin(lj, rj))
814 Element e = join(joined, lj, rj, rmOffs0, rmOffs1);
815 children.add(e);
817 else
819 if (lj != null)
821 children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1));
823 if (rj != null)
825 children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1));
829 // Transfer the right.
830 int n = right.getElementCount();
831 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++)
833 children.add(clone(joined, right.getElement(i)));
836 // Install the children.
837 Element[] c = new Element[children.size()];
838 c = (Element[]) children.toArray(c);
839 ((BranchElement) joined).replace(0, 0, c);
841 else
843 assert false : "Must not happen";
845 return joined;
849 * Performs the actual work for {@link #change}. The elements at the
850 * interval boundaries are split up (if necessary) so that the interval
851 * boundaries are located at element boundaries.
853 protected void changeUpdate()
855 boolean didEnd = split(offset, length);
856 if (! didEnd)
858 // need to do the other end
859 while (elementStack.size() != 0)
861 pop();
863 split(offset + length, 0);
865 while (elementStack.size() != 0)
867 pop();
872 * Modifies the element structure so that the specified interval starts and
873 * ends at an element boundary. Content and paragraph elements are split and
874 * created as necessary. This also updates the
875 * <code>DefaultDocumentEvent</code> to reflect the structural changes.
876 * The bulk work is delegated to {@link #changeUpdate()}.
878 * @param offset
879 * the start index of the interval to be changed
880 * @param length
881 * the length of the interval to be changed
882 * @param ev
883 * the <code>DefaultDocumentEvent</code> describing the change
885 public void change(int offset, int length, DefaultDocumentEvent ev)
887 prepareEdit(offset, length);
888 changeUpdate();
889 finishEdit(ev);
893 * Creates and returns a deep clone of the specified <code>clonee</code>
894 * with the specified parent as new parent.
896 * This method can only clone direct instances of {@link BranchElement}
897 * or {@link LeafElement}.
899 * @param parent the new parent
900 * @param clonee the element to be cloned
902 * @return the cloned element with the new parent
904 public Element clone(Element parent, Element clonee)
906 Element clone = clonee;
907 // We can only handle AbstractElements here.
908 if (clonee instanceof BranchElement)
910 BranchElement branchEl = (BranchElement) clonee;
911 BranchElement branchClone =
912 new BranchElement(parent, branchEl.getAttributes());
913 // Also clone all of the children.
914 int numChildren = branchClone.getElementCount();
915 Element[] cloneChildren = new Element[numChildren];
916 for (int i = 0; i < numChildren; ++i)
918 cloneChildren[i] = clone(branchClone,
919 branchClone.getElement(i));
921 branchClone.replace(0, 0, cloneChildren);
922 clone = branchClone;
924 else if (clonee instanceof LeafElement)
926 clone = new LeafElement(parent, clonee.getAttributes(),
927 clonee.getStartOffset(),
928 clonee.getEndOffset());
930 return clone;
933 private Element cloneAsNecessary(Element parent, Element clonee,
934 int rmOffs0, int rmOffs1)
936 Element cloned;
937 if (clonee.isLeaf())
939 cloned = createLeafElement(parent, clonee.getAttributes(),
940 clonee.getStartOffset(),
941 clonee.getEndOffset());
943 else
945 Element e = createBranchElement(parent, clonee.getAttributes());
946 int n = clonee.getElementCount();
947 ArrayList childrenList = new ArrayList(n);
948 for (int i = 0; i < n; i++)
950 Element elem = clonee.getElement(i);
951 if (elem.getStartOffset() < rmOffs0
952 || elem.getEndOffset() > rmOffs1)
954 childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
955 rmOffs1));
958 Element[] children = new Element[childrenList.size()];
959 children = (Element[]) childrenList.toArray(children);
960 ((BranchElement) e).replace(0, 0, children);
961 cloned = e;
963 return cloned;
967 * Inserts new <code>Element</code> in the document at the specified
968 * position. Most of the work is done by {@link #insertUpdate}, after some
969 * fields have been prepared for it.
971 * @param offset
972 * the location in the document at which the content is inserted
973 * @param length
974 * the length of the inserted content
975 * @param data
976 * the element specifications for the content to be inserted
977 * @param ev
978 * the document event that is updated to reflect the structural
979 * changes
981 public void insert(int offset, int length, ElementSpec[] data,
982 DefaultDocumentEvent ev)
984 if (length > 0)
986 prepareEdit(offset, length);
987 insertUpdate(data);
988 finishEdit(ev);
993 * Prepares the state of this object for performing an insert.
995 * @param offset the offset at which is inserted
996 * @param length the length of the inserted region
998 private void prepareEdit(int offset, int length)
1000 this.offset = offset;
1001 this.pos = offset;
1002 this.endOffset = offset + length;
1003 this.length = length;
1005 if (edits == null)
1006 edits = new ArrayList();
1007 else
1008 edits.clear();
1010 if (elementStack == null)
1011 elementStack = new Stack();
1012 else
1013 elementStack.clear();
1015 fracturedParent = null;
1016 fracturedChild = null;
1017 offsetLastIndex = false;
1018 offsetLastIndexReplace = false;
1022 * Finishes an insert. This applies all changes and updates
1023 * the DocumentEvent.
1025 * @param ev the document event
1027 private void finishEdit(DefaultDocumentEvent ev)
1029 // This for loop applies all the changes that were made and updates the
1030 // DocumentEvent.
1031 for (Iterator i = edits.iterator(); i.hasNext();)
1033 Edit edits = (Edit) i.next();
1034 Element[] removed = new Element[edits.removed.size()];
1035 removed = (Element[]) edits.removed.toArray(removed);
1036 Element[] added = new Element[edits.added.size()];
1037 added = (Element[]) edits.added.toArray(added);
1038 int index = edits.index;
1039 BranchElement parent = (BranchElement) edits.e;
1040 parent.replace(index, removed.length, added);
1041 ElementEdit ee = new ElementEdit(parent, index, removed, added);
1042 ev.addEdit(ee);
1044 edits.clear();
1045 elementStack.clear();
1049 * Inserts new content.
1051 * @param data the element specifications for the elements to be inserted
1053 protected void insertUpdate(ElementSpec[] data)
1055 // Push the current path to the stack.
1056 Element current = root;
1057 int index = current.getElementIndex(offset);
1058 while (! current.isLeaf())
1060 Element child = current.getElement(index);
1061 int editIndex = child.isLeaf() ? index : index + 1;
1062 Edit edit = new Edit(current, editIndex);
1063 elementStack.push(edit);
1064 current = child;
1065 index = current.getElementIndex(offset);
1068 // Create a copy of the original path.
1069 insertPath = new Edit[elementStack.size()];
1070 insertPath = (Edit[]) elementStack.toArray(insertPath);
1072 // No fracture yet.
1073 createdFracture = false;
1075 // Insert first content tag.
1076 int i = 0;
1077 recreateLeafs = false;
1078 int type = data[0].getType();
1079 if (type == ElementSpec.ContentType)
1081 // If the first tag is content we must treat it separately to allow
1082 // for joining properly to previous Elements and to ensure that
1083 // no extra LeafElements are erroneously inserted.
1084 insertFirstContentTag(data);
1085 pos += data[0].length;
1086 i = 1;
1088 else
1090 createFracture(data);
1091 i = 0;
1094 // Handle each ElementSpec individually.
1095 for (; i < data.length; i++)
1097 insertElement(data[i]);
1100 // Fracture if we haven't done yet.
1101 if (! createdFracture)
1102 fracture(-1);
1104 // Pop the remaining stack.
1105 while (elementStack.size() != 0)
1106 pop();
1108 // Offset last index if necessary.
1109 if (offsetLastIndex && offsetLastIndexReplace)
1110 insertPath[insertPath.length - 1].index++;
1112 // Make sure we havea an Edit for each path item that has a change.
1113 for (int p = insertPath.length - 1; p >= 0; p--)
1115 Edit edit = insertPath[p];
1116 if (edit.e == fracturedParent)
1117 edit.added.add(fracturedChild);
1118 if ((edit.added.size() > 0 || edit.removed.size() > 0)
1119 && ! edits.contains(edit))
1120 edits.add(edit);
1123 // Remove element that would be created by an insert at 0 with
1124 // an initial end tag.
1125 if (offset == 0 && fracturedParent != null
1126 && data[0].getType() == ElementSpec.EndTagType)
1128 int p;
1129 for (p = 0;
1130 p < data.length && data[p].getType() == ElementSpec.EndTagType;
1131 p++)
1134 Edit edit = insertPath[insertPath.length - p - 1];
1135 edit.index--;
1136 edit.removed.add(0, edit.e.getElement(edit.index));
1140 private void pop()
1142 Edit edit = (Edit) elementStack.peek();
1143 elementStack.pop();
1144 if ((edit.added.size() > 0) || (edit.removed.size() > 0))
1146 edits.add(edit);
1148 else if (! elementStack.isEmpty())
1150 Element e = edit.e;
1151 if (e.getElementCount() == 0)
1153 // If we pushed a branch element that didn't get
1154 // used, make sure its not marked as having been added.
1155 edit = (Edit) elementStack.peek();
1156 edit.added.remove(e);
1161 private void insertElement(ElementSpec spec)
1163 if (elementStack.isEmpty())
1164 return;
1166 Edit edit = (Edit) elementStack.peek();
1167 switch (spec.getType())
1169 case ElementSpec.StartTagType:
1170 switch (spec.getDirection())
1172 case ElementSpec.JoinFractureDirection:
1173 // Fracture the tree and ensure the appropriate element
1174 // is on top of the stack.
1175 if (! createdFracture)
1177 fracture(elementStack.size() - 1);
1179 if (! edit.isFracture)
1181 // If the parent isn't a fracture, then the fracture is
1182 // in fracturedChild.
1183 Edit newEdit = new Edit(fracturedChild, 0, true);
1184 elementStack.push(newEdit);
1186 else
1188 // Otherwise use the parent's first child.
1189 Element el = edit.e.getElement(0);
1190 Edit newEdit = new Edit(el, 0, true);
1191 elementStack.push(newEdit);
1193 break;
1194 case ElementSpec.JoinNextDirection:
1195 // Push the next paragraph element onto the stack so
1196 // future insertions are added to it.
1197 Element parent = edit.e.getElement(edit.index);
1198 if (parent.isLeaf())
1200 if (edit.index + 1 < edit.e.getElementCount())
1201 parent = edit.e.getElement(edit.index + 1);
1202 else
1203 assert false; // Must not happen.
1205 elementStack.push(new Edit(parent, 0, true));
1206 break;
1207 default:
1208 Element branch = createBranchElement(edit.e,
1209 spec.getAttributes());
1210 edit.added.add(branch);
1211 elementStack.push(new Edit(branch, 0));
1212 break;
1214 break;
1215 case ElementSpec.EndTagType:
1216 pop();
1217 break;
1218 case ElementSpec.ContentType:
1219 insertContentTag(spec, edit);
1220 break;
1225 * Inserts the first tag into the document.
1227 * @param data -
1228 * the data to be inserted.
1230 private void insertFirstContentTag(ElementSpec[] data)
1232 ElementSpec first = data[0];
1233 Edit edit = (Edit) elementStack.peek();
1234 Element current = edit.e.getElement(edit.index);
1235 int firstEndOffset = offset + first.length;
1236 boolean onlyContent = data.length == 1;
1237 switch (first.getDirection())
1239 case ElementSpec.JoinPreviousDirection:
1240 if (current.getEndOffset() != firstEndOffset && ! onlyContent)
1242 Element newEl1 = createLeafElement(edit.e,
1243 current.getAttributes(),
1244 current.getStartOffset(),
1245 firstEndOffset);
1246 edit.added.add(newEl1);
1247 edit.removed.add(current);
1248 if (current.getEndOffset() != endOffset)
1249 recreateLeafs = true;
1250 else
1251 offsetLastIndex = true;
1253 else
1255 offsetLastIndex = true;
1256 offsetLastIndexReplace = true;
1258 break;
1259 case ElementSpec.JoinNextDirection:
1260 if (offset != 0)
1262 Element newEl1 = createLeafElement(edit.e,
1263 current.getAttributes(),
1264 current.getStartOffset(),
1265 offset);
1266 edit.added.add(newEl1);
1267 Element next = edit.e.getElement(edit.index + 1);
1268 if (onlyContent)
1269 newEl1 = createLeafElement(edit.e, next.getAttributes(),
1270 offset, next.getEndOffset());
1271 else
1273 newEl1 = createLeafElement(edit.e, next.getAttributes(),
1274 offset, firstEndOffset);
1276 edit.added.add(newEl1);
1277 edit.removed.add(current);
1278 edit.removed.add(next);
1280 break;
1281 default: // OriginateDirection.
1282 if (current.getStartOffset() != offset)
1284 Element newEl = createLeafElement(edit.e,
1285 current.getAttributes(),
1286 current.getStartOffset(),
1287 offset);
1288 edit.added.add(newEl);
1290 edit.removed.add(current);
1291 Element newEl1 = createLeafElement(edit.e, first.getAttributes(),
1292 offset, firstEndOffset);
1293 edit.added.add(newEl1);
1294 if (current.getEndOffset() != endOffset)
1295 recreateLeafs = true;
1296 else
1297 offsetLastIndex = true;
1298 break;
1303 * Inserts a content element into the document structure.
1305 * @param tag -
1306 * the element spec
1308 private void insertContentTag(ElementSpec tag, Edit edit)
1310 int len = tag.getLength();
1311 int dir = tag.getDirection();
1312 if (dir == ElementSpec.JoinNextDirection)
1314 if (! edit.isFracture)
1316 Element first = null;
1317 if (insertPath != null)
1319 for (int p = insertPath.length - 1; p >= 0; p--)
1321 if (insertPath[p] == edit)
1323 if (p != insertPath.length - 1)
1324 first = edit.e.getElement(edit.index);
1325 break;
1329 if (first == null)
1330 first = edit.e.getElement(edit.index + 1);
1331 Element leaf = createLeafElement(edit.e, first.getAttributes(),
1332 pos, first.getEndOffset());
1333 edit.added.add(leaf);
1334 edit.removed.add(first);
1336 else
1338 Element first = edit.e.getElement(0);
1339 Element leaf = createLeafElement(edit.e, first.getAttributes(),
1340 pos, first.getEndOffset());
1341 edit.added.add(leaf);
1342 edit.removed.add(first);
1345 else
1347 Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos,
1348 pos + len);
1349 edit.added.add(leaf);
1352 pos += len;
1357 * This method fractures bottomost leaf in the elementStack. This
1358 * happens when the first inserted tag is not content.
1360 * @param data
1361 * the ElementSpecs used for the entire insertion
1363 private void createFracture(ElementSpec[] data)
1365 Edit edit = (Edit) elementStack.peek();
1366 Element child = edit.e.getElement(edit.index);
1367 if (offset != 0)
1369 Element newChild = createLeafElement(edit.e, child.getAttributes(),
1370 child.getStartOffset(), offset);
1371 edit.added.add(newChild);
1373 edit.removed.add(child);
1374 if (child.getEndOffset() != endOffset)
1375 recreateLeafs = true;
1376 else
1377 offsetLastIndex = true;
1380 private void fracture(int depth)
1382 int len = insertPath.length;
1383 int lastIndex = -1;
1384 boolean recreate = recreateLeafs;
1385 Edit lastEdit = insertPath[len - 1];
1386 boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount();
1387 int deepestChangedIndex = recreate ? len : - 1;
1388 int lastChangedIndex = len - 1;
1389 createdFracture = true;
1390 for (int i = len - 2; i >= 0; i--)
1392 Edit edit = insertPath[i];
1393 if (edit.added.size() > 0 || i == depth)
1395 lastIndex = i;
1396 if (! recreate && childChanged)
1398 recreate = true;
1399 if (deepestChangedIndex == -1)
1400 deepestChangedIndex = lastChangedIndex + 1;
1403 if (! childChanged && edit.index < edit.e.getElementCount())
1405 childChanged = true;
1406 lastChangedIndex = i;
1409 if (recreate)
1411 if (lastIndex == -1)
1412 lastIndex = len - 1;
1413 recreate(lastIndex, deepestChangedIndex);
1417 private void recreate(int startIndex, int endIndex)
1419 // Recreate the element representing the inserted index.
1420 Edit edit = insertPath[startIndex];
1421 Element child;
1422 Element newChild;
1423 int changeLength = insertPath.length;
1425 if (startIndex + 1 == changeLength)
1426 child = edit.e.getElement(edit.index);
1427 else
1428 child = edit.e.getElement(edit.index - 1);
1430 if(child.isLeaf())
1432 newChild = createLeafElement(edit.e, child.getAttributes(),
1433 Math.max(endOffset, child.getStartOffset()),
1434 child.getEndOffset());
1436 else
1438 newChild = createBranchElement(edit.e, child.getAttributes());
1440 fracturedParent = edit.e;
1441 fracturedChild = newChild;
1443 // Recreate all the elements to the right of the insertion point.
1444 Element parent = newChild;
1445 while (++startIndex < endIndex)
1447 boolean isEnd = (startIndex + 1) == endIndex;
1448 boolean isEndLeaf = (startIndex + 1) == changeLength;
1450 // Create the newChild, a duplicate of the elment at
1451 // index. This isn't done if isEnd and offsetLastIndex are true
1452 // indicating a join previous was done.
1453 edit = insertPath[startIndex];
1455 // Determine the child to duplicate, won't have to duplicate
1456 // if at end of fracture, or offseting index.
1457 if(isEnd)
1459 if(offsetLastIndex || ! isEndLeaf)
1460 child = null;
1461 else
1462 child = edit.e.getElement(edit.index);
1464 else
1466 child = edit.e.getElement(edit.index - 1);
1469 // Duplicate it.
1470 if(child != null)
1472 if(child.isLeaf())
1474 newChild = createLeafElement(parent, child.getAttributes(),
1475 Math.max(endOffset, child.getStartOffset()),
1476 child.getEndOffset());
1478 else
1480 newChild = createBranchElement(parent,
1481 child.getAttributes());
1484 else
1485 newChild = null;
1487 // Recreate the remaining children (there may be none).
1488 int childrenToMove = edit.e.getElementCount() - edit.index;
1489 Element[] children;
1490 int moveStartIndex;
1491 int childStartIndex = 1;
1493 if (newChild == null)
1495 // Last part of fracture.
1496 if (isEndLeaf)
1498 childrenToMove--;
1499 moveStartIndex = edit.index + 1;
1501 else
1503 moveStartIndex = edit.index;
1505 childStartIndex = 0;
1506 children = new Element[childrenToMove];
1508 else
1510 if (! isEnd)
1512 // Branch.
1513 childrenToMove++;
1514 moveStartIndex = edit.index;
1516 else
1518 // Last leaf, need to recreate part of it.
1519 moveStartIndex = edit.index + 1;
1521 children = new Element[childrenToMove];
1522 children[0] = newChild;
1525 for (int c = childStartIndex; c < childrenToMove; c++)
1527 Element toMove = edit.e.getElement(moveStartIndex++);
1528 children[c] = recreateFracturedElement(parent, toMove);
1529 edit.removed.add(toMove);
1531 ((BranchElement) parent).replace(0, 0, children);
1532 parent = newChild;
1537 private Element recreateFracturedElement(Element parent, Element toCopy)
1539 Element recreated;
1540 if(toCopy.isLeaf())
1542 recreated = createLeafElement(parent, toCopy.getAttributes(),
1543 Math.max(toCopy.getStartOffset(), endOffset),
1544 toCopy.getEndOffset());
1546 else
1548 Element newParent = createBranchElement(parent,
1549 toCopy.getAttributes());
1550 int childCount = toCopy.getElementCount();
1551 Element[] newChildren = new Element[childCount];
1552 for (int i = 0; i < childCount; i++)
1554 newChildren[i] = recreateFracturedElement(newParent,
1555 toCopy.getElement(i));
1557 ((BranchElement) newParent).replace(0, 0, newChildren);
1558 recreated = newParent;
1560 return recreated;
1563 private boolean split(int offs, int len)
1565 boolean splitEnd = false;
1566 // Push the path to the stack.
1567 Element e = root;
1568 int index = e.getElementIndex(offs);
1569 while (! e.isLeaf())
1571 elementStack.push(new Edit(e, index));
1572 e = e.getElement(index);
1573 index = e.getElementIndex(offs);
1576 Edit ec = (Edit) elementStack.peek();
1577 Element child = ec.e.getElement(ec.index);
1578 // Make sure there is something to do. If the
1579 // offset is already at a boundary then there is
1580 // nothing to do.
1581 if (child.getStartOffset() < offs && offs < child.getEndOffset())
1583 // We need to split, now see if the other end is within
1584 // the same parent.
1585 int index0 = ec.index;
1586 int index1 = index0;
1587 if (((offs + len) < ec.e.getEndOffset()) && (len != 0))
1589 // It's a range split in the same parent.
1590 index1 = ec.e.getElementIndex(offs+len);
1591 if (index1 == index0)
1593 // It's a three-way split.
1594 ec.removed.add(child);
1595 e = createLeafElement(ec.e, child.getAttributes(),
1596 child.getStartOffset(), offs);
1597 ec.added.add(e);
1598 e = createLeafElement(ec.e, child.getAttributes(),
1599 offs, offs + len);
1600 ec.added.add(e);
1601 e = createLeafElement(ec.e, child.getAttributes(),
1602 offs + len, child.getEndOffset());
1603 ec.added.add(e);
1604 return true;
1606 else
1608 child = ec.e.getElement(index1);
1609 if ((offs + len) == child.getStartOffset())
1611 // End is already on a boundary.
1612 index1 = index0;
1615 splitEnd = true;
1618 // Split the first location.
1619 pos = offs;
1620 child = ec.e.getElement(index0);
1621 ec.removed.add(child);
1622 e = createLeafElement(ec.e, child.getAttributes(),
1623 child.getStartOffset(), pos);
1624 ec.added.add(e);
1625 e = createLeafElement(ec.e, child.getAttributes(),
1626 pos, child.getEndOffset());
1627 ec.added.add(e);
1629 // Pick up things in the middle.
1630 for (int i = index0 + 1; i < index1; i++)
1632 child = ec.e.getElement(i);
1633 ec.removed.add(child);
1634 ec.added.add(child);
1637 if (index1 != index0)
1639 child = ec.e.getElement(index1);
1640 pos = offs + len;
1641 ec.removed.add(child);
1642 e = createLeafElement(ec.e, child.getAttributes(),
1643 child.getStartOffset(), pos);
1644 ec.added.add(e);
1645 e = createLeafElement(ec.e, child.getAttributes(),
1646 pos, child.getEndOffset());
1648 ec.added.add(e);
1651 return splitEnd;
1659 * An element type for sections. This is a simple BranchElement with a unique
1660 * name.
1662 protected class SectionElement extends BranchElement
1665 * Creates a new SectionElement.
1667 public SectionElement()
1669 super(null, null);
1673 * Returns the name of the element. This method always returns
1674 * &quot;section&quot;.
1676 * @return the name of the element
1678 public String getName()
1680 return SectionElementName;
1685 * Receives notification when any of the document's style changes and calls
1686 * {@link DefaultStyledDocument#styleChanged(Style)}.
1688 * @author Roman Kennke (kennke@aicas.com)
1690 private class StyleChangeListener implements ChangeListener
1694 * Receives notification when any of the document's style changes and calls
1695 * {@link DefaultStyledDocument#styleChanged(Style)}.
1697 * @param event
1698 * the change event
1700 public void stateChanged(ChangeEvent event)
1702 Style style = (Style) event.getSource();
1703 styleChanged(style);
1707 /** The serialization UID (compatible with JDK1.5). */
1708 private static final long serialVersionUID = 940485415728614849L;
1711 * The default size to use for new content buffers.
1713 public static final int BUFFER_SIZE_DEFAULT = 4096;
1716 * The <code>EditorBuffer</code> that is used to manage to
1717 * <code>Element</code> hierarchy.
1719 protected DefaultStyledDocument.ElementBuffer buffer;
1722 * Listens for changes on this document's styles and notifies styleChanged().
1724 private StyleChangeListener styleChangeListener;
1727 * Creates a new <code>DefaultStyledDocument</code>.
1729 public DefaultStyledDocument()
1731 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
1735 * Creates a new <code>DefaultStyledDocument</code> that uses the specified
1736 * {@link StyleContext}.
1738 * @param context
1739 * the <code>StyleContext</code> to use
1741 public DefaultStyledDocument(StyleContext context)
1743 this(new GapContent(BUFFER_SIZE_DEFAULT), context);
1747 * Creates a new <code>DefaultStyledDocument</code> that uses the specified
1748 * {@link StyleContext} and {@link Content} buffer.
1750 * @param content
1751 * the <code>Content</code> buffer to use
1752 * @param context
1753 * the <code>StyleContext</code> to use
1755 public DefaultStyledDocument(AbstractDocument.Content content,
1756 StyleContext context)
1758 super(content, context);
1759 buffer = new ElementBuffer(createDefaultRoot());
1760 setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
1764 * Adds a style into the style hierarchy. Unspecified style attributes can be
1765 * resolved in the <code>parent</code> style, if one is specified. While it
1766 * is legal to add nameless styles (<code>nm == null</code),
1767 * you must be aware that the client application is then responsible
1768 * for managing the style hierarchy, since unnamed styles cannot be
1769 * looked up by their name.
1771 * @param nm the name of the style or <code>null</code> if the style should
1772 * be unnamed
1773 * @param parent the parent in which unspecified style attributes are
1774 * resolved, or <code>null</code> if that is not necessary
1776 * @return the newly created <code>Style</code>
1778 public Style addStyle(String nm, Style parent)
1780 StyleContext context = (StyleContext) getAttributeContext();
1781 Style newStyle = context.addStyle(nm, parent);
1783 // Register change listener.
1784 if (styleChangeListener == null)
1785 styleChangeListener = new StyleChangeListener();
1786 newStyle.addChangeListener(styleChangeListener);
1788 return newStyle;
1792 * Create the default root element for this kind of <code>Document</code>.
1794 * @return the default root element for this kind of <code>Document</code>
1796 protected AbstractDocument.AbstractElement createDefaultRoot()
1798 Element[] tmp;
1799 SectionElement section = new SectionElement();
1801 BranchElement paragraph = new BranchElement(section, null);
1802 tmp = new Element[1];
1803 tmp[0] = paragraph;
1804 section.replace(0, 0, tmp);
1806 Element leaf = new LeafElement(paragraph, null, 0, 1);
1807 tmp = new Element[1];
1808 tmp[0] = leaf;
1809 paragraph.replace(0, 0, tmp);
1811 return section;
1815 * Returns the <code>Element</code> that corresponds to the character at the
1816 * specified position.
1818 * @param position
1819 * the position of which we query the corresponding
1820 * <code>Element</code>
1821 * @return the <code>Element</code> that corresponds to the character at the
1822 * specified position
1824 public Element getCharacterElement(int position)
1826 Element element = getDefaultRootElement();
1828 while (!element.isLeaf())
1830 int index = element.getElementIndex(position);
1831 element = element.getElement(index);
1834 return element;
1838 * Extracts a background color from a set of attributes.
1840 * @param attributes
1841 * the attributes from which to get a background color
1842 * @return the background color that correspond to the attributes
1844 public Color getBackground(AttributeSet attributes)
1846 StyleContext context = (StyleContext) getAttributeContext();
1847 return context.getBackground(attributes);
1851 * Returns the default root element.
1853 * @return the default root element
1855 public Element getDefaultRootElement()
1857 return buffer.getRootElement();
1861 * Extracts a font from a set of attributes.
1863 * @param attributes
1864 * the attributes from which to get a font
1865 * @return the font that correspond to the attributes
1867 public Font getFont(AttributeSet attributes)
1869 StyleContext context = (StyleContext) getAttributeContext();
1870 return context.getFont(attributes);
1874 * Extracts a foreground color from a set of attributes.
1876 * @param attributes
1877 * the attributes from which to get a foreground color
1878 * @return the foreground color that correspond to the attributes
1880 public Color getForeground(AttributeSet attributes)
1882 StyleContext context = (StyleContext) getAttributeContext();
1883 return context.getForeground(attributes);
1887 * Returns the logical <code>Style</code> for the specified position.
1889 * @param position
1890 * the position from which to query to logical style
1891 * @return the logical <code>Style</code> for the specified position
1893 public Style getLogicalStyle(int position)
1895 Element paragraph = getParagraphElement(position);
1896 AttributeSet attributes = paragraph.getAttributes();
1897 AttributeSet a = attributes.getResolveParent();
1898 // If the resolve parent is not of type Style, we return null.
1899 if (a instanceof Style)
1900 return (Style) a;
1901 return null;
1905 * Returns the paragraph element for the specified position. If the position
1906 * is outside the bounds of the document's root element, then the closest
1907 * element is returned. That is the last paragraph if
1908 * <code>position >= endIndex</code> or the first paragraph if
1909 * <code>position < startIndex</code>.
1911 * @param position
1912 * the position for which to query the paragraph element
1913 * @return the paragraph element for the specified position
1915 public Element getParagraphElement(int position)
1917 Element e = getDefaultRootElement();
1918 while (!e.isLeaf())
1919 e = e.getElement(e.getElementIndex(position));
1921 if (e != null)
1922 return e.getParentElement();
1923 return e;
1927 * Looks up and returns a named <code>Style</code>.
1929 * @param nm
1930 * the name of the <code>Style</code>
1931 * @return the found <code>Style</code> of <code>null</code> if no such
1932 * <code>Style</code> exists
1934 public Style getStyle(String nm)
1936 StyleContext context = (StyleContext) getAttributeContext();
1937 return context.getStyle(nm);
1941 * Removes a named <code>Style</code> from the style hierarchy.
1943 * @param nm
1944 * the name of the <code>Style</code> to be removed
1946 public void removeStyle(String nm)
1948 StyleContext context = (StyleContext) getAttributeContext();
1949 context.removeStyle(nm);
1953 * Sets text attributes for the fragment specified by <code>offset</code>
1954 * and <code>length</code>.
1956 * @param offset
1957 * the start offset of the fragment
1958 * @param length
1959 * the length of the fragment
1960 * @param attributes
1961 * the text attributes to set
1962 * @param replace
1963 * if <code>true</code>, the attributes of the current selection
1964 * are overridden, otherwise they are merged
1966 public void setCharacterAttributes(int offset, int length,
1967 AttributeSet attributes, boolean replace)
1969 // Exit early if length is 0, so no DocumentEvent is created or fired.
1970 if (length == 0)
1971 return;
1974 // Must obtain a write lock for this method. writeLock() and
1975 // writeUnlock() should always be in try/finally block to make
1976 // sure that locking happens in a balanced manner.
1977 writeLock();
1978 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
1979 length,
1980 DocumentEvent.EventType.CHANGE);
1982 // Modify the element structure so that the interval begins at an
1983 // element
1984 // start and ends at an element end.
1985 buffer.change(offset, length, ev);
1987 // Visit all paragraph elements within the specified interval
1988 int end = offset + length;
1989 Element curr;
1990 for (int pos = offset; pos < end;)
1992 // Get the CharacterElement at offset pos.
1993 curr = getCharacterElement(pos);
1994 if (pos == curr.getEndOffset())
1995 break;
1997 MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes();
1998 ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace));
1999 // If replace is true, remove all the old attributes.
2000 if (replace)
2001 a.removeAttributes(a);
2002 // Add all the new attributes.
2003 a.addAttributes(attributes);
2004 // Increment pos so we can check the next CharacterElement.
2005 pos = curr.getEndOffset();
2007 fireChangedUpdate(ev);
2008 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2010 finally
2012 writeUnlock();
2017 * Sets the logical style for the paragraph at the specified position.
2019 * @param position
2020 * the position at which the logical style is added
2021 * @param style
2022 * the style to set for the current paragraph
2024 public void setLogicalStyle(int position, Style style)
2026 Element el = getParagraphElement(position);
2027 // getParagraphElement doesn't return null but subclasses might so
2028 // we check for null here.
2029 if (el == null)
2030 return;
2033 writeLock();
2034 if (el instanceof AbstractElement)
2036 AbstractElement ael = (AbstractElement) el;
2037 ael.setResolveParent(style);
2038 int start = el.getStartOffset();
2039 int end = el.getEndOffset();
2040 DefaultDocumentEvent ev = new DefaultDocumentEvent(start,
2041 end - start,
2042 DocumentEvent.EventType.CHANGE);
2043 fireChangedUpdate(ev);
2044 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2046 else
2047 throw new AssertionError(
2048 "paragraph elements are expected to be"
2049 + "instances of AbstractDocument.AbstractElement");
2051 finally
2053 writeUnlock();
2058 * Sets text attributes for the paragraph at the specified fragment.
2060 * @param offset
2061 * the beginning of the fragment
2062 * @param length
2063 * the length of the fragment
2064 * @param attributes
2065 * the text attributes to set
2066 * @param replace
2067 * if <code>true</code>, the attributes of the current selection
2068 * are overridden, otherwise they are merged
2070 public void setParagraphAttributes(int offset, int length,
2071 AttributeSet attributes, boolean replace)
2075 // Must obtain a write lock for this method. writeLock() and
2076 // writeUnlock() should always be in try/finally blocks to make
2077 // sure that locking occurs in a balanced manner.
2078 writeLock();
2080 // Create a DocumentEvent to use for changedUpdate().
2081 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
2082 length,
2083 DocumentEvent.EventType.CHANGE);
2085 // Have to iterate through all the _paragraph_ elements that are
2086 // contained or partially contained in the interval
2087 // (offset, offset + length).
2088 Element rootElement = getDefaultRootElement();
2089 int startElement = rootElement.getElementIndex(offset);
2090 int endElement = rootElement.getElementIndex(offset + length - 1);
2091 if (endElement < startElement)
2092 endElement = startElement;
2094 for (int i = startElement; i <= endElement; i++)
2096 Element par = rootElement.getElement(i);
2097 MutableAttributeSet a = (MutableAttributeSet) par.getAttributes();
2098 // Add the change to the DocumentEvent.
2099 ev.addEdit(new AttributeUndoableEdit(par, attributes, replace));
2100 // If replace is true remove the old attributes.
2101 if (replace)
2102 a.removeAttributes(a);
2103 // Add the new attributes.
2104 a.addAttributes(attributes);
2106 fireChangedUpdate(ev);
2107 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2109 finally
2111 writeUnlock();
2116 * Called in response to content insert actions. This is used to update the
2117 * element structure.
2119 * @param ev
2120 * the <code>DocumentEvent</code> describing the change
2121 * @param attr
2122 * the attributes for the change
2124 protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
2126 int offs = ev.getOffset();
2127 int len = ev.getLength();
2128 int endOffs = offs + len;
2129 if (attr == null)
2130 attr = SimpleAttributeSet.EMPTY;
2132 // Paragraph attributes are fetched from the point _after_ the insertion.
2133 Element paragraph = getParagraphElement(endOffs);
2134 AttributeSet pAttr = paragraph.getAttributes();
2135 // Character attributes are fetched from the actual insertion point.
2136 Element paragraph2 = getParagraphElement(offs);
2137 int contIndex = paragraph2.getElementIndex(offs);
2138 Element content = paragraph2.getElement(contIndex);
2139 AttributeSet cAttr = content.getAttributes();
2141 boolean insertAtBoundary = content.getEndOffset() == endOffs;
2144 Segment s = new Segment();
2145 ArrayList buf = new ArrayList();
2146 ElementSpec lastStartTag = null;
2147 boolean insertAfterNewline = false;
2148 short lastStartDir = ElementSpec.OriginateDirection;
2150 // Special handle if we are inserting after a newline.
2151 if (offs > 0)
2153 getText(offs - 1, 1, s);
2154 if (s.array[s.offset] == '\n')
2156 insertAfterNewline = true;
2157 lastStartDir = insertAfterNewline(paragraph, paragraph2,
2158 pAttr, buf, offs,
2159 endOffs);
2160 // Search last start tag.
2161 for (int i = buf.size() - 1; i >= 0 && lastStartTag == null;
2162 i--)
2164 ElementSpec tag = (ElementSpec) buf.get(i);
2165 if (tag.getType() == ElementSpec.StartTagType)
2167 lastStartTag = tag;
2174 // If we are not inserting after a newline, the paragraph attributes
2175 // come from the paragraph under the insertion point.
2176 if (! insertAfterNewline)
2177 pAttr = paragraph2.getAttributes();
2179 // Scan text and build up the specs.
2180 getText(offs, len, s);
2181 int end = s.offset + s.count;
2182 int last = s.offset;
2183 for (int i = s.offset; i < end; i++)
2185 if (s.array[i] == '\n')
2187 int breakOffs = i + 1;
2188 buf.add(new ElementSpec(attr, ElementSpec.ContentType,
2189 breakOffs - last));
2190 buf.add(new ElementSpec(null, ElementSpec.EndTagType));
2191 lastStartTag = new ElementSpec(pAttr,
2192 ElementSpec.StartTagType);
2193 buf.add(lastStartTag);
2194 last = breakOffs;
2198 // Need to add a tailing content tag if we didn't finish at a boundary.
2199 if (last < end)
2201 buf.add(new ElementSpec(attr, ElementSpec.ContentType,
2202 end - last));
2205 // Now we need to fix up the directions of the specs.
2206 ElementSpec first = (ElementSpec) buf.get(0);
2207 int doclen = getLength();
2209 // Maybe join-previous the first tag if it is content and has
2210 // the same attributes as the previous character run.
2211 if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr))
2212 first.setDirection(ElementSpec.JoinPreviousDirection);
2214 // Join-fracture or join-next the last start tag if necessary.
2215 if (lastStartTag != null)
2217 if (insertAfterNewline)
2218 lastStartTag.setDirection(lastStartDir);
2219 else if (paragraph2.getEndOffset() != endOffs)
2220 lastStartTag.setDirection(ElementSpec.JoinFractureDirection);
2221 else
2223 Element par = paragraph2.getParentElement();
2224 int par2Index = par.getElementIndex(offs);
2225 if (par2Index + 1 < par.getElementCount()
2226 && ! par.getElement(par2Index + 1).isLeaf())
2227 lastStartTag.setDirection(ElementSpec.JoinNextDirection);
2231 // Join-next last tag if possible.
2232 if (insertAtBoundary && endOffs < doclen)
2234 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
2235 if (lastTag.getType() == ElementSpec.ContentType
2236 && ((lastStartTag == null
2237 && (paragraph == paragraph2 || insertAfterNewline))
2238 || (lastStartTag != null
2239 && lastStartTag.getDirection() != ElementSpec.OriginateDirection)))
2241 int nextIndex = paragraph.getElementIndex(endOffs);
2242 Element nextRun = paragraph.getElement(nextIndex);
2243 if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes()))
2244 lastTag.setDirection(ElementSpec.JoinNextDirection);
2248 else if (! insertAtBoundary && lastStartTag != null
2249 && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection)
2251 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
2252 if (lastTag.getType() == ElementSpec.ContentType
2253 && lastTag.getDirection() != ElementSpec.JoinPreviousDirection
2254 && attr.isEqual(cAttr))
2256 lastTag.setDirection(ElementSpec.JoinNextDirection);
2260 ElementSpec[] specs = new ElementSpec[buf.size()];
2261 specs = (ElementSpec[]) buf.toArray(specs);
2262 buffer.insert(offs, len, specs, ev);
2264 catch (BadLocationException ex)
2266 // Ignore this. Comment out for debugging.
2267 ex.printStackTrace();
2269 super.insertUpdate(ev, attr);
2272 private short insertAfterNewline(Element par1, Element par2,
2273 AttributeSet attr, ArrayList buf,
2274 int offs, int endOffs)
2276 short dir = 0;
2277 if (par1.getParentElement() == par2.getParentElement())
2279 ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType);
2280 buf.add(tag);
2281 tag = new ElementSpec(attr, ElementSpec.StartTagType);
2282 buf.add(tag);
2283 if (par2.getEndOffset() != endOffs)
2284 dir = ElementSpec.JoinFractureDirection;
2285 else
2287 Element par = par2.getParentElement();
2288 if (par.getElementIndex(offs) + 1 < par.getElementCount())
2289 dir = ElementSpec.JoinNextDirection;
2292 else
2294 // For text with more than 2 levels, find the common parent of
2295 // par1 and par2.
2296 ArrayList parentsLeft = new ArrayList();
2297 ArrayList parentsRight = new ArrayList();
2298 Element e = par2;
2299 while (e != null)
2301 parentsLeft.add(e);
2302 e = e.getParentElement();
2304 e = par1;
2305 int leftIndex = -1;
2306 while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1)
2308 parentsRight.add(e);
2309 e = e.getParentElement();
2312 if (e != null)
2315 // e is now the common parent.
2316 // Insert the end tags.
2317 for (int c = 0; c < leftIndex; c++)
2319 buf.add(new ElementSpec(null, ElementSpec.EndTagType));
2321 // Insert the start tags.
2322 for (int c = parentsRight.size() - 1; c >= 0; c--)
2324 Element el = (Element) parentsRight.get(c);
2325 ElementSpec tag = new ElementSpec(el.getAttributes(),
2326 ElementSpec.StartTagType);
2327 if (c > 0)
2328 tag.setDirection(ElementSpec.JoinNextDirection);
2329 buf.add(tag);
2331 if (parentsRight.size() > 0)
2332 dir = ElementSpec.JoinNextDirection;
2333 else
2334 dir = ElementSpec.JoinFractureDirection;
2336 else
2337 assert false;
2339 return dir;
2343 * A helper method to set up the ElementSpec buffer for the special case of an
2344 * insertion occurring immediately after a newline.
2346 * @param specs
2347 * the ElementSpec buffer to initialize.
2349 short handleInsertAfterNewline(Vector specs, int offset, int endOffset,
2350 Element prevParagraph, Element paragraph,
2351 AttributeSet a)
2353 if (prevParagraph.getParentElement() == paragraph.getParentElement())
2355 specs.add(new ElementSpec(a, ElementSpec.EndTagType));
2356 specs.add(new ElementSpec(a, ElementSpec.StartTagType));
2357 if (paragraph.getStartOffset() != endOffset)
2358 return ElementSpec.JoinFractureDirection;
2359 // If there is an Element after this one, use JoinNextDirection.
2360 Element parent = paragraph.getParentElement();
2361 if (parent.getElementCount() > (parent.getElementIndex(offset) + 1))
2362 return ElementSpec.JoinNextDirection;
2364 return ElementSpec.OriginateDirection;
2368 * Updates the document structure in response to text removal. This is
2369 * forwarded to the {@link ElementBuffer} of this document. Any changes to the
2370 * document structure are added to the specified document event and sent to
2371 * registered listeners.
2373 * @param ev
2374 * the document event that records the changes to the document
2376 protected void removeUpdate(DefaultDocumentEvent ev)
2378 super.removeUpdate(ev);
2379 buffer.remove(ev.getOffset(), ev.getLength(), ev);
2383 * Returns an enumeration of all style names.
2385 * @return an enumeration of all style names
2387 public Enumeration<?> getStyleNames()
2389 StyleContext context = (StyleContext) getAttributeContext();
2390 return context.getStyleNames();
2394 * Called when any of this document's styles changes.
2396 * @param style
2397 * the style that changed
2399 protected void styleChanged(Style style)
2401 // Nothing to do here. This is intended to be overridden by subclasses.
2405 * Inserts a bulk of structured content at once.
2407 * @param offset
2408 * the offset at which the content should be inserted
2409 * @param data
2410 * the actual content spec to be inserted
2412 protected void insert(int offset, ElementSpec[] data)
2413 throws BadLocationException
2415 if (data == null || data.length == 0)
2416 return;
2419 // writeLock() and writeUnlock() should always be in a try/finally
2420 // block so that locking balance is guaranteed even if some
2421 // exception is thrown.
2422 writeLock();
2424 // First we collect the content to be inserted.
2425 CPStringBuilder contentBuffer = new CPStringBuilder();
2426 for (int i = 0; i < data.length; i++)
2428 // Collect all inserts into one so we can get the correct
2429 // ElementEdit
2430 ElementSpec spec = data[i];
2431 if (spec.getArray() != null && spec.getLength() > 0)
2432 contentBuffer.append(spec.getArray(), spec.getOffset(),
2433 spec.getLength());
2436 int length = contentBuffer.length();
2438 // If there was no content inserted then exit early.
2439 if (length == 0)
2440 return;
2442 Content c = getContent();
2443 UndoableEdit edit = c.insertString(offset,
2444 contentBuffer.toString());
2446 // Create the DocumentEvent with the ElementEdit added
2447 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
2448 length,
2449 DocumentEvent.EventType.INSERT);
2451 ev.addEdit(edit);
2453 // Finally we must update the document structure and fire the insert
2454 // update event.
2455 buffer.insert(offset, length, data, ev);
2457 super.insertUpdate(ev, null);
2459 ev.end();
2460 fireInsertUpdate(ev);
2461 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2463 finally
2465 writeUnlock();
2470 * Initializes the <code>DefaultStyledDocument</code> with the specified
2471 * data.
2473 * @param data
2474 * the specification of the content with which the document is
2475 * initialized
2477 protected void create(ElementSpec[] data)
2482 // Clear content if there is some.
2483 int len = getLength();
2484 if (len > 0)
2485 remove(0, len);
2487 writeLock();
2489 // Now we insert the content.
2490 StringBuilder b = new StringBuilder();
2491 for (int i = 0; i < data.length; ++i)
2493 ElementSpec el = data[i];
2494 if (el.getArray() != null && el.getLength() > 0)
2495 b.append(el.getArray(), el.getOffset(), el.getLength());
2497 Content content = getContent();
2498 UndoableEdit cEdit = content.insertString(0, b.toString());
2500 len = b.length();
2501 DefaultDocumentEvent ev =
2502 new DefaultDocumentEvent(0, b.length(),
2503 DocumentEvent.EventType.INSERT);
2504 ev.addEdit(cEdit);
2506 buffer.create(len, data, ev);
2508 // For the bidi update.
2509 super.insertUpdate(ev, null);
2511 ev.end();
2512 fireInsertUpdate(ev);
2513 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2515 catch (BadLocationException ex)
2517 AssertionError err = new AssertionError("Unexpected bad location");
2518 err.initCause(ex);
2519 throw err;
2521 finally
2523 writeUnlock();