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 gnu
.java
.lang
.CPStringBuilder
;
43 import java
.awt
.Color
;
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
;
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
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
81 * A copy of the old attributes.
83 protected AttributeSet copy
;
88 protected AttributeSet newAttributes
;
91 * If the new attributes replaced the old attributes or if they only were
94 protected boolean isReplacing
;
97 * The element that has changed.
99 protected Element element
;
102 * Creates a new <code>AttributeUndoableEdit</code>.
105 * the element that changes attributes
109 * if the new attributes replace the old or only append to them
111 public AttributeUndoableEdit(Element el
, AttributeSet newAtts
,
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>.
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.
144 AttributeSet atts
= element
.getAttributes();
145 if (atts
instanceof MutableAttributeSet
)
147 MutableAttributeSet mutable
= (MutableAttributeSet
) atts
;
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
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.
209 * The direction of the tag.
214 * The offset of the content.
219 * The length of the content.
224 * The actual 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.
238 * the attributes for the element to be created
240 * the type of the tag
242 public ElementSpec(AttributeSet a
, short type
)
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.
253 * the attributes for the element to be created
255 * the type of the tag
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.
268 * the attributes for the element to be created
270 * the type of the tag
274 * the offset into the <code>txt</code> array
276 * the length of the element
278 public ElementSpec(AttributeSet a
, short type
, char[] txt
, int offs
, int len
)
285 direction
= OriginateDirection
;
289 * Sets the type of the element.
292 * the type of the element to be set
294 public void setType(short type
)
300 * Returns the type of the element.
302 * @return the type of the element
304 public short getType()
310 * Sets the direction of the element.
313 * the direction of the element to be set
315 public void setDirection(short dir
)
321 * Returns the direction of the element.
323 * @return the direction of the element
325 public short getDirection()
331 * Returns the attributes of the element.
333 * @return the attributes of the element
335 public AttributeSet
getAttributes()
341 * Returns the actual content of the element.
343 * @return the actual content of the element
345 public char[] getArray()
351 * Returns the offset of the content.
353 * @return the offset of the content
355 public int getOffset()
361 * Returns the length of the content.
363 * @return the length of the content
365 public int getLength()
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();
383 b
.append("StartTag");
400 case JoinPreviousDirection
:
401 b
.append("JoinPrevious");
403 case JoinNextDirection
:
404 b
.append("JoinNext");
406 case OriginateDirection
:
407 b
.append("Originate");
409 case JoinFractureDirection
:
410 b
.append("Fracture");
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.
439 /** The element to edit . */
442 /** The index of the change. */
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.
457 * Creates a new Edit for the specified element at index i.
459 * @param el the element
462 Edit(Element el
, int i
)
468 * Creates a new Edit for the specified element at index i.
470 * @param el the element
472 * @param frac if this is a fracture edit or not
474 Edit(Element el
, int i
, boolean 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. */
492 /** Holds the end offset for structural changes. */
493 private int endOffset
;
495 /** Holds the length of structural changes. */
498 /** Holds the position of the change. */
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.
539 * the root element for this <code>ElementBuffer</code>
541 public ElementBuffer(Element 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()
557 * Removes the content. This method sets some internal parameters and
558 * delegates the work to {@link #removeUpdate}.
561 * the offset from which content is remove
563 * the length of the removed content
565 * the document event that records the changes
567 public void remove(int offs
, int len
, DefaultDocumentEvent ev
)
569 prepareEdit(offs
, len
);
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
579 protected void removeUpdate()
581 removeElements(root
, offset
, endOffset
);
584 private boolean removeElements(Element elem
, int rmOffs0
, int rmOffs1
)
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
);
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
))
622 for (int i
= index0
; i
<= index1
; i
++)
624 ec
.removed
.add(elem
.getElement(i
));
626 Element e
= join(elem
, child0
, child1
, rmOffs0
, rmOffs1
);
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.
647 else if (child1
.getStartOffset() == rmOffs1
)
649 // End element not touched.
652 if (rmIndex0
<= rmIndex1
)
656 for (int i
= rmIndex0
; i
<= rmIndex1
; i
++)
658 ec
.removed
.add(elem
.getElement(i
));
662 if(removeElements(child0
, rmOffs0
, rmOffs1
))
664 ec
.removed
.add(0, child0
);
670 if(removeElements(child1
, rmOffs0
, rmOffs1
))
672 ec
.removed
.add(child1
);
681 // Return true if we no longer have any children.
682 if(elem
.getElementCount() == (ec
.removed
.size() - ec
.added
.size()))
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
);
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
);
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)
718 // Reset root element's attributes.
719 AttributeSet newAtts
= null;
720 if (n
> 0 && data
[0].getType() == ElementSpec
.StartTagType
)
721 newAtts
= data
[0].getAttributes();
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)
740 private boolean canJoin(Element e0
, Element e1
)
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
)
752 // Only join leaves if the attributes match, otherwise
753 // style information will be lost.
754 ret
= e0
.getAttributes().isEqual(e1
.getAttributes());
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();
764 ret
= name0
.equals(name1
);
765 else if (name1
!= null)
766 ret
= name1
.equals(name0
);
767 else // Both names null.
775 private Element
join(Element p
, Element left
, Element right
, int rmOffs0
,
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
)
799 Element rj
= right
.getElement(rjIndex
);
800 if (rj
.getStartOffset() == rmOffs1
)
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.
814 Element e
= join(joined
, lj
, rj
, rmOffs0
, rmOffs1
);
821 children
.add(cloneAsNecessary(joined
, lj
, rmOffs0
, rmOffs1
));
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
);
843 assert false : "Must not happen";
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
);
858 // need to do the other end
859 while (elementStack
.size() != 0)
863 split(offset
+ length
, 0);
865 while (elementStack
.size() != 0)
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()}.
879 * the start index of the interval to be changed
881 * the length of the interval to be changed
883 * the <code>DefaultDocumentEvent</code> describing the change
885 public void change(int offset
, int length
, DefaultDocumentEvent ev
)
887 prepareEdit(offset
, length
);
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
);
924 else if (clonee
instanceof LeafElement
)
926 clone
= new LeafElement(parent
, clonee
.getAttributes(),
927 clonee
.getStartOffset(),
928 clonee
.getEndOffset());
933 private Element
cloneAsNecessary(Element parent
, Element clonee
,
934 int rmOffs0
, int rmOffs1
)
939 cloned
= createLeafElement(parent
, clonee
.getAttributes(),
940 clonee
.getStartOffset(),
941 clonee
.getEndOffset());
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
,
958 Element
[] children
= new Element
[childrenList
.size()];
959 children
= (Element
[]) childrenList
.toArray(children
);
960 ((BranchElement
) e
).replace(0, 0, children
);
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.
972 * the location in the document at which the content is inserted
974 * the length of the inserted content
976 * the element specifications for the content to be inserted
978 * the document event that is updated to reflect the structural
981 public void insert(int offset
, int length
, ElementSpec
[] data
,
982 DefaultDocumentEvent ev
)
986 prepareEdit(offset
, length
);
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
;
1002 this.endOffset
= offset
+ length
;
1003 this.length
= length
;
1006 edits
= new ArrayList();
1010 if (elementStack
== null)
1011 elementStack
= new Stack();
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
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
);
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
);
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
);
1073 createdFracture
= false;
1075 // Insert first content tag.
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
;
1090 createFracture(data
);
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
)
1104 // Pop the remaining stack.
1105 while (elementStack
.size() != 0)
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
))
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
)
1130 p
< data
.length
&& data
[p
].getType() == ElementSpec
.EndTagType
;
1134 Edit edit
= insertPath
[insertPath
.length
- p
- 1];
1136 edit
.removed
.add(0, edit
.e
.getElement(edit
.index
));
1142 Edit edit
= (Edit
) elementStack
.peek();
1144 if ((edit
.added
.size() > 0) || (edit
.removed
.size() > 0))
1148 else if (! elementStack
.isEmpty())
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())
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
);
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
);
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);
1203 assert false; // Must not happen.
1205 elementStack
.push(new Edit(parent
, 0, true));
1208 Element branch
= createBranchElement(edit
.e
,
1209 spec
.getAttributes());
1210 edit
.added
.add(branch
);
1211 elementStack
.push(new Edit(branch
, 0));
1215 case ElementSpec
.EndTagType
:
1218 case ElementSpec
.ContentType
:
1219 insertContentTag(spec
, edit
);
1225 * Inserts the first tag into the document.
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(),
1246 edit
.added
.add(newEl1
);
1247 edit
.removed
.add(current
);
1248 if (current
.getEndOffset() != endOffset
)
1249 recreateLeafs
= true;
1251 offsetLastIndex
= true;
1255 offsetLastIndex
= true;
1256 offsetLastIndexReplace
= true;
1259 case ElementSpec
.JoinNextDirection
:
1262 Element newEl1
= createLeafElement(edit
.e
,
1263 current
.getAttributes(),
1264 current
.getStartOffset(),
1266 edit
.added
.add(newEl1
);
1267 Element next
= edit
.e
.getElement(edit
.index
+ 1);
1269 newEl1
= createLeafElement(edit
.e
, next
.getAttributes(),
1270 offset
, next
.getEndOffset());
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
);
1281 default: // OriginateDirection.
1282 if (current
.getStartOffset() != offset
)
1284 Element newEl
= createLeafElement(edit
.e
,
1285 current
.getAttributes(),
1286 current
.getStartOffset(),
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;
1297 offsetLastIndex
= true;
1303 * Inserts a content element into the document structure.
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
);
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
);
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
);
1347 Element leaf
= createLeafElement(edit
.e
, tag
.getAttributes(), pos
,
1349 edit
.added
.add(leaf
);
1357 * This method fractures bottomost leaf in the elementStack. This
1358 * happens when the first inserted tag is not content.
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
);
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;
1377 offsetLastIndex
= true;
1380 private void fracture(int depth
)
1382 int len
= insertPath
.length
;
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
)
1396 if (! recreate
&& childChanged
)
1399 if (deepestChangedIndex
== -1)
1400 deepestChangedIndex
= lastChangedIndex
+ 1;
1403 if (! childChanged
&& edit
.index
< edit
.e
.getElementCount())
1405 childChanged
= true;
1406 lastChangedIndex
= i
;
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
];
1423 int changeLength
= insertPath
.length
;
1425 if (startIndex
+ 1 == changeLength
)
1426 child
= edit
.e
.getElement(edit
.index
);
1428 child
= edit
.e
.getElement(edit
.index
- 1);
1432 newChild
= createLeafElement(edit
.e
, child
.getAttributes(),
1433 Math
.max(endOffset
, child
.getStartOffset()),
1434 child
.getEndOffset());
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.
1459 if(offsetLastIndex
|| ! isEndLeaf
)
1462 child
= edit
.e
.getElement(edit
.index
);
1466 child
= edit
.e
.getElement(edit
.index
- 1);
1474 newChild
= createLeafElement(parent
, child
.getAttributes(),
1475 Math
.max(endOffset
, child
.getStartOffset()),
1476 child
.getEndOffset());
1480 newChild
= createBranchElement(parent
,
1481 child
.getAttributes());
1487 // Recreate the remaining children (there may be none).
1488 int childrenToMove
= edit
.e
.getElementCount() - edit
.index
;
1491 int childStartIndex
= 1;
1493 if (newChild
== null)
1495 // Last part of fracture.
1499 moveStartIndex
= edit
.index
+ 1;
1503 moveStartIndex
= edit
.index
;
1505 childStartIndex
= 0;
1506 children
= new Element
[childrenToMove
];
1514 moveStartIndex
= edit
.index
;
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
);
1537 private Element
recreateFracturedElement(Element parent
, Element toCopy
)
1542 recreated
= createLeafElement(parent
, toCopy
.getAttributes(),
1543 Math
.max(toCopy
.getStartOffset(), endOffset
),
1544 toCopy
.getEndOffset());
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
;
1563 private boolean split(int offs
, int len
)
1565 boolean splitEnd
= false;
1566 // Push the path to the stack.
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
1581 if (child
.getStartOffset() < offs
&& offs
< child
.getEndOffset())
1583 // We need to split, now see if the other end is within
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
);
1598 e
= createLeafElement(ec
.e
, child
.getAttributes(),
1601 e
= createLeafElement(ec
.e
, child
.getAttributes(),
1602 offs
+ len
, child
.getEndOffset());
1608 child
= ec
.e
.getElement(index1
);
1609 if ((offs
+ len
) == child
.getStartOffset())
1611 // End is already on a boundary.
1618 // Split the first location.
1620 child
= ec
.e
.getElement(index0
);
1621 ec
.removed
.add(child
);
1622 e
= createLeafElement(ec
.e
, child
.getAttributes(),
1623 child
.getStartOffset(), pos
);
1625 e
= createLeafElement(ec
.e
, child
.getAttributes(),
1626 pos
, child
.getEndOffset());
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
);
1641 ec
.removed
.add(child
);
1642 e
= createLeafElement(ec
.e
, child
.getAttributes(),
1643 child
.getStartOffset(), pos
);
1645 e
= createLeafElement(ec
.e
, child
.getAttributes(),
1646 pos
, child
.getEndOffset());
1659 * An element type for sections. This is a simple BranchElement with a unique
1662 protected class SectionElement
extends BranchElement
1665 * Creates a new SectionElement.
1667 public SectionElement()
1673 * Returns the name of the element. This method always returns
1674 * "section".
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)}.
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}.
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.
1751 * the <code>Content</code> buffer to use
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
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
);
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()
1799 SectionElement section
= new SectionElement();
1801 BranchElement paragraph
= new BranchElement(section
, null);
1802 tmp
= new Element
[1];
1804 section
.replace(0, 0, tmp
);
1806 Element leaf
= new LeafElement(paragraph
, null, 0, 1);
1807 tmp
= new Element
[1];
1809 paragraph
.replace(0, 0, tmp
);
1815 * Returns the <code>Element</code> that corresponds to the character at the
1816 * specified 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
);
1838 * Extracts a background color from a set of 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.
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.
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.
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
)
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>.
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();
1919 e
= e
.getElement(e
.getElementIndex(position
));
1922 return e
.getParentElement();
1927 * Looks up and returns a named <code>Style</code>.
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.
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>.
1957 * the start offset of the fragment
1959 * the length of the fragment
1961 * the text attributes to set
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.
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.
1978 DefaultDocumentEvent ev
= new DefaultDocumentEvent(offset
,
1980 DocumentEvent
.EventType
.CHANGE
);
1982 // Modify the element structure so that the interval begins at an
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
;
1990 for (int pos
= offset
; pos
< end
;)
1992 // Get the CharacterElement at offset pos.
1993 curr
= getCharacterElement(pos
);
1994 if (pos
== curr
.getEndOffset())
1997 MutableAttributeSet a
= (MutableAttributeSet
) curr
.getAttributes();
1998 ev
.addEdit(new AttributeUndoableEdit(curr
, attributes
, replace
));
1999 // If replace is true, remove all the old attributes.
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
));
2017 * Sets the logical style for the paragraph at the specified position.
2020 * the position at which the logical style is added
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.
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
,
2042 DocumentEvent
.EventType
.CHANGE
);
2043 fireChangedUpdate(ev
);
2044 fireUndoableEditUpdate(new UndoableEditEvent(this, ev
));
2047 throw new AssertionError(
2048 "paragraph elements are expected to be"
2049 + "instances of AbstractDocument.AbstractElement");
2058 * Sets text attributes for the paragraph at the specified fragment.
2061 * the beginning of the fragment
2063 * the length of the fragment
2065 * the text attributes to set
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.
2080 // Create a DocumentEvent to use for changedUpdate().
2081 DefaultDocumentEvent ev
= new DefaultDocumentEvent(offset
,
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.
2102 a
.removeAttributes(a
);
2103 // Add the new attributes.
2104 a
.addAttributes(attributes
);
2106 fireChangedUpdate(ev
);
2107 fireUndoableEditUpdate(new UndoableEditEvent(this, ev
));
2116 * Called in response to content insert actions. This is used to update the
2117 * element structure.
2120 * the <code>DocumentEvent</code> describing the change
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
;
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.
2153 getText(offs
- 1, 1, s
);
2154 if (s
.array
[s
.offset
] == '\n')
2156 insertAfterNewline
= true;
2157 lastStartDir
= insertAfterNewline(paragraph
, paragraph2
,
2160 // Search last start tag.
2161 for (int i
= buf
.size() - 1; i
>= 0 && lastStartTag
== null;
2164 ElementSpec tag
= (ElementSpec
) buf
.get(i
);
2165 if (tag
.getType() == ElementSpec
.StartTagType
)
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
,
2190 buf
.add(new ElementSpec(null, ElementSpec
.EndTagType
));
2191 lastStartTag
= new ElementSpec(pAttr
,
2192 ElementSpec
.StartTagType
);
2193 buf
.add(lastStartTag
);
2198 // Need to add a tailing content tag if we didn't finish at a boundary.
2201 buf
.add(new ElementSpec(attr
, ElementSpec
.ContentType
,
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
);
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
)
2277 if (par1
.getParentElement() == par2
.getParentElement())
2279 ElementSpec tag
= new ElementSpec(attr
, ElementSpec
.EndTagType
);
2281 tag
= new ElementSpec(attr
, ElementSpec
.StartTagType
);
2283 if (par2
.getEndOffset() != endOffs
)
2284 dir
= ElementSpec
.JoinFractureDirection
;
2287 Element par
= par2
.getParentElement();
2288 if (par
.getElementIndex(offs
) + 1 < par
.getElementCount())
2289 dir
= ElementSpec
.JoinNextDirection
;
2294 // For text with more than 2 levels, find the common parent of
2296 ArrayList parentsLeft
= new ArrayList();
2297 ArrayList parentsRight
= new ArrayList();
2302 e
= e
.getParentElement();
2306 while (e
!= null && (leftIndex
= parentsLeft
.indexOf(e
)) == 1)
2308 parentsRight
.add(e
);
2309 e
= e
.getParentElement();
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
);
2328 tag
.setDirection(ElementSpec
.JoinNextDirection
);
2331 if (parentsRight
.size() > 0)
2332 dir
= ElementSpec
.JoinNextDirection
;
2334 dir
= ElementSpec
.JoinFractureDirection
;
2343 * A helper method to set up the ElementSpec buffer for the special case of an
2344 * insertion occurring immediately after a newline.
2347 * the ElementSpec buffer to initialize.
2349 short handleInsertAfterNewline(Vector specs
, int offset
, int endOffset
,
2350 Element prevParagraph
, Element paragraph
,
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.
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.
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.
2408 * the offset at which the content should be inserted
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)
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.
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
2430 ElementSpec spec
= data
[i
];
2431 if (spec
.getArray() != null && spec
.getLength() > 0)
2432 contentBuffer
.append(spec
.getArray(), spec
.getOffset(),
2436 int length
= contentBuffer
.length();
2438 // If there was no content inserted then exit early.
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
,
2449 DocumentEvent
.EventType
.INSERT
);
2453 // Finally we must update the document structure and fire the insert
2455 buffer
.insert(offset
, length
, data
, ev
);
2457 super.insertUpdate(ev
, null);
2460 fireInsertUpdate(ev
);
2461 fireUndoableEditUpdate(new UndoableEditEvent(this, ev
));
2470 * Initializes the <code>DefaultStyledDocument</code> with the specified
2474 * the specification of the content with which the document is
2477 protected void create(ElementSpec
[] data
)
2482 // Clear content if there is some.
2483 int len
= getLength();
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());
2501 DefaultDocumentEvent ev
=
2502 new DefaultDocumentEvent(0, b
.length(),
2503 DocumentEvent
.EventType
.INSERT
);
2506 buffer
.create(len
, data
, ev
);
2508 // For the bidi update.
2509 super.insertUpdate(ev
, null);
2512 fireInsertUpdate(ev
);
2513 fireUndoableEditUpdate(new UndoableEditEvent(this, ev
));
2515 catch (BadLocationException ex
)
2517 AssertionError err
= new AssertionError("Unexpected bad location");