2 * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
\r
4 * The program is provided "as is" without any warranty express or
\r
5 * implied, including the warranty of non-infringement and the implied
\r
6 * warranties of merchantibility and fitness for a particular purpose.
\r
7 * IBM will not be liable for any damages suffered by you as a result
\r
8 * of using the Program. In no event will IBM be liable for any
\r
9 * special, indirect or consequential damages or lost profits even if
\r
10 * IBM has been advised of the possibility of their occurrence. IBM
\r
11 * will not be liable for any third party claims against you.
\r
13 package com.ibm.richtext.styledtext;
\r
15 import com.ibm.richtext.textlayout.attributes.AttributeMap;
\r
17 import java.io.Externalizable;
\r
18 import java.io.ObjectInput;
\r
19 import java.io.ObjectOutput;
\r
20 import java.io.IOException;
\r
21 import java.text.CharacterIterator;
\r
24 * This class is an implementation of MText, a modifyable, styled text
\r
25 * storage model. Additionally, it supports persistance through the
\r
26 * Externalizable interface.
\r
31 10/28/96 {jf} - split the character and paragraph style access and setter function around...
\r
32 just to keep things interesting.
\r
33 8/7/96 {jf} - moved paragraph break implementation from AbstractText into Style text.
\r
34 - added countStyles, getStyles, and ReplaceStyles implementation.
\r
36 8/14/96 sfb eliminated StyleSheetIterator
\r
38 8/29/96 {jbr} changed iter-based replace method - doesn't call at() unless it is safe to do so
\r
39 Also, added checkStartAndLimit for debugging
\r
41 7/31/98 Switched from Style to AttributeMap
\r
45 public final class StyledText extends MText implements Externalizable
\r
47 static final String COPYRIGHT =
\r
48 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
\r
49 private static final int CURRENT_VERSION = 1;
\r
50 private static final long serialVersionUID = 22356934;
\r
52 /* unicode storage */
\r
53 private MCharBuffer fCharBuffer;
\r
54 /* character style storage */
\r
55 private MStyleBuffer fStyleBuffer;
\r
56 /* paragraph style storage */
\r
57 private MParagraphBuffer fParagraphBuffer;
\r
59 private transient int fTimeStamp = 0;
\r
60 private transient int[] fDamagedRange = { Integer.MAX_VALUE,
\r
61 Integer.MIN_VALUE };
\r
63 private static class ForceModifier extends StyleModifier {
\r
65 private AttributeMap fStyle = AttributeMap.EMPTY_ATTRIBUTE_MAP;
\r
67 void setStyle(AttributeMap style) {
\r
72 public AttributeMap modifyStyle(AttributeMap style) {
\r
78 // Keep this around foruse in replaceCharStylesWith. OK since
\r
79 // this class isn't threadsafe anyway.
\r
80 private transient ForceModifier forceModifier = null;
\r
82 //======================================================
\r
84 //======================================================
\r
86 * Create an empty text object.
\r
94 * Create an empty text object ready to hold at least capacity chars.
\r
95 * @param capacity the minimum capacity of the internal text buffer
\r
97 public StyledText(int capacity)
\r
99 fCharBuffer = capacity>0? new CharBuffer(capacity) : new CharBuffer();
\r
100 fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP);
\r
101 fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
\r
105 * Create a text object with the characters in the string,
\r
106 * in the given style.
\r
107 * @param string the initial contents
\r
108 * @param initialStyle the style of the initial text
\r
110 public StyledText(String string, AttributeMap initialStyle)
\r
112 fCharBuffer = new CharBuffer(string.length());
\r
113 fCharBuffer.replace(0, 0, string, 0, string.length());
\r
115 fStyleBuffer = new StyleBuffer(this, initialStyle);
\r
116 fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
\r
120 * Create a text object from the given source.
\r
121 * @param source the text to copy
\r
123 public StyledText(MConstText source) {
\r
129 * Create a text object from a subrange of the given source.
\r
130 * @param source the text to copy from
\r
131 * @param srcStart the index of the first character to copy
\r
132 * @param srcLimit the index after the last character to copy
\r
134 public StyledText(MConstText source, int srcStart, int srcLimit) {
\r
136 replace(0, 0, source, srcStart, srcLimit);
\r
139 public void writeExternal(ObjectOutput out) throws IOException {
\r
141 out.writeInt(CURRENT_VERSION);
\r
142 out.writeObject(fCharBuffer);
\r
143 out.writeObject(fStyleBuffer);
\r
144 out.writeObject(fParagraphBuffer);
\r
147 public void readExternal(ObjectInput in) throws IOException,
\r
148 ClassNotFoundException {
\r
150 int version = in.readInt();
\r
151 if (version != CURRENT_VERSION) {
\r
152 throw new IOException("Invalid version of StyledText: " + version);
\r
154 fCharBuffer = (MCharBuffer) in.readObject();
\r
155 fStyleBuffer = (MStyleBuffer) in.readObject();
\r
156 fParagraphBuffer = (MParagraphBuffer) in.readObject();
\r
158 resetDamagedRange();
\r
161 //======================================================
\r
162 // MConstText INTERFACES
\r
163 //======================================================
\r
165 //--------------------------------------------------------
\r
166 // character access
\r
167 //--------------------------------------------------------
\r
169 * Return the character at offset <code>pos</code>.
\r
170 * @param pos a valid offset into the text
\r
171 * @return the character at offset <code>pos</code>
\r
173 public char at(int pos)
\r
175 return fCharBuffer.at(pos);
\r
179 * Copy the characters in the range [<code>start</code>, <code>limit</code>)
\r
180 * into the array <code>dst</code>, beginning at <code>dstStart</code>.
\r
181 * @param start offset of first character which will be copied into the array
\r
182 * @param limit offset immediately after the last character which will be copied into the array
\r
183 * @param dst array in which to copy characters. The length of <code>dst</code> must be at least
\r
184 * (<code>dstStart + limit - start</code>).
\r
186 public void extractChars(int start, int limit, char[] dst, int dstStart)
\r
188 fCharBuffer.at(start, limit, dst, dstStart);
\r
191 //-------------------------------------------------------
\r
192 // text model creation
\r
193 //-------------------------------------------------------
\r
195 * Create an MConstText containing the characters and styles in the range
\r
196 * [<code>start</code>, <code>limit</code>).
\r
197 * @param start offset of first character in the new text
\r
198 * @param limit offset immediately after the last character in the new text
\r
199 * @return an MConstText object containing the characters and styles in the given range
\r
201 public MConstText extract(int start, int limit)
\r
203 return extractWritable(start, limit);
\r
207 * Create an MText containing the characters and styles in the range
\r
208 * [<code>start</code>, <code>limit</code>).
\r
209 * @param start offset of first character in the new text
\r
210 * @param limit offset immediately after the last character in the new text
\r
211 * @return an MConstText object containing the characters and styles in the given range
\r
213 public MText extractWritable(int start, int limit)
\r
215 MText text = new StyledText();
\r
216 text.replace(0, 0, this, start, limit);
\r
217 text.resetDamagedRange();
\r
221 //--------------------------------------------------------
\r
223 //--------------------------------------------------------
\r
225 * Return the length of the MConstText object. The length is the number of characters in the text.
\r
226 * @return the length of the MConstText object
\r
228 public int length()
\r
230 return fCharBuffer.length();
\r
234 * Create a <code>CharacterIterator</code> over the range [<code>start</code>, <code>limit</code>).
\r
235 * @param start the beginning of the iterator's range
\r
236 * @param limit the limit of the iterator's range
\r
237 * @return a valid <code>CharacterIterator</code> over the specified range
\r
238 * @see java.text.CharacterIterator
\r
240 public CharacterIterator createCharacterIterator(int start, int limit)
\r
242 return fCharBuffer.createCharacterIterator(start, limit);
\r
245 //--------------------------------------------------------
\r
246 // character styles
\r
247 //--------------------------------------------------------
\r
250 * Return the index of the first character in the character style run
\r
251 * containing pos. All characters in a style run have the same character
\r
253 * @return the style at offset <code>pos</code>
\r
255 public int characterStyleStart(int pos) {
\r
257 checkPos(pos, LESS_THAN_LENGTH);
\r
258 return fStyleBuffer.styleStart(pos);
\r
262 * Return the index after the last character in the character style run
\r
263 * containing pos. All characters in a style run have the same character
\r
265 * @return the style at offset <code>pos</code>
\r
267 public int characterStyleLimit(int pos) {
\r
269 checkPos(pos, NOT_GREATER_THAN_LENGTH);
\r
270 return fStyleBuffer.styleLimit(pos);
\r
274 * Return the style applied to the character at offset <code>pos</code>.
\r
275 * @param pos a valid offset into the text
\r
276 * @return the style at offset <code>pos</code>
\r
278 public AttributeMap characterStyleAt(int pos)
\r
280 checkPos(pos, NOT_GREATER_THAN_LENGTH);
\r
281 return fStyleBuffer.styleAt(pos);
\r
284 //--------------------------------------------------------
\r
285 // paragraph boundaries and styles
\r
286 //--------------------------------------------------------
\r
288 * Return the start of the paragraph containing the character at offset <code>pos</code>.
\r
289 * @param pos a valid offset into the text
\r
290 * @return the start of the paragraph containing the character at offset <code>pos</code>
\r
292 public int paragraphStart(int pos)
\r
294 checkPos(pos, NOT_GREATER_THAN_LENGTH);
\r
295 return fParagraphBuffer.paragraphStart(pos);
\r
299 * Return the limit of the paragraph containing the character at offset <code>pos</code>.
\r
300 * @param pos a valid offset into the text
\r
301 * @return the limit of the paragraph containing the character at offset <code>pos</code>
\r
303 public int paragraphLimit(int pos)
\r
305 checkPos(pos, NOT_GREATER_THAN_LENGTH);
\r
306 return fParagraphBuffer.paragraphLimit(pos);
\r
310 * Return the paragraph style applied to the paragraph containing offset <code>pos</code>.
\r
311 * @param pos a valid offset into the text
\r
312 * @return the paragraph style in effect at <code>pos</code>
\r
314 public AttributeMap paragraphStyleAt(int pos)
\r
316 checkPos(pos, NOT_GREATER_THAN_LENGTH);
\r
317 return fParagraphBuffer.paragraphStyleAt(pos);
\r
321 * Return the current time stamp. The time stamp is
\r
322 * incremented whenever the contents of the MConstText changes.
\r
323 * @return the current paragraph style time stamp
\r
325 public int getTimeStamp() {
\r
330 //======================================================
\r
331 // MText INTERFACES
\r
332 //======================================================
\r
333 //--------------------------------------------------------
\r
334 // character modfication functions
\r
335 //--------------------------------------------------------
\r
337 private void updateDamagedRange(int deleteStart,
\r
339 int insertLength) {
\r
341 fDamagedRange[0] = Math.min(fDamagedRange[0], deleteStart);
\r
343 if (fDamagedRange[1] >= deleteLimit) {
\r
344 int lengthChange = insertLength - (deleteLimit-deleteStart);
\r
345 fDamagedRange[1] += lengthChange;
\r
348 fDamagedRange[1] = deleteStart + insertLength;
\r
353 * Replace the characters and styles in the range [<code>start</code>, <code>limit</code>) with the characters
\r
354 * and styles in <code>srcText</code> in the range [<code>srcStart</code>, <code>srcLimit</code>). <code>srcText</code> is not
\r
356 * @param start the offset at which the replace operation begins
\r
357 * @param limit the offset at which the replace operation ends. The character and style at
\r
358 * <code>limit</code> is not modified.
\r
359 * @param text the source for the new characters and styles
\r
360 * @param srcStart the offset into <code>srcText</code> where new characters and styles will be obtained
\r
361 * @param srcLimit the offset into <code>srcText</code> where the new characters and styles end
\r
363 public void replace(int start, int limit, MConstText text, int srcStart, int srcLimit)
\r
365 if (text == this) {
\r
366 text = new StyledText(text);
\r
369 if (start == limit && srcStart == srcLimit) {
\r
373 checkStartLimit(start, limit);
\r
375 updateDamagedRange(start, limit, srcLimit-srcStart);
\r
377 fCharBuffer.replace(start, limit, text, srcStart, srcLimit);
\r
378 fStyleBuffer.replace(start, limit, text, srcStart, srcLimit);
\r
379 fParagraphBuffer.replace(start, limit, text, srcStart, srcLimit, fDamagedRange);
\r
384 * Replace the characters and styles in the range [<code>start</code>, <code>limit</code>) with the characters
\r
385 * and styles in <code>srcText</code>. <code>srcText</code> is not
\r
387 * @param start the offset at which the replace operation begins
\r
388 * @param limit the offset at which the replace operation ends. The character and style at
\r
389 * <code>limit</code> is not modified.
\r
390 * @param text the source for the new characters and styles
\r
392 public void replace(int start, int limit, MConstText text) {
\r
394 replace(start, limit, text, 0, text.length());
\r
398 * Replace the characters in the range [<code>start</code>, <code>limit</code>) with the characters
\r
399 * in <code>srcChars</code> in the range [<code>srcStart</code>, <code>srcLimit</code>). New characters take on the style
\r
400 * <code>charsStyle</code>.
\r
401 * <code>srcChars</code> is not modified.
\r
402 * @param start the offset at which the replace operation begins
\r
403 * @param limit the offset at which the replace operation ends. The character at
\r
404 * <code>limit</code> is not modified.
\r
405 * @param srcChars the source for the new characters
\r
406 * @param srcStart the offset into <code>srcChars</code> where new characters will be obtained
\r
407 * @param srcLimit the offset into <code>srcChars</code> where the new characters end
\r
408 * @param charsStyle the style of the new characters
\r
410 public void replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit, AttributeMap charsStyle)
\r
412 checkStartLimit(start, limit);
\r
414 if (start == limit && srcStart == srcLimit) {
\r
418 updateDamagedRange(start, limit, srcLimit-srcStart);
\r
420 fCharBuffer.replace(start, limit, srcChars, srcStart, srcLimit);
\r
422 replaceCharStylesWith(start, limit, start + (srcLimit-srcStart), charsStyle);
\r
424 fParagraphBuffer.deleteText(start, limit, fDamagedRange);
\r
425 fParagraphBuffer.insertText(start, srcChars, srcStart, srcLimit);
\r
430 private void replaceCharStylesWith(int start, int oldLimit, int newLimit, AttributeMap style) {
\r
432 if (start < oldLimit) {
\r
433 fStyleBuffer.deleteText(start, oldLimit);
\r
435 if (start < newLimit) {
\r
436 if (forceModifier == null) {
\r
437 forceModifier = new ForceModifier();
\r
439 forceModifier.setStyle(style);
\r
440 fStyleBuffer.insertText(start, newLimit);
\r
441 fStyleBuffer.modifyStyles(start, newLimit, forceModifier, null);
\r
446 * Replace the characters in the range [<code>start</code>, <code>limit</code>) with the character <code>srcChar</code>.
\r
447 * The new character takes on the style <code>charStyle</code>
\r
448 * @param start the offset at which the replace operation begins
\r
449 * @param limit the offset at which the replace operation ends. The character at
\r
450 * <code>limit</code> is not modified.
\r
451 * @param srcChar the new character
\r
452 * @param charStyle the style of the new character
\r
454 public void replace(int start, int limit, char srcChar, AttributeMap charStyle)
\r
456 checkStartLimit(start, limit);
\r
458 updateDamagedRange(start, limit, 1);
\r
460 fCharBuffer.replace(start, limit, srcChar);
\r
462 replaceCharStylesWith(start, limit, start + 1, charStyle);
\r
464 if (start < limit) {
\r
465 fParagraphBuffer.deleteText(start, limit, fDamagedRange);
\r
468 fParagraphBuffer.insertText(start, srcChar);
\r
474 * Replace the entire contents of this MText (both characters and styles) with
\r
475 * the contents of <code>srcText</code>.
\r
476 * @param srcText the source for the new characters and styles
\r
478 public void replaceAll(MConstText srcText)
\r
480 replace(0, length(), srcText, 0, srcText.length());
\r
484 * Insert the contents of <code>srcText</code> (both characters and styles) into this
\r
485 * MText at the position specified by <code>pos</code>.
\r
486 * @param pos The character offset where the new text is to be inserted.
\r
487 * @param srcText The text to insert.
\r
489 public void insert(int pos, MConstText srcText)
\r
491 replace(pos, pos, srcText, 0, srcText.length());
\r
495 * Append the contents of <code>srcText</code> (both characters and styles) to the
\r
496 * end of this MText.
\r
497 * @param srcText The text to append.
\r
499 public void append(MConstText srcText)
\r
501 replace(length(), length(), srcText, 0, srcText.length());
\r
505 * Delete the specified range of characters (and styles).
\r
506 * @param start Offset of the first character to delete.
\r
507 * @param limit Offset of the first character after the range to delete.
\r
509 public void remove(int start, int limit)
\r
511 replace(start, limit, (char[])null, 0, 0, AttributeMap.EMPTY_ATTRIBUTE_MAP);
\r
515 * Delete all characters and styles. Always increments time stamp.
\r
517 public void remove()
\r
519 // rather than going through replace(), just reinitialize the StyledText,
\r
520 // letting the old data structures fall on the floor
\r
521 fCharBuffer = new CharBuffer();
\r
522 fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP);
\r
523 fParagraphBuffer = new ParagraphBuffer(fCharBuffer);
\r
525 fDamagedRange[0] = fDamagedRange[1] = 0;
\r
528 //--------------------------------------------------------
\r
529 // storage management
\r
530 //--------------------------------------------------------
\r
533 * Minimize the amount of memory used by the MText object.
\r
535 public void compress() {
\r
537 fCharBuffer.compress();
\r
538 fStyleBuffer.compress();
\r
539 fParagraphBuffer.compress();
\r
542 //--------------------------------------------------------
\r
543 // style modification
\r
544 //--------------------------------------------------------
\r
547 * Set the style of all characters in the MText object to
\r
548 * <code>AttributeMap.EMPTY_ATTRIBUTE_MAP</code>.
\r
550 public void removeCharacterStyles() {
\r
552 fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP);
\r
554 fDamagedRange[0] = 0;
\r
555 fDamagedRange[1] = length();
\r
559 * Invoke the given modifier on all character styles from start to limit.
\r
560 * @param modifier the modifier to apply to the range.
\r
561 * @param start the start of the range of text to modify.
\r
562 * @param limit the limit of the range of text to modify.
\r
564 public void modifyCharacterStyles(int start, int limit, StyleModifier modifier) {
\r
566 checkStartLimit(start, limit);
\r
567 boolean modified = fStyleBuffer.modifyStyles(start,
\r
577 * Invoke the given modifier on all paragraph styles in paragraphs
\r
578 * containing characters in the range [start, limit).
\r
579 * @param modifier the modifier to apply to the range.
\r
580 * @param start the start of the range of text to modify.
\r
581 * @param limit the limit of the range of text to modify.
\r
583 public void modifyParagraphStyles(int start, int limit, StyleModifier modifier) {
\r
585 checkStartLimit(start, limit);
\r
586 boolean modified = fParagraphBuffer.modifyParagraphStyles(start,
\r
596 * Reset the damaged range to an empty interval, and begin accumulating the damaged
\r
597 * range. The damaged range includes every index where a character, character style,
\r
598 * or paragraph style has changed.
\r
599 * @see #damagedRangeStart
\r
600 * @see #damagedRangeLimit
\r
602 public void resetDamagedRange() {
\r
604 fDamagedRange[0] = Integer.MAX_VALUE;
\r
605 fDamagedRange[1] = Integer.MIN_VALUE;
\r
609 * Return the start of the damaged range.
\r
611 * <code>Integer.MAX_VALUE</code> and the limit is
\r
612 * <code>Integer.MIN_VALUE</code>, then the damaged range
\r
614 * @return the start of the damaged range
\r
615 * @see #damagedRangeLimit
\r
616 * @see #resetDamagedRange
\r
618 public int damagedRangeStart() {
\r
620 return fDamagedRange[0];
\r
624 * Return the limit of the damaged range.
\r
626 * <code>Integer.MAX_VALUE</code> and the limit is
\r
627 * <code>Integer.MIN_VALUE</code>, then the damaged range
\r
629 * @return the limit of the damaged range
\r
630 * @see #damagedRangeStart
\r
631 * @see #resetDamagedRange
\r
633 public int damagedRangeLimit() {
\r
635 return fDamagedRange[1];
\r
638 public String toString()
\r
641 for (int i = 0; i < length(); i++) {
\r
647 //======================================================
\r
649 //======================================================
\r
651 /* check a range to see if it is well formed and within the bounds of the text */
\r
652 private void checkStartLimit(int start, int limit)
\r
654 if (start > limit) {
\r
655 //System.out.println("Start is less than limit. start:"+start+"; limit:"+limit);
\r
656 throw new IllegalArgumentException("Start is greater than limit. start:"+start+"; limit:"+limit);
\r
660 //System.out.println("Start is negative. start:"+start);
\r
661 throw new IllegalArgumentException("Start is negative. start:"+start);
\r
664 if (limit > length()) {
\r
665 //System.out.println("Limit is greater than length. limit:"+limit);
\r
666 throw new IllegalArgumentException("Limit is greater than length. limit:"+limit);
\r
670 private static final boolean LESS_THAN_LENGTH = false;
\r
671 private static final boolean NOT_GREATER_THAN_LENGTH = true;
\r
673 private void checkPos(int pos, boolean endAllowed) {
\r
675 int lastValidPos = length();
\r
676 if (endAllowed == LESS_THAN_LENGTH) {
\r
680 if (pos < 0 || pos > lastValidPos) {
\r
681 throw new IllegalArgumentException("Position is out of range.");
\r