package org.eclipse.swt.custom; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved | |
*/ | |
import org.eclipse.swt.*; | |
import org.eclipse.swt.events.*; | |
import org.eclipse.swt.internal.Compatibility; | |
import org.eclipse.swt.widgets.*; | |
import java.io.*; | |
import java.util.Vector; | |
class DefaultContent implements StyledTextContent { | |
private final static String LineDelimiter = System.getProperty("line.separator"); | |
Vector textListeners = new Vector(); // stores text listeners for event sending | |
char[] textStore = new char[0]; // stores the actual text | |
int gapStart = -1; // the character position start of the gap | |
int gapEnd = -1; // the character position after the end of the gap | |
int gapLine = -1; // the line on which the gap exists, the gap will always be associated | |
// with one line | |
int highWatermark = 300; | |
int lowWatermark = 50; | |
int[][] lines = new int[50][2]; // array of character positions and lengths representing | |
// the lines of text | |
int lineCount = 0; // the number of lines of text | |
int expandExp = 1; // the expansion exponent, used to increase the lines array exponentially | |
int replaceExpandExp = 1; // the expansion exponent, used to increase the lines array exponentially | |
/** | |
* Creates a new DefaultContent and initializes it. A <code>StyledTextContent</> will always have | |
* at least one empty line. | |
*/ | |
DefaultContent() { | |
super(); | |
setText(""); | |
} | |
/** | |
* Adds a line to the end of the line indexes array. Increases the size of the array if necessary. | |
* <code>lineCount</code> is updated to reflect the new entry. | |
* <p> | |
* | |
* @param start the start of the line | |
* @param length the length of the line | |
*/ | |
void addLineIndex(int start, int length) { | |
int size = lines.length; | |
if (lineCount == size) { | |
// expand the lines by powers of 2 | |
int[][] newLines = new int[size+Compatibility.pow2(expandExp)][2]; | |
System.arraycopy(lines, 0, newLines, 0, size); | |
lines = newLines; | |
expandExp++; | |
} | |
int[] range = new int[] {start, length}; | |
lines[lineCount] = range; | |
lineCount++; | |
} | |
/** | |
* Adds a line index to the end of <code>linesArray</code>. Increases the | |
* size of the array if necessary and returns a new array. | |
* <p> | |
* | |
* @param start the start of the line | |
* @param length the length of the line | |
* @param linesArray the array to which to add the line index | |
* @param count the position at which to add the line | |
* @return a new array of line indexes | |
*/ | |
int[][] addLineIndex(int start, int length, int[][] linesArray, int count) { | |
int size = linesArray.length; | |
int[][] newLines = linesArray; | |
if (count == size) { | |
newLines = new int[size+Compatibility.pow2(replaceExpandExp)][2]; | |
replaceExpandExp++; | |
System.arraycopy(linesArray, 0, newLines, 0, size); | |
} | |
int[] range = new int[] {start, length}; | |
newLines[count] = range; | |
return newLines; | |
} | |
/** | |
* Adds a <code>TextChangeListener</code> listening for | |
* <code>TextChangingEvent</code> and <code>TextChangedEvent</code>. A | |
* <code>TextChangingEvent</code> is sent before changes to the text occur. | |
* A <code>TextChangedEvent</code> is sent after changes to the text | |
* occured. | |
* <p> | |
* | |
* @param listener the listener | |
* @exception IllegalArgumentException <ul> | |
* <li>ERROR_NULL_ARGUMENT when listener is null</li> | |
* </ul> | |
*/ | |
public void addTextChangeListener(TextChangeListener listener) { | |
if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); | |
StyledTextListener typedListener = new StyledTextListener(listener); | |
textListeners.addElement(typedListener); | |
} | |
/** | |
* Adjusts the gap to accomodate a text change that is occurring. | |
* <p> | |
* | |
* @param position the position at which a change is occurring | |
* @param sizeHint the size of the change | |
* @param line the line where the gap will go | |
*/ | |
void adjustGap(int position, int sizeHint, int line) { | |
if (position == gapStart) { | |
// text is being inserted at the gap position | |
int size = (gapEnd - gapStart) - sizeHint; | |
if (lowWatermark <= size && size <= highWatermark) | |
return; | |
} else if ((position + sizeHint == gapStart) && (sizeHint < 0)) { | |
// text is being deleted at the gap position | |
int size = (gapEnd - gapStart) - sizeHint; | |
if (lowWatermark <= size && size <= highWatermark) | |
return; | |
} | |
moveAndResizeGap(position, sizeHint, line); | |
} | |
/** | |
* Calculates the indexes of each line in the text store. Assumes no gap exists. | |
* Optimized to do less checking. | |
*/ | |
void indexLines(){ | |
int start = 0; | |
lineCount = 0; | |
int textLength = textStore.length; | |
int i; | |
for (i=start; i<textLength; i++) { | |
char ch = textStore[i]; | |
if (ch == SWT.CR) { | |
// see if the next character is a LF | |
if (i + 1 < textLength) { | |
ch = textStore[i+1]; | |
if (ch == SWT.LF) { | |
i++; | |
} | |
} | |
addLineIndex(start, i - start + 1); | |
start = i + 1; | |
} else if (ch == SWT.LF) { | |
addLineIndex(start, i - start + 1); | |
start = i + 1; | |
} | |
} | |
addLineIndex(start, i - start); | |
} | |
/** | |
* Returns whether or not the given character is a line delimiter. Both CR and LF | |
* are valid line delimiters. | |
* <p> | |
* | |
* @param ch the character to test | |
* @return true if ch is a delimiter, false otherwise | |
*/ | |
boolean isDelimiter(char ch) { | |
if (ch == SWT.CR) return true; | |
if (ch == SWT.LF) return true; | |
return false; | |
} | |
/** | |
* Determine whether or not the replace operation is valid. DefaultContent will not allow | |
* the /r/n line delimiter to be split or partially deleted. | |
* <p> | |
* | |
* @param start start offset of text to replace | |
* @param replaceLength start offset of text to replace | |
* @param newText start offset of text to replace | |
*/ | |
protected boolean isValidReplace(int start, int replaceLength, String newText){ | |
if (replaceLength == 0) { | |
// inserting text, see if the \r\n line delimiter is being split | |
if (start == 0) return true; | |
if (start == getCharCount()) return true; | |
char before = getTextRange(start - 1, 1).charAt(0); | |
if (before == '\r') { | |
char after = getTextRange(start, 1).charAt(0); | |
if (after == '\n') return false; | |
} | |
} else { | |
// deleting text, see if part of a \r\n line delimiter is being deleted | |
char startChar = getTextRange(start, 1).charAt(0); | |
if (startChar == '\n') { | |
// see if char before delete position is \r | |
if (start != 0) { | |
char before = getTextRange(start - 1, 1).charAt(0); | |
if (before == '\r') return false; | |
} | |
} | |
char endChar = getTextRange(start + replaceLength - 1, 1).charAt(0); | |
if (endChar == '\r') { | |
// see if char after delete position is \n | |
if (start + replaceLength != getCharCount()) { | |
char after = getTextRange(start + replaceLength, 1).charAt(0); | |
if (after == '\n') return false; | |
} | |
} | |
} | |
return true; | |
} | |
/** | |
* Calculates the indexes of each line of text in the given range. | |
* <p> | |
* | |
* @param offset the logical start offset of the text lineate | |
* @param length the length of the text to lineate, includes gap | |
* @param numLines the number of lines to initially allocate for the line index array, | |
* passed in for efficiency (the exact number of lines may be known) | |
* @return a line indexes array where each line is identified by a start offset and | |
* a length | |
*/ | |
int[][] indexLines(int offset, int length, int numLines){ | |
int[][] indexedLines = new int[numLines][2]; | |
int start = 0; | |
int lineCnt = 0; | |
int i; | |
replaceExpandExp = 1; | |
for (i=start; i<length; i++) { | |
int location = i + offset; | |
if ((location >= gapStart) && (location < gapEnd)) { | |
// ignore the gap | |
} else { | |
char ch = textStore[location]; | |
if (ch == SWT.CR) { | |
// see if the next character is a LF | |
if (location+1 < textStore.length) { | |
ch = textStore[location+1]; | |
if (ch == SWT.LF) { | |
i++; | |
} | |
} | |
indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCnt); | |
lineCnt++; | |
start = i + 1; | |
} else if (ch == SWT.LF) { | |
indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCnt); | |
lineCnt++; | |
start = i + 1; | |
} | |
} | |
} | |
int[][] newLines = new int[lineCnt+1][2]; | |
System.arraycopy(indexedLines, 0, newLines, 0, lineCnt); | |
int[] range = new int[] {start, i - start}; | |
newLines[lineCnt]=range; | |
return newLines; | |
} | |
/** | |
* Inserts text. | |
* <p> | |
* | |
* @param position the position at which to insert the text | |
* @param length the text to insert | |
*/ | |
void insert(int position, String text) { | |
if (text.length() == 0) return; | |
int startLine = getLineAtOffset(position); | |
int change = text.length(); | |
boolean endInsert = position == getCharCount(); | |
adjustGap(position, change, startLine); | |
// during an insert the gap will be adjusted to start at | |
// position and it will be associated with startline, the | |
// inserted text will be placed in the gap | |
int startLineOffset = getOffsetAtLine(startLine); | |
// at this point, startLineLength will include the start line | |
// and all of the newly inserted text | |
int startLineLength = getPhysicalLine(startLine).length(); | |
if (change > 0) { | |
// shrink gap | |
gapStart += (change); | |
for (int i = 0; i < text.length(); i++) | |
textStore[position + i]= text.charAt(i); | |
} | |
// figure out the number of new lines that have been inserted | |
int [][] newLines = indexLines(startLineOffset, startLineLength, 10); | |
// only insert an empty line if it is the last line in the text | |
int numNewLines = newLines.length - 1; | |
if (newLines[numNewLines][1] == 0) { | |
// last inserted line is a new line | |
if (endInsert) { | |
// insert happening at end of the text, leave numNewLines as | |
// is since the last new line will not be concatenated with another | |
// line | |
numNewLines += 1; | |
} else { | |
numNewLines -= 1; | |
} | |
} | |
// make room for the new lines | |
expandLinesBy(numNewLines); | |
// shift down the lines after the replace line | |
for (int i = lineCount-1; i > startLine; i--) { | |
lines[i + numNewLines]=lines[i]; | |
} | |
// insert the new lines | |
for (int i=0; i<numNewLines; i++) { | |
newLines[i][0] += startLineOffset; | |
lines[startLine + i]=newLines[i]; | |
} | |
// update the last inserted line | |
if (numNewLines < newLines.length) { | |
newLines[numNewLines][0] += startLineOffset; | |
lines[startLine + numNewLines] = newLines[numNewLines]; | |
} | |
lineCount += numNewLines; | |
gapLine = getLineAtPhysicalOffset(gapStart); | |
} | |
/** | |
* Moves the gap and adjusts its size in anticipation of a text change. | |
* The gap is resized to actual size + the specified size and moved to the given | |
* position. | |
* <p> | |
* | |
* @param position the position at which a change is occurring | |
* @param sizeHint the size of the change | |
* @param line the line where the gap should be put | |
*/ | |
void moveAndResizeGap(int position, int size, int newGapLine) { | |
char[] content = null; | |
int oldSize = gapEnd - gapStart; | |
int newSize; | |
if (size > 0) { | |
newSize = highWatermark + size; | |
} else { | |
newSize = lowWatermark - size; | |
} | |
// remove the old gap from the lines information | |
if (gapExists()) { | |
// adjust the line length | |
lines[gapLine][1] = lines[gapLine][1] - oldSize; | |
// adjust the offsets of the lines after the gapLine | |
for (int i=gapLine+1; i<lineCount; i++) { | |
lines[i][0]=lines[i][0]-oldSize; | |
} | |
} | |
if (newSize < 0) { | |
if (oldSize > 0) { | |
// removing the gap | |
content = new char[textStore.length - oldSize]; | |
System.arraycopy(textStore, 0, content, 0, gapStart); | |
System.arraycopy(textStore, gapEnd, content, gapStart, content.length - gapStart); | |
textStore= content; | |
} | |
gapStart = gapEnd= position; | |
return; | |
} | |
content = new char[textStore.length + (newSize - oldSize)]; | |
int newGapStart = position; | |
int newGapEnd = newGapStart + newSize; | |
if (oldSize == 0) { | |
System.arraycopy(textStore, 0, content, 0, newGapStart); | |
System.arraycopy(textStore, newGapStart, content, newGapEnd, content.length - newGapEnd); | |
} else if (newGapStart < gapStart) { | |
int delta = gapStart - newGapStart; | |
System.arraycopy(textStore, 0, content, 0, newGapStart); | |
System.arraycopy(textStore, newGapStart, content, newGapEnd, delta); | |
System.arraycopy(textStore, gapEnd, content, newGapEnd + delta, textStore.length - gapEnd); | |
} else { | |
int delta = newGapStart - gapStart; | |
System.arraycopy(textStore, 0, content, 0, gapStart); | |
System.arraycopy(textStore, gapEnd, content, gapStart, delta); | |
System.arraycopy(textStore, gapEnd + delta, content, newGapEnd, content.length - newGapEnd); | |
} | |
textStore = content; | |
gapStart = newGapStart; | |
gapEnd = newGapEnd; | |
// add the new gap to the lines information | |
if (gapExists()) { | |
gapLine = newGapLine; | |
// adjust the line length | |
int gapLength = gapEnd - gapStart; | |
lines[gapLine][1] = lines[gapLine][1] + (gapLength); | |
// adjust the offsets of the lines after the gapLine | |
for (int i=gapLine+1; i<lineCount; i++) { | |
lines[i][0]=lines[i][0]+gapLength; | |
} | |
} | |
} | |
/** | |
* Returns the number of lines that are in the specified text. | |
* <p> | |
* | |
* @param startOffset the start of the text to lineate | |
* @param length the length of the text to lineate | |
* @return number of lines | |
*/ | |
int lineCount(int startOffset, int length){ | |
if (length == 0) { | |
return 0; | |
} | |
int lineCnt = 0; | |
int count = 0; | |
int i = startOffset; | |
if (i >= gapStart) { | |
i += gapEnd - gapStart; | |
} | |
while (count < length) { | |
if ((i >= gapStart) && (i < gapEnd)) { | |
// ignore the gap | |
} else { | |
char ch = textStore[i]; | |
if (ch == SWT.CR) { | |
// see if the next character is a LF | |
if (i + 1 < textStore.length) { | |
ch = textStore[i+1]; | |
if (ch == SWT.LF) { | |
i++; | |
count++; | |
} | |
} | |
lineCnt++; | |
} else if (ch == SWT.LF) { | |
lineCnt++; | |
} | |
count++; | |
} | |
i++; | |
} | |
return lineCnt; | |
} | |
/** | |
* Returns the number of lines that are in the specified text. | |
* <p> | |
* | |
* @param text the text to lineate | |
* @return number of lines in the text | |
*/ | |
int lineCount(String text){ | |
int lineCount = 0; | |
int length = text.length(); | |
for (int i = 0; i < length; i++) { | |
char ch = text.charAt(i); | |
if (ch == SWT.CR) { | |
if (i + 1 < length && text.charAt(i + 1) == SWT.LF) { | |
i++; | |
} | |
lineCount++; | |
} | |
else | |
if (ch == SWT.LF) { | |
lineCount++; | |
} | |
} | |
return lineCount; | |
} | |
/** | |
* @return the logical length of the text store | |
*/ | |
public int getCharCount() { | |
int length = gapEnd - gapStart; | |
return (textStore.length - length); | |
} | |
/** | |
* Returns the line at <code>index</code> without delimiters. | |
* <p> | |
* | |
* @param index the index of the line to return | |
* @return the logical line text (i.e., without the gap) | |
* @exception IllegalArgumentException <ul> | |
* <li>ERROR_INVALID_ARGUMENT when index is out of range</li> | |
* </ul> | |
*/ | |
public String getLine(int index) { | |
if ((index >= lineCount) || (index < 0)) error(SWT.ERROR_INVALID_ARGUMENT); | |
int start = lines[index][0]; | |
int length = lines[index][1]; | |
int end = start + length - 1; | |
if (!gapExists() || (end < gapStart) || (start >= gapEnd)) { | |
// line is before or after the gap | |
while ((length - 1 >= 0) && isDelimiter(textStore[start+length-1])) { | |
length--; | |
} | |
return new String(textStore, start, length); | |
} else { | |
// gap is in the specified range, strip out the gap | |
StringBuffer buf = new StringBuffer(); | |
int gapLength = gapEnd - gapStart; | |
buf.append(textStore, start, gapStart - start); | |
buf.append(textStore, gapEnd, length - gapLength - (gapStart - start)); | |
length = buf.length(); | |
while ((length - 1 >=0) && isDelimiter(buf.charAt(length-1))) { | |
length--; | |
} | |
return Compatibility.substring(buf, 0, length); | |
} | |
} | |
/** | |
* Returns the line delimiter that should be used by the StyledText | |
* widget when inserting new lines. This delimiter may be different than the | |
* delimiter that is used by the <code>StyledTextContent</code> interface. | |
* <p> | |
* | |
* @return the platform line delimiter as specified in the line.separator | |
* system property. | |
*/ | |
public String getLineDelimiter() { | |
return LineDelimiter; | |
} | |
/** | |
* Returns the line at the given index with delimiters. | |
* <p> | |
* @param index the index of the line to return | |
* @return the logical line text (i.e., without the gap) with delimiters | |
*/ | |
String getFullLine(int index) { | |
int start = lines[index][0]; | |
int length = lines[index][1]; | |
int end = start + length - 1; | |
if (!gapExists() || (end < gapStart) || (start >= gapEnd)) { | |
// line is before or after the gap | |
return new String(textStore, start, length); | |
} else { | |
// gap is in the specified range, strip out the gap | |
StringBuffer buf = new StringBuffer(); | |
int gapLength = gapEnd - gapStart; | |
buf.append(textStore, start, gapStart - start); | |
buf.append(textStore, gapEnd, length - gapLength - (gapStart - start)); | |
return buf.toString(); | |
} | |
} | |
/** | |
* Returns the physical line at the given index (i.e., with delimiters and the gap). | |
* <p> | |
* | |
* @param index the line index | |
* @return the physical line | |
*/ | |
String getPhysicalLine(int index) { | |
int start = lines[index][0]; | |
int length = lines[index][1]; | |
return getPhysicalText(start, length); | |
} | |
/** | |
* @return the number of lines in the text store | |
*/ | |
public int getLineCount(){ | |
return lineCount; | |
} | |
/** | |
* Returns the line at the given offset. | |
* <p> | |
* | |
* @param charPosition logical character offset (i.e., does not include gap) | |
* @return the line index | |
* @exception IllegalArgumentException <ul> | |
* <li>ERROR_INVALID_ARGUMENT when charPosition is out of range</li> | |
* </ul> | |
*/ | |
public int getLineAtOffset(int charPosition){ | |
int position; | |
if ((charPosition > getCharCount()) || (charPosition < 0)) error(SWT.ERROR_INVALID_ARGUMENT); | |
if (charPosition < gapStart) { | |
// position is before the gap | |
position = charPosition; | |
} else { | |
// position includes the gap | |
position = charPosition + (gapEnd - gapStart); | |
} | |
// if last line and the line is not empty you can ask for | |
// a position that doesn't exist (the one to the right of the | |
// last character) - for inserting | |
if (lineCount > 0) { | |
int lastLine = lineCount - 1; | |
if (position == lines[lastLine][0] + lines[lastLine][1]) | |
return lastLine; | |
} | |
int high = lineCount; | |
int low = -1; | |
int index = lineCount; | |
while (high - low > 1) { | |
index = (high + low) / 2; | |
int lineStart = lines[index][0]; | |
int lineEnd = lineStart + lines[index][1] - 1; | |
if (position <= lineStart) { | |
high = index; | |
} else if (position <= lineEnd) { | |
high = index; | |
break; | |
} else { | |
low = index; | |
} | |
} | |
return high; | |
} | |
/** | |
* Returns the line index at the given physical offset. | |
* <p> | |
* | |
* @param position physical character offset (i.e., includes gap) | |
* @return the line index | |
*/ | |
int getLineAtPhysicalOffset(int position){ | |
int high = lineCount; | |
int low = -1; | |
int index = lineCount; | |
while (high - low > 1) { | |
index = (high + low) / 2; | |
int lineStart = lines[index][0]; | |
int lineEnd = lineStart + lines[index][1] - 1; | |
if (position <= lineStart) { | |
high = index; | |
} else if (position <= lineEnd) { | |
high = index; | |
break; | |
} else { | |
low = index; | |
} | |
} | |
return high; | |
} | |
/** | |
* Returns the logical offset of the given line. | |
* <p> | |
* | |
* @param lineIndex index of line | |
* @return the logical starting offset of the line. When there are not any lines, | |
* getOffsetAtLine(0) is a valid call that should answer 0. | |
* @exception IllegalArgumentException <ul> | |
* <li>ERROR_INVALID_ARGUMENT when lineIndex is out of range</li> | |
* </ul> | |
*/ | |
public int getOffsetAtLine(int lineIndex) { | |
if (lineIndex == 0) return 0; | |
if ((lineIndex >= lineCount) || (lineIndex < 0)) error(SWT.ERROR_INVALID_ARGUMENT); | |
int start = lines[lineIndex][0]; | |
if (start > gapEnd) { | |
return start - (gapEnd - gapStart); | |
} else { | |
return start; | |
} | |
} | |
/** | |
* Increases the line indexes array to accomodate more lines. | |
* <p> | |
* | |
* @param numLines the number to increase the array by | |
*/ | |
void expandLinesBy(int numLines) { | |
int size = lines.length; | |
if (size - lineCount >= numLines) { | |
return; | |
} | |
int[][] newLines = new int[size+Math.max(10, numLines)][2]; | |
System.arraycopy(lines, 0, newLines, 0, size); | |
lines = newLines; | |
} | |
/** | |
* Reports an SWT error. | |
* <p> | |
* | |
* @param code the error code | |
*/ | |
void error (int code) { | |
SWT.error(code); | |
} | |
/** | |
* Returns whether or not a gap exists in the text store. | |
* <p> | |
* | |
* @return true if gap exists, false otherwise | |
*/ | |
boolean gapExists() { | |
return gapStart != gapEnd; | |
} | |
/** | |
* Returns a string representing the continous content of | |
* the text store. | |
* <p> | |
* | |
* @param start the physical start offset of the text to return | |
* @param length the physical length of the text to return | |
* @return the text | |
*/ | |
String getPhysicalText(int start, int length) { | |
return new String(textStore, start, length); | |
} | |
/** | |
* Returns a string representing the logical content of | |
* the text store (i.e., gap stripped out). | |
* <p> | |
* | |
* @param start the logical start offset of the text to return | |
* @param length the logical length of the text to return | |
* @return the text | |
*/ | |
public String getTextRange(int start, int length) { | |
if (textStore == null) | |
return ""; | |
if (length == 0) | |
return ""; | |
int end= start + length; | |
if (!gapExists() || (end < gapStart)) | |
return new String(textStore, start, length); | |
if (gapStart < start) { | |
int gapLength= gapEnd - gapStart; | |
return new String(textStore, start + gapLength , length); | |
} | |
StringBuffer buf = new StringBuffer(); | |
buf.append(textStore, start, gapStart - start); | |
buf.append(textStore, gapEnd, end - gapStart); | |
return buf.toString(); | |
} | |
/** | |
* Removes the specified <code>TextChangeListener</code>. | |
* <p> | |
* | |
* @param listener the listener | |
* @exception IllegalArgumentException <ul> | |
* <li>ERROR_NULL_ARGUMENT when listener is null</li> | |
* </ul> | |
*/ | |
public void removeTextChangeListener(TextChangeListener listener){ | |
if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); | |
for (int i=0; i<textListeners.size(); i++) { | |
TypedListener typedListener = (TypedListener) textListeners.elementAt(i); | |
if (typedListener.getEventListener () == listener) { | |
textListeners.removeElementAt(i); | |
break; | |
} | |
} | |
} | |
/** | |
* Replaces the text with <code>newText</code> starting at position <code>start</code> | |
* for a length of <code>replaceLength</code>. Notifies the appropriate listeners. | |
* <p> | |
* | |
* When sending the TextChangingEvent, <code>newLineCount</code> is the number of | |
* lines that are going to be inserted and <code>replaceLineCount</code> is | |
* the number of lines that are going to be deleted, based on the change | |
* that occurs visually. For example: | |
* <ul> | |
* <li>(replaceText,newText) ==> (replaceLineCount,newLineCount) | |
* <li>("","\n") ==> (0,1) | |
* <li>("\n\n","a") ==> (2,0) | |
* </ul> | |
* </p> | |
* | |
* @param start start offset of text to replace | |
* @param replaceLength start offset of text to replace | |
* @param newText start offset of text to replace | |
* | |
* @exception SWTException <ul> | |
* <li>ERROR_INVALID_ARGUMENT when the text change results in a multi byte | |
* line delimiter being split or partially deleted. Splitting a line | |
* delimiter by inserting text between the CR and LF characters of the | |
* \r\n delimiter or deleting part of this line delimiter is not supported</li> | |
* </ul> | |
*/ | |
public void replaceTextRange(int start, int replaceLength, String newText){ | |
// check for invalid replace operations | |
if (!isValidReplace(start, replaceLength, newText)) SWT.error(SWT.ERROR_INVALID_ARGUMENT); | |
// inform listeners | |
StyledTextEvent event = new StyledTextEvent(this); | |
event.type = StyledText.TextChanging; | |
event.start = start; | |
event.replaceLineCount = lineCount(start, replaceLength); | |
event.text = newText; | |
event.newLineCount = lineCount(newText); | |
event.replaceCharCount = replaceLength; | |
event.newCharCount = newText.length(); | |
sendTextEvent(event); | |
// first delete the text to be replaced | |
delete(start, replaceLength, event.replaceLineCount + 1); | |
// then insert the new text | |
insert(start, newText); | |
// inform listeners | |
event = new StyledTextEvent(this); | |
event.type = StyledText.TextChanged; | |
sendTextEvent(event); | |
// printLines(); | |
} | |
/** | |
* Sends the text listeners the TextChanged event. | |
*/ | |
void sendTextEvent(StyledTextEvent event) { | |
for (int i=0; i<textListeners.size(); i++) { | |
((StyledTextListener)textListeners.elementAt(i)).handleEvent(event); | |
} | |
} | |
/** | |
* Sets the content to text and removes the gap since there are no sensible predictions | |
* about where the next change will occur. | |
* <p> | |
* | |
* @param text the text | |
*/ | |
public void setText (String text){ | |
textStore = text.toCharArray(); | |
gapStart = -1; | |
gapEnd = -1; | |
expandExp = 1; | |
indexLines(); | |
StyledTextEvent event = new StyledTextEvent(this); | |
event.type = StyledText.TextSet; | |
event.text = ""; | |
sendTextEvent(event); | |
} | |
/** | |
* Deletes text. | |
* <p> | |
* @param position the position at which the text to delete starts | |
* @param length the length of the text to delete | |
* @param numLines the number of lines that are being deleted | |
*/ | |
void delete(int position, int length, int numLines) { | |
if (length == 0) return; | |
int startLine = getLineAtOffset(position); | |
int startLineOffset = getOffsetAtLine(startLine); | |
int endLine = getLineAtOffset(position + length); | |
String endText = ""; | |
boolean splittingDelimiter = false; | |
if (position + length < getCharCount()) { | |
endText = getTextRange(position + length - 1, 2); | |
if ((endText.charAt(0) == SWT.CR) && (endText.charAt(1) == SWT.LF)) { | |
splittingDelimiter = true; | |
} | |
} | |
adjustGap(position + length, -length, startLine); | |
int [][] oldLines = indexLines(position, length + (gapEnd - gapStart), numLines); | |
// enlarge the gap - the gap can be enlarged either to the | |
// right or left | |
if (position + length == gapStart) { | |
gapStart -= length; | |
} else { | |
gapEnd += length; | |
} | |
// figure out the length of the new concatenated line, do so by | |
// finding the first line delmiter after position | |
int j = position; | |
boolean eol = false; | |
while (j < textStore.length && !eol) { | |
if (j < gapStart || j >= gapEnd) { | |
char ch = textStore[j]; | |
if (isDelimiter(ch)) { | |
if (j + 1 < textStore.length) | |
if (ch == SWT.CR && (textStore[j+1] == SWT.LF)) | |
j++; | |
eol = true; | |
} | |
} | |
j++; | |
} | |
// update the line where the deletion started | |
lines[startLine][1] = (position - startLineOffset) + (j - position); | |
// figure out the number of lines that have been deleted | |
int numOldLines = oldLines.length - 1; | |
if (splittingDelimiter) numOldLines -= 1; | |
// shift up the lines after the last deleted line, no need to update | |
// the offset or length of the lines | |
for (int i = endLine + 1; i < lineCount; i++) { | |
lines[i - numOldLines]=lines[i]; | |
} | |
lineCount -= numOldLines; | |
gapLine = getLineAtPhysicalOffset(gapStart); | |
} | |
} |