KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > DefaultStyledDocument


1 /*
2  * @(#)DefaultStyledDocument.java 1.124 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.awt.Color JavaDoc;
10 import java.awt.Component JavaDoc;
11 import java.awt.Font JavaDoc;
12 import java.awt.FontMetrics JavaDoc;
13 import java.awt.font.TextAttribute JavaDoc;
14 import java.util.Enumeration JavaDoc;
15 import java.util.Hashtable JavaDoc;
16 import java.util.Stack JavaDoc;
17 import java.util.Vector JavaDoc;
18 import java.util.ArrayList JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.ObjectInputStream JavaDoc;
21 import java.io.ObjectOutputStream JavaDoc;
22 import java.io.Serializable JavaDoc;
23 import javax.swing.Icon JavaDoc;
24 import javax.swing.event.*;
25 import javax.swing.undo.AbstractUndoableEdit JavaDoc;
26 import javax.swing.undo.CannotRedoException JavaDoc;
27 import javax.swing.undo.CannotUndoException JavaDoc;
28 import javax.swing.undo.UndoableEdit JavaDoc;
29 import javax.swing.SwingUtilities JavaDoc;
30
31 /**
32  * A document that can be marked up with character and paragraph
33  * styles in a manner similar to the Rich Text Format. The element
34  * structure for this document represents style crossings for
35  * style runs. These style runs are mapped into a paragraph element
36  * structure (which may reside in some other structure). The
37  * style runs break at paragraph boundaries since logical styles are
38  * assigned to paragraph boundaries.
39  * <p>
40  * <strong>Warning:</strong>
41  * Serialized objects of this class will not be compatible with
42  * future Swing releases. The current serialization support is
43  * appropriate for short term storage or RMI between applications running
44  * the same version of Swing. As of 1.4, support for long term storage
45  * of all JavaBeans<sup><font size="-2">TM</font></sup>
46  * has been added to the <code>java.beans</code> package.
47  * Please see {@link java.beans.XMLEncoder}.
48  *
49  * @author Timothy Prinzing
50  * @version 1.124 05/05/04
51  * @see Document
52  * @see AbstractDocument
53  */

54 public class DefaultStyledDocument extends AbstractDocument JavaDoc implements StyledDocument JavaDoc {
55
56     /**
57      * Constructs a styled document.
58      *
59      * @param c the container for the content
60      * @param styles resources and style definitions which may
61      * be shared across documents
62      */

63     public DefaultStyledDocument(Content c, StyleContext JavaDoc styles) {
64     super(c, styles);
65     listeningStyles = new Vector JavaDoc();
66     buffer = new ElementBuffer(createDefaultRoot());
67     Style JavaDoc defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
68     setLogicalStyle(0, defaultStyle);
69     }
70
71     /**
72      * Constructs a styled document with the default content
73      * storage implementation and a shared set of styles.
74      *
75      * @param styles the styles
76      */

77     public DefaultStyledDocument(StyleContext JavaDoc styles) {
78     this(new GapContent JavaDoc(BUFFER_SIZE_DEFAULT), styles);
79     }
80
81     /**
82      * Constructs a default styled document. This buffers
83      * input content by a size of <em>BUFFER_SIZE_DEFAULT</em>
84      * and has a style context that is scoped by the lifetime
85      * of the document and is not shared with other documents.
86      */

87     public DefaultStyledDocument() {
88     this(new GapContent JavaDoc(BUFFER_SIZE_DEFAULT), new StyleContext JavaDoc());
89     }
90
91     /**
92      * Gets the default root element.
93      *
94      * @return the root
95      * @see Document#getDefaultRootElement
96      */

97     public Element JavaDoc getDefaultRootElement() {
98     return buffer.getRootElement();
99     }
100
101     /**
102      * Initialize the document to reflect the given element
103      * structure (i.e. the structure reported by the
104      * <code>getDefaultRootElement</code> method. If the
105      * document contained any data it will first be removed.
106      */

107     protected void create(ElementSpec[] data) {
108     try {
109         if (getLength() != 0) {
110         remove(0, getLength());
111         }
112         writeLock();
113
114         // install the content
115
Content c = getContent();
116         int n = data.length;
117         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
118         for (int i = 0; i < n; i++) {
119         ElementSpec es = data[i];
120         if (es.getLength() > 0) {
121             sb.append(es.getArray(), es.getOffset(), es.getLength());
122         }
123         }
124         UndoableEdit JavaDoc cEdit = c.insertString(0, sb.toString());
125
126         // build the event and element structure
127
int length = sb.length();
128         DefaultDocumentEvent evnt =
129         new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT);
130         evnt.addEdit(cEdit);
131         buffer.create(length, data, evnt);
132
133         // update bidi (possibly)
134
super.insertUpdate(evnt, null);
135
136         // notify the listeners
137
evnt.end();
138         fireInsertUpdate(evnt);
139         fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
140     } catch (BadLocationException JavaDoc ble) {
141         throw new StateInvariantError JavaDoc("problem initializing");
142     } finally {
143         writeUnlock();
144     }
145     
146     }
147
148     /**
149      * Inserts new elements in bulk. This is useful to allow
150      * parsing with the document in an unlocked state and
151      * prepare an element structure modification. This method
152      * takes an array of tokens that describe how to update an
153      * element structure so the time within a write lock can
154      * be greatly reduced in an asynchronous update situation.
155      * <p>
156      * This method is thread safe, although most Swing methods
157      * are not. Please see
158      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
159      * and Swing</A> for more information.
160      *
161      * @param offset the starting offset >= 0
162      * @param data the element data
163      * @exception BadLocationException for an invalid starting offset
164      */

