001: /*
002: * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
003: *
004: * The program is provided "as is" without any warranty express or
005: * implied, including the warranty of non-infringement and the implied
006: * warranties of merchantibility and fitness for a particular purpose.
007: * IBM will not be liable for any damages suffered by you as a result
008: * of using the Program. In no event will IBM be liable for any
009: * special, indirect or consequential damages or lost profits even if
010: * IBM has been advised of the possibility of their occurrence. IBM
011: * will not be liable for any third party claims against you.
012: */
013: package com.ibm.richtext.styledtext;
014:
015: import java.io.Externalizable;
016: import java.io.ObjectInput;
017: import java.io.ObjectOutput;
018: import java.io.IOException;
019:
020: import com.ibm.richtext.textlayout.attributes.AttributeMap;
021:
022: /*
023: 8/2/96
024: Added setIteratorUsingRun method
025:
026: 8/5/96
027: No longer has to be constructed with an MText.
028:
029: 8/8/96
030: Added replace method, which reads styles from a StyleRunIterator.
031: Also, added a constructor which takes a StyleRunIterator.
032: These methods are for copy/paste support.
033: 8/16/96
034: StyleBuffer now takes MConstText instead of MText where possible.
035:
036: 10/23/96
037: Some old commented-out code removed for aesthetic reasons.
038:
039: 7/31/98 Switched to AttributeMap
040: */
041:
042: /**
043: * StyleBuffer implements <tt>MStyleBuffer</tt>. It maintains
044: * <tt>AttributeMap</tt> objects to apply to the text in an <tt>MText</tt> object,
045: * and the
046: * intervals on which those styles apply.
047: * <p>
048: * StyleBuffer stores the intervals on which styles apply in a <tt>RunArray</tt>
049: * object (see <tt>RunArray</tt> for more information). The styles are stored in
050: * an array of <tt>AttributeMap</tt> objects.
051: * <p>
052: * <tt>RunArray</tt> maintains an array of integers which represent offsets into text.
053: * The array has a "positive" region in which offsets are given as positive distances
054: * from the start of the text, and a "negative" region in which offsets are given as
055: * negative distances from the end of the text. Between the positive and negative regions
056: * is a gap, into which new offsets may be inserted. This storage scheme allows for
057: * efficient response to a series of editing operations which occur in the same area of the
058: * text.
059: * <p>
060: * StyleBuffer uses the offsets in <tt>RunArray</tt> as the boundaries of style runs.
061: * A style run begins at each offset in <tt>RunArray</tt>, and each style run continues to
062: * the next offset. The style array is kept in sync with the array of offsets in <tt>RunArray</tt>;
063: * that is, the style which begins at RunArray[i] is stored in StyleArray[i].
064: * <p>
065: * The first entry in the <tt>RunArray</tt> is always 0.
066: *
067: * @author John Raley
068: *
069: * @see AttributeMap
070: * @see MText
071: * @see RunArray
072: */
073:
074: final class StyleBuffer extends MStyleBuffer implements Externalizable {
075:
076: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
077: /**
078: * Creates a new style buffer with length equal to the length of <tt>text</tt>,
079: * and with a single run of <tt>defaultStyle</tt>.
080: */
081: private static final long serialVersionUID = 22356934;
082:
083: private static final int CURRENT_VERSION = 1;
084: private static final int kInitialSize = 10;
085: private RunArray fRunArray;
086:
087: private AttributeMap fStyleTable[];
088:
089: StyleBuffer(MConstText text, AttributeMap initialStyle) {
090:
091: this (text.length(), initialStyle);
092: }
093:
094: /**
095: * Creates a new style buffer with length <tt>initialLength</tt> and with a
096: * single run of <tt>defaultStyle</tt>.
097: */
098:
099: StyleBuffer(int initialLength, AttributeMap initialStyle) {
100:
101: fRunArray = new RunArray(kInitialSize, initialLength);
102: fRunArray.fPosEnd = 0;
103: fRunArray.fRunStart[0] = 0;
104:
105: fStyleTable = new AttributeMap[kInitialSize]; // do I really want to do this???
106:
107: fStyleTable[0] = initialStyle;
108: }
109:
110: /**
111: * Note: this constructor is ONLY for use by the Serialization
112: * mechanism. It does not leave this object in a valid state!
113: */
114: public StyleBuffer() {
115: }
116:
117: public void writeExternal(ObjectOutput out) throws IOException {
118:
119: compress();
120: out.writeInt(CURRENT_VERSION);
121: out.writeObject(fRunArray);
122: out.writeObject(fStyleTable);
123: }
124:
125: public void readExternal(ObjectInput in) throws IOException,
126: ClassNotFoundException {
127:
128: if (in.readInt() != CURRENT_VERSION) {
129: throw new IOException("Invalid version of StyleBuffer");
130: }
131: fRunArray = (RunArray) in.readObject();
132: fStyleTable = (AttributeMap[]) in.readObject();
133: }
134:
135: /**
136: * Shift style and run tables such that the last positive run begins before the given position.
137: * Since there is always a run start at 0, this method ensures that the first run will not be shifted.
138: * This is called by: <tt>insertText</tt> and <tt>deleteText</tt>.
139: * @param pos a position in the text.
140: */
141:
142: private void shiftTableTo(int pos) {
143:
144: if (pos == 0)
145: pos = 1;
146:
147: int oldNegStart = fRunArray.fNegStart;
148: int oldPosEnd = fRunArray.fPosEnd;
149:
150: fRunArray.shiftTableTo(pos);
151:
152: if (oldPosEnd > fRunArray.fPosEnd)
153: System.arraycopy(fStyleTable, fRunArray.fPosEnd + 1,
154: fStyleTable, fRunArray.fNegStart, oldPosEnd
155: - fRunArray.fPosEnd);
156: else if (oldNegStart < fRunArray.fNegStart)
157: System.arraycopy(fStyleTable, oldNegStart, fStyleTable,
158: oldPosEnd + 1, fRunArray.fNegStart - oldNegStart);
159: }
160:
161: /**
162: * Update the style table to reflect a change in the RunArray's size.
163: */
164: private void handleArrayResize(int oldNegStart) {
165:
166: AttributeMap newStyleTable[] = new AttributeMap[fRunArray
167: .getArrayLength()];
168: System.arraycopy(fStyleTable, 0, newStyleTable, 0,
169: fRunArray.fPosEnd + 1);
170: System.arraycopy(fStyleTable, oldNegStart, newStyleTable,
171: fRunArray.fNegStart,
172: (fRunArray.getArrayLength() - fRunArray.fNegStart));
173: fStyleTable = newStyleTable;
174: }
175:
176: /**
177: * Minimize the amount of storage used by this object.
178: */
179: void compress() {
180:
181: int oldNegStart = fRunArray.fNegStart;
182: fRunArray.compress();
183: if (fRunArray.fNegStart != oldNegStart) {
184: handleArrayResize(oldNegStart);
185: }
186: }
187:
188: /**
189: * Increase the storage capacity of the style and run tables if no room remains.
190: */
191: private void expandStyleTableIfFull() {
192:
193: if (fRunArray.fPosEnd + 1 == fRunArray.fNegStart) {
194:
195: int oldNegStart = fRunArray.fNegStart;
196: fRunArray.expandRunTable();
197: handleArrayResize(oldNegStart);
198: }
199: }
200:
201: /*
202: public MStyleRunIterator createStyleRunIterator(int start, int limit) {
203:
204: return new StyleRunIterator(start, limit);
205: }
206: */
207: /**
208: * Respond to an insertion in the text. The length of the last style run which
209: * begins before <tt>start</tt> is increased by <tt>length</tt>. The run table
210: * is shifted such that the run into which text was inserted is the last positive run.
211: * This implementation assumes that all styles propogate.
212: * @param start the offset where the insertion began
213: * @param length the number of characters inserted
214: */
215: public void insertText(int start, int limit) {
216:
217: shiftTableTo(start);
218: fRunArray.addToCurTextLength(limit - start);
219: }
220:
221: /**
222: * Respond to a deletion in the text. The last style run before
223: * <tt>start</tt> is truncated to end at <tt>start</tt>. The
224: * style run containing (<tt>start</tt>+<tt>length</tt>) is set to begin
225: * at (<tt>start</tt>+<tt>length</tt>). Runs in between are deleted.
226: * If the deletion occurs entirely within one style run, the length of the style
227: * run is reduced by <tt>length</tt>.
228: * This implementation assumes that all styles propogate.
229: * This method shifts the run table such that the run in which the delete began
230: * is the last positive run. Other methods depend on this "side effect".
231: * @param start the offset where the deletion began
232: * @param length the offset where the deletion stopped
233: */
234: public void deleteText(int start, int limit) {
235:
236: int length = limit - start;
237:
238: // An optimization - if a whole run wasn't deleted we don't
239: // need to check for run merging, which could be expensive.
240: boolean wholeRunDeleted = false;
241:
242: shiftTableTo(start);
243:
244: int firstRunLimit = fRunArray.getCurTextLength();
245: if (fRunArray.fNegStart < fRunArray.getArrayLength())
246: firstRunLimit += fRunArray.fRunStart[fRunArray.fNegStart];
247:
248: if (limit == fRunArray.getCurTextLength()) {
249: fRunArray.fNegStart = fRunArray.getArrayLength();
250: } else if (limit >= firstRunLimit) {
251:
252: int end = fRunArray.findRunContaining(limit);
253: if (end != fRunArray.fPosEnd) {
254: fRunArray.fRunStart[end] = limit
255: - fRunArray.getCurTextLength();
256: fRunArray.fNegStart = end;
257: wholeRunDeleted = true;
258: }
259: }
260:
261: if (fRunArray.fNegStart != fRunArray.getArrayLength()) {
262: if (start == 0 && limit >= firstRunLimit) {
263: // the first style run was deleted; move first "negative" run into
264: // first position
265: fStyleTable[0] = fStyleTable[fRunArray.fNegStart++];
266: } else if (wholeRunDeleted) {
267: if (fStyleTable[fRunArray.fNegStart]
268: .equals(fStyleTable[fRunArray.fPosEnd])) {
269: // merge style runs
270: fRunArray.fNegStart++;
271: }
272: }
273: }
274:
275: fRunArray.addToCurTextLength(-length);
276:
277: fRunArray.runStartsChanged();
278: //System.out.println("In deleteText: number of style runs = " + numRuns(this));
279: }
280:
281: /**
282: * Arrange style table so that old styles in the provided range are removed, and
283: * new styles can be inserted into the insertion gap.
284: * After calling this method, new style starts and styles may be placed
285: * in the insertion gaps of fRunArray.fStyleStart and fStyleTable.
286: * @param start offset in the text where insertion operation begins
287: * @param limit offset in the text where previous styles resume
288: */
289: private void prepareStyleInsert(int start) {
290:
291: if (start == 0) {
292:
293: // fRunArray.fPosEnd should be 0 if we're in this branch.
294:
295: if (fRunArray.getCurTextLength() > 0) {
296:
297: /* Move first existing style run to negative end of buffer.
298: Don't do this if length==0; that is, if there is no real
299: style run at 0.
300: */
301:
302: fRunArray.fNegStart--;
303: fStyleTable[fRunArray.fNegStart] = fStyleTable[0];
304: fRunArray.fRunStart[fRunArray.fNegStart] = -fRunArray
305: .getCurTextLength();
306: }
307:
308: fRunArray.fPosEnd = -1;
309: } else {
310:
311: // consistency check: start should be in current gap
312: if (fRunArray.fRunStart[fRunArray.fPosEnd] >= start) {
313: throw new Error(
314: "Inconsistent state! Start should be within insertion gap.");
315: }
316:
317: int endOfInsertionGap = fRunArray.getCurTextLength();
318: if (fRunArray.fNegStart < fRunArray.getArrayLength()) {
319: endOfInsertionGap += fRunArray.fRunStart[fRunArray.fNegStart];
320: }
321:
322: if (endOfInsertionGap < start) {
323: throw new Error(
324: "Inconsistent state! Start should be within insertion gap.");
325: }
326:
327: // if no break at start (on negative end of buffer) make one
328:
329: if (endOfInsertionGap != start) {
330:
331: // split style run in insertion gap
332:
333: expandStyleTableIfFull();
334:
335: fRunArray.fNegStart--;
336: fStyleTable[fRunArray.fNegStart] = fStyleTable[fRunArray.fPosEnd];
337: fRunArray.fRunStart[fRunArray.fNegStart] = start
338: - fRunArray.getCurTextLength();
339:
340: //System.out.println("splitting run.");
341: }
342: }
343: }
344:
345: public boolean modifyStyles(int start, int limit,
346: StyleModifier modifier, int[] damagedRange) {
347:
348: if (limit == start) {
349: return false;
350: }
351:
352: shiftTableTo(start);
353:
354: int currentRunStart = start;
355: AttributeMap oldStyle;
356: AttributeMap mergeStyle = fStyleTable[fRunArray.fPosEnd];
357:
358: if (fRunArray.fNegStart < fRunArray.getArrayLength()
359: && fRunArray.fRunStart[fRunArray.fNegStart]
360: + fRunArray.getCurTextLength() == start) {
361:
362: oldStyle = fStyleTable[fRunArray.fNegStart];
363: ++fRunArray.fNegStart;
364: } else {
365: oldStyle = mergeStyle;
366: }
367:
368: boolean modifiedAnywhere = false;
369: for (;;) {
370:
371: boolean modified = false;
372:
373: // push new style into gap on positive side
374: AttributeMap newStyle = modifier.modifyStyle(oldStyle);
375: if (damagedRange != null && !newStyle.equals(oldStyle)) {
376: modified = modifiedAnywhere = true;
377: damagedRange[0] = Math.min(currentRunStart,
378: damagedRange[0]);
379: }
380:
381: if (!newStyle.equals(mergeStyle)) {
382:
383: if (currentRunStart != 0) {
384: expandStyleTableIfFull();
385: ++fRunArray.fPosEnd;
386: }
387:
388: fStyleTable[fRunArray.fPosEnd] = newStyle;
389: fRunArray.fRunStart[fRunArray.fPosEnd] = currentRunStart;
390: }
391:
392: mergeStyle = newStyle;
393:
394: int nextRunStart = fRunArray
395: .getLogicalRunStart(fRunArray.fNegStart);
396:
397: if (limit > nextRunStart) {
398: oldStyle = fStyleTable[fRunArray.fNegStart];
399: currentRunStart = nextRunStart;
400: if (modified) {
401: damagedRange[1] = Math.max(currentRunStart,
402: damagedRange[1]);
403: }
404: ++fRunArray.fNegStart;
405: } else {
406: if (limit < nextRunStart
407: && !oldStyle.equals(mergeStyle)) {
408: expandStyleTableIfFull();
409: ++fRunArray.fPosEnd;
410: fStyleTable[fRunArray.fPosEnd] = oldStyle;
411: fRunArray.fRunStart[fRunArray.fPosEnd] = limit;
412: }
413: if (modified) {
414: damagedRange[1] = Math.max(limit, damagedRange[1]);
415: }
416: break;
417: }
418: }
419:
420: // merge last run if needed
421: if ((fRunArray.fNegStart < fRunArray.getArrayLength())
422: && (fStyleTable[fRunArray.fNegStart]
423: .equals(fStyleTable[fRunArray.fPosEnd]))) {
424: fRunArray.fNegStart++;
425: }
426:
427: fRunArray.runStartsChanged();
428:
429: return modifiedAnywhere;
430: }
431:
432: public int styleStart(int pos) {
433:
434: if (pos == fRunArray.getCurTextLength()) {
435: return pos;
436: }
437:
438: return fRunArray.getLogicalRunStart(fRunArray
439: .findRunContaining(pos));
440: }
441:
442: public int styleLimit(int pos) {
443:
444: if (pos == fRunArray.getCurTextLength()) {
445: return pos;
446: }
447:
448: int run = fRunArray.findRunContaining(pos);
449:
450: if (run == fRunArray.fPosEnd) {
451: run = fRunArray.fNegStart;
452: } else {
453: ++run;
454: }
455:
456: return fRunArray.getLogicalRunStart(run);
457: }
458:
459: /**
460: * Return style at location <tt>pos</tt>.
461: * @param pos an offset into the text
462: * @returns the style of the character at <tt>offset</tt>
463: */
464: public AttributeMap styleAt(int pos) {
465:
466: return fStyleTable[fRunArray.findRunContaining(pos)];
467: }
468:
469: /*
470: * Set run start, run length, and run value in an iterator. This method is
471: * only called by a <tt>StyleRunIterator</tt>.
472: * @param pos an offset into the text. The iterator's run start and run limit are
473: * set to the run containing <tt>pos</tt>.
474: * @param iter the iterator to set
475: */
476: void setIterator(int pos, StyleRunIterator iter) {
477:
478: if ((pos < 0) || (pos > fRunArray.getCurTextLength())) {
479:
480: iter.set(null, 0, 0, kNoRun);
481: return;
482: }
483:
484: int run = fRunArray.findRunContaining(pos);
485:
486: setIteratorUsingRun(run, iter);
487: }
488:
489: /**
490: * Set run start, run length, and run value in an iterator. This method is
491: * only called by a <tt>StyleRunIterator</tt>.
492: * @param run the index of the run to which the iterator should be set
493: * @param iter the iterator to set
494: */
495: private void setIteratorUsingRun(int run, StyleRunIterator iter) {
496:
497: int lastValidRun = fRunArray.lastRun();
498:
499: if (run < 0 || run > lastValidRun) {
500:
501: iter.set(null, 0, 0, kNoRun);
502: return;
503: }
504:
505: if (run == fRunArray.fPosEnd + 1)
506: run = fRunArray.fNegStart;
507: else if (run == fRunArray.fNegStart - 1)
508: run = fRunArray.fPosEnd;
509:
510: int runStart = fRunArray.fRunStart[run];
511: if (runStart < 0)
512: runStart += fRunArray.getCurTextLength();
513:
514: AttributeMap style = fStyleTable[run];
515:
516: int nextRun;
517:
518: if (run == fRunArray.fPosEnd)
519: nextRun = fRunArray.fNegStart;
520: else
521: nextRun = run + 1;
522:
523: int runLimit;
524:
525: if (nextRun >= fRunArray.getArrayLength())
526: runLimit = fRunArray.getCurTextLength();
527: else {
528: runLimit = fRunArray.fRunStart[nextRun];
529: if (runLimit < 0)
530: runLimit += fRunArray.getCurTextLength();
531: }
532:
533: //System.out.println("setIterator: pos="+pos+", runStart="+runStart+", runLimit="+runLimit+
534: // ", run="+run+", fPosEnd="+fPosEnd);
535:
536: iter.set(style, runStart, runLimit, run);
537: }
538:
539: public void replace(int start, int limit, MConstText srcText,
540: int srcStart, int srcLimit) {
541: deleteText(start, limit);
542: if (srcStart == srcLimit)
543: return;
544: prepareStyleInsert(start);
545: for (int j2 = srcStart; j2 < srcLimit; j2 = srcText
546: .characterStyleLimit(j2)) {
547: AttributeMap attributeMap = srcText.characterStyleAt(j2);
548: if (fRunArray.fPosEnd < 0
549: || !fStyleTable[fRunArray.fPosEnd]
550: .equals(attributeMap)) {
551: expandStyleTableIfFull();
552: fRunArray.fPosEnd++;
553: fRunArray.fRunStart[fRunArray.fPosEnd] = j2 - srcStart
554: + start;
555: fStyleTable[fRunArray.fPosEnd] = attributeMap;
556: }
557: }
558: fRunArray.addToCurTextLength(srcLimit - srcStart);
559: if (fRunArray.fNegStart < fRunArray.getArrayLength()
560: && fStyleTable[fRunArray.fNegStart]
561: .equals(fStyleTable[fRunArray.fPosEnd]))
562: fRunArray.fNegStart++;
563: }
564:
565: private static final int kNoRun = -42; // iterator use
566:
567: private final class StyleRunIterator /*implements MStyleRunIterator*/{
568:
569: StyleRunIterator(int start, int limit) {
570: reset(start, limit, start);
571: }
572:
573: public void reset(int start, int limit, int pos) {
574: fStart = start;
575: fLimit = limit;
576: setIterator(fStart, this );
577: }
578:
579: public boolean isValid() {
580: return fStyle != null;
581: }
582:
583: public void next() {
584: if (fRunLimit < fLimit) {
585: fCurrentRun++;
586: setIteratorUsingRun(fCurrentRun, this );
587: } else
588: set(null, 0, 0, kNoRun);
589: }
590:
591: public void prev() {
592: if (fRunStart > fStart) {
593: fCurrentRun--;
594: setIteratorUsingRun(fCurrentRun, this );
595: } else
596: set(null, 0, 0, kNoRun);
597: }
598:
599: public void set(int pos) {
600: if (pos >= fStart && pos < fLimit) {
601: setIterator(pos, this );
602: } else {
603: set(null, 0, 0, kNoRun);
604: }
605: }
606:
607: void set(AttributeMap style, int start, int limit,
608: int currentRun) {
609: fStyle = style;
610: fCurrentRun = currentRun;
611: fRunStart = start < fStart ? fStart : start;
612: fRunLimit = limit > fLimit ? fLimit : limit;
613: }
614:
615: public void reset(int start, int limit) {
616: reset(start, limit, start);
617: }
618:
619: public void first() {
620: set(fStart);
621: }
622:
623: public void last() {
624: set(fLimit - 1);
625: }
626:
627: public int rangeStart() {
628: return fStart;
629: }
630:
631: public int rangeLimit() {
632: return fLimit;
633: }
634:
635: public int rangeLength() {
636: return fLimit - fStart;
637: }
638:
639: public AttributeMap style() {
640: return fStyle;
641: }
642:
643: public int runStart() {
644: return fRunStart;
645: }
646:
647: public int runLimit() {
648: return fRunLimit;
649: }
650:
651: public int runLength() {
652: return fRunLimit - fRunStart;
653: }
654:
655: private int fStart;
656: private int fLimit;
657: private AttributeMap fStyle;
658: private int fRunStart;
659: private int fRunLimit;
660: private int fCurrentRun;
661: }
662: }
|