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 java.io.Externalizable;
\r
16 import java.io.ObjectInput;
\r
17 import java.io.ObjectOutput;
\r
18 import java.io.IOException;
\r
20 import com.ibm.richtext.textlayout.attributes.AttributeMap;
\r
24 Added setIteratorUsingRun method
\r
27 No longer has to be constructed with an MText.
\r
30 Added replace method, which reads styles from a StyleRunIterator.
\r
31 Also, added a constructor which takes a StyleRunIterator.
\r
32 These methods are for copy/paste support.
\r
34 StyleBuffer now takes MConstText instead of MText where possible.
\r
37 Some old commented-out code removed for aesthetic reasons.
\r
39 7/31/98 Switched to AttributeMap
\r
43 * StyleBuffer implements <tt>MStyleBuffer</tt>. It maintains
\r
44 * <tt>AttributeMap</tt> objects to apply to the text in an <tt>MText</tt> object,
\r
46 * intervals on which those styles apply.
\r
48 * StyleBuffer stores the intervals on which styles apply in a <tt>RunArray</tt>
\r
49 * object (see <tt>RunArray</tt> for more information). The styles are stored in
\r
50 * an array of <tt>AttributeMap</tt> objects.
\r
52 * <tt>RunArray</tt> maintains an array of integers which represent offsets into text.
\r
53 * The array has a "positive" region in which offsets are given as positive distances
\r
54 * from the start of the text, and a "negative" region in which offsets are given as
\r
55 * negative distances from the end of the text. Between the positive and negative regions
\r
56 * is a gap, into which new offsets may be inserted. This storage scheme allows for
\r
57 * efficient response to a series of editing operations which occur in the same area of the
\r
60 * StyleBuffer uses the offsets in <tt>RunArray</tt> as the boundaries of style runs.
\r
61 * A style run begins at each offset in <tt>RunArray</tt>, and each style run continues to
\r
62 * the next offset. The style array is kept in sync with the array of offsets in <tt>RunArray</tt>;
\r
63 * that is, the style which begins at RunArray[i] is stored in StyleArray[i].
\r
65 * The first entry in the <tt>RunArray</tt> is always 0.
\r
67 * @author John Raley
\r
74 final class StyleBuffer extends MStyleBuffer implements Externalizable {
\r
76 static final String COPYRIGHT =
\r
77 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
\r
79 * Creates a new style buffer with length equal to the length of <tt>text</tt>,
\r
80 * and with a single run of <tt>defaultStyle</tt>.
\r
82 private static final long serialVersionUID = 22356934;
\r
84 private static final int CURRENT_VERSION = 1;
\r
85 private static final int kInitialSize = 10;
\r
86 private RunArray fRunArray;
\r
88 private AttributeMap fStyleTable[];
\r
90 StyleBuffer(MConstText text, AttributeMap initialStyle) {
\r
92 this(text.length(), initialStyle);
\r
96 * Creates a new style buffer with length <tt>initialLength</tt> and with a
\r
97 * single run of <tt>defaultStyle</tt>.
\r
100 StyleBuffer(int initialLength, AttributeMap initialStyle) {
\r
102 fRunArray = new RunArray(kInitialSize, initialLength);
\r
103 fRunArray.fPosEnd = 0;
\r
104 fRunArray.fRunStart[0] = 0;
\r
106 fStyleTable = new AttributeMap[kInitialSize]; // do I really want to do this???
\r
108 fStyleTable[0] = initialStyle;
\r
112 * Note: this constructor is ONLY for use by the Serialization
\r
113 * mechanism. It does not leave this object in a valid state!
\r
115 public StyleBuffer() {
\r
118 public void writeExternal(ObjectOutput out) throws IOException {
\r
121 out.writeInt(CURRENT_VERSION);
\r
122 out.writeObject(fRunArray);
\r
123 out.writeObject(fStyleTable);
\r
126 public void readExternal(ObjectInput in) throws IOException,
\r
127 ClassNotFoundException {
\r
129 if (in.readInt() != CURRENT_VERSION) {
\r
130 throw new IOException("Invalid version of StyleBuffer");
\r
132 fRunArray = (RunArray) in.readObject();
\r
133 fStyleTable = (AttributeMap[]) in.readObject();
\r
137 * Shift style and run tables such that the last positive run begins before the given position.
\r
138 * Since there is always a run start at 0, this method ensures that the first run will not be shifted.
\r
139 * This is called by: <tt>insertText</tt> and <tt>deleteText</tt>.
\r
140 * @param pos a position in the text.
\r
143 private void shiftTableTo(int pos) {
\r
148 int oldNegStart = fRunArray.fNegStart;
\r
149 int oldPosEnd = fRunArray.fPosEnd;
\r
151 fRunArray.shiftTableTo(pos);
\r
153 if (oldPosEnd > fRunArray.fPosEnd)
\r
154 System.arraycopy(fStyleTable, fRunArray.fPosEnd+1,
\r
155 fStyleTable, fRunArray.fNegStart,
\r
156 oldPosEnd-fRunArray.fPosEnd);
\r
157 else if (oldNegStart < fRunArray.fNegStart)
\r
158 System.arraycopy(fStyleTable, oldNegStart,
\r
159 fStyleTable, oldPosEnd+1,
\r
160 fRunArray.fNegStart-oldNegStart);
\r
164 * Update the style table to reflect a change in the RunArray's size.
\r
166 private void handleArrayResize(int oldNegStart) {
\r
168 AttributeMap newStyleTable[] = new AttributeMap[fRunArray.getArrayLength()];
\r
169 System.arraycopy(fStyleTable, 0, newStyleTable, 0, fRunArray.fPosEnd+1);
\r
170 System.arraycopy(fStyleTable, oldNegStart, newStyleTable, fRunArray.fNegStart, (fRunArray.getArrayLength()-fRunArray.fNegStart));
\r
171 fStyleTable = newStyleTable;
\r
175 * Minimize the amount of storage used by this object.
\r
179 int oldNegStart = fRunArray.fNegStart;
\r
180 fRunArray.compress();
\r
181 if (fRunArray.fNegStart != oldNegStart) {
\r
182 handleArrayResize(oldNegStart);
\r
187 * Increase the storage capacity of the style and run tables if no room remains.
\r
189 private void expandStyleTableIfFull() {
\r
191 if (fRunArray.fPosEnd + 1 == fRunArray.fNegStart) {
\r
193 int oldNegStart = fRunArray.fNegStart;
\r
194 fRunArray.expandRunTable();
\r
195 handleArrayResize(oldNegStart);
\r
200 public MStyleRunIterator createStyleRunIterator(int start, int limit) {
\r
202 return new StyleRunIterator(start, limit);
\r
206 * Respond to an insertion in the text. The length of the last style run which
\r
207 * begins before <tt>start</tt> is increased by <tt>length</tt>. The run table
\r
208 * is shifted such that the run into which text was inserted is the last positive run.
\r
209 * This implementation assumes that all styles propogate.
\r
210 * @param start the offset where the insertion began
\r
211 * @param length the number of characters inserted
\r
213 public void insertText(int start, int limit) {
\r
215 shiftTableTo(start);
\r
216 fRunArray.addToCurTextLength(limit - start);
\r
220 * Respond to a deletion in the text. The last style run before
\r
221 * <tt>start</tt> is truncated to end at <tt>start</tt>. The
\r
222 * style run containing (<tt>start</tt>+<tt>length</tt>) is set to begin
\r
223 * at (<tt>start</tt>+<tt>length</tt>). Runs in between are deleted.
\r
224 * If the deletion occurs entirely within one style run, the length of the style
\r
225 * run is reduced by <tt>length</tt>.
\r
226 * This implementation assumes that all styles propogate.
\r
227 * This method shifts the run table such that the run in which the delete began
\r
228 * is the last positive run. Other methods depend on this "side effect".
\r
229 * @param start the offset where the deletion began
\r
230 * @param length the offset where the deletion stopped
\r
232 public void deleteText(int start, int limit) {
\r
234 int length = limit - start;
\r
236 // An optimization - if a whole run wasn't deleted we don't
\r
237 // need to check for run merging, which could be expensive.
\r
238 boolean wholeRunDeleted = false;
\r
240 shiftTableTo(start);
\r
242 int firstRunLimit = fRunArray.getCurTextLength();
\r
243 if (fRunArray.fNegStart < fRunArray.getArrayLength())
\r
244 firstRunLimit += fRunArray.fRunStart[fRunArray.fNegStart];
\r
246 if (limit == fRunArray.getCurTextLength()) {
\r
247 fRunArray.fNegStart = fRunArray.getArrayLength();
\r
249 else if (limit >= firstRunLimit) {
\r
251 int end = fRunArray.findRunContaining(limit);
\r
252 if (end != fRunArray.fPosEnd) {
\r
253 fRunArray.fRunStart[end] = limit - fRunArray.getCurTextLength();
\r
254 fRunArray.fNegStart = end;
\r
255 wholeRunDeleted = true;
\r
259 if (fRunArray.fNegStart != fRunArray.getArrayLength()) {
\r
260 if (start == 0 && limit >= firstRunLimit) {
\r
261 // the first style run was deleted; move first "negative" run into
\r
263 fStyleTable[0] = fStyleTable[fRunArray.fNegStart++];
\r
265 else if (wholeRunDeleted) {
\r
266 if (fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd])) {
\r
267 // merge style runs
\r
268 fRunArray.fNegStart++;
\r
273 fRunArray.addToCurTextLength(-length);
\r
275 fRunArray.runStartsChanged();
\r
276 //System.out.println("In deleteText: number of style runs = " + numRuns(this));
\r
280 * Arrange style table so that old styles in the provided range are removed, and
\r
281 * new styles can be inserted into the insertion gap.
\r
282 * After calling this method, new style starts and styles may be placed
\r
283 * in the insertion gaps of fRunArray.fStyleStart and fStyleTable.
\r
284 * @param start offset in the text where insertion operation begins
\r
285 * @param limit offset in the text where previous styles resume
\r
287 private void prepareStyleInsert(int start) {
\r
291 // fRunArray.fPosEnd should be 0 if we're in this branch.
\r
293 if (fRunArray.getCurTextLength() > 0) {
\r
295 /* Move first existing style run to negative end of buffer.
\r
296 Don't do this if length==0; that is, if there is no real
\r
300 fRunArray.fNegStart--;
\r
301 fStyleTable[fRunArray.fNegStart] = fStyleTable[0];
\r
302 fRunArray.fRunStart[fRunArray.fNegStart] = -fRunArray.getCurTextLength();
\r
305 fRunArray.fPosEnd = -1;
\r
309 // consistency check: start should be in current gap
\r
310 if (fRunArray.fRunStart[fRunArray.fPosEnd] >= start) {
\r
311 throw new Error("Inconsistent state! Start should be within insertion gap.");
\r
314 int endOfInsertionGap = fRunArray.getCurTextLength();
\r
315 if (fRunArray.fNegStart < fRunArray.getArrayLength()) {
\r
316 endOfInsertionGap += fRunArray.fRunStart[fRunArray.fNegStart];
\r
319 if (endOfInsertionGap < start) {
\r
320 throw new Error("Inconsistent state! Start should be within insertion gap.");
\r
323 // if no break at start (on negative end of buffer) make one
\r
325 if (endOfInsertionGap != start) {
\r
327 // split style run in insertion gap
\r
329 expandStyleTableIfFull();
\r
331 fRunArray.fNegStart--;
\r
332 fStyleTable[fRunArray.fNegStart] = fStyleTable[fRunArray.fPosEnd];
\r
333 fRunArray.fRunStart[fRunArray.fNegStart] = start - fRunArray.getCurTextLength();
\r
335 //System.out.println("splitting run.");
\r
340 public boolean modifyStyles(int start,
\r
342 StyleModifier modifier,
\r
343 int[] damagedRange) {
\r
345 if (limit == start) {
\r
349 shiftTableTo(start);
\r
351 int currentRunStart = start;
\r
352 AttributeMap oldStyle;
\r
353 AttributeMap mergeStyle = fStyleTable[fRunArray.fPosEnd];
\r
355 if (fRunArray.fNegStart < fRunArray.getArrayLength() &&
\r
356 fRunArray.fRunStart[fRunArray.fNegStart]+fRunArray.getCurTextLength() == start) {
\r
358 oldStyle = fStyleTable[fRunArray.fNegStart];
\r
359 ++fRunArray.fNegStart;
\r
362 oldStyle = mergeStyle;
\r
365 boolean modifiedAnywhere = false;
\r
368 boolean modified = false;
\r
370 // push new style into gap on positive side
\r
371 AttributeMap newStyle = modifier.modifyStyle(oldStyle);
\r
372 if (damagedRange != null && !newStyle.equals(oldStyle)) {
\r
373 modified = modifiedAnywhere = true;
\r
374 damagedRange[0] = Math.min(currentRunStart, damagedRange[0]);
\r
377 if (!newStyle.equals(mergeStyle)) {
\r
379 if (currentRunStart != 0) {
\r
380 expandStyleTableIfFull();
\r
381 ++fRunArray.fPosEnd;
\r
384 fStyleTable[fRunArray.fPosEnd] = newStyle;
\r
385 fRunArray.fRunStart[fRunArray.fPosEnd] = currentRunStart;
\r
388 mergeStyle = newStyle;
\r
390 int nextRunStart = fRunArray.getLogicalRunStart(fRunArray.fNegStart);
\r
392 if (limit > nextRunStart) {
\r
393 oldStyle = fStyleTable[fRunArray.fNegStart];
\r
394 currentRunStart = nextRunStart;
\r
396 damagedRange[1] = Math.max(currentRunStart, damagedRange[1]);
\r
398 ++fRunArray.fNegStart;
\r
401 if (limit < nextRunStart && !oldStyle.equals(mergeStyle)) {
\r
402 expandStyleTableIfFull();
\r
403 ++fRunArray.fPosEnd;
\r
404 fStyleTable[fRunArray.fPosEnd] = oldStyle;
\r
405 fRunArray.fRunStart[fRunArray.fPosEnd] = limit;
\r
408 damagedRange[1] = Math.max(limit, damagedRange[1]);
\r
414 // merge last run if needed
\r
415 if ((fRunArray.fNegStart < fRunArray.getArrayLength()) &&
\r
416 (fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd]))) {
\r
417 fRunArray.fNegStart++;
\r
420 fRunArray.runStartsChanged();
\r
422 return modifiedAnywhere;
\r
425 public int styleStart(int pos) {
\r
427 if (pos == fRunArray.getCurTextLength()) {
\r
431 return fRunArray.getLogicalRunStart(fRunArray.findRunContaining(pos));
\r
434 public int styleLimit(int pos) {
\r
436 if (pos == fRunArray.getCurTextLength()) {
\r
440 int run = fRunArray.findRunContaining(pos);
\r
442 if (run == fRunArray.fPosEnd) {
\r
443 run = fRunArray.fNegStart;
\r
449 return fRunArray.getLogicalRunStart(run);
\r
453 * Return style at location <tt>pos</tt>.
\r
454 * @param pos an offset into the text
\r
455 * @returns the style of the character at <tt>offset</tt>
\r
457 public AttributeMap styleAt(int pos) {
\r
459 return fStyleTable[ fRunArray.findRunContaining(pos) ];
\r
463 * Set run start, run length, and run value in an iterator. This method is
\r
464 * only called by a <tt>StyleRunIterator</tt>.
\r
465 * @param pos an offset into the text. The iterator's run start and run limit are
\r
466 * set to the run containing <tt>pos</tt>.
\r
467 * @param iter the iterator to set
\r
469 void setIterator(int pos, StyleRunIterator iter) {
\r
471 if ((pos < 0) || (pos > fRunArray.getCurTextLength())) {
\r
473 iter.set(null, 0, 0, kNoRun);
\r
477 int run = fRunArray.findRunContaining(pos);
\r
479 setIteratorUsingRun(run, iter);
\r
483 * Set run start, run length, and run value in an iterator. This method is
\r
484 * only called by a <tt>StyleRunIterator</tt>.
\r
485 * @param run the index of the run to which the iterator should be set
\r
486 * @param iter the iterator to set
\r
488 private void setIteratorUsingRun(int run, StyleRunIterator iter) {
\r
490 int lastValidRun = fRunArray.lastRun();
\r
492 if (run < 0 || run > lastValidRun) {
\r
494 iter.set(null, 0, 0, kNoRun);
\r
498 if (run == fRunArray.fPosEnd+1)
\r
499 run = fRunArray.fNegStart;
\r
500 else if (run == fRunArray.fNegStart-1)
\r
501 run = fRunArray.fPosEnd;
\r
503 int runStart = fRunArray.fRunStart[run];
\r
505 runStart += fRunArray.getCurTextLength();
\r
507 AttributeMap style = fStyleTable[run];
\r
511 if (run == fRunArray.fPosEnd)
\r
512 nextRun = fRunArray.fNegStart;
\r
518 if (nextRun >= fRunArray.getArrayLength())
\r
519 runLimit = fRunArray.getCurTextLength();
\r
521 runLimit = fRunArray.fRunStart[nextRun];
\r
523 runLimit += fRunArray.getCurTextLength();
\r
526 //System.out.println("setIterator: pos="+pos+", runStart="+runStart+", runLimit="+runLimit+
\r
527 // ", run="+run+", fPosEnd="+fPosEnd);
\r
529 iter.set(style, runStart, runLimit, run);
\r
532 public void replace(int start, int limit, MConstText srcText, int srcStart, int srcLimit)
\r
534 deleteText(start, limit);
\r
535 if (srcStart == srcLimit)
\r
537 prepareStyleInsert(start);
\r
538 for (int j2 = srcStart; j2 < srcLimit; j2 = srcText.characterStyleLimit(j2))
\r
540 AttributeMap attributeMap = srcText.characterStyleAt(j2);
\r
541 if (fRunArray.fPosEnd < 0 || !fStyleTable[fRunArray.fPosEnd].equals(attributeMap))
\r
543 expandStyleTableIfFull();
\r
544 fRunArray.fPosEnd++;
\r
545 fRunArray.fRunStart[fRunArray.fPosEnd] = j2 - srcStart + start;
\r
546 fStyleTable[fRunArray.fPosEnd] = attributeMap;
\r
549 fRunArray.addToCurTextLength(srcLimit - srcStart);
\r
550 if (fRunArray.fNegStart < fRunArray.getArrayLength() && fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd]))
\r
551 fRunArray.fNegStart++;
\r
554 private static final int kNoRun = -42; // iterator use
\r
556 private final class StyleRunIterator /*implements MStyleRunIterator*/ {
\r
558 StyleRunIterator(int start, int limit)
\r
560 reset(start, limit, start);
\r
563 public void reset(int start, int limit, int pos)
\r
567 setIterator(fStart, this);
\r
570 public boolean isValid()
\r
572 return fStyle != null;
\r
577 if (fRunLimit < fLimit) {
\r
579 setIteratorUsingRun(fCurrentRun, this);
\r
582 set(null, 0, 0, kNoRun);
\r
587 if (fRunStart > fStart) {
\r
589 setIteratorUsingRun(fCurrentRun, this);
\r
592 set(null, 0, 0, kNoRun);
\r
595 public void set(int pos)
\r
597 if (pos >= fStart && pos < fLimit) {
\r
598 setIterator(pos, this);
\r
600 set(null, 0, 0, kNoRun);
\r
604 void set(AttributeMap style, int start, int limit, int currentRun)
\r
607 fCurrentRun = currentRun;
\r
608 fRunStart = start < fStart ? fStart : start;
\r
609 fRunLimit = limit > fLimit ? fLimit : limit;
\r
612 public void reset(int start, int limit)
\r
614 reset(start, limit, start);
\r
617 public void first()
\r
627 public int rangeStart()
\r
632 public int rangeLimit()
\r
637 public int rangeLength()
\r
639 return fLimit - fStart;
\r
642 public AttributeMap style()
\r
647 public int runStart()
\r
652 public int runLimit()
\r
657 public int runLength()
\r
659 return fRunLimit - fRunStart;
\r
662 private int fStart;
\r
663 private int fLimit;
\r
664 private AttributeMap fStyle;
\r
665 private int fRunStart;
\r
666 private int fRunLimit;
\r
667 private int fCurrentRun;
\r