165     protected void insert(int offset, ElementSpec[] data) throws BadLocationException JavaDoc {
166     if (data == null || data.length == 0) {
167         return;
168     }
169
170     try {
171         writeLock();
172
173         // install the content
174
Content c = getContent();
175         int n = data.length;
176         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
177         for (int i = 0; i < n; i++) {
178         ElementSpec es = data[i];
179         if (es.getLength() > 0) {
180             sb.append(es.getArray(), es.getOffset(), es.getLength());
181         }
182         }
183         if (sb.length() == 0) {
184         // Nothing to insert, bail.
185
return;
186         }
187         UndoableEdit JavaDoc cEdit = c.insertString(offset, sb.toString());
188
189         // create event and build the element structure
190
int length = sb.length();
191         DefaultDocumentEvent evnt =
192         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT);
193         evnt.addEdit(cEdit);
194         buffer.insert(offset, length, data, evnt);
195         
196         // update bidi (possibly)
197
super.insertUpdate(evnt, null);
198
199         // notify the listeners
200
evnt.end();
201         fireInsertUpdate(evnt);
202         fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
203     } finally {
204         writeUnlock();
205     }
206     }
207
208     /**
209      * Adds a new style into the logical style hierarchy. Style attributes
210      * resolve from bottom up so an attribute specified in a child
211      * will override an attribute specified in the parent.
212      *
213      * @param nm the name of the style (must be unique within the
214      * collection of named styles). The name may be null if the style
215      * is unnamed, but the caller is responsible
216      * for managing the reference returned as an unnamed style can't
217      * be fetched by name. An unnamed style may be useful for things
218      * like character attribute overrides such as found in a style
219      * run.
220      * @param parent the parent style. This may be null if unspecified
221      * attributes need not be resolved in some other style.
222      * @return the style
223      */

224     public Style JavaDoc addStyle(String JavaDoc nm, Style JavaDoc parent) {
225     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
226     return styles.addStyle(nm, parent);
227     }
228
229     /**
230      * Removes a named style previously added to the document.
231      *
232      * @param nm the name of the style to remove
233      */

234     public void removeStyle(String JavaDoc nm) {
235     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
236     styles.removeStyle(nm);
237     }
238
239     /**
240      * Fetches a named style previously added.
241      *
242      * @param nm the name of the style
243      * @return the style
244      */

245     public Style JavaDoc getStyle(String JavaDoc nm) {
246     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
247     return styles.getStyle(nm);
248     }
249
250
251     /**
252      * Fetches the list of of style names.
253      *
254      * @return all the style names
255      */

