blob: ce595a219026aaf7d36b40466ba4d5d7890e0d0b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.custom;
import java.util.*;
import org.eclipse.swt.*;
import org.eclipse.swt.accessibility.*;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.printing.*;
import org.eclipse.swt.widgets.*;
/**
* A StyledText is an editable user interface object that displays lines
* of text. The following style attributes can be defined for the text:
* <ul>
* <li>foreground color
* <li>background color
* <li>font style (bold, italic, bold-italic, regular)
* <li>underline
* <li>strikeout
* </ul>
* <p>
* In addition to text style attributes, the background color of a line may
* be specified.
* </p>
* <p>
* There are two ways to use this widget when specifying text style information.
* You may use the API that is defined for StyledText or you may define your own
* LineStyleListener. If you define your own listener, you will be responsible
* for maintaining the text style information for the widget. IMPORTANT: You may
* not define your own listener and use the StyledText API. The following
* StyledText API is not supported if you have defined a LineStyleListener:
* <ul>
* <li>getStyleRangeAtOffset(int)
* <li>getStyleRanges()
* <li>replaceStyleRanges(int,int,StyleRange[])
* <li>setStyleRange(StyleRange)
* <li>setStyleRanges(StyleRange[])
* </ul>
* </p>
* <p>
* There are two ways to use this widget when specifying line background colors.
* You may use the API that is defined for StyledText or you may define your own
* LineBackgroundListener. If you define your own listener, you will be responsible
* for maintaining the line background color information for the widget.
* IMPORTANT: You may not define your own listener and use the StyledText API.
* The following StyledText API is not supported if you have defined a
* LineBackgroundListener:
* <ul>
* <li>getLineBackground(int)
* <li>setLineBackground(int,int,Color)
* </ul>
* </p>
* <p>
* The content implementation for this widget may also be user-defined. To do so,
* you must implement the StyledTextContent interface and use the StyledText API
* setContent(StyledTextContent) to initialize the widget.
* </p>
* <p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
* <dl>
* <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
* <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey
* </dl>
*/
public class StyledText extends Canvas {
static final char TAB = '\t';
static final String PlatformLineDelimiter = System.getProperty("line.separator");
static final int BIDI_CARET_WIDTH = 3;
static final int DEFAULT_WIDTH = 64;
static final int DEFAULT_HEIGHT = 64;
static final int V_SCROLL_RATE = 50;
static final int H_SCROLL_RATE = 10;
static final int ExtendedModify = 3000;
static final int LineGetBackground = 3001;
static final int LineGetStyle = 3002;
static final int TextChanging = 3003;
static final int TextSet = 3004;
static final int VerifyKey = 3005;
static final int TextChanged = 3006;
static final int LineGetSegments = 3007;
Color selectionBackground; // selection background color
Color selectionForeground; // selection foreground color
StyledTextContent logicalContent; // native content (default or user specified)
StyledTextContent content; // line wrapping content, same as logicalContent if word wrap is off
DisplayRenderer renderer;
Listener listener;
TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
DefaultLineStyler defaultLineStyler;// used for setStyles API when no LineStyleListener is registered
LineCache lineCache;
boolean userLineStyle = false; // true=widget is using a user defined line style listener for line styles. false=widget is using the default line styler to store line styles
boolean userLineBackground = false; // true=widget is using a user defined line background listener for line backgrounds. false=widget is using the default line styler to store line backgrounds
int verticalScrollOffset = 0; // pixel based
int horizontalScrollOffset = 0; // pixel based
int topIndex = 0; // top visible line
int lastPaintTopIndex = -1;
int topOffset = 0; // offset of first character in top line
int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new
// visible lines during Resize callback
int clientAreaWidth = 0; // the client area width. Needed during Resize callback to determine
// if line wrap needs to be recalculated
int lineHeight; // line height=font height
int tabLength = 4; // number of characters in a tab
int leftMargin;
int topMargin;
int rightMargin;
int bottomMargin;
Cursor ibeamCursor;
int columnX; // keep track of the horizontal caret position
// when changing lines/pages. Fixes bug 5935
int caretOffset = 0;
Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection
Point clipboardSelection; // x and y are start and end caret offsets of previous selection
int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text
Point doubleClickSelection; // selection after last mouse double click
boolean editable = true;
boolean wordWrap = false;
boolean doubleClickEnabled = true; // see getDoubleClickEnabled
boolean overwrite = false; // insert/overwrite edit mode
int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default.
Hashtable keyActionMap = new Hashtable();
Color background = null; // workaround for bug 4791
Color foreground = null; //
Clipboard clipboard;
boolean mouseDown = false;
boolean mouseDoubleClick = false; // true=a double click ocurred. Don't do mouse swipe selection.
int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left)
int autoScrollDistance = 0;
int lastTextChangeStart; // cache data of the
int lastTextChangeNewLineCount; // last text changing
int lastTextChangeNewCharCount; // event for use in the
int lastTextChangeReplaceLineCount; // text changed handler
int lastTextChangeReplaceCharCount;
boolean isMirrored;
boolean bidiColoring = false; // apply the BIDI algorithm on text segments of the same color
Image leftCaretBitmap = null;
Image rightCaretBitmap = null;
int caretDirection = SWT.NULL;
boolean advancing = true;
Caret defaultCaret = null;
boolean updateCaretDirection = true;
final static boolean IS_GTK;
final static boolean DOUBLE_BUFFER;
static {
String platform = SWT.getPlatform();
IS_GTK = "gtk".equals(platform);
DOUBLE_BUFFER = true;
}
/**
* The Printing class implements printing of a range of text.
* An instance of <class>Printing </class> is returned in the
* StyledText#print(Printer) API. The run() method may be
* invoked from any thread.
*/
static class Printing implements Runnable {
final static int LEFT = 0; // left aligned header/footer segment
final static int CENTER = 1; // centered header/footer segment
final static int RIGHT = 2; // right aligned header/footer segment
StyledText parent;
Printer printer;
PrintRenderer renderer;
StyledTextPrintOptions printOptions;
StyledTextContent printerContent; // copy of the widget content
Rectangle clientArea; // client area to print on
Font printerFont;
FontData displayFontData;
Hashtable printerColors; // printer color cache for line backgrounds and style
Hashtable lineBackgrounds = new Hashtable(); // cached line backgrounds
Hashtable lineStyles = new Hashtable(); // cached line styles
Hashtable bidiSegments = new Hashtable(); // cached bidi segments when running on a bidi platform
GC gc; // printer GC
int pageWidth; // width of a printer page in pixels
int startPage; // first page to print
int endPage; // last page to print
int pageSize; // number of lines on a page
int startLine; // first (wrapped) line to print
int endLine; // last (wrapped) line to print
boolean singleLine; // widget single line mode
Point selection = null; // selected text
boolean mirrored; //indicates the printing gc should be mirrored
/**
* Creates an instance of <class>Printing</class>.
* Copies the widget content and rendering data that needs
* to be requested from listeners.
* </p>
* @param parent StyledText widget to print.
* @param printer printer device to print on.
* @param printOptions print options
*/
Printing(StyledText parent, Printer printer, StyledTextPrintOptions printOptions) {
PrinterData data = printer.getPrinterData();
this.parent = parent;
this.printer = printer;
this.printOptions = printOptions;
this.mirrored = (parent.getStyle() & SWT.MIRRORED) != 0;
singleLine = parent.isSingleLine();
startPage = 1;
endPage = Integer.MAX_VALUE;
if (data.scope == PrinterData.PAGE_RANGE) {
startPage = data.startPage;
endPage = data.endPage;
if (endPage < startPage) {
int temp = endPage;
endPage = startPage;
startPage = temp;
}
}
else
if (data.scope == PrinterData.SELECTION) {
selection = parent.getSelectionRange();
}
displayFontData = parent.getFont().getFontData()[0];
copyContent(parent.getContent());
cacheLineData(printerContent);
}
/**
* Caches the bidi segments of the given line.
* </p>
* @param lineOffset offset of the line to cache bidi segments for.
* Relative to the start of the document.
* @param line line to cache bidi segments for.
*/
void cacheBidiSegments(int lineOffset, String line) {
int[] segments = parent.getBidiSegments(lineOffset, line);
if (segments != null) {
bidiSegments.put(new Integer(lineOffset), segments);
}
}
/**
* Caches the line background color of the given line.
* </p>
* @param lineOffset offset of the line to cache the background
* color for. Relative to the start of the document.
* @param line line to cache the background color for
*/
void cacheLineBackground(int lineOffset, String line) {
StyledTextEvent event = parent.getLineBackgroundData(lineOffset, line);
if (event != null) {
lineBackgrounds.put(new Integer(lineOffset), event);
}
}
/**
* Caches all line data that needs to be requested from a listener.
* </p>
* @param printerContent <class>StyledTextContent</class> to request
* line data for.
*/
void cacheLineData(StyledTextContent printerContent) {
for (int i = 0; i < printerContent.getLineCount(); i++) {
int lineOffset = printerContent.getOffsetAtLine(i);
String line = printerContent.getLine(i);
if (printOptions.printLineBackground) {
cacheLineBackground(lineOffset, line);
}
if (printOptions.printTextBackground ||
printOptions.printTextForeground ||
printOptions.printTextFontStyle) {
cacheLineStyle(lineOffset, line);
}
if (parent.isBidi()) {
cacheBidiSegments(lineOffset, line);
}
}
}
/**
* Caches all line styles of the given line.
* </p>
* @param lineOffset offset of the line to cache the styles for.
* Relative to the start of the document.
* @param line line to cache the styles for.
*/
void cacheLineStyle(int lineOffset, String line) {
StyledTextEvent event = parent.getLineStyleData(lineOffset, line);
if (event != null) {
StyleRange[] styles = event.styles;
for (int i = 0; i < styles.length; i++) {
StyleRange styleCopy = null;
if (!printOptions.printTextBackground && styles[i].background != null) {
styleCopy = (StyleRange) styles[i].clone();
styleCopy.background = null;
}
if (!printOptions.printTextForeground && styles[i].foreground != null) {
if (styleCopy == null) {
styleCopy = (StyleRange) styles[i].clone();
}
styleCopy.foreground = null;
}
if (!printOptions.printTextFontStyle && styles[i].fontStyle != SWT.NORMAL) {
if (styleCopy == null) {
styleCopy = (StyleRange) styles[i].clone();
}
styleCopy.fontStyle = SWT.NORMAL;
}
if (styleCopy != null) {
styles[i] = styleCopy;
}
}
lineStyles.put(new Integer(lineOffset), event);
}
}
/**
* Copies the text of the specified <class>StyledTextContent</class>.
* </p>
* @param original the <class>StyledTextContent</class> to copy.
*/
void copyContent(StyledTextContent original) {
int insertOffset = 0;
printerContent = new DefaultContent();
for (int i = 0; i < original.getLineCount(); i++) {
int insertEndOffset;
if (i < original.getLineCount() - 1) {
insertEndOffset = original.getOffsetAtLine(i + 1);
}
else {
insertEndOffset = original.getCharCount();
}
printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset));
insertOffset = insertEndOffset;
}
}
/**
* Replaces all display colors in the cached line backgrounds and
* line styles with printer colors.
*/
void createPrinterColors() {
Enumeration values = lineBackgrounds.elements();
printerColors = new Hashtable();
while (values.hasMoreElements()) {
StyledTextEvent event = (StyledTextEvent) values.nextElement();
event.lineBackground = getPrinterColor(event.lineBackground);
}
values = lineStyles.elements();
while (values.hasMoreElements()) {
StyledTextEvent event = (StyledTextEvent) values.nextElement();
for (int i = 0; i < event.styles.length; i++) {
StyleRange style = event.styles[i];
Color printerBackground = getPrinterColor(style.background);
Color printerForeground = getPrinterColor(style.foreground);
if (printerBackground != style.background ||
printerForeground != style.foreground) {
style = (StyleRange) style.clone();
style.background = printerBackground;
style.foreground = printerForeground;
event.styles[i] = style;
}
}
}
}
/**
* Disposes of the resources and the <class>PrintRenderer</class>.
*/
void dispose() {
if (printerColors != null) {
Enumeration colors = printerColors.elements();
while (colors.hasMoreElements()) {
Color color = (Color) colors.nextElement();
color.dispose();
}
printerColors = null;
}
if (gc != null) {
gc.dispose();
gc = null;
}
if (printerFont != null) {
printerFont.dispose();
printerFont = null;
}
if (renderer != null) {
renderer.dispose();
renderer = null;
}
}
/**
* Finish printing the indicated page.
*
* @param page page that was printed
*/
void endPage(int page) {
printDecoration(page, false);
printer.endPage();
}
/**
* Creates a <class>PrintRenderer</class> and calculate the line range
* to print.
*/
void initializeRenderer() {
Rectangle trim = printer.computeTrim(0, 0, 0, 0);
Point dpi = printer.getDPI();
printerFont = new Font(printer, displayFontData.getName(), displayFontData.getHeight(), SWT.NORMAL);
clientArea = printer.getClientArea();
pageWidth = clientArea.width;
// one inch margin around text
clientArea.x = dpi.x + trim.x;
clientArea.y = dpi.y + trim.y;
clientArea.width -= (clientArea.x + trim.width);
clientArea.height -= (clientArea.y + trim.height);
// make the orientation of the printer gc match the control
int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
gc = new GC(printer, style);
gc.setFont(printerFont);
renderer = new PrintRenderer(
printer, printerFont, gc, printerContent,
lineBackgrounds, lineStyles, bidiSegments,
parent.tabLength, clientArea);
if (printOptions.header != null) {
int lineHeight = renderer.getLineHeight();
clientArea.y += lineHeight * 2;
clientArea.height -= lineHeight * 2;
}
if (printOptions.footer != null) {
clientArea.height -= renderer.getLineHeight() * 2;
}
pageSize = clientArea.height / renderer.getLineHeight();
StyledTextContent content = renderer.getContent();
startLine = 0;
if (singleLine) {
endLine = 0;
}
else {
endLine = content.getLineCount() - 1;
}
PrinterData data = printer.getPrinterData();
if (data.scope == PrinterData.PAGE_RANGE) {
startLine = (startPage - 1) * pageSize;
}
else
if (data.scope == PrinterData.SELECTION) {
startLine = content.getLineAtOffset(selection.x);
if (selection.y > 0) {
endLine = content.getLineAtOffset(selection.x + selection.y - 1);
}
else {
endLine = startLine - 1;
}
}
}
/**
* Returns the printer color for the given display color.
* </p>
* @param color display color
* @return color create on the printer with the same RGB values
* as the display color.
*/
Color getPrinterColor(Color color) {
Color printerColor = null;
if (color != null) {
printerColor = (Color) printerColors.get(color);
if (printerColor == null) {
printerColor = new Color(printer, color.getRGB());
printerColors.put(color, printerColor);
}
}
return printerColor;
}
/**
* Prints the lines in the specified page range.
*/
void print() {
StyledTextContent content = renderer.getContent();
Color background = gc.getBackground();
Color foreground = gc.getForeground();
int lineHeight = renderer.getLineHeight();
int paintY = clientArea.y;
int page = startPage;
for (int i = startLine; i <= endLine && page <= endPage; i++, paintY += lineHeight) {
String line = content.getLine(i);
if (paintY == clientArea.y) {
startPage(page);
}
renderer.drawLine(
line, i, paintY, gc, background, foreground, true);
if (paintY + lineHeight * 2 > clientArea.y + clientArea.height) {
// close full page
endPage(page);
paintY = clientArea.y - lineHeight;
page++;
}
}
if (paintY > clientArea.y) {
// close partial page
endPage(page);
}
}
/**
* Print header or footer decorations.
*
* @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
* @param header true = print the header, false = print the footer
*/
void printDecoration(int page, boolean header) {
int lastSegmentIndex = 0;
final int SegmentCount = 3;
String text;
if (header) {
text = printOptions.header;
}
else {
text = printOptions.footer;
}
if (text == null) {
return;
}
for (int i = 0; i < SegmentCount; i++) {
int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
String segment;
if (segmentIndex == -1) {
segment = text.substring(lastSegmentIndex);
printDecorationSegment(segment, i, page, header);
break;
}
else {
segment = text.substring(lastSegmentIndex, segmentIndex);
printDecorationSegment(segment, i, page, header);
lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length();
}
}
}
/**
* Print one segment of a header or footer decoration.
* Headers and footers have three different segments.
* One each for left aligned, centered, and right aligned text.
*
* @param segment decoration segment to print
* @param alignment alignment of the segment. 0=left, 1=center, 2=right
* @param page page number to print, if specified in the decoration segment.
* @param header true = print the header, false = print the footer
*/
void printDecorationSegment(String segment, int alignment, int page, boolean header) {
int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
if (pageIndex != -1) {
final int PageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
StringBuffer buffer = new StringBuffer(segment.substring (0, pageIndex));
buffer.append (page);
buffer.append (segment.substring(pageIndex + PageTagLength));
segment = buffer.toString();
}
if (segment.length() > 0) {
int segmentWidth;
int drawX = 0;
int drawY = 0;
TextLayout layout = new TextLayout(printer);
layout.setText(segment);
layout.setFont(printerFont);
segmentWidth = layout.getLineBounds(0).width;
if (header) {
drawY = clientArea.y - renderer.getLineHeight() * 2;
}
else {
drawY = clientArea.y + clientArea.height + renderer.getLineHeight();
}
if (alignment == LEFT) {
drawX = clientArea.x;
}
else
if (alignment == CENTER) {
drawX = (pageWidth - segmentWidth) / 2;
}
else
if (alignment == RIGHT) {
drawX = clientArea.x + clientArea.width - segmentWidth;
}
layout.draw(gc, drawX, drawY);
layout.dispose();
}
}
/**
* Starts a print job and prints the pages specified in the constructor.
*/
public void run() {
String jobName = printOptions.jobName;
if (jobName == null) {
jobName = "Printing";
}
if (printer.startJob(jobName)) {
createPrinterColors();
initializeRenderer();
print();
dispose();
printer.endJob();
}
}
/**
* Start printing a new page.
*
* @param page page number to be started
*/
void startPage(int page) {
printer.startPage();
printDecoration(page, true);
}
}
/**
* The <code>RTFWriter</code> class is used to write widget content as
* rich text. The implementation complies with the RTF specification
* version 1.5.
* <p>
* toString() is guaranteed to return a valid RTF string only after
* close() has been called.
* </p>
* <p>
* Whole and partial lines and line breaks can be written. Lines will be
* formatted using the styles queried from the LineStyleListener, if
* set, or those set directly in the widget. All styles are applied to
* the RTF stream like they are rendered by the widget. In addition, the
* widget font name and size is used for the whole text.
* </p>
*/
class RTFWriter extends TextWriter {
static final int DEFAULT_FOREGROUND = 0;
static final int DEFAULT_BACKGROUND = 1;
Vector colorTable = new Vector();
boolean WriteUnicode;
/**
* Creates a RTF writer that writes content starting at offset "start"
* in the document. <code>start</code> and <code>length</code>can be set to specify partial
* lines.
* <p>
*
* @param start start offset of content to write, 0 based from
* beginning of document
* @param length length of content to write
*/
public RTFWriter(int start, int length) {
super(start, length);
colorTable.addElement(getForeground());
colorTable.addElement(getBackground());
setUnicode();
}
/**
* Closes the RTF writer. Once closed no more content can be written.
* <b>NOTE:</b> <code>toString()</code> does not return a valid RTF string until
* <code>close()</code> has been called.
*/
public void close() {
if (!isClosed()) {
writeHeader();
write("\n}}\0");
super.close();
}
}
/**
* Returns the index of the specified color in the RTF color table.
* <p>
*
* @param color the color
* @param defaultIndex return value if color is null
* @return the index of the specified color in the RTF color table
* or "defaultIndex" if "color" is null.
*/
int getColorIndex(Color color, int defaultIndex) {
int index;
if (color == null) {
index = defaultIndex;
}
else {
index = colorTable.indexOf(color);
if (index == -1) {
index = colorTable.size();
colorTable.addElement(color);
}
}
return index;
}
/**
* Determines if Unicode RTF should be written.
* Don't write Unicode RTF on Windows 95/98/ME or NT.
*/
void setUnicode() {
final String Win95 = "windows 95";
final String Win98 = "windows 98";
final String WinME = "windows me";
final String WinNT = "windows nt";
String osName = System.getProperty("os.name").toLowerCase();
String osVersion = System.getProperty("os.version");
int majorVersion = 0;
if (osName.startsWith(WinNT) && osVersion != null) {
int majorIndex = osVersion.indexOf('.');
if (majorIndex != -1) {
osVersion = osVersion.substring(0, majorIndex);
try {
majorVersion = Integer.parseInt(osVersion);
}
catch (NumberFormatException exception) {
// ignore exception. version number remains unknown.
// will write without Unicode
}
}
}
if (!osName.startsWith(Win95) &&
!osName.startsWith(Win98) &&
!osName.startsWith(WinME) &&
(!osName.startsWith(WinNT) || majorVersion > 4)) {
WriteUnicode = true;
}
else {
WriteUnicode = false;
}
}
/**
* Appends the specified segment of "string" to the RTF data.
* Copy from <code>start</code> up to, but excluding, <code>end</code>.
* <p>
*
* @param string string to copy a segment from. Must not contain
* line breaks. Line breaks should be written using writeLineDelimiter()
* @param start start offset of segment. 0 based.
* @param end end offset of segment
*/
void write(String string, int start, int end) {
for (int index = start; index < end; index++) {
char ch = string.charAt(index);
if (ch > 0xFF && WriteUnicode) {
// write the sub string from the last escaped character
// to the current one. Fixes bug 21698.
if (index > start) {
write(string.substring(start, index));
}
write("\\u");
write(Integer.toString((short) ch));
write(' '); // control word delimiter
start = index + 1;
}
else
if (ch == '}' || ch == '{' || ch == '\\') {
// write the sub string from the last escaped character
// to the current one. Fixes bug 21698.
if (index > start) {
write(string.substring(start, index));
}
write('\\');
write(ch);
start = index + 1;
}
}
// write from the last escaped character to the end.
// Fixes bug 21698.
if (start < end) {
write(string.substring(start, end));
}
}
/**
* Writes the RTF header including font table and color table.
*/
void writeHeader() {
StringBuffer header = new StringBuffer();
FontData fontData = getFont().getFontData()[0];
header.append("{\\rtf1\\ansi");
// specify code page, necessary for copy to work in bidi
// systems that don't support Unicode RTF.
String cpg = System.getProperty("file.encoding").toLowerCase();
if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
cpg = cpg.substring(2, cpg.length());
header.append("\\ansicpg");
header.append(cpg);
}
header.append("\\uc0\\deff0{\\fonttbl{\\f0\\fnil ");
header.append(fontData.getName());
header.append(";}}\n{\\colortbl");
for (int i = 0; i < colorTable.size(); i++) {
Color color = (Color) colorTable.elementAt(i);
header.append("\\red");
header.append(color.getRed());
header.append("\\green");
header.append(color.getGreen());
header.append("\\blue");
header.append(color.getBlue());
header.append(";");
}
// some RTF readers ignore the deff0 font tag. Explicitly
// set the font for the whole document to work around this.
header.append("}\n{\\f0\\fs");
// font size is specified in half points
header.append(fontData.getHeight() * 2);
header.append(" ");
write(header.toString(), 0);
}
/**
* Appends the specified line text to the RTF data. Lines will be formatted
* using the styles queried from the LineStyleListener, if set, or those set
* directly in the widget.
* <p>
*
* @param line line text to write as RTF. Must not contain line breaks
* Line breaks should be written using writeLineDelimiter()
* @param lineOffset offset of the line. 0 based from the start of the
* widget document. Any text occurring before the start offset or after the
* end offset specified during object creation is ignored.
* @exception SWTException <ul>
* <li>ERROR_IO when the writer is closed.</li>
* </ul>
*/
public void writeLine(String line, int lineOffset) {
StyleRange[] styles = new StyleRange[0];
Color lineBackground = null;
StyledTextEvent event;
if (isClosed()) {
SWT.error(SWT.ERROR_IO);
}
event = renderer.getLineStyleData(lineOffset, line);
if (event != null) {
styles = event.styles;
}
event = renderer.getLineBackgroundData(lineOffset, line);
if (event != null) {
lineBackground = event.lineBackground;
}
if (lineBackground == null) {
lineBackground = getBackground();
}
writeStyledLine(line, lineOffset, styles, lineBackground);
}
/**
* Appends the specified line delmimiter to the RTF data.
* <p>
*
* @param lineDelimiter line delimiter to write as RTF.
* @exception SWTException <ul>
* <li>ERROR_IO when the writer is closed.</li>
* </ul>
*/
public void writeLineDelimiter(String lineDelimiter) {
if (isClosed()) {
SWT.error(SWT.ERROR_IO);
}
write(lineDelimiter, 0, lineDelimiter.length());
write("\\par ");
}
/**
* Appends the specified line text to the RTF data.
* Use the colors and font styles specified in "styles" and "lineBackground".
* Formatting is written to reflect the text rendering by the text widget.
* Style background colors take precedence over the line background color.
* Background colors are written using the \highlight tag (vs. the \cb tag).
* <p>
*
* @param line line text to write as RTF. Must not contain line breaks
* Line breaks should be written using writeLineDelimiter()
* @param lineOffset offset of the line. 0 based from the start of the
* widget document. Any text occurring before the start offset or after the
* end offset specified during object creation is ignored.
* @param styles styles to use for formatting. Must not be null.
* @param lineBackground line background color to use for formatting.
* May be null.
*/
void writeStyledLine(String line, int lineOffset, StyleRange[] styles, Color lineBackground) {
int lineLength = line.length();
int lineIndex;
int copyEnd;
int startOffset = getStart();
int endOffset = startOffset + super.getCharCount();
int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
int writeOffset = startOffset - lineOffset;
if (writeOffset >= line.length()) {
return; // whole line is outside write range
}
else
if (writeOffset > 0) {
lineIndex = writeOffset; // line starts before RTF write start
}
else {
lineIndex = 0;
}
if (lineBackground != null) {
write("{\\highlight");
write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
write(" ");
}
for (int i = 0; i < styles.length; i++) {
StyleRange style = styles[i];
int start = style.start - lineOffset;
int end = start + style.length;
int colorIndex;
// skip over partial first line
if (end < writeOffset) {
continue;
}
// style starts beyond line end or RTF write end
if (start >= lineEndOffset) {
break;
}
// write any unstyled text
if (lineIndex < start) {
// copy to start of style
// style starting betond end of write range or end of line
// is guarded against above.
write(line, lineIndex, start);
lineIndex = start;
}
// write styled text
colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
write("{\\cf");
write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
if (colorIndex != DEFAULT_BACKGROUND) {
write("\\highlight");
write(colorIndex);
}
if ((style.fontStyle & SWT.BOLD) != 0) {
write("\\b");
}
if ((style.fontStyle & SWT.ITALIC) != 0) {
write("\\i");
}
if (style.underline) {
write("\\ul");
}
if (style.strikeout) {
write("\\strike");
}
write(" ");
// copy to end of style or end of write range or end of line
copyEnd = Math.min(end, lineEndOffset);
// guard against invalid styles and let style processing continue
copyEnd = Math.max(copyEnd, lineIndex);
write(line, lineIndex, copyEnd);
if ((style.fontStyle & SWT.BOLD) != 0) {
write("\\b0");
}
if ((style.fontStyle & SWT.ITALIC) != 0) {
write("\\i0");
}
if (style.underline) {
write("\\ul0");
}
if (style.strikeout) {
write("\\strike0");
}
write("}");
lineIndex = copyEnd;
}
// write unstyled text at the end of the line
if (lineIndex < lineEndOffset) {
write(line, lineIndex, lineEndOffset);
}
if (lineBackground != null) {
write("}");
}
}
}
/**
* The <code>TextWriter</code> class is used to write widget content to
* a string. Whole and partial lines and line breaks can be written. To write
* partial lines, specify the start and length of the desired segment
* during object creation.
* <p>
* </b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close()
* has been called.
*/
class TextWriter {
private StringBuffer buffer;
private int startOffset; // offset of first character that will be written
private int endOffset; // offset of last character that will be written.
// 0 based from the beginning of the widget text.
private boolean isClosed = false;
/**
* Creates a writer that writes content starting at offset "start"
* in the document. <code>start</code> and <code>length</code> can be set to specify partial lines.
* <p>
*
* @param start start offset of content to write, 0 based from beginning of document
* @param length length of content to write
*/
public TextWriter(int start, int length) {
buffer = new StringBuffer(length);
startOffset = start;
endOffset = start + length;
}
/**
* Closes the writer. Once closed no more content can be written.
* <b>NOTE:</b> <code>toString()</code> is not guaranteed to return a valid string unless
* the writer is closed.
*/
public void close() {
if (!isClosed) {
isClosed = true;
}
}
/**
* Returns the number of characters to write.
* @return the integer number of characters to write
*/
public int getCharCount() {
return endOffset - startOffset;
}
/**
* Returns the offset where writing starts. 0 based from the start of
* the widget text. Used to write partial lines.
* @return the integer offset where writing starts
*/
public int getStart() {
return startOffset;
}
/**
* Returns whether the writer is closed.
* @return a boolean specifying whether or not the writer is closed
*/
public boolean isClosed() {
return isClosed;
}
/**
* Returns the string. <code>close()</code> must be called before <code>toString()</code>
* is guaranteed to return a valid string.
*
* @return the string
*/
public String toString() {
return buffer.toString();
}
/**
* Appends the given string to the data.
*/
void write(String string) {
buffer.append(string);
}
/**
* Inserts the given string to the data at the specified offset.
* Do nothing if "offset" is < 0 or > getCharCount()
* <p>
*
* @param string text to insert
* @param offset offset in the existing data to insert "string" at.
*/
void write(String string, int offset) {
if (offset < 0 || offset > buffer.length()) {
return;
}
buffer.insert(offset, string);
}
/**
* Appends the given int to the data.
*/
void write(int i) {
buffer.append(i);
}
/**
* Appends the given character to the data.
*/
void write(char i) {
buffer.append(i);
}
/**
* Appends the specified line text to the data.
* <p>
*
* @param line line text to write. Must not contain line breaks
* Line breaks should be written using writeLineDelimiter()
* @param lineOffset offset of the line. 0 based from the start of the
* widget document. Any text occurring before the start offset or after the
* end offset specified during object creation is ignored.
* @exception SWTException <ul>
* <li>ERROR_IO when the writer is closed.</li>
* </ul>
*/
public void writeLine(String line, int lineOffset) {
int lineLength = line.length();
int lineIndex;
int copyEnd;
int writeOffset = startOffset - lineOffset;
if (isClosed) {
SWT.error(SWT.ERROR_IO);
}
if (writeOffset >= lineLength) {
return; // whole line is outside write range
}
else
if (writeOffset > 0) {
lineIndex = writeOffset; // line starts before write start
}
else {
lineIndex = 0;
}
copyEnd = Math.min(lineLength, endOffset - lineOffset);
if (lineIndex < copyEnd) {
write(line.substring(lineIndex, copyEnd));
}
}
/**
* Appends the specified line delmimiter to the data.
* <p>
*
* @param lineDelimiter line delimiter to write
* @exception SWTException <ul>
* <li>ERROR_IO when the writer is closed.</li>
* </ul>
*/
public void writeLineDelimiter(String lineDelimiter) {
if (isClosed) {
SWT.error(SWT.ERROR_IO);
}
write(lineDelimiter);
}
}
/**
* LineCache provides an interface to calculate and invalidate
* line based data.
* Implementors need to return a line width in <code>getWidth</code>.
*/
interface LineCache {
/**
* Calculates the lines in the specified range.
* <p>
*
* @param startLine first line to calculate
* @param lineCount number of lines to calculate
*/
public void calculate(int startLine, int lineCount);
/**
* Returns a width that will be used by the <code>StyledText</code>
* widget to size a horizontal scroll bar.
* <p>
*
* @return the line width
*/
public int getWidth();
/**
* Resets the lines in the specified range.
* This method is called in <code>StyledText.redraw()</code>
* and allows implementors to call redraw themselves during reset.
* <p>
*
* @param startLine the first line to reset
* @param lineCount the number of lines to reset
* @param calculateMaxWidth true=implementors should retain a
* valid width even if it is affected by the reset operation.
* false=the width may be set to 0
*/
public void redrawReset(int startLine, int lineCount, boolean calculateMaxWidth);
/**
* Resets the lines in the specified range.
* <p>
*
* @param startLine the first line to reset
* @param lineCount the number of lines to reset
* @param calculateMaxWidth true=implementors should retain a
* valid width even if it is affected by the reset operation.
* false=the width may be set to 0
*/
public void reset(int startLine, int lineCount, boolean calculateMaxWidth);
/**
* Called when a text change occurred.
* <p>
*
* @param startOffset the start offset of the text change
* @param newLineCount the number of inserted lines
* @param replaceLineCount the number of deleted lines
* @param newCharCount the number of new characters
* @param replaceCharCount the number of deleted characters
*/
public void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount);
}
/**
* Keeps track of line widths and the longest line in the
* StyledText document.
* Line widths are calculated when requested by a call to
* <code>calculate</code> and cached until reset by a call
* to <code>redrawReset</code> or <code>reset</code>.
*/
class ContentWidthCache implements LineCache {
StyledText parent; // parent widget, used to create a GC for line measuring
int[] lineWidth; // width in pixel of each line in the document, -1 for unknown width
StyledTextContent content; // content to use for line width calculation
int lineCount; // number of lines in lineWidth array
int maxWidth; // maximum line width of all measured lines
int maxWidthLineIndex; // index of the widest line
/**
* Creates a new <code>ContentWidthCache</code> and allocates space
* for the given number of lines.
* <p>
*
* @param parent the StyledText widget used to create a GC for
* line measuring
* @param content a StyledTextContent containing the initial number
* of lines to allocate space for
*/
public ContentWidthCache(StyledText parent, StyledTextContent content) {
this.parent = parent;
this.content = content;
this.lineCount = content.getLineCount();
lineWidth = new int[lineCount];
reset(0, lineCount, false);
}
/**
* Calculates the width of each line in the given range if it has
* not been calculated yet.
* If any line in the given range is wider than the currently widest
* line, the maximum line width is updated,
* <p>
*
* @param startLine first line to calculate the line width of
* @param lineCount number of lines to calculate the line width for
*/
public void calculate(int startLine, int lineCount) {
int caretWidth = 0;
int endLine = startLine + lineCount;
if (startLine < 0 || endLine > lineWidth.length) {
return;
}
caretWidth = getCaretWidth();
for (int i = startLine; i < endLine; i++) {
if (lineWidth[i] == -1) {
String line = content.getLine(i);
int lineOffset = content.getOffsetAtLine(i);
lineWidth[i] = contentWidth(line, lineOffset) + caretWidth;
}
if (lineWidth[i] > maxWidth) {
maxWidth = lineWidth[i];
maxWidthLineIndex = i;
}
}
}
/**
* Calculates the width of the visible lines in the specified
* range.
* <p>
*
* @param startLine the first changed line
* @param newLineCount the number of inserted lines
*/
void calculateVisible(int startLine, int newLineCount) {
int topIndex = parent.getTopIndex();
int bottomLine = Math.min(getPartialBottomIndex(), startLine + newLineCount);
startLine = Math.max(startLine, topIndex);
calculate(startLine, bottomLine - startLine + 1);
}
/**
* Measures the width of the given line.
* <p>
*
* @param line the line to measure
* @param lineOffset start offset of the line to measure, relative
* to the start of the document
* @return the width of the given line
*/
int contentWidth(String line, int lineOffset) {
TextLayout layout = renderer.getTextLayout(line, lineOffset);
Rectangle rect = layout.getLineBounds(0);
renderer.disposeTextLayout(layout);
return rect.x + rect.width + leftMargin + rightMargin;
}
/**
* Grows the <code>lineWidth</code> array to accomodate new line width
* information.
* <p>
*
* @param numLines the number of elements to increase the array by
*/
void expandLines(int numLines) {
int size = lineWidth.length;
if (size - lineCount >= numLines) {
return;
}
int[] newLines = new int[Math.max(size * 2, size + numLines)];
System.arraycopy(lineWidth, 0, newLines, 0, size);
lineWidth = newLines;
reset(size, lineWidth.length - size, false);
}
/**
* Returns the width of the longest measured line.
* <p>
*
* @return the width of the longest measured line.
*/
public int getWidth() {
return maxWidth;
}
/**
* Updates the line width array to reflect inserted or deleted lines.
* <p>
*
* @param startLine the starting line of the change that took place
* @param delta the number of lines in the change, > 0 indicates lines inserted,
* < 0 indicates lines deleted
*/
void linesChanged(int startLine, int delta) {
boolean inserting = delta > 0;
if (delta == 0) {
return;
}
if (inserting) {
// shift the lines down to make room for new lines
expandLines(delta);
for (int i = lineCount - 1; i >= startLine; i--) {
lineWidth[i + delta] = lineWidth[i];
}
// reset the new lines
for (int i = startLine + 1; i <= startLine + delta && i < lineWidth.length; i++) {
lineWidth[i] = -1;
}
// have new lines been inserted above the longest line?
if (maxWidthLineIndex >= startLine) {
maxWidthLineIndex += delta;
}
}
else {
// shift up the lines
for (int i = startLine - delta; i < lineCount; i++) {
lineWidth[i+delta] = lineWidth[i];
}
// has the longest line been removed?
if (maxWidthLineIndex > startLine && maxWidthLineIndex <= startLine - delta) {
maxWidth = 0;
maxWidthLineIndex = -1;
}
else
if (maxWidthLineIndex >= startLine - delta) {
maxWidthLineIndex += delta;
}
}
lineCount += delta;
}
/**
* Resets the line width of the lines in the specified range.
* <p>
*
* @param startLine the first line to reset
* @param lineCount the number of lines to reset
* @param calculateMaxWidth true=if the widest line is being
* reset the maximum width of all remaining cached lines is
* calculated. false=the maximum width is set to 0 if the
* widest line is being reset.
*/
public void redrawReset(int startLine, int lineCount, boolean calculateMaxWidth) {
reset(startLine, lineCount, calculateMaxWidth);
}
/**
* Resets the line width of the lines in the specified range.
* <p>
*
* @param startLine the first line to reset
* @param lineCount the number of lines to reset
* @param calculateMaxWidth true=if the widest line is being
* reset the maximum width of all remaining cached lines is
* calculated. false=the maximum width is set to 0 if the
* widest line is being reset.
*/
public void reset(int startLine, int lineCount, boolean calculateMaxWidth) {
int endLine = startLine + lineCount;
if (startLine < 0 || endLine > lineWidth.length) {
return;
}
for (int i = startLine; i < endLine; i++) {
lineWidth[i] = -1;
}
// if the longest line is one of the reset lines, the maximum line
// width is no longer valid
if (maxWidthLineIndex >= startLine && maxWidthLineIndex < endLine) {
maxWidth = 0;
maxWidthLineIndex = -1;
if (calculateMaxWidth) {
for (int i = 0; i < lineCount; i++) {
if (lineWidth[i] > maxWidth) {
maxWidth = lineWidth[i];
maxWidthLineIndex = i;
}
}
}
}
}
/**
* Updates the line width array to reflect a text change.
* Lines affected by the text change will be reset.
* <p>
*
* @param startOffset the start offset of the text change
* @param newLineCount the number of inserted lines
* @param replaceLineCount the number of deleted lines
* @param newCharCount the number of new characters
* @param replaceCharCount the number of deleted characters
*/
public void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount) {
int startLine = parent.getLineAtOffset(startOffset);
boolean removedMaxLine = (maxWidthLineIndex > startLine && maxWidthLineIndex <= startLine + replaceLineCount);
// entire text deleted?
if (startLine == 0 && replaceLineCount == lineCount) {
lineCount = newLineCount;
lineWidth = new int[lineCount];
reset(0, lineCount, false);
maxWidth = 0;
}
else {
linesChanged(startLine, -replaceLineCount);
linesChanged(startLine, newLineCount);
lineWidth[startLine] = -1;
}
// only calculate the visible lines. otherwise measurements of changed lines
// outside the visible area may subsequently change again without the
// lines ever being visible.
calculateVisible(startLine, newLineCount);
// maxWidthLineIndex will be -1 (i.e., unknown line width) if the widget has
// not been visible yet and the changed lines have therefore not been
// calculated above.
if (removedMaxLine ||
(maxWidthLineIndex != -1 && lineWidth[maxWidthLineIndex] < maxWidth)) {
// longest line has been removed or changed and is now shorter.
// need to recalculate maximum content width for all lines
maxWidth = 0;
for (int i = 0; i < lineCount; i++) {
if (lineWidth[i] > maxWidth) {
maxWidth = lineWidth[i];
maxWidthLineIndex = i;
}
}
}
}
}
/**
* Updates the line wrapping of the content.
* The line wrapping must always be in a consistent state.
* Therefore, when <code>reset</code> or <code>redrawReset</code>
* is called, the line wrapping is recalculated immediately
* instead of in <code>calculate</code>.
*/
class WordWrapCache implements LineCache {
StyledText parent;
WrappedContent visualContent;
/**
* Creates a new <code>WordWrapCache</code> and calculates an initial
* line wrapping.
* <p>
*
* @param parent the StyledText widget to wrap content in.
* @param content the content provider that does the actual line wrapping.
*/
public WordWrapCache(StyledText parent, WrappedContent content) {
this.parent = parent;
visualContent = content;
visualContent.wrapLines();
}
/**
* Do nothing. Lines are wrapped immediately after reset.
* <p>
*
* @param startLine first line to calculate
* @param lineCount number of lines to calculate
*/
public void calculate(int startLine, int lineCount) {
}
/**
* Returns the client area width. Lines are wrapped so there
* is no horizontal scroll bar.
* <p>
*
* @return the line width
*/
public int getWidth() {
return parent.getClientArea().width;
}
/**
* Wraps the lines in the specified range.
* This method is called in <code>StyledText.redraw()</code>.
* A redraw is therefore not necessary.
* <p>
*
* @param startLine the first line to reset
* @param lineCount the number of lines to reset
* @param calculateMaxWidth true=implementors should retain a
* valid width even if it is affected by the reset operation.
* false=the width may be set to 0
*/
public void redrawReset(int startLine, int lineCount, boolean calculateMaxWidth) {
if (lineCount == visualContent.getLineCount()) {
// do a full rewrap if all lines are reset
visualContent.wrapLines();
}
else {
visualContent.reset(startLine, lineCount);
}
}
/**
* Rewraps the lines in the specified range and redraws
* the widget if the line wrapping has changed.
* <p>
*
* @param startLine the first line to reset
* @param lineCount the number of lines to reset
* @param calculateMaxWidth true=implementors should retain a
* valid width even if it is affected by the reset operation.
* false=the width may be set to 0
*/
public void reset(int startLine, int lineCount, boolean calculateMaxWidth) {
int itemCount = getPartialBottomIndex() - topIndex + 1;
int[] oldLineOffsets = new int[itemCount];
for (int i = 0; i < itemCount; i++) {
oldLineOffsets[i] = visualContent.getOffsetAtLine(i + topIndex);
}
redrawReset(startLine, lineCount, calculateMaxWidth);
// check for cases which will require a full redraw
if (getPartialBottomIndex() - topIndex + 1 != itemCount) {
// number of visible lines has changed
parent.internalRedraw();
}
else {
for (int i = 0; i < itemCount; i++) {
if (visualContent.getOffsetAtLine(i + topIndex) != oldLineOffsets[i]) {
// wrapping of one of the visible lines has changed
parent.internalRedraw();
break;
}
}
}
}
/**
* Passes the text change notification to the line wrap content.
* <p>
*
* @param startOffset the start offset of the text change
* @param newLineCount the number of inserted lines
* @param replaceLineCount the number of deleted lines
* @param newCharCount the number of new characters
* @param replaceCharCount the number of deleted characters
*/
public void textChanged(int startOffset, int newLineCount, int replaceLineCount, int newCharCount, int replaceCharCount) {
int startLine = visualContent.getLineAtOffset(startOffset);
visualContent.textChanged(startOffset, newLineCount, replaceLineCount, newCharCount, replaceCharCount);
// if we are wrapping then it is possible for a deletion on the last
// line of text to shorten the total text length by a line. If this
// occurs then the startIndex must be adjusted such that a redraw will
// be performed if a visible region is affected. fixes bug 42947.
if (wordWrap) {
int lineCount = content.getLineCount();
if (startLine >= lineCount) startLine = lineCount - 1;
}
if (startLine <= getPartialBottomIndex()) {
// only redraw if the text change affects text inside or above
// the visible lines. if it is below the visible lines it will
// not affect the word wrapping. fixes bug 14047.
parent.internalRedraw();
}
}
}
/**
* Constructs a new instance of this class given its parent
* and a style value describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
* </p>
*
* @param parent a widget which will be the parent of the new instance (cannot be null)
* @param style the style of widget to construct
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* </ul>
*
* @see SWT#FULL_SELECTION
* @see SWT#MULTI
* @see SWT#READ_ONLY
* @see SWT#SINGLE
* @see SWT#WRAP
* @see #getStyle
*/
public StyledText(Composite parent, int style) {
super(parent, checkStyle(style | SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND));
// set the bg/fg in the OS to ensure that these are the same as StyledText, necessary
// for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
super.setForeground(getForeground());
super.setBackground(getBackground());
Display display = getDisplay();
isMirrored = (super.getStyle() & SWT.MIRRORED) != 0;
if ((style & SWT.READ_ONLY) != 0) {
setEditable(false);
}
leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
if ((style & SWT.SINGLE) != 0 && (style & SWT.BORDER) != 0) {
leftMargin = topMargin = rightMargin = bottomMargin = 2;
}
clipboard = new Clipboard(display);
installDefaultContent();
initializeRenderer();
if ((style & SWT.WRAP) != 0) {
setWordWrap(true);
}
else {
lineCache = new ContentWidthCache(this, content);
}
defaultCaret = new Caret(this, SWT.NULL);
if (isBidiCaret()) {
createCaretBitmaps();
Runnable runnable = new Runnable() {
public void run() {
int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
if (direction == caretDirection) return;
if (getCaret() != defaultCaret) return;
int lineIndex = getCaretLine();
String line = content.getLine(lineIndex);
int lineOffset = content.getOffsetAtLine(lineIndex);
int offsetInLine = caretOffset - lineOffset;
int newCaretX = getXAtOffset(line, lineIndex, offsetInLine);
setCaretLocation(newCaretX, getCaretLine(), direction);
}
};
BidiUtil.addLanguageListener(handle, runnable);
}
setCaret(defaultCaret);
calculateScrollBars();
createKeyBindings();
ibeamCursor = new Cursor(display, SWT.CURSOR_IBEAM);
setCursor(ibeamCursor);
installListeners();
installDefaultLineStyler();
initializeAccessible();
}
/**
* Adds an extended modify listener. An ExtendedModify event is sent by the
* widget when the widget text has changed.
* <p>
*
* @param extendedModifyListener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
checkWidget();
if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
addListener(ExtendedModify, typedListener);
}
/**
* Maps a key to an action.
* One action can be associated with N keys. However, each key can only
* have one action (key:action is N:1 relation).
* <p>
*
* @param key a key code defined in SWT.java or a character.
* Optionally ORd with a state mask. Preferred state masks are one or more of
* SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
* differences. However, there may be cases where using the specific state masks
* (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
* @param action one of the predefined actions defined in ST.java.
* Use SWT.NULL to remove a key binding.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setKeyBinding(int key, int action) {
checkWidget();
int keyValue = key & SWT.KEY_MASK;
int modifierValue = key & SWT.MODIFIER_MASK;
char keyChar = (char)keyValue;
if (Compatibility.isLetter(keyChar)) {
// make the keybinding case insensitive by adding it
// in its upper and lower case form
char ch = Character.toUpperCase(keyChar);
int newKey = ch | modifierValue;
if (action == SWT.NULL) {
keyActionMap.remove(new Integer(newKey));
}
else {
keyActionMap.put(new Integer(newKey), new Integer(action));
}
ch = Character.toLowerCase(keyChar);
newKey = ch | modifierValue;
if (action == SWT.NULL) {
keyActionMap.remove(new Integer(newKey));
}
else {
keyActionMap.put(new Integer(newKey), new Integer(action));
}
} else {
if (action == SWT.NULL) {
keyActionMap.remove(new Integer(key));
}
else {
keyActionMap.put(new Integer(key), new Integer(action));
}
}
}
/**
* Adds a bidirectional segment listener. A BidiSegmentEvent is sent
* whenever a line of text is measured or rendered. The user can
* specify text ranges in the line that should be treated as if they
* had a different direction than the surrounding text.
* This may be used when adjacent segments of right-to-left text should
* not be reordered relative to each other.
* E.g., Multiple Java string literals in a right-to-left language
* should generally remain in logical order to each other, that is, the
* way they are stored.
* <p>
*
* @param listener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
* @see BidiSegmentEvent
* @since 2.0
*/
public void addBidiSegmentListener(BidiSegmentListener listener) {
checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
StyledTextListener typedListener = new StyledTextListener(listener);
addListener(LineGetSegments, typedListener);
}
/**
* Adds a line background listener. A LineGetBackground event is sent by the
* widget to determine the background color for a line.
* <p>
*
* @param listener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void addLineBackgroundListener(LineBackgroundListener listener) {
checkWidget();
if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
if (!userLineBackground) {
removeLineBackgroundListener(defaultLineStyler);
defaultLineStyler.setLineBackground(0, logicalContent.getLineCount(), null);
userLineBackground = true;
}
StyledTextListener typedListener = new StyledTextListener(listener);
addListener(LineGetBackground, typedListener);
}
/**
* Adds a line style listener. A LineGetStyle event is sent by the widget to
* determine the styles for a line.
* <p>
*
* @param listener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void addLineStyleListener(LineStyleListener listener) {
checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (!userLineStyle) {
removeLineStyleListener(defaultLineStyler);
defaultLineStyler.setStyleRange(null);
userLineStyle = true;
}
StyledTextListener typedListener = new StyledTextListener(listener);
addListener(LineGetStyle, typedListener);
}
/**
* Adds a modify listener. A Modify event is sent by the widget when the widget text
* has changed.
* <p>
*
* @param modifyListener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void addModifyListener(ModifyListener modifyListener) {
checkWidget();
if (modifyListener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
TypedListener typedListener = new TypedListener(modifyListener);
addListener(SWT.Modify, typedListener);
}
/**
* Adds a selection listener. A Selection event is sent by the widget when the
* selection has changed.
* <p>
* When <code>widgetSelected</code> is called, the event x amd y fields contain
* the start and end caret indices of the selection.
* <code>widgetDefaultSelected</code> is not called for StyledTexts.
* </p>
*
* @param listener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void addSelectionListener(SelectionListener listener) {
checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
TypedListener typedListener = new TypedListener(listener);
addListener(SWT.Selection, typedListener);
}
/**
* Adds a verify key listener. A VerifyKey event is sent by the widget when a key
* is pressed. The widget ignores the key press if the listener sets the doit field
* of the event to false.
* <p>
*
* @param listener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void addVerifyKeyListener(VerifyKeyListener listener) {
checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
StyledTextListener typedListener = new StyledTextListener(listener);
addListener(VerifyKey, typedListener);
}
/**
* Adds a verify listener. A Verify event is sent by the widget when the widget text
* is about to change. The listener can set the event text and the doit field to
* change the text that is set in the widget or to force the widget to ignore the
* text change.
* <p>
*
* @param verifyListener the listener
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void addVerifyListener(VerifyListener verifyListener) {
checkWidget();
if (verifyListener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
TypedListener typedListener = new TypedListener(verifyListener);
addListener(SWT.Verify, typedListener);
}
/**
* Appends a string to the text at the end of the widget.
* <p>
*
* @param string the string to be appended
* @see #replaceTextRange(int,int,String)
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when listener is null</li>
* </ul>
*/
public void append(String string) {
checkWidget();
if (string == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
int lastChar = Math.max(getCharCount(), 0);
replaceTextRange(lastChar, 0, string);
}
/**
* Calculates the width of the widest visible line.
*/
void calculateContentWidth() {
lineCache = getLineCache(content);
lineCache.calculate(topIndex, getPartialBottomIndex() - topIndex + 1);
}
/**
* Calculates the scroll bars
*/
void calculateScrollBars() {
ScrollBar horizontalBar = getHorizontalBar();
ScrollBar verticalBar = getVerticalBar();
setScrollBars();
if (verticalBar != null) {
verticalBar.setIncrement(getVerticalIncrement());
}
if (horizontalBar != null) {
horizontalBar.setIncrement(getHorizontalIncrement());
}
}
/**
* Calculates the top index based on the current vertical scroll offset.
* The top index is the index of the topmost fully visible line or the
* topmost partially visible line if no line is fully visible.
* The top index starts at 0.
*/
void calculateTopIndex() {
int oldTopIndex = topIndex;
int verticalIncrement = getVerticalIncrement();
int clientAreaHeight = getClientArea().height;
if (verticalIncrement == 0) {
return;
}
topIndex = Compatibility.ceil(verticalScrollOffset, verticalIncrement);
// Set top index to partially visible top line if no line is fully
// visible but at least some of the widget client area is visible.
// Fixes bug 15088.
if (topIndex > 0) {
if (clientAreaHeight > 0) {
int bottomPixel = verticalScrollOffset + clientAreaHeight;
int fullLineTopPixel = topIndex * verticalIncrement;
int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
// set top index to partially visible line if no line fully fits in
// client area or if space is available but not used (the latter should
// never happen because we use claimBottomFreeSpace)
if (fullLineVisibleHeight < verticalIncrement) {
topIndex--;
}
}
else
if (topIndex >= content.getLineCount()) {
topIndex = content.getLineCount() - 1;
}
}
if (topIndex != oldTopIndex) {
topOffset = content.getOffsetAtLine(topIndex);
lineCache.calculate(topIndex, getPartialBottomIndex() - topIndex + 1);
setHorizontalScrollBar();
}
}
/**
* Hides the scroll bars if widget is created in single line mode.
*/
static int checkStyle(int style) {
if ((style & SWT.SINGLE) != 0) {
style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI);
} else {
style |= SWT.MULTI;
if ((style & SWT.WRAP) != 0) {
style &= ~SWT.H_SCROLL;
}
}
return style;
}
/**
* Scrolls down the text to use new space made available by a resize or by
* deleted lines.
*/
void claimBottomFreeSpace() {
int newVerticalOffset = Math.max(0, content.getLineCount() * lineHeight - getClientArea().height);
if (newVerticalOffset < verticalScrollOffset) {
// Scroll up so that empty lines below last text line are used.
// Fixes 1GEYJM0
setVerticalScrollOffset(newVerticalOffset, true);
}
}
/**
* Scrolls text to the right to use new space made available by a resize.
*/
void claimRightFreeSpace() {
int newHorizontalOffset = Math.max(0, lineCache.getWidth() - (getClientArea().width - leftMargin - rightMargin));
if (newHorizontalOffset < horizontalScrollOffset) {
// item is no longer drawn past the right border of the client area
// align the right end of the item with the right border of the
// client area (window is scrolled right).
scrollHorizontalBar(newHorizontalOffset - horizontalScrollOffset);
}
}
/**
* Clears the widget margin.
*
* @param gc GC to render on
* @param background background color to use for clearing the margin
* @param clientArea widget client area dimensions
*/
void clearMargin(GC gc, Color background, Rectangle clientArea, int y) {
// clear the margin background
gc.setBackground(background);
if (topMargin > 0) {
gc.fillRectangle(0, -y, clientArea.width, topMargin);
}
if (bottomMargin > 0) {
gc.fillRectangle(0, clientArea.height - bottomMargin - y, clientArea.width, bottomMargin);
}
if (leftMargin > 0) {
gc.fillRectangle(0, -y, leftMargin, clientArea.height);
}
if (rightMargin > 0) {
gc.fillRectangle(clientArea.width - rightMargin, -y, rightMargin, clientArea.height);
}
}
/**
* Removes the widget selection.
* <p>
*
* @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
*/
void clearSelection(boolean sendEvent) {
int selectionStart = selection.x;
int selectionEnd = selection.y;
int length = content.getCharCount();
resetSelection();
// redraw old selection, if any
if (selectionEnd - selectionStart > 0) {
// called internally to remove selection after text is removed
// therefore make sure redraw range is valid.
int redrawStart = Math.min(selectionStart, length);
int redrawEnd = Math.min(selectionEnd, length);
if (redrawEnd - redrawStart > 0) {
internalRedrawRange(redrawStart, redrawEnd - redrawStart, true);
}
if (sendEvent) {
sendSelectionEvent();
}
}
}
public Point computeSize (int wHint, int hHint, boolean changed) {
checkWidget();
int count, width, height;
boolean singleLine = (getStyle() & SWT.SINGLE) != 0;
if (singleLine) {
count = 1;
} else {
count = content.getLineCount();
}
if (wHint != SWT.DEFAULT) {
width = wHint;
}
else {
width = DEFAULT_WIDTH;
}
if (wHint == SWT.DEFAULT) {
LineCache computeLineCache = lineCache;
if (wordWrap) {
// set non-wrapping content width calculator. Ensures ideal line width
// that does not required wrapping. Fixes bug 31195.
computeLineCache = new ContentWidthCache(this, logicalContent);
if (!singleLine) {
count = logicalContent.getLineCount();
}
}
// Only calculate what can actually be displayed.
// Do this because measuring each text line is a
// time-consuming process.
int visibleCount = Math.min (count, getDisplay().getBounds().height / lineHeight);
computeLineCache.calculate(0, visibleCount);
width = computeLineCache.getWidth() + leftMargin + rightMargin;
}
else
if (wordWrap && !singleLine) {
// calculate to wrap to width hint. Fixes bug 20377.
// don't wrap live content. Fixes bug 38344.
WrappedContent wrappedContent = new WrappedContent(renderer, logicalContent);
wrappedContent.wrapLines(width);
count = wrappedContent.getLineCount();
}
if (hHint != SWT.DEFAULT) {
height = hHint;
}
else {
height = count * lineHeight + topMargin + bottomMargin;
}
// Use default values if no text is defined.
if (width == 0) {
width = DEFAULT_WIDTH;
}
if (height == 0) {
if (singleLine) {
height = lineHeight;
}
else {
height = DEFAULT_HEIGHT;
}
}
Rectangle rect = computeTrim(0, 0, width, height);
return new Point (rect.width, rect.height);
}
/**
* Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
* The text will be put on the clipboard in plain text format and RTF format.
* The <code>DND.CLIPBOARD</code> clipboard is used for data that is
* transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
* by menu action.
*
* <p>
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void copy() {
checkWidget();
copy(DND.CLIPBOARD);
}
/**
* Copies the selected text to the specified clipboard. The text will be put in the
* clipboard in plain text format and RTF format.
*
* <p>The clipboardType is one of the clipboard constants defined in class
* <code>DND</code>. The <code>DND.CLIPBOARD</code> clipboard is
* used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
* or by menu action. The <code>DND.SELECTION_CLIPBOARD</code>
* clipboard is used for data that is transferred by selecting text and pasting
* with the middle mouse button.</p>
*
* @param clipboardType indicates the type of clipboard
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @since 3.1
*/
public void copy(int clipboardType) {
checkWidget();
if (clipboardType != DND.CLIPBOARD &&
clipboardType != DND.SELECTION_CLIPBOARD) return;
int length = selection.y - selection.x;
if (length > 0) {
try {
setClipboardContent(selection.x, length, clipboardType);
}
catch (SWTError error) {
// Copy to clipboard failed. This happens when another application
// is accessing the clipboard while we copy. Ignore the error.
// Fixes 1GDQAVN
// Rethrow all other errors. Fixes bug 17578.
if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
throw error;
}
}
}
}
/**
* Returns a string that uses only the line delimiter specified by the
* StyledTextContent implementation.
* Returns only the first line if the widget has the SWT.SINGLE style.
* <p>
*
* @param text the text that may have line delimiters that don't
* match the model line delimiter. Possible line delimiters
* are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
* @return the converted text that only uses the line delimiter
* specified by the model. Returns only the first line if the widget
* has the SWT.SINGLE style.
*/
String getModelDelimitedText(String text) {
StringBuffer convertedText;
String delimiter = getLineDelimiter();
int length = text.length();
int crIndex = 0;
int lfIndex = 0;
int i = 0;
if (length == 0) {
return text;
}
convertedText = new StringBuffer(length);
while (i < length) {
if (crIndex != -1) {
crIndex = text.indexOf(SWT.CR, i);
}
if (lfIndex != -1) {
lfIndex = text.indexOf(SWT.LF, i);
}
if (lfIndex == -1 && crIndex == -1) { // no more line breaks?
break;
}
else // CR occurs before LF or no LF present?
if ((crIndex < lfIndex && crIndex != -1) || lfIndex == -1) {
convertedText.append(text.substring(i, crIndex));
if (lfIndex == crIndex + 1) { // CR/LF combination?
i = lfIndex + 1;
}
else {
i = crIndex + 1;
}
}
else { // LF occurs before CR!
convertedText.append(text.substring(i, lfIndex));
i = lfIndex + 1;
}
if (isSingleLine()) {
break;
}
convertedText.append(delimiter);
}
// copy remaining text if any and if not in single line mode or no
// text copied thus far (because there only is one line)
if (i < length && (!isSingleLine() || convertedText.length() == 0)) {
convertedText.append(text.substring(i));
}
return convertedText.toString();
}
/**
* Creates default key bindings.
*/
void createKeyBindings() {
int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT;
int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
// Navigation
setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
setKeyBinding(SWT.HOME, ST.LINE_START);
setKeyBinding(SWT.END, ST.LINE_END);
setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START);
setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END);
setKeyBinding(nextKey, ST.COLUMN_NEXT);
setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
// Selection
setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START);
setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END);
setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT);
setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
// Modification
// Cut, Copy, Paste
setKeyBinding('X' | SWT.MOD1, ST.CUT);
setKeyBinding('C' | SWT.MOD1, ST.COPY);
setKeyBinding('V' | SWT.MOD1, ST.PASTE);
// Cut, Copy, Paste Wordstar style
setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT);
setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY);
setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE);
setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS);
setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS);
setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT);
// Miscellaneous
setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
}
/**
* Create the bitmaps to use for the caret in bidi mode. This
* method only needs to be called upon widget creation and when the
* font changes (the caret bitmap height needs to match font height).
*/
void createCaretBitmaps() {
int caretWidth = BIDI_CARET_WIDTH;
Display display = getDisplay();
if (leftCaretBitmap != null) {
if (defaultCaret != null && leftCaretBitmap.equals(defaultCaret.getImage())) {
defaultCaret.setImage(null);
}
leftCaretBitmap.dispose();
}
leftCaretBitmap = new Image(display, caretWidth, lineHeight);
GC gc = new GC (leftCaretBitmap);
gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
gc.fillRectangle(0, 0, caretWidth, lineHeight);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.drawLine(0,0,0,lineHeight);
gc.drawLine(0,0,caretWidth-1,0);
gc.drawLine(0,1,1,1);
gc.dispose();
if (rightCaretBitmap != null) {
if (defaultCaret != null && rightCaretBitmap.equals(defaultCaret.getImage())) {
defaultCaret.setImage(null);
}
rightCaretBitmap.dispose();
}
rightCaretBitmap = new Image(display, caretWidth, lineHeight);
gc = new GC (rightCaretBitmap);
gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
gc.fillRectangle(0, 0, caretWidth, lineHeight);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight);
gc.drawLine(0,0,caretWidth-1,0);
gc.drawLine(caretWidth-1,1,1,1);
gc.dispose();
}
/**
* Moves the selected text to the clipboard. The text will be put in the
* clipboard in plain text format and RTF format.
* <p>
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void cut(){
checkWidget();
int length = selection.y - selection.x;
if (length > 0) {
try {
setClipboardContent(selection.x, length, DND.CLIPBOARD);
}
catch (SWTError error) {
// Copy to clipboard failed. This happens when another application
// is accessing the clipboard while we copy. Ignore the error.
// Fixes 1GDQAVN
// Rethrow all other errors. Fixes bug 17578.
if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
throw error;
}
// Abort cut operation if copy to clipboard fails.
// Fixes bug 21030.
return;
}
doDelete();
}
}
/**
* A mouse move event has occurred. See if we should start autoscrolling. If
* the move position is outside of the client area, initiate autoscrolling.
* Otherwise, we've moved back into the widget so end autoscrolling.
*/
void doAutoScroll(Event event) {
Rectangle area = getClientArea();
if (event.y > area.height) {
doAutoScroll(SWT.DOWN, event.y - area.height);
}
else
if (event.y < 0) {
doAutoScroll(SWT.UP, -event.y);
}
else
if (event.x < leftMargin && !wordWrap) {
doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
}
else
if (event.x > area.width - leftMargin - rightMargin && !wordWrap) {
doAutoScroll(ST.COLUMN_NEXT, event.x - (area.width - leftMargin - rightMargin));
}
else {
endAutoScroll();
}
}
/**
* Initiates autoscrolling.
* <p>
*
* @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
*/
void doAutoScroll(int direction, int distance) {
Runnable timer = null;
autoScrollDistance = distance;
// If we're already autoscrolling in the given direction do nothing
if (autoScrollDirection == direction) {
return;
}
final Display display = getDisplay();
// Set a timer that will simulate the user pressing and holding
// down a cursor key (i.e., arrowUp, arrowDown).
if (direction == SWT.UP) {
timer = new Runnable() {
public void run() {
if (autoScrollDirection == SWT.UP) {
int lines = (autoScrollDistance / getLineHeight()) + 1;
doSelectionPageUp(lines);
display.timerExec(V_SCROLL_RATE, this);
}
}
};
autoScrollDirection = direction;
display.timerExec(V_SCROLL_RATE, timer);
} else if (direction == SWT.DOWN) {
timer = new Runnable() {
public void run() {
if (autoScrollDirection == SWT.DOWN) {
int lines = (autoScrollDistance / getLineHeight()) + 1;
doSelectionPageDown(lines);
display.timerExec(V_SCROLL_RATE, this);
}
}
};
autoScrollDirection = direction;
display.timerExec(V_SCROLL_RATE, timer);
} else if (direction == ST.COLUMN_NEXT) {
timer = new Runnable() {
public void run() {
if (autoScrollDirection == ST.COLUMN_NEXT) {
doVisualNext();
setMouseWordSelectionAnchor();
doMouseSelection();
display.timerExec(H_SCROLL_RATE, this);
}
}
};
autoScrollDirection = direction;
display.timerExec(H_SCROLL_RATE, timer);
} else if (direction == ST.COLUMN_PREVIOUS) {
timer = new Runnable() {
public void run() {
if (autoScrollDirection == ST.COLUMN_PREVIOUS) {
doVisualPrevious();
setMouseWordSelectionAnchor();
doMouseSelection();
display.timerExec(H_SCROLL_RATE, this);
}
}
};
autoScrollDirection = direction;
display.timerExec(H_SCROLL_RATE, timer);
}
}
/**
* Deletes the previous character. Delete the selected text if any.
* Move the caret in front of the deleted text.
*/
void doBackspace() {
Event event = new Event();
event.text = "";
if (selection.x != selection.y) {
event.start = selection.x;
event.end = selection.y;
sendKeyEvent(event);
}
else
if (caretOffset > 0) {
int line = content.getLineAtOffset(caretOffset);
int lineOffset = content.getOffsetAtLine(line);
if (caretOffset == lineOffset) {
lineOffset = content.getOffsetAtLine(line - 1);
event.start = lineOffset + content.getLine(line - 1).length();
event.end = caretOffset;
}
else {
String lineText = content.getLine(line);
TextLayout layout = renderer.getTextLayout(lineText, lineOffset);
int start = layout.getPreviousOffset(caretOffset - lineOffset, SWT.MOVEMENT_CHAR);
renderer.disposeTextLayout(layout);
event.start = start + lineOffset;
event.end = caretOffset;
}
sendKeyEvent(event);
}
}
/**
* Replaces the selection with the character or insert the character at the
* current caret position if no selection exists.
* If a carriage return was typed replace it with the line break character
* used by the widget on this platform.
* <p>
*
* @param key the character typed by the user
*/
void doContent(char key) {
Event event;
if (textLimit > 0 &&
content.getCharCount() - (selection.y - selection.x) >= textLimit) {
return;
}
event = new Event();
event.start = selection.x;
event.end = selection.y;
// replace a CR line break with the widget line break
// CR does not make sense on Windows since most (all?) applications
// don't recognize CR as a line break.
if (key == SWT.CR || key == SWT.LF) {
if (!isSingleLine()) {
event.text = getLineDelimiter();
}
}
// no selection and overwrite mode is on and the typed key is not a
// tab character (tabs are always inserted without overwriting)?
else
if (selection.x == selection.y && overwrite && key != TAB) {
int lineIndex = content.getLineAtOffset(event.end);
int lineOffset = content.getOffsetAtLine(lineIndex);
String line = content.getLine(lineIndex);
// replace character at caret offset if the caret is not at the
// end of the line
if (event.end < lineOffset + line.length()) {
event.end++;
}
event.text = new String(new char[] {key});
}
else {
event.text = new String(new char[] {key});
}
if (event.text != null) {
sendKeyEvent(event);
}
}
/**
* Moves the caret after the last character of the widget content.
*/
void doContentEnd() {
// place caret at end of first line if receiver is in single
// line mode. fixes 4820.
if (isSingleLine()) {
doLineEnd();
}
else {
int length = content.getCharCount();
if (caretOffset < length) {
caretOffset = length;
showCaret();
}
}
}
/**
* Moves the caret in front of the first character of the widget content.
*/
void doContentStart() {
if (caretOffset > 0) {
caretOffset = 0;
showCaret();
}
}
/**
* Moves the caret to the start of the selection if a selection exists.
* Otherwise, if no selection exists move the cursor according to the
* cursor selection rules.
* <p>
*
* @see #doSelectionCursorPrevious
*/
void doCursorPrevious() {
advancing = false;
if (selection.y - selection.x > 0) {
int caretLine;
caretOffset = selection.x;
caretLine = getCaretLine();
showCaret(caretLine);
}
else {
doSelectionCursorPrevious();
}
}
/**
* Moves the caret to the end of the selection if a selection exists.
* Otherwise, if no selection exists move the cursor according to the
* cursor selection rules.
* <p>
*
* @see #doSelectionCursorNext
*/
void doCursorNext() {
advancing = true;
if (selection.y - selection.x > 0) {
int caretLine;
caretOffset = selection.y;
caretLine = getCaretLine();
showCaret(caretLine);
}
else {
doSelectionCursorNext();
}
}
/**
* Deletes the next character. Delete the selected text if any.
*/
void doDelete() {
Event event = new Event();
event.text = "";
if (selection.x != selection.y) {
event.start = selection.x;
event.end = selection.y;
sendKeyEvent(event);
}
else
if (caretOffset < content.getCharCount()) {
int line = content.getLineAtOffset(caretOffset);
int lineOffset = content.getOffsetAtLine(line);
int lineLength = content.getLine(line).length();
if (caretOffset == lineOffset + lineLength) {
event.start = caretOffset;
event.end = content.getOffsetAtLine(line + 1);
}
else {
event.start = caretOffset;
event.end = getClusterNext(caretOffset, line);
}
sendKeyEvent(event);
}
}
/**
* Deletes the next word.
*/
void doDeleteWordNext() {
if (selection.x != selection.y) {
// if a selection exists, treat the as if
// only the delete key was pressed
doDelete();
} else {
Event event = new Event();
event.text = "";
event.start = caretOffset;
event.end = getWordEnd(caretOffset);
sendKeyEvent(event);
}
}
/**
* Deletes the previous word.
*/
void doDeleteWordPrevious() {
if (selection.x != selection.y) {
// if a selection exists, treat as if
// only the backspace key was pressed
doBackspace();
} else {
Event event = new Event();
event.text = "";
event.start = getWordStart(caretOffset);
event.end = caretOffset;
sendKeyEvent(event);
}
}
/**
* Moves the caret one line down and to the same character offset relative
* to the beginning of the line. Move the caret to the end of the new line
* if the new line is shorter than the character offset.
*
* @return index of the new line relative to the first line in the document
*/
int doLineDown() {
if (isSingleLine()) {
return 0;
}
// allow line down action only if receiver is not in single line mode.
// fixes 4820.
int caretLine = getCaretLine();
if (caretLine < content.getLineCount() - 1) {
caretLine++;
caretOffset = getOffsetAtMouseLocation(columnX, caretLine);
}
return caretLine;
}
/**
* Moves the caret to the end of the line.
*/
void doLineEnd() {
int caretLine = getCaretLine();
int lineOffset = content.getOffsetAtLine(caretLine);
int lineLength = content.getLine(caretLine).length();
int lineEndOffset = lineOffset + lineLength;
if (caretOffset < lineEndOffset) {
caretOffset = lineEndOffset;
showCaret();
}
}
/**
* Moves the caret to the beginning of the line.
*/
void doLineStart() {
int caretLine = getCaretLine();
int lineOffset = content.getOffsetAtLine(caretLine);
if (caretOffset > lineOffset) {
caretOffset = lineOffset;
showCaret(caretLine);
}
}
/**
* Moves the caret one line up and to the same character offset relative
* to the beginning of the line. Move the caret to the end of the new line
* if the new line is shorter than the character offset.
*
* @return index of the new line relative to the first line in the document
*/
int doLineUp() {
int caretLine = getCaretLine();
if (caretLine > 0) {
caretLine--;
caretOffset = getOffsetAtMouseLocation(columnX, caretLine);
}
return caretLine;
}
/**
* Moves the caret to the specified location.
* <p>
*
* @param x x location of the new caret position
* @param y y location of the new caret position
* @param select the location change is a selection operation.
* include the line delimiter in the selection
*/
void doMouseLocationChange(int x, int y, boolean select) {
int line = (y + verticalScrollOffset) / lineHeight;
int lineCount = content.getLineCount();
int newCaretOffset;
int newCaretLine;
boolean oldAdvancing = advancing;
updateCaretDirection = true;
if (line > lineCount - 1) {
line = lineCount - 1;
}
// allow caret to be placed below first line only if receiver is
// not in single line mode. fixes 4820.
if (line < 0 || (isSingleLine() && line > 0)) {
return;
}
newCaretOffset = getOffsetAtMouseLocation(x, line);
if (mouseDoubleClick) {
// double click word select the previous/next word. fixes bug 15610
newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
}
newCaretLine = content.getLineAtOffset(newCaretOffset);
// Is the mouse within the left client area border or on
// a different line? If not the autoscroll selection
// could be incorrectly reset. Fixes 1GKM3XS
if (y >= 0 && y < getClientArea().height &&
(x >= 0 && x < getClientArea().width || wordWrap ||
newCaretLine != content.getLineAtOffset(caretOffset))) {
if (newCaretOffset != caretOffset || advancing != oldAdvancing) {
caretOffset = newCaretOffset;
if (select) {
doMouseSelection();
}
showCaret();
}
}
if (!select) {
caretOffset = newCaretOffset;
clearSelection(true);
}
}
/**
* Updates the selection based on the caret position
*/
void doMouseSelection() {
if (caretOffset <= selection.x ||
(caretOffset > selection.x &&
caretOffset < selection.y && selectionAnchor == selection.x)) {
doSelection(ST.COLUMN_PREVIOUS);
}
else {
doSelection(ST.COLUMN_NEXT);
}
}
/**
* Returns the offset of the word at the specified offset.
* If the current selection extends from high index to low index
* (i.e., right to left, or caret is at left border of selecton on
* non-bidi platforms) the start offset of the word preceeding the
* selection is returned. If the current selection extends from
* low index to high index the end offset of the word following
* the selection is returned.
*
* @param x mouse x location
* @param newCaretOffset caret offset of the mouse cursor location
* @param line line index of the mouse cursor location
*/
int doMouseWordSelect(int x, int newCaretOffset, int line) {
int wordOffset;
// flip selection anchor based on word selection direction from
// base double click. Always do this here (and don't rely on doAutoScroll)
// because auto scroll only does not cover all possible mouse selections
// (e.g., mouse x < 0 && mouse y > caret line y)
if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) {
selectionAnchor = doubleClickSelection.y;
}
else
if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) {
selectionAnchor = doubleClickSelection.x;
}
if (x >= 0 && x < getClientArea().width) {
// find the previous/next word
if (caretOffset == selection.x) {
wordOffset = getWordStart(newCaretOffset);
}
else {
wordOffset = getWordEndNoSpaces(newCaretOffset);
}
// mouse word select only on same line mouse cursor is on
if (content.getLineAtOffset(wordOffset) == line) {
newCaretOffset = wordOffset;
}
}
return newCaretOffset;
}
/**
* Scrolls one page down so that the last line (truncated or whole)
* of the current page becomes the fully visible top line.
* The caret is scrolled the same number of lines so that its location
* relative to the top line remains the same. The exception is the end
* of the text where a full page scroll is not possible. In this case
* the caret is moved after the last character.
* <p>
*
* @param select whether or not to select the page
*/
void doPageDown(boolean select, int lines) {
int lineCount = content.getLineCount();
int oldColumnX = columnX;
int oldHScrollOffset = horizontalScrollOffset;
int caretLine;
// do nothing if in single line mode. fixes 5673
if (isSingleLine()) {
return;
}
caretLine = getCaretLine();
if (caretLine < lineCount - 1) {
int verticalMaximum = lineCount * getVerticalIncrement();
int pageSize = getClientArea().height;
int scrollLines = Math.min(lineCount - caretLine - 1, lines);
int scrollOffset;
// ensure that scrollLines never gets negative and at leat one
// line is scrolled. fixes bug 5602.
scrollLines = Math.max(1, scrollLines);
caretLine += scrollLines;
caretOffset = getOffsetAtMouseLocation(columnX, caretLine);
if (select) {
doSelection(ST.COLUMN_NEXT);
}
// scroll one page down or to the bottom
scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
if (scrollOffset + pageSize > verticalMaximum) {
scrollOffset = verticalMaximum - pageSize;
}
if (scrollOffset > verticalScrollOffset) {
setVerticalScrollOffset(scrollOffset, true);
}
}
// explicitly go to the calculated caret line. may be different
// from content.getLineAtOffset(caretOffset) when in word wrap mode
showCaret(caretLine);
// restore the original horizontal caret position
int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
columnX = oldColumnX + hScrollChange;
}
/**
* Moves the cursor to the end of the last fully visible line.
*/
void doPageEnd() {
// go to end of line if in single line mode. fixes 5673
if (isSingleLine()) {
doLineEnd();
}
else {
int line = getBottomIndex();
int bottomCaretOffset = content.getOffsetAtLine(line) + content.getLine(line).length();
if (caretOffset < bottomCaretOffset) {
caretOffset = bottomCaretOffset;
showCaret();
}
}
}
/**
* Moves the cursor to the beginning of the first fully visible line.
*/
void doPageStart() {
int topCaretOffset = content.getOffsetAtLine(topIndex);
if (caretOffset > topCaretOffset) {
caretOffset = topCaretOffset;
// explicitly go to the calculated caret line. may be different
// from content.getLineAtOffset(caretOffset) when in word wrap mode
showCaret(topIndex);
}
}
/**
* Scrolls one page up so that the first line (truncated or whole)
* of the current page becomes the fully visible last line.
* The caret is scrolled the same number of lines so that its location
* relative to the top line remains the same. The exception is the beginning
* of the text where a full page scroll is not possible. In this case the
* caret is moved in front of the first character.
*/
void doPageUp(boolean select, int lines) {
int oldColumnX = columnX;
int oldHScrollOffset = horizontalScrollOffset;
int caretLine = getCaretLine();
if (caretLine > 0) {
int scrollLines = Math.max(1, Math.min(caretLine, lines));
int scrollOffset;
caretLine -= scrollLines;
caretOffset = getOffsetAtMouseLocation(columnX, caretLine);
if (select) {
doSelection(ST.COLUMN_PREVIOUS);
}
// scroll one page up or to the top
scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
if (scrollOffset < verticalScrollOffset) {
setVerticalScrollOffset(scrollOffset, true);
}
}
// explicitly go to the calculated caret line. may be different
// from content.getLineAtOffset(caretOffset) when in word wrap mode
showCaret(caretLine);
// restore the original horizontal caret position
int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
columnX = oldColumnX + hScrollChange;
}
/**
* Updates the selection to extend to the current caret position.
*/
void doSelection(int direction) {
int redrawStart = -1;
int redrawEnd = -1;
if (selectionAnchor == -1) {
selectionAnchor = selection.x;
}
if (direction == ST.COLUMN_PREVIOUS) {
if (caretOffset < selection.x) {
// grow selection
redrawEnd = selection.x;
redrawStart = selection.x = caretOffset;
// check if selection has reversed direction
if (selection.y != selectionAnchor) {
redrawEnd = selection.y;
selection.y = selectionAnchor;
}
}
else // test whether selection actually changed. Fixes 1G71EO1
if (selectionAnchor == selection.x && caretOffset < selection.y) {
// caret moved towards selection anchor (left side of selection).
// shrink selection
redrawEnd = selection.y;
redrawStart = selection.y = caretOffset;
}
}
else {
if (caretOffset > selection.y) {
// grow selection
redrawStart = selection.y;
redrawEnd = selection.y = caretOffset;
// check if selection has reversed direction
if (selection.x != selectionAnchor) {
redrawStart = selection.x;
selection.x = selectionAnchor;
}
}
else // test whether selection actually changed. Fixes 1G71EO1
if (selectionAnchor == selection.y && caretOffset > selection.x) {
// caret moved towards selection anchor (right side of selection).
// shrink selection
redrawStart = selection.x;
redrawEnd = selection.x = caretOffset;
}
}
if (redrawStart != -1 && redrawEnd != -1) {
internalRedrawRange(redrawStart, redrawEnd - redrawStart, true);
sendSelectionEvent();
}
}
/**
* Moves the caret to the next character or to the beginning of the
* next line if the cursor is at the end of a line.
*/
void doSelectionCursorNext() {
int caretLine = getCaretLine();
int lineOffset = content.getOffsetAtLine(caretLine);
int offsetInLine = caretOffset - lineOffset;
advancing = true;
if (offsetInLine < content.getLine(caretLine).length()) {
caretOffset = getClusterNext(caretOffset, caretLine);
showCaret();
}
else
if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
// only go to next line if not in single line mode. fixes 5673
caretLine++;
caretOffset = content.getOffsetAtLine(caretLine);
// explicitly go to the calculated caret line. may be different
// from content.getLineAtOffset(caretOffset) when in word wrap mode
showCaret(caretLine);
}
}
/**
* Moves the caret to the previous character or to the end of the previous
* line if the cursor is at the beginning of a line.
*/
void doSelectionCursorPrevious() {
int caretLine = getCaretLine();
int lineOffset = content.getOffsetAtLine(caretLine);
int offsetInLine = caretOffset - lineOffset;
advancing = false;
if (offsetInLine > 0) {
caretOffset = getClusterPrevious(caretOffset, caretLine);
showCaret(caretLine);
}
else
if (caretLine > 0) {
caretLine--;
lineOffset = content.getOffsetAtLine(caretLine);
caretOffset = lineOffset + content.getLine(caretLine).length();
showCaret();
}
}
/**
* Moves the caret one line down and to the same character offset relative
* to the beginning of the line. Moves the caret to the end of the new line
* if the new line is shorter than the character offset.
* Moves the caret to the end of the text if the caret already is on the
* last line.
* Adjusts the selection according to the caret change. This can either add
* to or subtract from the old selection, depending on the previous selection
* direction.
*/
void doSelectionLineDown() {
int oldColumnX;
int caretLine;
int lineStartOffset;
if (isSingleLine()) {
return;
}
caretLine = getCaretLine();
lineStartOffset = content.getOffsetAtLine(caretLine);
// reset columnX on selection
oldColumnX = columnX = getXAtOffset(
content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);
if (caretLine == content.getLineCount() - 1) {
caretOffset = content.getCharCount();
}
else {
caretLine = doLineDown();
}
setMouseWordSelectionAnchor();
// select first and then scroll to reduce flash when key
// repeat scrolls lots of lines
doSelection(ST.COLUMN_NEXT);
// explicitly go to the calculated caret line. may be different
// from content.getLineAtOffset(caretOffset) when in word wrap mode
showCaret(caretLine);
// save the original horizontal caret position
columnX = oldColumnX;
}
/**
* Moves the caret one line up and to the same character offset relative
* to the beginning of the line. Moves the caret to the end of the new line
* if the new line is shorter than the character offset.
* Moves the caret to the beginning of the document if it is already on the
* first line.
* Adjusts the selection according to the caret change. This can either add
* to or subtract from the old selection, depending on the previous selection
* direction.
*/
void doSelectionLineUp() {
int oldColumnX;
int caretLine = getCaretLine();
int lineStartOffset = content.getOffsetAtLine(caretLine);
// reset columnX on selection
oldColumnX = columnX = getXAtOffset(
content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);
if (caretLine == 0) {
caretOffset = 0;
}
else {
caretLine = doLineUp();
}
setMouseWordSelectionAnchor();
// explicitly go to the calculated caret line. may be different
// from content.getLineAtOffset(caretOffset) when in word wrap mode
showCaret(caretLine);
doSelection(ST.COLUMN_PREVIOUS);
// save the original horizontal caret position
columnX = oldColumnX;
}
/**
* Scrolls one page down so that the last line (truncated or whole)
* of the current page becomes the fully visible top line.
* The caret is scrolled the same number of lines so that its location
* relative to the top line remains the same. The exception is the end
* of the text where a full page scroll is not possible. In this case
* the caret is moved after the last character.
* <p>
* Adjusts the selection according to the caret change. This can either add
* to or subtract from the old selection, depending on the previous selection
* direction.
* </p>
*/
void doSelectionPageDown(int lines) {
int oldColumnX;
int caretLine = getCaretLine();
int lineStartOffset = content.getOffsetAtLine(caretLine);
// reset columnX on selection
oldColumnX = columnX = getXAtOffset(
content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);
doPageDown(true, lines);
columnX = oldColumnX;
}
/**
* Scrolls one page up so that the first line (truncated or whole)
* of the current page becomes the fully visible last line.
* The caret is scrolled the same number of lines so that its location
* relative to the top line remains the same. The exception is the beginning
* of the text where a full page scroll is not possible. In this case the
* caret is moved in front of the first character.
* <p>
* Adjusts the selection according to the caret change. This can either add
* to or subtract from the old selection, depending on the previous selection
* direction.
* </p>
*/
void doSelectionPageUp(int lines) {
int oldColumnX;
int caretLine = getCaretLine();
int lineStartOffset = content.getOffsetAtLine(caretLine);
// reset columnX on selection
oldColumnX = columnX = getXAtOffset(
content.getLine(caretLine), caretLine, caretOffset - lineStartOffset);
doPageUp(true, lines);
columnX = oldColumnX;
}
/**
* Moves the caret to the end of the next word .
*/
void doSelectionWordNext() {
int newCaretOffset = getWordEnd(caretOffset);
// Force symmetrical movement for word next and previous. Fixes 14536
advancing = false;
// don't change caret position if in single line mode and the cursor
// would be on a different line. fixes 5673
if (!isSingleLine() ||
content.getLineAtOffset(caretOffset) == content.getLineAtOffset(newCaretOffset)) {
caretOffset = newCaretOffset;
showCaret();
}
}
/**
* Moves the caret to the start of the previous word.
*/
void doSelectionWordPrevious() {
int caretLine;
advancing = false;
caretOffset = getWordStart(caretOffset);
caretLine = content.getLineAtOffset(caretOffset);
// word previous always comes from bottom line. when
// wrapping lines, stay on bottom line when on line boundary
if (wordWrap && caretLine < content.getLineCount() - 1 &&
caretOffset == content.getOffsetAtLine(caretLine + 1)) {
caretLine++;
}
showCaret(caretLine);
}
/**
* Moves the caret one character to the left. Do not go to the previous line.
* When in a bidi locale and at a R2L character the caret is moved to the
* beginning of the R2L segment (visually right) and then one character to the
* left (visually left because it's now in a L2R segment).
*/
void doVisualPrevious() {
caretOffset = getClusterPrevious(caretOffset, getCaretLine());
showCaret();
}
/**
* Moves the caret one character to the right. Do not go to the next line.
* When in a bidi locale and at a R2L character the caret is moved to the
* end of the R2L segment (visually left) and then one character to the
* right (visually right because it's now in a L2R segment).
*/
void doVisualNext() {
caretOffset = getClusterNext(caretOffset, getCaretLine());
showCaret();
}
/**
* Moves the caret to the end of the next word.
* If a selection exists, move the caret to the end of the selection
* and remove the selection.
*/
void doWordNext() {
if (selection.y - selection.x > 0) {
int caretLine;
caretOffset = selection.y;
caretLine = getCaretLine();
showCaret(caretLine);
}
else {
doSelectionWordNext();
}
}
/**
* Moves the caret to the start of the previous word.
* If a selection exists, move the caret to the start of the selection
* and remove the selection.
*/
void doWordPrevious() {
if (selection.y - selection.x > 0) {
int caretLine;
caretOffset = selection.x;
caretLine = getCaretLine();
showCaret(caretLine);
}
else {
doSelectionWordPrevious();
}
}
/**
* Draws the specified rectangle.
* Draw directly without invalidating the affected area when clearBackground is
* false.
* <p>
*
* @param x the x position
* @param y the y position
* @param width the width
* @param height the height
* @param clearBackground true=clear the background by invalidating the requested
* redraw area, false=draw the foreground directly without invalidating the
* redraw area.
*/
void draw(int x, int y, int width, int height, boolean clearBackground) {
if (clearBackground) {
redraw(x + leftMargin, y + topMargin, width, height, true);
}
else {
int startLine = (y + verticalScrollOffset) / lineHeight;
int endY = y + height;
int paintYFromTopLine = (startLine - topIndex) * lineHeight;
int topLineOffset = (topIndex * lineHeight - verticalScrollOffset);
int paintY = paintYFromTopLine + topLineOffset + topMargin; // adjust y position for pixel based scrolling
int lineCount = content.getLineCount();
Color background = getBackground();
Color foreground = getForeground();
GC gc = getGC();
if (isSingleLine()) {
lineCount = 1;
}
for (int i = startLine; paintY < endY && i < lineCount; i++, paintY += lineHeight) {
String line = content.getLine(i);
renderer.drawLine(line, i, paintY, gc, background, foreground, clearBackground);
}
gc.dispose();
}
}
/**
* Ends the autoscroll process.
*/
void endAutoScroll() {
autoScrollDirection = SWT.NULL;
}
public Color getBackground() {
checkWidget();
if (background == null) {
return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
}
return background;
}
/**
* Returns the baseline, in pixels.
*
* @return baseline the baseline
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @since 3.0
*/
public int getBaseline() {
checkWidget();
return renderer.getBaseline();
}
/**
* Gets the BIDI coloring mode. When true the BIDI text display
* algorithm is applied to segments of text that are the same
* color.
*
* @return the current coloring mode
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* <p>
* @deprecated use BidiSegmentListener instead.
* </p>
*/
public boolean getBidiColoring() {
checkWidget();
return bidiColoring;
}
/**
* Returns the index of the last fully visible line.
* <p>
*
* @return index of the last fully visible line.
*/
int getBottomIndex() {
int lineCount = 1;
if (lineHeight != 0) {
// calculate the number of lines that are fully visible
int partialTopLineHeight = topIndex * lineHeight - verticalScrollOffset;
lineCount = (getClientArea().height - partialTopLineHeight) / lineHeight;
}
return Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
}
/**
* Returns the caret position relative to the start of the text.
* <p>
*
* @return the caret position relative to the start of the text.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getCaretOffset() {
checkWidget();
return caretOffset;
}
/**
* Returns the caret offset at the given x location in the line.
* The caret offset is the offset of the character where the caret will be
* placed when a mouse click occurs. The caret offset will be the offset of
* the character after the clicked one if the mouse click occurs at the second
* half of a character.
* Doesn't properly handle ligatures and other context dependent characters
* unless the current locale is a bidi locale.
* Ligatures are handled properly as long as they don't occur at lineXOffset.
* <p>
*
* @param line text of the line to calculate the offset in
* @param lineOffset offset of the first character in the line.
* 0 based from the beginning of the document.
* @param lineXOffset x location in the line
* @return caret offset at the x location relative to the start of the line.
*/
int getOffsetAtX(String line, int lineOffset, int lineXOffset) {
int x = lineXOffset - leftMargin + horizontalScrollOffset;
TextLayout layout = renderer.getTextLayout(line, lineOffset);
int[] trailing = new int[1];
int offsetInLine = layout.getOffset(x, 0, trailing);
advancing = false;
if (trailing[0] != 0) {
int lineLength = line.length();
if (offsetInLine + trailing[0] >= lineLength) {
offsetInLine = lineLength;
advancing = true;
} else {
int level;
int offset = offsetInLine;
while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
if (offset == 0 && Character.isDigit(line.charAt(offset))) {
level = isMirrored() ? 1 : 0;
} else {
level = layout.getLevel(offset) & 0x1;
}
offsetInLine += trailing[0];
int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
advancing = (level ^ trailingLevel) != 0;
}
}
renderer.disposeTextLayout(layout);
return offsetInLine;
}
/**
* Returns the caret width.
* <p>
*
* @return the caret width, 0 if caret is null.
*/
int getCaretWidth() {
Caret caret = getCaret();
if (caret == null) return 0;
return caret.getSize().x;
}
Object getClipboardContent(int clipboardType) {
TextTransfer plainTextTransfer = TextTransfer.getInstance();
return clipboard.getContents(plainTextTransfer, clipboardType);
}
int getClusterNext(int offset, int lineIndex) {
String line = content.getLine(lineIndex);
int lineOffset = content.getOffsetAtLine(lineIndex);
TextLayout layout = renderer.getTextLayout(line, lineOffset);
offset -= lineOffset;
offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
offset += lineOffset;
renderer.disposeTextLayout(layout);
return offset;
}
int getClusterPrevious(int offset, int lineIndex) {
String line = content.getLine(lineIndex);
int lineOffset = content.getOffsetAtLine(lineIndex);
TextLayout layout = renderer.getTextLayout(line, lineOffset);
offset -= lineOffset;
offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
offset += lineOffset;
renderer.disposeTextLayout(layout);
return offset;
}
/**
* Returns the content implementation that is used for text storage
* or null if no user defined content implementation has been set.
* <p>
*
* @return content implementation that is used for text storage or null
* if no user defined content implementation has been set.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public StyledTextContent getContent() {
checkWidget();
return logicalContent;
}
/**
* Returns whether the widget implements double click mouse behavior.
* <p>
*
* @return true if double clicking a word selects the word, false if double clicks
* have the same effect as regular mouse clicks
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public boolean getDoubleClickEnabled() {
checkWidget();
return doubleClickEnabled;
}
/**
* Returns whether the widget content can be edited.
* <p>
*
* @return true if content can be edited, false otherwise
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public boolean getEditable() {
checkWidget();
return editable;
}
public Color getForeground() {
checkWidget();
if (foreground == null) {
return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
}
return foreground;
}
/**
* Return a GC to use for rendering and update the cached font style to
* represent the current style.
* <p>
*
* @return GC.
*/
GC getGC() {
return new GC(this);
}
/**
* Returns the horizontal scroll increment.
* <p>
*
* @return horizontal scroll increment.
*/
int getHorizontalIncrement() {
GC gc = getGC();
int increment = gc.getFontMetrics().getAverageCharWidth();
gc.dispose();
return increment;
}
/**
* Returns the horizontal scroll offset relative to the start of the line.
* <p>
*
* @return horizontal scroll offset relative to the start of the line,
* measured in character increments starting at 0, if > 0 the content is scrolled
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getHorizontalIndex() {
checkWidget();
return horizontalScrollOffset / getHorizontalIncrement();
}
/**
* Returns the horizontal scroll offset relative to the start of the line.
* <p>
*
* @return the horizontal scroll offset relative to the start of the line,
* measured in pixel starting at 0, if > 0 the content is scrolled.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getHorizontalPixel() {
checkWidget();
return horizontalScrollOffset;
}
/**
* Returns the action assigned to the key.
* Returns SWT.NULL if there is no action associated with the key.
* <p>
*
* @param key a key code defined in SWT.java or a character.
* Optionally ORd with a state mask. Preferred state masks are one or more of
* SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
* differences. However, there may be cases where using the specific state masks
* (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
* @return one of the predefined actions defined in ST.java or SWT.NULL
* if there is no action associated with the key.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getKeyBinding(int key) {
checkWidget();
Integer action = (Integer) keyActionMap.get(new Integer(key));
int intAction;
if (action == null) {
intAction = SWT.NULL;
}
else {
intAction = action.intValue();
}
return intAction;
}
/**
* Gets the number of characters.
* <p>
*
* @return number of characters in the widget
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getCharCount() {
checkWidget();
return content.getCharCount();
}
/**
* Returns the background color of the line at the given index.
* Returns null if a LineBackgroundListener has been set or if no background
* color has been specified for the line. Should not be called if a
* LineBackgroundListener has been set since the listener maintains the
* line background colors.
*
* @param index the index of the line
* @return the background color of the line at the given index.
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
* </ul>
*/
public Color getLineBackground(int index) {
checkWidget();
Color lineBackground = null;
if (index < 0 || index > logicalContent.getLineCount()) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
if (!userLineBackground) {
lineBackground = defaultLineStyler.getLineBackground(index);
}
return lineBackground;
}
/**
* Returns the line background data for the given line or null if
* there is none.
* <p>
* @param lineOffset offset of the line start relative to the start
* of the content.
* @param line line to get line background data for
* @return line background data for the given line.
*/
StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
return sendLineEvent(LineGetBackground, lineOffset, line);
}
/**
* Gets the number of text lines.
* <p>
*
* @return the number of lines in the widget
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getLineCount() {
checkWidget();
return getLineAtOffset(getCharCount()) + 1;
}
/**
* Returns the number of lines that can be completely displayed in the
* widget client area.
* <p>
*
* @return number of lines that can be completely displayed in the widget
* client area.
*/
int getLineCountWhole() {
int lineCount;
if (lineHeight != 0) {
lineCount = getClientArea().height / lineHeight;
}
else {
lineCount = 1;
}
return lineCount;
}
/**
* Returns the line at the specified offset in the text
* where 0 &lt= offset &lt= getCharCount() so that getLineAtOffset(getCharCount())
* returns the line of the insert location.
*
* @param offset offset relative to the start of the content.
* 0 <= offset <= getCharCount()
* @return line at the specified offset in the text
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
* </ul>
*/
public int getLineAtOffset(int offset) {
checkWidget();
if (offset < 0 || offset > getCharCount()) {
SWT.error(SWT.ERROR_INVALID_RANGE);
}
return logicalContent.getLineAtOffset(offset);
}
/**
* Returns the line delimiter used for entering new lines by key down
* or paste operation.
* <p>
*
* @return line delimiter used for entering new lines by key down
* or paste operation.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public String getLineDelimiter() {
checkWidget();
return content.getLineDelimiter();
}
/**
* Returns a StyledTextEvent that can be used to request data such
* as styles and background color for a line.
* The specified line may be a visual (wrapped) line if in word
* wrap mode. The returned object will always be for a logical
* (unwrapped) line.
* <p>
*
* @param lineOffset offset of the line. This may be the offset of
* a visual line if the widget is in word wrap mode.
* @param line line text. This may be the text of a visualline if
* the widget is in word wrap mode.
* @return StyledTextEvent that can be used to request line data
* for the given line.
*/
StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
StyledTextEvent event = null;
if (isListening(eventType)) {
event = new StyledTextEvent(logicalContent);
if (wordWrap) {
// if word wrap is on, the line offset and text may be visual (wrapped)
int lineIndex = logicalContent.getLineAtOffset(lineOffset);
event.detail = logicalContent.getOffsetAtLine(lineIndex);
event.text = logicalContent.getLine(lineIndex);
}
else {
event.detail = lineOffset;
event.text = line;
}
notifyListeners(eventType, event);
}
return event;
}
/**
* Returns the line height.
* <p>
*
* @return line height in pixel.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getLineHeight() {
checkWidget();
return lineHeight;
}
/**
* Returns a LineCache implementation. Depending on whether or not
* word wrap is on this may be a line wrapping or line width
* calculating implementaiton.
* <p>
*
* @param content StyledTextContent to create the LineCache on.
* @return a LineCache implementation
*/
LineCache getLineCache(StyledTextContent content) {
LineCache lineCache;
if (wordWrap) {
lineCache = new WordWrapCache(this, (WrappedContent) content);
}
else {
lineCache = new ContentWidthCache(this, content);
}
return lineCache;
}
/**
* Returns the line style data for the given line or null if there is
* none. If there is a LineStyleListener but it does not set any styles,
* the StyledTextEvent.styles field will be initialized to an empty
* array.
* <p>
*
* @param lineOffset offset of the line start relative to the start of
* the content.
* @param line line to get line styles for
* @return line style data for the given line. Styles may start before
* line start and end after line end
*/
StyledTextEvent getLineStyleData(int lineOffset, String line) {
return sendLineEvent(LineGetStyle, lineOffset, line);
}
/**
* Returns the x, y location of the upper left corner of the character
* bounding box at the specified offset in the text. The point is
* relative to the upper left corner of the widget client area.
* <p>
*
* @param offset offset relative to the start of the content.
* 0 <= offset <= getCharCount()
* @return x, y location of the upper left corner of the character
* bounding box at the specified offset in the text.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
* </ul>
*/
public Point getLocationAtOffset(int offset) {
checkWidget();
if (offset < 0 || offset > getCharCount()) {
SWT.error(SWT.ERROR_INVALID_RANGE);
}
int line = content.getLineAtOffset(offset);
int lineOffset = content.getOffsetAtLine(line);
String lineContent = content.getLine(line);
int x = getXAtOffset(lineContent, line, offset - lineOffset);
int y = line * lineHeight - verticalScrollOffset;
return new Point(x, y);
}
/**
* Returns the character offset of the first character of the given line.
* <p>
*
* @param lineIndex index of the line, 0 based relative to the first
* line in the content. 0 <= lineIndex < getLineCount(), except
* lineIndex may always be 0
* @return offset offset of the first character of the line, relative to
* the beginning of the document. The first character of the document is
* at offset 0.
* When there are not any lines, getOffsetAtLine(0) is a valid call that
* answers 0.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
* </ul>
* @since 2.0
*/
public int getOffsetAtLine(int lineIndex) {
checkWidget();
if (lineIndex < 0 ||
(lineIndex > 0 && lineIndex >= logicalContent.getLineCount())) {
SWT.error(SWT.ERROR_INVALID_RANGE);
}
return logicalContent.getOffsetAtLine(lineIndex);
}
/**
* Returns the offset of the character at the given location relative
* to the first character in the document.
* The return value reflects the character offset that the caret will
* be placed at if a mouse click occurred at the specified location.
* If the x coordinate of the location is beyond the center of a character
* the returned offset will be behind the character.
* <p>
*
* @param point the origin of character bounding box relative to
* the origin of the widget client area.
* @return offset of the character at the given location relative
* to the first character in the document.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT when point is null</li>
* <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
* </ul>
*/
public int getOffsetAtLocation(Point point) {
checkWidget();
TextLayout layout;
int line;
int lineOffset;
int offsetInLine;
String lineText;
if (point == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
// is y above first line or is x before first column?
if (point.y + verticalScrollOffset < 0 || point.x + horizontalScrollOffset < 0) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
line = (getTopPixel() + point.y) / lineHeight;
// does the referenced line exist?
if (line >= content.getLineCount()) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
lineText = content.getLine(line);
lineOffset = content.getOffsetAtLine(line);
int x = point.x - leftMargin + horizontalScrollOffset;
layout = renderer.getTextLayout(lineText, lineOffset);
Rectangle rect = layout.getLineBounds(0);
if (x > rect.x + rect.width) {
renderer.disposeTextLayout(layout);
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
int[] trailing = new int[1];
offsetInLine = layout.getOffset(x, 0, trailing);
if (offsetInLine != lineText.length() - 1) {
offsetInLine = Math.min(lineText.length(), offsetInLine + trailing[0]);
}
renderer.disposeTextLayout(layout);
return lineOffset + offsetInLine;
}
/**
* Returns the offset at the specified x location in the specified line.
* <p>
*
* @param x x location of the mouse location
* @param line line the mouse location is in
* @return the offset at the specified x location in the specified line,
* relative to the beginning of the document
*/
int getOffsetAtMouseLocation(int x, int line) {
String lineText = content.getLine(line);
int lineOffset = content.getOffsetAtLine(line);
return getOffsetAtX(lineText, lineOffset, x) + lineOffset;
}
/**
* Return the orientation of the receiver.
*
* @return the orientation style
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @since 2.1.2
*/
public int getOrientation () {
checkWidget();
return isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
}
/**
* Returns the index of the last partially visible line.
*
* @return index of the last partially visible line.
*/
int getPartialBottomIndex() {
int partialLineCount = Compatibility.ceil(getClientArea().height, lineHeight);
return Math.min(content.getLineCount(), topIndex + partialLineCount) - 1;
}
/**
* Returns the content in the specified range using the platform line
* delimiter to separate lines.
* <p>
*
* @param writer the TextWriter to write line text into
* @return the content in the specified range using the platform line
* delimiter to separate lines as written by the specified TextWriter.
*/
String getPlatformDelimitedText(TextWriter writer) {
int end = writer.getStart() + writer.getCharCount();
int startLine = logicalContent.getLineAtOffset(writer.getStart());
int endLine = logicalContent.getLineAtOffset(end);
String endLineText = logicalContent.getLine(endLine);
int endLineOffset = logicalContent.getOffsetAtLine(endLine);
for (int i = startLine; i <= endLine; i++) {
writer.writeLine(logicalContent.getLine(i), logicalContent.getOffsetAtLine(i));
if (i < endLine) {
writer.writeLineDelimiter(PlatformLineDelimiter);
}
}
if (end > endLineOffset + endLineText.length()) {
writer.writeLineDelimiter(PlatformLineDelimiter);
}
writer.close();
return writer.toString();
}
/**
* Returns the selection.
* <p>
* Text selections are specified in terms of caret positions. In a text
* widget that contains N characters, there are N+1 caret positions,
* ranging from 0..N
* <p>
*
* @return start and end of the selection, x is the offset of the first
* selected character, y is the offset after the last selected character.
* The selection values returned are visual (i.e., x will always always be
* <= y). To determine if a selection is right-to-left (RtoL) vs. left-to-right
* (LtoR), compare the caretOffset to the start and end of the selection
* (e.g., caretOffset == start of selection implies that the selection is RtoL).
* @see #getSelectionRange
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public Point getSelection() {
checkWidget();
return new Point(selection.x, selection.y);
}
/**
* Returns the selection.
* <p>
*
* @return start and length of the selection, x is the offset of the
* first selected character, relative to the first character of the
* widget content. y is the length of the selection.
* The selection values returned are visual (i.e., length will always always be
* positive). To determine if a selection is right-to-left (RtoL) vs. left-to-right
* (LtoR), compare the caretOffset to the start and end of the selection
* (e.g., caretOffset == start of selection implies that the selection is RtoL).
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public Point getSelectionRange() {
checkWidget();
return new Point(selection.x, selection.y - selection.x);
}
/**
* Returns the receiver's selection background color.
*
* @return the selection background color
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @since 2.1
*/
public Color getSelectionBackground() {
checkWidget();
if (selectionBackground == null) {
return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
}
return selectionBackground;
}
/**
* Gets the number of selected characters.
* <p>
*
* @return the number of selected characters.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getSelectionCount() {
checkWidget();
return getSelectionRange().y;
}
/**
* Returns the receiver's selection foreground color.
*
* @return the selection foreground color
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @since 2.1
*/
public Color getSelectionForeground() {
checkWidget();
if (selectionForeground == null) {
return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
}
return selectionForeground;
}
/**
* Returns the selected text.
* <p>
*
* @return selected text, or an empty String if there is no selection.
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public String getSelectionText() {
checkWidget();
return content.getTextRange(selection.x, selection.y - selection.x);
}
public int getStyle() {
int style = super.getStyle();
style &= ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.MIRRORED);
if (isMirrored()) {
style |= SWT.RIGHT_TO_LEFT | SWT.MIRRORED;
} else {
style |= SWT.LEFT_TO_RIGHT;
}
return style;
}
/**
* Returns the text segments that should be treated as if they
* had a different direction than the surrounding text.
* <p>
*
* @param lineOffset offset of the first character in the line.
* 0 based from the beginning of the document.
* @param line text of the line to specify bidi segments for
* @return text segments that should be treated as if they had a
* different direction than the surrounding text. Only the start
* index of a segment is specified, relative to the start of the
* line. Always starts with 0 and ends with the line length.
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the segment indices returned
* by the listener do not start with 0, are not in ascending order,
* exceed the line length or have duplicates</li>
* </ul>
*/
int [] getBidiSegments(int lineOffset, String line) {
if (!isListening(LineGetSegments)) {
return getBidiSegmentsCompatibility(line, lineOffset);
}
StyledTextEvent event = sendLineEvent(LineGetSegments, lineOffset, line);
int lineLength = line.length();
int[] segments;
if (event == null || event.segments == null || event.segments.length == 0) {
segments = new int[] {0, lineLength};
}
else {
int segmentCount = event.segments.length;
// test segment index consistency
if (event.segments[0] != 0) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
for (int i = 1; i < segmentCount; i++) {
if (event.segments[i] <= event.segments[i - 1] || event.segments[i] > lineLength) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
}
// ensure that last segment index is line end offset
if (event.segments[segmentCount - 1] != lineLength) {
segments = new int[segmentCount + 1];
System.arraycopy(event.segments, 0, segments, 0, segmentCount);
segments[segmentCount] = lineLength;
}
else {
segments = event.segments;
}
}
return segments;
}
/**
* @see #getBidiSegments
* Supports deprecated setBidiColoring API. Remove when API is removed.
*/
int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
StyledTextEvent event;
StyleRange [] styles = new StyleRange [0];
int lineLength = line.length();
if (!bidiColoring) {
return new int[] {0, lineLength};
}
event = renderer.getLineStyleData(lineOffset, line);
if (event != null) {
styles = event.styles;
}
if (styles.length == 0) {
return new int[] {0, lineLength};
}
int k=0, count = 1;
while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) {
k++;
}
int[] offsets = new int[(styles.length - k) * 2 + 2];
for (int i = k; i < styles.length; i++) {
StyleRange style = styles[i];
int styleLineStart = Math.max(style.start - lineOffset, 0);
int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
styleLineEnd = Math.min (styleLineEnd, line.length ());
if (i > 0 && count > 1 &&
((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
(styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
style.similarTo(styles[i-1])) {
offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
} else {
if (styleLineStart > offsets[count - 1]) {
offsets[count] = styleLineStart;
count++;