blob: fd5aca987d0e025d76e4f6456b11bb9534569eef [file] [log] [blame]
/*
* (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
*
* The program is provided "as is" without any warranty express or
* implied, including the warranty of non-infringement and the implied
* warranties of merchantibility and fitness for a particular purpose.
* IBM will not be liable for any damages suffered by you as a result
* of using the Program. In no event will IBM be liable for any
* special, indirect or consequential damages or lost profits even if
* IBM has been advised of the possibility of their occurrence. IBM
* will not be liable for any third party claims against you.
*/
package com.ibm.richtext.styledtext;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.IOException;
import com.ibm.richtext.textlayout.attributes.AttributeMap;
/*
8/2/96
Added setIteratorUsingRun method
8/5/96
No longer has to be constructed with an MText.
8/8/96
Added replace method, which reads styles from a StyleRunIterator.
Also, added a constructor which takes a StyleRunIterator.
These methods are for copy/paste support.
8/16/96
StyleBuffer now takes MConstText instead of MText where possible.
10/23/96
Some old commented-out code removed for aesthetic reasons.
7/31/98 Switched to AttributeMap
*/
/**
* StyleBuffer implements <tt>MStyleBuffer</tt>. It maintains
* <tt>AttributeMap</tt> objects to apply to the text in an <tt>MText</tt> object,
* and the
* intervals on which those styles apply.
* <p>
* StyleBuffer stores the intervals on which styles apply in a <tt>RunArray</tt>
* object (see <tt>RunArray</tt> for more information). The styles are stored in
* an array of <tt>AttributeMap</tt> objects.
* <p>
* <tt>RunArray</tt> maintains an array of integers which represent offsets into text.
* The array has a "positive" region in which offsets are given as positive distances
* from the start of the text, and a "negative" region in which offsets are given as
* negative distances from the end of the text. Between the positive and negative regions
* is a gap, into which new offsets may be inserted. This storage scheme allows for
* efficient response to a series of editing operations which occur in the same area of the
* text.
* <p>
* StyleBuffer uses the offsets in <tt>RunArray</tt> as the boundaries of style runs.
* A style run begins at each offset in <tt>RunArray</tt>, and each style run continues to
* the next offset. The style array is kept in sync with the array of offsets in <tt>RunArray</tt>;
* that is, the style which begins at RunArray[i] is stored in StyleArray[i].
* <p>
* The first entry in the <tt>RunArray</tt> is always 0.
*
* @author John Raley
*
* @see AttributeMap
* @see MText
* @see RunArray
*/
final class StyleBuffer extends MStyleBuffer implements Externalizable {
static final String COPYRIGHT =
"(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
/**
* Creates a new style buffer with length equal to the length of <tt>text</tt>,
* and with a single run of <tt>defaultStyle</tt>.
*/
private static final long serialVersionUID = 22356934;
private static final int CURRENT_VERSION = 1;
private static final int kInitialSize = 10;
private RunArray fRunArray;
private AttributeMap fStyleTable[];
StyleBuffer(MConstText text, AttributeMap initialStyle) {
this(text.length(), initialStyle);
}
/**
* Creates a new style buffer with length <tt>initialLength</tt> and with a
* single run of <tt>defaultStyle</tt>.
*/
StyleBuffer(int initialLength, AttributeMap initialStyle) {
fRunArray = new RunArray(kInitialSize, initialLength);
fRunArray.fPosEnd = 0;
fRunArray.fRunStart[0] = 0;
fStyleTable = new AttributeMap[kInitialSize]; // do I really want to do this???
fStyleTable[0] = initialStyle;
}
/**
* Note: this constructor is ONLY for use by the Serialization
* mechanism. It does not leave this object in a valid state!
*/
public StyleBuffer() {
}
public void writeExternal(ObjectOutput out) throws IOException {
compress();
out.writeInt(CURRENT_VERSION);
out.writeObject(fRunArray);
out.writeObject(fStyleTable);
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
if (in.readInt() != CURRENT_VERSION) {
throw new IOException("Invalid version of StyleBuffer");
}
fRunArray = (RunArray) in.readObject();
fStyleTable = (AttributeMap[]) in.readObject();
}
/**
* Shift style and run tables such that the last positive run begins before the given position.
* Since there is always a run start at 0, this method ensures that the first run will not be shifted.
* This is called by: <tt>insertText</tt> and <tt>deleteText</tt>.
* @param pos a position in the text.
*/
private void shiftTableTo(int pos) {
if (pos == 0)
pos = 1;
int oldNegStart = fRunArray.fNegStart;
int oldPosEnd = fRunArray.fPosEnd;
fRunArray.shiftTableTo(pos);
if (oldPosEnd > fRunArray.fPosEnd)
System.arraycopy(fStyleTable, fRunArray.fPosEnd+1,
fStyleTable, fRunArray.fNegStart,
oldPosEnd-fRunArray.fPosEnd);
else if (oldNegStart < fRunArray.fNegStart)
System.arraycopy(fStyleTable, oldNegStart,
fStyleTable, oldPosEnd+1,
fRunArray.fNegStart-oldNegStart);
}
/**
* Update the style table to reflect a change in the RunArray's size.
*/
private void handleArrayResize(int oldNegStart) {
AttributeMap newStyleTable[] = new AttributeMap[fRunArray.getArrayLength()];
System.arraycopy(fStyleTable, 0, newStyleTable, 0, fRunArray.fPosEnd+1);
System.arraycopy(fStyleTable, oldNegStart, newStyleTable, fRunArray.fNegStart, (fRunArray.getArrayLength()-fRunArray.fNegStart));
fStyleTable = newStyleTable;
}
/**
* Minimize the amount of storage used by this object.
*/
void compress() {
int oldNegStart = fRunArray.fNegStart;
fRunArray.compress();
if (fRunArray.fNegStart != oldNegStart) {
handleArrayResize(oldNegStart);
}
}
/**
* Increase the storage capacity of the style and run tables if no room remains.
*/
private void expandStyleTableIfFull() {
if (fRunArray.fPosEnd + 1 == fRunArray.fNegStart) {
int oldNegStart = fRunArray.fNegStart;
fRunArray.expandRunTable();
handleArrayResize(oldNegStart);
}
}
/*
public MStyleRunIterator createStyleRunIterator(int start, int limit) {
return new StyleRunIterator(start, limit);
}
*/
/**
* Respond to an insertion in the text. The length of the last style run which
* begins before <tt>start</tt> is increased by <tt>length</tt>. The run table
* is shifted such that the run into which text was inserted is the last positive run.
* This implementation assumes that all styles propogate.
* @param start the offset where the insertion began
* @param length the number of characters inserted
*/
public void insertText(int start, int limit) {
shiftTableTo(start);
fRunArray.addToCurTextLength(limit - start);
}
/**
* Respond to a deletion in the text. The last style run before
* <tt>start</tt> is truncated to end at <tt>start</tt>. The
* style run containing (<tt>start</tt>+<tt>length</tt>) is set to begin
* at (<tt>start</tt>+<tt>length</tt>). Runs in between are deleted.
* If the deletion occurs entirely within one style run, the length of the style
* run is reduced by <tt>length</tt>.
* This implementation assumes that all styles propogate.
* This method shifts the run table such that the run in which the delete began
* is the last positive run. Other methods depend on this "side effect".
* @param start the offset where the deletion began
* @param length the offset where the deletion stopped
*/
public void deleteText(int start, int limit) {
int length = limit - start;
// An optimization - if a whole run wasn't deleted we don't
// need to check for run merging, which could be expensive.
boolean wholeRunDeleted = false;
shiftTableTo(start);
int firstRunLimit = fRunArray.getCurTextLength();
if (fRunArray.fNegStart < fRunArray.getArrayLength())
firstRunLimit += fRunArray.fRunStart[fRunArray.fNegStart];
if (limit == fRunArray.getCurTextLength()) {
fRunArray.fNegStart = fRunArray.getArrayLength();
}
else if (limit >= firstRunLimit) {
int end = fRunArray.findRunContaining(limit);
if (end != fRunArray.fPosEnd) {
fRunArray.fRunStart[end] = limit - fRunArray.getCurTextLength();
fRunArray.fNegStart = end;
wholeRunDeleted = true;
}
}
if (fRunArray.fNegStart != fRunArray.getArrayLength()) {
if (start == 0 && limit >= firstRunLimit) {
// the first style run was deleted; move first "negative" run into
// first position
fStyleTable[0] = fStyleTable[fRunArray.fNegStart++];
}
else if (wholeRunDeleted) {
if (fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd])) {
// merge style runs
fRunArray.fNegStart++;
}
}
}
fRunArray.addToCurTextLength(-length);
fRunArray.runStartsChanged();
//System.out.println("In deleteText: number of style runs = " + numRuns(this));
}
/**
* Arrange style table so that old styles in the provided range are removed, and
* new styles can be inserted into the insertion gap.
* After calling this method, new style starts and styles may be placed
* in the insertion gaps of fRunArray.fStyleStart and fStyleTable.
* @param start offset in the text where insertion operation begins
* @param limit offset in the text where previous styles resume
*/
private void prepareStyleInsert(int start) {
if (start == 0) {
// fRunArray.fPosEnd should be 0 if we're in this branch.
if (fRunArray.getCurTextLength() > 0) {
/* Move first existing style run to negative end of buffer.
Don't do this if length==0; that is, if there is no real
style run at 0.
*/
fRunArray.fNegStart--;
fStyleTable[fRunArray.fNegStart] = fStyleTable[0];
fRunArray.fRunStart[fRunArray.fNegStart] = -fRunArray.getCurTextLength();
}
fRunArray.fPosEnd = -1;
}
else {
// consistency check: start should be in current gap
if (fRunArray.fRunStart[fRunArray.fPosEnd] >= start) {
throw new Error("Inconsistent state! Start should be within insertion gap.");
}
int endOfInsertionGap = fRunArray.getCurTextLength();
if (fRunArray.fNegStart < fRunArray.getArrayLength()) {
endOfInsertionGap += fRunArray.fRunStart[fRunArray.fNegStart];
}
if (endOfInsertionGap < start) {
throw new Error("Inconsistent state! Start should be within insertion gap.");
}
// if no break at start (on negative end of buffer) make one
if (endOfInsertionGap != start) {
// split style run in insertion gap
expandStyleTableIfFull();
fRunArray.fNegStart--;
fStyleTable[fRunArray.fNegStart] = fStyleTable[fRunArray.fPosEnd];
fRunArray.fRunStart[fRunArray.fNegStart] = start - fRunArray.getCurTextLength();
//System.out.println("splitting run.");
}
}
}
public boolean modifyStyles(int start,
int limit,
StyleModifier modifier,
int[] damagedRange) {
if (limit == start) {
return false;
}
shiftTableTo(start);
int currentRunStart = start;
AttributeMap oldStyle;
AttributeMap mergeStyle = fStyleTable[fRunArray.fPosEnd];
if (fRunArray.fNegStart < fRunArray.getArrayLength() &&
fRunArray.fRunStart[fRunArray.fNegStart]+fRunArray.getCurTextLength() == start) {
oldStyle = fStyleTable[fRunArray.fNegStart];
++fRunArray.fNegStart;
}
else {
oldStyle = mergeStyle;
}
boolean modifiedAnywhere = false;
for(;;) {
boolean modified = false;
// push new style into gap on positive side
AttributeMap newStyle = modifier.modifyStyle(oldStyle);
if (damagedRange != null && !newStyle.equals(oldStyle)) {
modified = modifiedAnywhere = true;
damagedRange[0] = Math.min(currentRunStart, damagedRange[0]);
}
if (!newStyle.equals(mergeStyle)) {
if (currentRunStart != 0) {
expandStyleTableIfFull();
++fRunArray.fPosEnd;
}
fStyleTable[fRunArray.fPosEnd] = newStyle;
fRunArray.fRunStart[fRunArray.fPosEnd] = currentRunStart;
}
mergeStyle = newStyle;
int nextRunStart = fRunArray.getLogicalRunStart(fRunArray.fNegStart);
if (limit > nextRunStart) {
oldStyle = fStyleTable[fRunArray.fNegStart];
currentRunStart = nextRunStart;
if (modified) {
damagedRange[1] = Math.max(currentRunStart, damagedRange[1]);
}
++fRunArray.fNegStart;
}
else {
if (limit < nextRunStart && !oldStyle.equals(mergeStyle)) {
expandStyleTableIfFull();
++fRunArray.fPosEnd;
fStyleTable[fRunArray.fPosEnd] = oldStyle;
fRunArray.fRunStart[fRunArray.fPosEnd] = limit;
}
if (modified) {
damagedRange[1] = Math.max(limit, damagedRange[1]);
}
break;
}
}
// merge last run if needed
if ((fRunArray.fNegStart < fRunArray.getArrayLength()) &&
(fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd]))) {
fRunArray.fNegStart++;
}
fRunArray.runStartsChanged();
return modifiedAnywhere;
}
public int styleStart(int pos) {
if (pos == fRunArray.getCurTextLength()) {
return pos;
}
return fRunArray.getLogicalRunStart(fRunArray.findRunContaining(pos));
}
public int styleLimit(int pos) {
if (pos == fRunArray.getCurTextLength()) {
return pos;
}
int run = fRunArray.findRunContaining(pos);
if (run == fRunArray.fPosEnd) {
run = fRunArray.fNegStart;
}
else {
++run;
}
return fRunArray.getLogicalRunStart(run);
}
/**
* Return style at location <tt>pos</tt>.
* @param pos an offset into the text
* @returns the style of the character at <tt>offset</tt>
*/
public AttributeMap styleAt(int pos) {
return fStyleTable[ fRunArray.findRunContaining(pos) ];
}
/*
* Set run start, run length, and run value in an iterator. This method is
* only called by a <tt>StyleRunIterator</tt>.
* @param pos an offset into the text. The iterator's run start and run limit are
* set to the run containing <tt>pos</tt>.
* @param iter the iterator to set
*/
void setIterator(int pos, StyleRunIterator iter) {
if ((pos < 0) || (pos > fRunArray.getCurTextLength())) {
iter.set(null, 0, 0, kNoRun);
return;
}
int run = fRunArray.findRunContaining(pos);
setIteratorUsingRun(run, iter);
}
/**
* Set run start, run length, and run value in an iterator. This method is
* only called by a <tt>StyleRunIterator</tt>.
* @param run the index of the run to which the iterator should be set
* @param iter the iterator to set
*/
private void setIteratorUsingRun(int run, StyleRunIterator iter) {
int lastValidRun = fRunArray.lastRun();
if (run < 0 || run > lastValidRun) {
iter.set(null, 0, 0, kNoRun);
return;
}
if (run == fRunArray.fPosEnd+1)
run = fRunArray.fNegStart;
else if (run == fRunArray.fNegStart-1)
run = fRunArray.fPosEnd;
int runStart = fRunArray.fRunStart[run];
if (runStart < 0)
runStart += fRunArray.getCurTextLength();
AttributeMap style = fStyleTable[run];
int nextRun;
if (run == fRunArray.fPosEnd)
nextRun = fRunArray.fNegStart;
else
nextRun = run + 1;
int runLimit;
if (nextRun >= fRunArray.getArrayLength())
runLimit = fRunArray.getCurTextLength();
else {
runLimit = fRunArray.fRunStart[nextRun];
if (runLimit < 0)
runLimit += fRunArray.getCurTextLength();
}
//System.out.println("setIterator: pos="+pos+", runStart="+runStart+", runLimit="+runLimit+
// ", run="+run+", fPosEnd="+fPosEnd);
iter.set(style, runStart, runLimit, run);
}
public void replace(int start, int limit, MConstText srcText, int srcStart, int srcLimit)
{
deleteText(start, limit);
if (srcStart == srcLimit)
return;
prepareStyleInsert(start);
for (int j2 = srcStart; j2 < srcLimit; j2 = srcText.characterStyleLimit(j2))
{
AttributeMap attributeMap = srcText.characterStyleAt(j2);
if (fRunArray.fPosEnd < 0 || !fStyleTable[fRunArray.fPosEnd].equals(attributeMap))
{
expandStyleTableIfFull();
fRunArray.fPosEnd++;
fRunArray.fRunStart[fRunArray.fPosEnd] = j2 - srcStart + start;
fStyleTable[fRunArray.fPosEnd] = attributeMap;
}
}
fRunArray.addToCurTextLength(srcLimit - srcStart);
if (fRunArray.fNegStart < fRunArray.getArrayLength() && fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd]))
fRunArray.fNegStart++;
}
private static final int kNoRun = -42; // iterator use
private final class StyleRunIterator /*implements MStyleRunIterator*/ {
StyleRunIterator(int start, int limit)
{
reset(start, limit, start);
}
public void reset(int start, int limit, int pos)
{
fStart = start;
fLimit = limit;
setIterator(fStart, this);
}
public boolean isValid()
{
return fStyle != null;
}
public void next()
{
if (fRunLimit < fLimit) {
fCurrentRun++;
setIteratorUsingRun(fCurrentRun, this);
}
else
set(null, 0, 0, kNoRun);
}
public void prev()
{
if (fRunStart > fStart) {
fCurrentRun--;
setIteratorUsingRun(fCurrentRun, this);
}
else
set(null, 0, 0, kNoRun);
}
public void set(int pos)
{
if (pos >= fStart && pos < fLimit) {
setIterator(pos, this);
} else {
set(null, 0, 0, kNoRun);
}
}
void set(AttributeMap style, int start, int limit, int currentRun)
{
fStyle = style;
fCurrentRun = currentRun;
fRunStart = start < fStart ? fStart : start;
fRunLimit = limit > fLimit ? fLimit : limit;
}
public void reset(int start, int limit)
{
reset(start, limit, start);
}
public void first()
{
set(fStart);
}
public void last()
{
set(fLimit - 1);
}
public int rangeStart()
{
return fStart;
}
public int rangeLimit()
{
return fLimit;
}
public int rangeLength()
{
return fLimit - fStart;
}
public AttributeMap style()
{
return fStyle;
}
public int runStart()
{
return fRunStart;
}
public int runLimit()
{
return fRunLimit;
}
public int runLength()
{
return fRunLimit - fRunStart;
}
private int fStart;
private int fLimit;
private AttributeMap fStyle;
private int fRunStart;
private int fRunLimit;
private int fCurrentRun;
}
}