256     public Enumeration JavaDoc<?> getStyleNames() {
257     return ((StyleContext JavaDoc) getAttributeContext()).getStyleNames();
258     }
259
260     /**
261      * Sets the logical style to use for the paragraph at the
262      * given position. If attributes aren't explicitly set
263      * for character and paragraph attributes they will resolve
264      * through the logical style assigned to the paragraph, which
265      * in turn may resolve through some hierarchy completely
266      * independent of the element hierarchy in the document.
267      * <p>
268      * This method is thread safe, although most Swing methods
269      * are not. Please see
270      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
271      * and Swing</A> for more information.
272      *
273      * @param pos the offset from the start of the document >= 0
274      * @param s the logical style to assign to the paragraph, null if none
275      */

276     public void setLogicalStyle(int pos, Style JavaDoc s) {
277     Element JavaDoc paragraph = getParagraphElement(pos);
278     if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
279         try {
280         writeLock();
281         StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
282         ((AbstractElement)paragraph).setResolveParent(s);
283         int p0 = paragraph.getStartOffset();
284         int p1 = paragraph.getEndOffset();
285         DefaultDocumentEvent e =
286           new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
287         e.addEdit(edit);
288         e.end();
289         fireChangedUpdate(e);
290         fireUndoableEditUpdate(new UndoableEditEvent(this, e));
291         } finally {
292         writeUnlock();
293         }
294     }
295     }
296
297     /**
298      * Fetches the logical style assigned to the paragraph
299      * represented by the given position.
300      *
301      * @param p the location to translate to a paragraph
302      * and determine the logical style assigned >= 0. This
303      * is an offset from the start of the document.
304      * @return the style, null if none
305      */

306     public Style JavaDoc getLogicalStyle(int p) {
307     Style JavaDoc s = null;
308     Element JavaDoc paragraph = getParagraphElement(p);
309     if (paragraph != null) {
310         AttributeSet JavaDoc a = paragraph.getAttributes();
311         AttributeSet JavaDoc parent = a.getResolveParent();
312         if (parent instanceof Style JavaDoc) {
313         s = (Style JavaDoc) parent;
314         }
315     }
316     return s;
317     }
318
319     /**
320      * Sets attributes for some part of the document.
321      * A write lock is held by this operation while changes
322      * are being made, and a DocumentEvent is sent to the listeners
323      * after the change has been successfully completed.
324      * <p>
325      * This method is thread safe, although most Swing methods
326      * are not. Please see
327      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
328      * and Swing</A> for more information.
329      *
330      * @param offset the offset in the document >= 0
331      * @param length the length >= 0
332      * @param s the attributes
333      * @param replace true if the previous attributes should be replaced
334      * before setting the new attributes
335      */

336     public void setCharacterAttributes(int offset, int length, AttributeSet JavaDoc s, boolean replace) {
337         if (length == 0) {
338             return;
339         }
340     try {
341         writeLock();
342         DefaultDocumentEvent changes =
343         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
344
345         // split elements that need it
346
buffer.change(offset, length, changes);
347
348         AttributeSet JavaDoc sCopy = s.copyAttributes();
349
350         // PENDING(prinz) - this isn't a very efficient way to iterate
351
int lastEnd = Integer.MAX_VALUE;
352         for (int pos = offset; pos < (offset + length); pos = lastEnd) {
353         Element JavaDoc run = getCharacterElement(pos);
354         lastEnd = run.getEndOffset();
355                 if (pos == lastEnd) {
356                     // offset + length beyond length of document, bail.
357
break;
358                 }
359         MutableAttributeSet JavaDoc attr = (MutableAttributeSet JavaDoc) run.getAttributes();
360         changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
361         if (replace) {
362             attr.removeAttributes(attr);
363         }
364         attr.addAttributes(s);
365         }
366         changes.end();
367         fireChangedUpdate(changes);
368         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
369     } finally {
370         writeUnlock();
371     }
372
373     }
374
375     /**
376      * Sets attributes for a paragraph.
377      * <p>
378      * This method is thread safe, although most Swing methods
379      * are not. Please see
380      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
381      * and Swing</A> for more information.
382      *
383      * @param offset the offset into the paragraph >= 0
384      * @param length the number of characters affected >= 0
385      * @param s the attributes
386      * @param replace whether to replace existing attributes, or merge them
387      */

388     public void setParagraphAttributes(int offset, int length, AttributeSet JavaDoc s,
389                        boolean replace) {
390     try {
391         writeLock();
392         DefaultDocumentEvent changes =
393         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
394
395         AttributeSet JavaDoc sCopy = s.copyAttributes();
396
397         // PENDING(prinz) - this assumes a particular element structure
398
Element JavaDoc section = getDefaultRootElement();
399         int index0 = section.getElementIndex(offset);
400         int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
401             boolean isI18N = Boolean.TRUE.equals(getProperty(I18NProperty));
402             boolean hasRuns = false;
403         for (int i = index0; i <= index1; i++) {
404         Element JavaDoc paragraph = section.getElement(i);
405         MutableAttributeSet JavaDoc attr = (MutableAttributeSet JavaDoc) paragraph.getAttributes();
406         changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
407         if (replace) {
408             attr.removeAttributes(attr);
409         }
410         attr.addAttributes(s);
411                 if (isI18N && !hasRuns) {
412                     hasRuns = (attr.getAttribute(TextAttribute.RUN_DIRECTION) != null);
413                 }
414         }
415
416             if (hasRuns) {
417                 updateBidi( changes );
418             }
419
420         changes.end();
421         fireChangedUpdate(changes);
422         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
423     } finally {
424         writeUnlock();
425     }
426     }
427
428     /**
429      * Gets the paragraph element at the offset <code>pos</code>.
430      * A paragraph consists of at least one child Element, which is usually
431      * a leaf.
432      *
433      * @param pos the starting offset >= 0
434      * @return the element
435      */

436     public Element JavaDoc getParagraphElement(int pos) {
437     Element JavaDoc e = null;
438     for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
439         int index = e.getElementIndex(pos);
440         e = e.getElement(index);
441     }
442     if(e != null)
443         return e.getParentElement();
444     return e;
445     }
446
447     /**
448      * Gets a character element based on a position.
449      *
450      * @param pos the position in the document >= 0
451      * @return the element
452      */

453     public Element JavaDoc getCharacterElement(int pos) {
454     Element JavaDoc e = null;
455     for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
456         int index = e.getElementIndex(pos);
457         e = e.getElement(index);
458     }
459     return e;
460     }
461
462     // --- local methods -------------------------------------------------
463

464     /**
465      * Updates document structure as a result of text insertion. This
466      * will happen within a write lock. This implementation simply
467      * parses the inserted content for line breaks and builds up a set
468      * of instructions for the element buffer.
469      *
470      * @param chng a description of the document change
471      * @param attr the attributes
472      */

473     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet JavaDoc attr) {
474     int offset = chng.getOffset();
475     int length = chng.getLength();
476     if (attr == null) {
477         attr = SimpleAttributeSet.EMPTY;
478     }
479
480     // Paragraph attributes should come from point after insertion.
481
// You really only notice this when inserting at a paragraph
482
// boundary.
483
Element JavaDoc paragraph = getParagraphElement(offset + length);
484     AttributeSet JavaDoc pattr = paragraph.getAttributes();
485     // Character attributes should come from actual insertion point.
486
Element JavaDoc pParagraph = getParagraphElement(offset);
487     Element JavaDoc run = pParagraph.getElement(pParagraph.getElementIndex
488                         (offset));
489     int endOffset = offset + length;
490     boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
491     AttributeSet JavaDoc cattr = run.getAttributes();
492
493     try {
494         Segment JavaDoc s = new Segment JavaDoc();
495         Vector JavaDoc parseBuffer = new Vector JavaDoc();
496         ElementSpec lastStartSpec = null;
497         boolean insertingAfterNewline = false;
498         short lastStartDirection = ElementSpec.OriginateDirection;
499         // Check if the previous character was a newline.
500
if (offset > 0) {
501         getText(offset - 1, 1, s);
502         if (s.array[s.offset] == '\n') {
503             // Inserting after a newline.
504
insertingAfterNewline = true;
505             lastStartDirection = createSpecsForInsertAfterNewline
506                       (paragraph, pParagraph, pattr, parseBuffer,
507                    offset, endOffset);
508             for(int counter = parseBuffer.size() - 1; counter >= 0;
509             counter--) {
510             ElementSpec spec = (ElementSpec)parseBuffer.
511                                 elementAt(counter);
512             if(spec.getType() == ElementSpec.StartTagType) {
513                 lastStartSpec = spec;
514                 break;
515             }
516             }
517         }
518         }
519         // If not inserting after a new line, pull the attributes for
520
// new paragraphs from the paragraph under the insertion point.
521
if(!insertingAfterNewline)
522         pattr = pParagraph.getAttributes();
523
524         getText(offset, length, s);
525         char[] txt = s.array;
526         int n = s.offset + s.count;
527         int lastOffset = s.offset;
528
529         for (int i = s.offset; i < n; i++) {
530         if (txt[i] == '\n') {
531             int breakOffset = i + 1;
532             parseBuffer.addElement(
533                         new ElementSpec(attr, ElementSpec.ContentType,
534                            breakOffset - lastOffset));
535             parseBuffer.addElement(
536                         new ElementSpec(null, ElementSpec.EndTagType));
537             lastStartSpec = new ElementSpec(pattr, ElementSpec.
538                            StartTagType);
539             parseBuffer.addElement(lastStartSpec);
540             lastOffset = breakOffset;
541         }
542         }
543         if (lastOffset < n) {
544         parseBuffer.addElement(
545                     new ElementSpec(attr, ElementSpec.ContentType,
546                        n - lastOffset));
547         }
548
549         ElementSpec first = (ElementSpec) parseBuffer.firstElement();
550
551         int docLength = getLength();
552
553         // Check for join previous of first content.
554
if(first.getType() == ElementSpec.ContentType &&
555            cattr.isEqual(attr)) {
556         first.setDirection(ElementSpec.JoinPreviousDirection);
557         }
558
559         // Do a join fracture/next for last start spec if necessary.
560
if(lastStartSpec != null) {
561         if(insertingAfterNewline) {
562             lastStartSpec.setDirection(lastStartDirection);
563         }
564         // Join to the fracture if NOT inserting at the end
565
// (fracture only happens when not inserting at end of
566
// paragraph).
567
else if(pParagraph.getEndOffset() != endOffset) {
568             lastStartSpec.setDirection(ElementSpec.
569                            JoinFractureDirection);
570         }
571         // Join to next if parent of pParagraph has another
572
// element after pParagraph, and it isn't a leaf.
573
else {
574             Element JavaDoc parent = pParagraph.getParentElement();
575             int pParagraphIndex = parent.getElementIndex(offset);
576             if((pParagraphIndex + 1) < parent.getElementCount() &&
577                !parent.getElement(pParagraphIndex + 1).isLeaf()) {
578             lastStartSpec.setDirection(ElementSpec.
579                            JoinNextDirection);
580             }
581         }
582         }
583
584         // Do a JoinNext for last spec if it is content, it doesn't
585
// already have a direction set, no new paragraphs have been
586
// inserted or a new paragraph has been inserted and its join
587
// direction isn't originate, and the element at endOffset
588
// is a leaf.
589
if(insertingAtBoundry && endOffset < docLength) {
590         ElementSpec last = (ElementSpec) parseBuffer.lastElement();
591         if(last.getType() == ElementSpec.ContentType &&
592            last.getDirection() != ElementSpec.JoinPreviousDirection &&
593            ((lastStartSpec == null && (paragraph == pParagraph ||
594                            insertingAfterNewline)) ||
595             (lastStartSpec != null && lastStartSpec.getDirection() !=
596              ElementSpec.OriginateDirection))) {
597             Element JavaDoc nextRun = paragraph.getElement(paragraph.
598                        getElementIndex(endOffset));
599             // Don't try joining to a branch!
600
if(nextRun.isLeaf() &&
601                attr.isEqual(nextRun.getAttributes())) {
602             last.setDirection(ElementSpec.JoinNextDirection);
603             }
604         }
605         }
606         // If not inserting at boundary and there is going to be a
607
// fracture, then can join next on last content if cattr
608
// matches the new attributes.
609
else if(!insertingAtBoundry && lastStartSpec != null &&
610             lastStartSpec.getDirection() ==
611             ElementSpec.JoinFractureDirection) {
612         ElementSpec last = (ElementSpec) parseBuffer.lastElement();
613         if(last.getType() == ElementSpec.ContentType &&
614            last.getDirection() != ElementSpec.JoinPreviousDirection &&
615            attr.isEqual(cattr)) {
616             last.setDirection(ElementSpec.JoinNextDirection);
617         }
618         }
619
620         // Check for the composed text element. If it is, merge the character attributes
621
// into this element as well.
622
if (Utilities.isComposedTextAttributeDefined(attr)) {
623             ((MutableAttributeSet JavaDoc)attr).addAttributes(cattr);
624             ((MutableAttributeSet JavaDoc)attr).addAttribute(AbstractDocument.ElementNameAttribute,
625                                                  AbstractDocument.ContentElementName);
626         }
627
628         ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
629         parseBuffer.copyInto(spec);
630         buffer.insert(offset, length, spec, chng);
631     } catch (BadLocationException JavaDoc bl) {
632     }
633
634         super.insertUpdate( chng, attr );
635     }
636
637     /**
638      * This is called by insertUpdate when inserting after a new line.
639      * It generates, in <code>parseBuffer</code>, ElementSpecs that will
640      * position the stack in <code>paragraph</code>.<p>
641      * It returns the direction the last StartSpec should have (this don't
642      * necessarily create the last start spec).
643      */

644     short createSpecsForInsertAfterNewline(Element JavaDoc paragraph,
645                 Element JavaDoc pParagraph, AttributeSet JavaDoc pattr, Vector JavaDoc parseBuffer,
646                          int offset, int endOffset) {
647     // Need to find the common parent of pParagraph and paragraph.
648
if(paragraph.getParentElement() == pParagraph.getParentElement()) {
649         // The simple (and common) case that pParagraph and
650
// paragraph have the same parent.
651
ElementSpec spec = new ElementSpec(pattr, ElementSpec.EndTagType);
652         parseBuffer.addElement(spec);
653         spec = new ElementSpec(pattr, ElementSpec.StartTagType);
654         parseBuffer.addElement(spec);
655         if(pParagraph.getEndOffset() != endOffset)
656         return ElementSpec.JoinFractureDirection;
657
658         Element JavaDoc parent = pParagraph.getParentElement();
659         if((parent.getElementIndex(offset) + 1) < parent.getElementCount())
660         return ElementSpec.JoinNextDirection;
661     }
662     else {
663         // Will only happen for text with more than 2 levels.
664
// Find the common parent of a paragraph and pParagraph
665
Vector JavaDoc leftParents = new Vector JavaDoc();
666         Vector JavaDoc rightParents = new Vector JavaDoc();
667         Element JavaDoc e = pParagraph;
668         while(e != null) {
669         leftParents.addElement(e);
670         e = e.getParentElement();
671         }
672         e = paragraph;
673         int leftIndex = -1;
674         while(e != null && (leftIndex = leftParents.indexOf(e)) == -1) {
675         rightParents.addElement(e);
676         e = e.getParentElement();
677         }
678         if(e != null) {
679         // e identifies the common parent.
680
// Build the ends.
681
for(int counter = 0; counter < leftIndex;
682             counter++) {
683             parseBuffer.addElement(new ElementSpec
684                           (null, ElementSpec.EndTagType));
685         }
686         // And the starts.
687
ElementSpec spec = null;
688         for(int counter = rightParents.size() - 1;
689             counter >= 0; counter--) {
690             spec = new ElementSpec(((Element JavaDoc)rightParents.
691                    elementAt(counter)).getAttributes(),
692                    ElementSpec.StartTagType);
693             if(counter > 0)
694             spec.setDirection(ElementSpec.JoinNextDirection);
695             parseBuffer.addElement(spec);
696         }
697         // If there are right parents, then we generated starts
698
// down the right subtree and there will be an element to
699
// join to.
700
if(rightParents.size() > 0)
701             return ElementSpec.JoinNextDirection;
702         // No right subtree, e.getElement(endOffset) is a
703
// leaf. There will be a facture.
704
return ElementSpec.JoinFractureDirection;
705         }
706         // else: Could throw an exception here, but should never get here!
707
}
708     return ElementSpec.OriginateDirection;
709     }
710
711     /**
712      * Updates document structure as a result of text removal.
713      *
714      * @param chng a description of the document change
715      */

716     protected void removeUpdate(DefaultDocumentEvent chng) {
717         super.removeUpdate(chng);
718     buffer.remove(chng.getOffset(), chng.getLength(), chng);
719     }
720
721     /**
722      * Creates the root element to be used to represent the
723      * default document structure.
724      *
725      * @return the element base
726      */

727     protected AbstractElement createDefaultRoot() {
728     // grabs a write-lock for this initialization and
729
// abandon it during initialization so in normal
730
// operation we can detect an illegitimate attempt
731
// to mutate attributes.
732
writeLock();
733     BranchElement section = new SectionElement();
734     BranchElement paragraph = new BranchElement(section, null);
735
736     LeafElement brk = new LeafElement(paragraph, null, 0, 1);
737     Element JavaDoc[] buff = new Element JavaDoc[1];
738     buff[0] = brk;
739     paragraph.replace(0, 0, buff);
740
741     buff[0] = paragraph;
742     section.replace(0, 0, buff);
743     writeUnlock();
744     return section;
745     }
746
747     /**
748      * Gets the foreground color from an attribute set.
749      *
750      * @param attr the attribute set
751      * @return the color
752      */

753     public Color JavaDoc getForeground(AttributeSet JavaDoc attr) {
754     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
755     return styles.getForeground(attr);
756     }
757
758     /**
759      * Gets the background color from an attribute set.
760      *
761      * @param attr the attribute set
762      * @return the color
763      */

764     public Color JavaDoc getBackground(AttributeSet JavaDoc attr) {
765     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
766     return styles.getBackground(attr);
767     }
768
769     /**
770      * Gets the font from an attribute set.
771      *
772      * @param attr the attribute set
773      * @return the font
774      */

775     public Font JavaDoc getFont(AttributeSet JavaDoc attr) {
776     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
777     return styles.getFont(attr);
778     }
779
780     /**
781      * Called when any of this document's styles have changed.
782      * Subclasses may wish to be intelligent about what gets damaged.
783      *
784      * @param style The Style that has changed.
785      */

786     protected void styleChanged(Style JavaDoc style) {
787         // Only propagate change updated if have content
788
if (getLength() != 0) {
789             // lazily create a ChangeUpdateRunnable
790
if (updateRunnable == null) {
791                 updateRunnable = new ChangeUpdateRunnable();
792             }
793             
794             // We may get a whole batch of these at once, so only
795
// queue the runnable if it is not already pending
796
synchronized(updateRunnable) {
797                 if (!updateRunnable.isPending) {
798                     SwingUtilities.invokeLater(updateRunnable);
799                     updateRunnable.isPending = true;
800                 }
801             }
802         }
803     }
804
805     /**
806      * Adds a document listener for notification of any changes.
807      *
808      * @param listener the listener
809      * @see Document#addDocumentListener
810      */

811     public void addDocumentListener(DocumentListener listener) {
812     synchronized(listeningStyles) {
813         int oldDLCount = listenerList.getListenerCount
814                                   (DocumentListener.class);
815         super.addDocumentListener(listener);
816         if (oldDLCount == 0) {
817         if (styleContextChangeListener == null) {
818             styleContextChangeListener =
819                           createStyleContextChangeListener();
820         }
821         if (styleContextChangeListener != null) {
822             StyleContext JavaDoc styles = (StyleContext JavaDoc)getAttributeContext();
823             styles.addChangeListener(styleContextChangeListener);
824         }
825         updateStylesListeningTo();
826         }
827     }
828     }
829
830     /**
831      * Removes a document listener.
832      *
833      * @param listener the listener
834      * @see Document#removeDocumentListener
835      */

836     public void removeDocumentListener(DocumentListener listener) {
837     synchronized(listeningStyles) {
838         super.removeDocumentListener(listener);
839         if (listenerList.getListenerCount(DocumentListener.class) == 0) {
840         for (int counter = listeningStyles.size() - 1; counter >= 0;
841              counter--) {
842             ((Style JavaDoc)listeningStyles.elementAt(counter)).
843                         removeChangeListener(styleChangeListener);
844         }
845         listeningStyles.removeAllElements();
846         if (styleContextChangeListener != null) {
847             StyleContext JavaDoc styles = (StyleContext JavaDoc)getAttributeContext();
848             styles.removeChangeListener(styleContextChangeListener);
849         }
850         }
851     }
852     }
853
854     /**
855      * Returns a new instance of StyleChangeHandler.
856      */

857     ChangeListener createStyleChangeListener() {
858     return new StyleChangeHandler();
859     }