Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
10713 lines (10616 sloc) 357 KB
/*******************************************************************************
* Copyright (c) 2000, 2018 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
* Andrey Loskutov <loskutov@gmx.de> - bug 488172
* Stefan Xenos (Google) - bug 487254 - StyledText.getTopIndex() can return negative values
* Angelo Zerr <angelo.zerr@gmail.com> - Customize different line spacing of StyledText - Bug 522020
* Karsten Thoms <thoms@itemis.de> - bug 528746 add getOffsetAtPoint(Point)
*******************************************************************************/
package org.eclipse.swt.custom;
import java.util.*;
import java.util.List;
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>
* <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, OrientationChange
* </dl>
* </p><p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
*
* @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a>
* @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a>
* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
* @noextend This class is not intended to be subclassed by clients.
*/
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 PREVIOUS_OFFSET_TRAILING = 0;
static final int OFFSET_LEADING = 1;
static final String STYLEDTEXT_KEY = "org.eclipse.swt.internal.cocoa.styledtext"; //$NON-NLS-1$
Color selectionBackground; // selection background color
Color selectionForeground; // selection foreground color
StyledTextContent content; // native content (default or user specified)
StyledTextRenderer renderer;
Listener listener;
TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
int verticalScrollOffset = 0; // pixel based
int horizontalScrollOffset = 0; // pixel based
boolean alwaysShowScroll = true;
int ignoreResize = 0;
int topIndex = 0; // top visible line
int topIndexY;
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 tabLength = 4; // number of characters in a tab
int [] tabs;
int leftMargin;
int topMargin;
int rightMargin;
int bottomMargin;
Color marginColor;
int columnX; // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
int caretOffset;
int caretAlignment;
Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection (x <= y)
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; // text is wrapped automatically
boolean visualWrap = false; // process line breaks inside logical lines (inserted by BidiSegmentEvent)
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.
Map<Integer, Integer> keyActionMap = new HashMap<>();
Color background = null; // workaround for bug 4791
Color foreground = null; //
Clipboard clipboard;
int clickCount;
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;
int lastCharCount = 0;
int lastLineBottom; // the bottom pixel of the last line been replaced
boolean bidiColoring = false; // apply the BIDI algorithm on text segments of the same color
Image leftCaretBitmap = null;
Image rightCaretBitmap = null;
int caretDirection = SWT.NULL;
int caretWidth = 0;
Caret defaultCaret = null;
boolean updateCaretDirection = true;
boolean fixedLineHeight;
boolean dragDetect = true;
IME ime;
Cursor cursor;
int alignment;
boolean justify;
int indent, wrapIndent;
int lineSpacing;
int alignmentMargin;
int newOrientation = SWT.NONE;
int accCaretOffset;
Accessible acc;
AccessibleControlAdapter accControlAdapter;
AccessibleAttributeAdapter accAttributeAdapter;
AccessibleEditableTextListener accEditableTextListener;
AccessibleTextExtendedAdapter accTextExtendedAdapter;
AccessibleAdapter accAdapter;
//block selection
boolean blockSelection;
int blockXAnchor = -1, blockYAnchor = -1;
int blockXLocation = -1, blockYLocation = -1;
final static boolean IS_MAC, IS_GTK;
static {
String platform = SWT.getPlatform();
IS_MAC = "cocoa".equals(platform);
IS_GTK = "gtk".equals(platform);
}
/**
* The Printing class implements printing of a range of text.
* An instance of <code>Printing</code> 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
Printer printer;
StyledTextRenderer printerRenderer;
StyledTextPrintOptions printOptions;
Rectangle clientArea;
FontData fontData;
Font printerFont;
Map<Resource, Resource> resources;
int tabLength;
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 scope; // scope of print job
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
int lineSpacing;
int printMargin;
/**
* Creates an instance of <code>Printing</code>.
* 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 styledText, Printer printer, StyledTextPrintOptions printOptions) {
this.printer = printer;
this.printOptions = printOptions;
this.mirrored = (styledText.getStyle() & SWT.MIRRORED) != 0;
singleLine = styledText.isSingleLine();
startPage = 1;
endPage = Integer.MAX_VALUE;
PrinterData data = printer.getPrinterData();
scope = data.scope;
if (scope == PrinterData.PAGE_RANGE) {
startPage = data.startPage;
endPage = data.endPage;
if (endPage < startPage) {
int temp = endPage;
endPage = startPage;
startPage = temp;
}
} else if (scope == PrinterData.SELECTION) {
selection = styledText.getSelectionRange();
}
printerRenderer = new StyledTextRenderer(printer, null);
printerRenderer.setContent(copyContent(styledText.getContent()));
cacheLineData(styledText);
}
/**
* Caches all line data that needs to be requested from a listener.
* </p>
* @param printerContent <code>StyledTextContent</code> to request
* line data for.
*/
void cacheLineData(StyledText styledText) {
StyledTextRenderer renderer = styledText.renderer;
renderer.copyInto(printerRenderer);
fontData = styledText.getFont().getFontData()[0];
tabLength = styledText.tabLength;
int lineCount = printerRenderer.lineCount;
if (styledText.isListening(ST.LineGetBackground) || (styledText.isListening(ST.LineGetSegments)) || styledText.isListening(ST.LineGetStyle)) {
StyledTextContent content = printerRenderer.content;
for (int i = 0; i < lineCount; i++) {
String line = content.getLine(i);
int lineOffset = content.getOffsetAtLine(i);
StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
if (event != null && event.lineBackground != null) {
printerRenderer.setLineBackground(i, 1, event.lineBackground);
}
event = styledText.getBidiSegments(lineOffset, line);
if (event != null) {
printerRenderer.setLineSegments(i, 1, event.segments);
printerRenderer.setLineSegmentChars(i, 1, event.segmentsChars);
}
event = styledText.getLineStyleData(lineOffset, line);
if (event != null) {
printerRenderer.setLineIndent(i, 1, event.indent);
printerRenderer.setLineAlignment(i, 1, event.alignment);
printerRenderer.setLineJustify(i, 1, event.justify);
printerRenderer.setLineBullet(i, 1, event.bullet);
StyleRange[] styles = event.styles;
if (styles != null && styles.length > 0) {
printerRenderer.setStyleRanges(event.ranges, styles);
}
}
}
}
Point screenDPI = styledText.getDisplay().getDPI();
Point printerDPI = printer.getDPI();
resources = new HashMap<> ();
for (int i = 0; i < lineCount; i++) {
Color color = printerRenderer.getLineBackground(i, null);
if (color != null) {
if (printOptions.printLineBackground) {
Color printerColor = (Color)resources.get(color);
if (printerColor == null) {
printerColor = new Color (printer, color.getRGB());
resources.put(color, printerColor);
}
printerRenderer.setLineBackground(i, 1, printerColor);
} else {
printerRenderer.setLineBackground(i, 1, null);
}
}
int indent = printerRenderer.getLineIndent(i, 0);
if (indent != 0) {
printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
}
}
StyleRange[] styles = printerRenderer.styles;
for (int i = 0; i < printerRenderer.styleCount; i++) {
StyleRange style = styles[i];
Font font = style.font;
if (style.font != null) {
Font printerFont = (Font)resources.get(font);
if (printerFont == null) {
printerFont = new Font (printer, font.getFontData());
resources.put(font, printerFont);
}
style.font = printerFont;
}
Color color = style.foreground;
if (color != null) {
Color printerColor = (Color)resources.get(color);
if (printOptions.printTextForeground) {
if (printerColor == null) {
printerColor = new Color (printer, color.getRGB());
resources.put(color, printerColor);
}
style.foreground = printerColor;
} else {
style.foreground = null;
}
}
color = style.background;
if (color != null) {
Color printerColor = (Color)resources.get(color);
if (printOptions.printTextBackground) {
if (printerColor == null) {
printerColor = new Color (printer, color.getRGB());
resources.put(color, printerColor);
}
style.background = printerColor;
} else {
style.background = null;
}
}
if (!printOptions.printTextFontStyle) {
style.fontStyle = SWT.NORMAL;
}
style.rise = style.rise * printerDPI.y / screenDPI.y;
GlyphMetrics metrics = style.metrics;
if (metrics != null) {
metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
metrics.width = metrics.width * printerDPI.x / screenDPI.x;
}
}
lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
if (printOptions.printLineNumbers) {
printMargin = 3 * printerDPI.x / screenDPI.x;
}
}
/**
* Copies the text of the specified <code>StyledTextContent</code>.
* </p>
* @param original the <code>StyledTextContent</code> to copy.
*/
StyledTextContent copyContent(StyledTextContent original) {
StyledTextContent printerContent = new DefaultContent();
int insertOffset = 0;
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;
}
return printerContent;
}
/**
* Disposes of the resources and the <code>PrintRenderer</code>.
*/
void dispose() {
if (gc != null) {
gc.dispose();
gc = null;
}
if (resources != null) {
for (Resource resource : resources.values()) {
resource.dispose();
}
resources = null;
}
if (printerFont != null) {
printerFont.dispose();
printerFont = null;
}
if (printerRenderer != null) {
printerRenderer.dispose();
printerRenderer = null;
}
}
void init() {
Rectangle trim = printer.computeTrim(0, 0, 0, 0);
Point dpi = printer.getDPI();
printerFont = new Font(printer, fontData.getName(), fontData.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);
int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
gc = new GC(printer, style);
gc.setFont(printerFont);
printerRenderer.setFont(printerFont, tabLength);
int lineHeight = printerRenderer.getLineHeight();
if (printOptions.header != null) {
clientArea.y += lineHeight * 2;
clientArea.height -= lineHeight * 2;
}
if (printOptions.footer != null) {
clientArea.height -= lineHeight * 2;
}
// TODO not wrapped
StyledTextContent content = printerRenderer.content;
startLine = 0;
endLine = singleLine ? 0 : content.getLineCount() - 1;
if (scope == PrinterData.PAGE_RANGE) {
int pageSize = clientArea.height / lineHeight;//WRONG
startLine = (startPage - 1) * pageSize;
} else if (scope == PrinterData.SELECTION) {
startLine = content.getLineAtOffset(selection.x);
if (selection.y > 0) {
endLine = content.getLineAtOffset(selection.x + selection.y - 1);
} else {
endLine = startLine - 1;
}
}
}
/**
* Prints the lines in the specified page range.
*/
void print() {
Color background = gc.getBackground();
Color foreground = gc.getForeground();
int paintY = clientArea.y;
int paintX = clientArea.x;
int width = clientArea.width;
int page = startPage;
int pageBottom = clientArea.y + clientArea.height;
int orientation = gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT);
TextLayout printLayout = null;
if (printOptions.printLineNumbers || printOptions.header != null || printOptions.footer != null) {
printLayout = new TextLayout(printer);
printLayout.setFont(printerFont);
}
if (printOptions.printLineNumbers) {
int numberingWidth = 0;
int count = endLine - startLine + 1;
String[] lineLabels = printOptions.lineLabels;
if (lineLabels != null) {
for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
if (lineLabels[i] != null) {
printLayout.setText(lineLabels[i]);
int lineWidth = printLayout.getBounds().width;
numberingWidth = Math.max(numberingWidth, lineWidth);
}
}
} else {
StringBuilder buffer = new StringBuilder("0");
while ((count /= 10) > 0) buffer.append("0");
printLayout.setText(buffer.toString());
numberingWidth = printLayout.getBounds().width;
}
numberingWidth += printMargin;
if (numberingWidth > width) numberingWidth = width;
paintX += numberingWidth;
width -= numberingWidth;
}
for (int i = startLine; i <= endLine && page <= endPage; i++) {
if (paintY == clientArea.y) {
printer.startPage();
printDecoration(page, true, printLayout);
}
TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
Color lineBackground = printerRenderer.getLineBackground(i, background);
int paragraphBottom = paintY + layout.getBounds().height;
if (paragraphBottom <= pageBottom) {
//normal case, the whole paragraph fits in the current page
printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
paintY = paragraphBottom;
} else {
int lineCount = layout.getLineCount();
while (paragraphBottom > pageBottom && lineCount > 0) {
lineCount--;
paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
}
if (lineCount == 0) {
//the whole paragraph goes to the next page
printDecoration(page, false, printLayout);
printer.endPage();
page++;
if (page <= endPage) {
printer.startPage();
printDecoration(page, true, printLayout);
paintY = clientArea.y;
printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
paintY += layout.getBounds().height;
}
} else {
//draw paragraph top in the current page and paragraph bottom in the next
int height = paragraphBottom - paintY;
gc.setClipping(clientArea.x, paintY, clientArea.width, height);
printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
gc.setClipping((Rectangle)null);
printDecoration(page, false, printLayout);
printer.endPage();
page++;
if (page <= endPage) {
printer.startPage();
printDecoration(page, true, printLayout);
paintY = clientArea.y - height;
int layoutHeight = layout.getBounds().height;
gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
gc.setClipping((Rectangle)null);
paintY += layoutHeight;
}
}
}
printerRenderer.disposeTextLayout(layout);
}
if (page <= endPage && paintY > clientArea.y) {
// close partial page
printDecoration(page, false, printLayout);
printer.endPage();
}
if (printLayout != null) printLayout.dispose();
}
/**
* 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, TextLayout layout) {
String text = header ? printOptions.header : printOptions.footer;
if (text == null) return;
int lastSegmentIndex = 0;
for (int i = 0; i < 3; i++) {
int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
String segment;
if (segmentIndex == -1) {
segment = text.substring(lastSegmentIndex);
printDecorationSegment(segment, i, page, header, layout);
break;
} else {
segment = text.substring(lastSegmentIndex, segmentIndex);
printDecorationSegment(segment, i, page, header, layout);
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, TextLayout layout) {
int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
if (pageIndex != -1) {
int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
StringBuilder buffer = new StringBuilder(segment.substring (0, pageIndex));
buffer.append (page);
buffer.append (segment.substring(pageIndex + pageTagLength));
segment = buffer.toString();
}
if (segment.length() > 0) {
layout.setText(segment);
int segmentWidth = layout.getBounds().width;
int segmentHeight = printerRenderer.getLineHeight();
int drawX = 0, drawY;
if (alignment == LEFT) {
drawX = clientArea.x;
} else if (alignment == CENTER) {
drawX = (pageWidth - segmentWidth) / 2;
} else if (alignment == RIGHT) {
drawX = clientArea.x + clientArea.width - segmentWidth;
}
if (header) {
drawY = clientArea.y - segmentHeight * 2;
} else {
drawY = clientArea.y + clientArea.height + segmentHeight;
}
layout.draw(gc, drawX, drawY);
}
}
void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) {
if (background != null) {
Rectangle rect = layout.getBounds();
gc.setBackground(background);
gc.fillRectangle(x, y, rect.width, rect.height);
// int lineCount = layout.getLineCount();
// for (int i = 0; i < lineCount; i++) {
// Rectangle rect = layout.getLineBounds(i);
// rect.x += paintX;
// rect.y += paintY + layout.getSpacing();
// rect.width = width;//layout bounds
// gc.fillRectangle(rect);
// }
}
if (printOptions.printLineNumbers) {
FontMetrics metrics = layout.getLineMetrics(0);
printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
printLayout.setDescent(metrics.getDescent());
String[] lineLabels = printOptions.lineLabels;
if (lineLabels != null) {
if (0 <= index && index < lineLabels.length && lineLabels[index] != null) {
printLayout.setText(lineLabels[index]);
} else {
printLayout.setText("");
}
} else {
printLayout.setText(String.valueOf(index));
}
int paintX = x - printMargin - printLayout.getBounds().width;
printLayout.draw(gc, paintX, y);
printLayout.setAscent(-1);
printLayout.setDescent(-1);
}
gc.setForeground(foreground);
layout.draw(gc, x, y);
}
/**
* Starts a print job and prints the pages specified in the constructor.
*/
@Override
public void run() {
String jobName = printOptions.jobName;
if (jobName == null) {
jobName = "Printing";
}
if (printer.startJob(jobName)) {
init();
print();
dispose();
printer.endJob();
}
}
}
/**
* 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;
List<Color> colorTable;
List<Font> fontTable;
/**
* 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.
*
* @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 = new ArrayList<>();
fontTable = new ArrayList<>();
colorTable.add(getForeground());
colorTable.add(getBackground());
fontTable.add(getFont());
}
/**
* 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.
*/
@Override
public void close() {
if (!isClosed()) {
writeHeader();
write("\n}}\0");
super.close();
}
}
/**
* Returns the index of the specified color in the RTF color table.
*
* @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) {
if (color == null) return defaultIndex;
int index = colorTable.indexOf(color);
if (index == -1) {
index = colorTable.size();
colorTable.add(color);
}
return index;
}
/**
* Returns the index of the specified color in the RTF color table.
*
* @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 getFontIndex(Font font) {
int index = fontTable.indexOf(font);
if (index == -1) {
index = fontTable.size();
fontTable.add(font);
}
return index;
}
/**
* Appends the specified segment of "string" to the RTF data.
* Copy from <code>start</code> up to, but excluding, <code>end</code>.
*
* @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 > 0x7F) {
// 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('?'); // ANSI representation (1 byte long, \\uc1)
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() {
StringBuilder header = new StringBuilder();
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("\\uc1\\deff0{\\fonttbl{\\f0\\fnil ");
header.append(fontData.getName());
header.append(";");
for (int i = 1; i < fontTable.size(); i++) {
header.append("\\f");
header.append(i);
header.append(" ");
FontData fd = fontTable.get(i).getFontData()[0];
header.append(fd.getName());
header.append(";");
}
header.append("}}\n{\\colortbl");
for (int i = 0; i < colorTable.size(); i++) {
Color color = colorTable.get(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.
*
* @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>
*/
@Override
public void writeLine(String line, int lineOffset) {
if (isClosed()) {
SWT.error(SWT.ERROR_IO);
}
int lineIndex = content.getLineAtOffset(lineOffset);
int lineAlignment, lineIndent;
boolean lineJustify;
int[] ranges;
StyleRange[] styles;
StyledTextEvent event = getLineStyleData(lineOffset, line);
if (event != null) {
lineAlignment = event.alignment;
lineIndent = event.indent;
lineJustify = event.justify;
ranges = event.ranges;
styles = event.styles;
} else {
lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
lineIndent = renderer.getLineIndent(lineIndex, indent);
lineJustify = renderer.getLineJustify(lineIndex, justify);
ranges = renderer.getRanges(lineOffset, line.length());
styles = renderer.getStyleRanges(lineOffset, line.length(), false);
}
if (styles == null) styles = new StyleRange[0];
Color lineBackground = renderer.getLineBackground(lineIndex, null);
event = getLineBackgroundData(lineOffset, line);
if (event != null && event.lineBackground != null) lineBackground = event.lineBackground;
writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify);
}
/**
* Appends the specified line delimiter to the RTF data.
*
* @param lineDelimiter line delimiter to write as RTF.
* @exception SWTException <ul>
* <li>ERROR_IO when the writer is closed.</li>
* </ul>
*/
@Override
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.
* <p>
* 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 \chshdng0\chcbpat 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, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify) {
int lineLength = line.length();
int startOffset = getStart();
int writeOffset = startOffset - lineOffset;
if (writeOffset >= lineLength) return;
int lineIndex = Math.max(0, writeOffset);
write("\\fi");
write(indent);
switch (alignment) {
case SWT.LEFT: write("\\ql"); break;
case SWT.CENTER: write("\\qc"); break;
case SWT.RIGHT: write("\\qr"); break;
}
if (justify) write("\\qj");
write(" ");
if (lineBackground != null) {
write("{\\chshdng0\\chcbpat");
write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
write(" ");
}
int endOffset = startOffset + super.getCharCount();
int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
for (int i = 0; i < styles.length; i++) {
StyleRange style = styles[i];
int start, end;
if (ranges != null) {
start = ranges[i << 1] - lineOffset;
end = start + ranges[(i << 1) + 1];
} else {
start = style.start - lineOffset;
end = start + style.length;
}
// 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 beyond end of write range or end of line
// is guarded against above.
write(line, lineIndex, start);
lineIndex = start;
}
// write styled text
write("{\\cf");
write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
if (colorIndex != DEFAULT_BACKGROUND) {
write("\\chshdng0\\chcbpat");
write(colorIndex);
}
int fontStyle = style.fontStyle;
Font font = style.font;
if (font != null) {
int fontIndex = getFontIndex(font);
write("\\f");
write(fontIndex);
FontData fontData = font.getFontData()[0];
write("\\fs");
write(fontData.getHeight() * 2);
fontStyle = fontData.getStyle();
}
if ((fontStyle & SWT.BOLD) != 0) {
write("\\b");
}
if ((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
int copyEnd = Math.min(end, lineEndOffset);
// guard against invalid styles and let style processing continue
copyEnd = Math.max(copyEnd, lineIndex);
write(line, lineIndex, copyEnd);
if ((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.
* </p>
*/
class TextWriter {
private StringBuilder 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.
*
* @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 StringBuilder(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
*/
@Override
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.
* <p>
* 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.
*
* @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) {
if (isClosed) {
SWT.error(SWT.ERROR_IO);
}
int writeOffset = startOffset - lineOffset;
int lineLength = line.length();
int lineIndex;
if (writeOffset >= lineLength) {
return; // whole line is outside write range
} else if (writeOffset > 0) {
lineIndex = writeOffset; // line starts before write start
} else {
lineIndex = 0;
}
int copyEnd = Math.min(lineLength, endOffset - lineOffset);
if (lineIndex < copyEnd) {
write(line.substring(lineIndex, copyEnd));
}
}
/**
* Appends the specified line delimiter to the data.
*
* @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);
}
}
/**
* 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));
// set the 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.setDragDetect(false);
Display display = getDisplay();
fixedLineHeight = true;
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;
}
alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
if (alignment == 0) alignment = SWT.LEFT;
clipboard = new Clipboard(display);
installDefaultContent();
renderer = new StyledTextRenderer(getDisplay(), this);
renderer.setContent(content);
renderer.setFont(getFont(), tabLength);
ime = new IME(this, SWT.NONE);
defaultCaret = new Caret(this, SWT.NONE);
if ((style & SWT.WRAP) != 0) {
setWordWrap(true);
}
if (isBidiCaret()) {
createCaretBitmaps();
Runnable runnable = () -> {
int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
if (direction == caretDirection) return;
if (getCaret() != defaultCaret) return;
Point newCaretPos = getPointAtOffset(caretOffset);
setCaretLocation(newCaretPos, direction);
};
BidiUtil.addLanguageListener(this, runnable);
}
setCaret(defaultCaret);
calculateScrollBars();
createKeyBindings();
super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
installListeners();
initializeAccessible();
setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
if (IS_MAC) setData(STYLEDTEXT_KEY);
}
/**
* Adds an extended modify listener. An ExtendedModify event is sent by the
* widget when the widget text has changed.
*
* @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(ST.ExtendedModify, typedListener);
}
/**
* Adds a bidirectional segment listener.
* <p>
* A BidiSegmentEvent is sent
* whenever a line of text is measured or rendered. You 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);
addListener(ST.LineGetSegments, new StyledTextListener(listener));
resetCache(0, content.getLineCount());
setCaretLocation();
super.redraw();
}
/**
* Adds a caret listener. CaretEvent is sent when the caret offset changes.
*
* @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>
*
* @since 3.5
*/
public void addCaretListener(CaretListener listener) {
checkWidget();
if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
addListener(ST.CaretMoved, new StyledTextListener(listener));
}
/**
* Adds a line background listener. A LineGetBackground event is sent by the
* widget to determine the background color for a line.
*
* @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 (!isListening(ST.LineGetBackground)) {
renderer.clearLineBackground(0, content.getLineCount());
}
addListener(ST.LineGetBackground, new StyledTextListener(listener));
}
/**
* Adds a line style listener. A LineGetStyle event is sent by the widget to
* determine the styles for a line.
*
* @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 (!isListening(ST.LineGetStyle)) {
setStyleRanges(0, 0, null, null, true);
renderer.clearLineStyle(0, content.getLineCount());
}
addListener(ST.LineGetStyle, new StyledTextListener(listener));
setCaretLocation();
}
/**
* Adds a modify listener. A Modify event is sent by the widget when the widget text
* has changed.
*
* @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);
addListener(SWT.Modify, new TypedListener(modifyListener));
}
/**
* Adds a paint object listener. A paint object event is sent by the widget when an object
* needs to be drawn.
*
* @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>
*
* @since 3.2
*
* @see PaintObjectListener
* @see PaintObjectEvent
*/
public void addPaintObjectListener(PaintObjectListener listener) {
checkWidget();
if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
addListener(ST.PaintObject, new StyledTextListener(listener));
}
/**
* Adds a selection listener. A Selection event is sent by the widget when the
* user changes the selection.
* <p>
* When <code>widgetSelected</code> is called, the event x and y fields contain
* the start and end caret indices of the selection. The selection values returned are visual
* (i.e., x will always always be <= y).
* No event is sent when the caret is moved while the selection length is 0.
* </p><p>
* <code>widgetDefaultSelected</code> is not called for StyledTexts.
* </p>
*
* @param listener the listener which should be notified when the user changes the receiver's selection
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @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>
*
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
*/
public void addSelectionListener(SelectionListener listener) {
checkWidget();
if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
addListener(SWT.Selection, new TypedListener(listener));
}
/**
* 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.
*
* @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);
addListener(ST.VerifyKey, new StyledTextListener(listener));
}
/**
* 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.
*
* @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);
addListener(SWT.Verify, new TypedListener(verifyListener));
}
/**
* Adds a word movement listener. A movement event is sent when the boundary
* of a word is needed. For example, this occurs during word next and word
* previous actions.
*
* @param movementListener 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 MovementEvent
* @see MovementListener
* @see #removeWordMovementListener
*
* @since 3.3
*/
public void addWordMovementListener(MovementListener movementListener) {
checkWidget();
if (movementListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
addListener(ST.WordNext, new StyledTextListener(movementListener));
addListener(ST.WordPrevious, new StyledTextListener(movementListener));
}
/**
* Appends a string to the text at the end of the widget.
*
* @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 scroll bars
*/
void calculateScrollBars() {
ScrollBar horizontalBar = getHorizontalBar();
ScrollBar verticalBar = getVerticalBar();
setScrollBars(true);
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 delta) {
int oldDelta = delta;
int oldTopIndex = topIndex;
int oldTopIndexY = topIndexY;
if (isFixedLineHeight()) {
int verticalIncrement = getVerticalIncrement();
if (verticalIncrement == 0) {
return;
}
topIndex = Compatibility.ceil(getVerticalScrollOffset(), 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 = getVerticalScrollOffset() + 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 = getVerticalScrollOffset() / verticalIncrement;
}
} else if (topIndex >= content.getLineCount()) {
topIndex = content.getLineCount() - 1;
}
}
} else {
if (delta >= 0) {
delta -= topIndexY;
int lineIndex = topIndex;
int lineCount = content.getLineCount();
while (lineIndex < lineCount) {
if (delta <= 0) break;
delta -= renderer.getLineHeight(lineIndex++);
}
if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
topIndex = lineIndex;
topIndexY = -delta;
} else {
topIndex = lineIndex - 1;
topIndexY = -renderer.getLineHeight(topIndex) - delta;
}
} else {
delta -= topIndexY;
int lineIndex = topIndex;
while (lineIndex > 0) {
int lineHeight = renderer.getLineHeight(lineIndex - 1);
if (delta + lineHeight > 0) break;
delta += lineHeight;
lineIndex--;
}
if (lineIndex == 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
topIndex = lineIndex;
topIndexY = - delta;
} else {
topIndex = lineIndex - 1;
topIndexY = - renderer.getLineHeight(topIndex) - delta;
}
}
}
if (topIndex < 0) {
// TODO: This logging is in place to determine why topIndex is getting set to negative values.
// It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
System.err.println("StyledText: topIndex was " + topIndex
+ ", isFixedLineHeight() = " + isFixedLineHeight()
+ ", delta = " + delta
+ ", content.getLineCount() = " + content.getLineCount()
+ ", clientAreaHeight = " + clientAreaHeight
+ ", oldTopIndex = " + oldTopIndex
+ ", oldTopIndexY = " + oldTopIndexY
+ ", getVerticalScrollOffset = " + getVerticalScrollOffset()
+ ", oldDelta = " + oldDelta
+ ", getVerticalIncrement() = " + getVerticalIncrement());
topIndex = 0;
}
if (topIndex != oldTopIndex || oldTopIndexY != topIndexY) {
int width = renderer.getWidth();
renderer.calculateClientArea();
if (width != renderer.getWidth()) {
setScrollBars(false);
}
}
}
/**
* 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;
}
}
style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND;
/* Clear SWT.CENTER to avoid the conflict with SWT.EMBEDDED */
return style & ~SWT.CENTER;
}
/**
* Scrolls down the text to use new space made available by a resize or by
* deleted lines.
*/
void claimBottomFreeSpace() {
if (ime.getCompositionOffset() != -1) return;
if (isFixedLineHeight()) {
int newVerticalOffset = Math.max(0, renderer.getHeight() - clientAreaHeight);
if (newVerticalOffset < getVerticalScrollOffset()) {
scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
}
} else {
int bottomIndex = getPartialBottomIndex();
int height = getLinePixel(bottomIndex + 1);
if (clientAreaHeight > height) {
scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
}
}
}
/**
* Scrolls text to the right to use new space made available by a resize.
*/
void claimRightFreeSpace() {
int newHorizontalOffset = Math.max(0, renderer.getWidth() - clientAreaWidth);
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).
scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
}
}
void clearBlockSelection(boolean reset, boolean sendEvent) {
if (reset) resetSelection();
blockXAnchor = blockYAnchor = -1;
blockXLocation = blockYLocation = -1;
caretDirection = SWT.NULL;
updateCaretVisibility();
super.redraw();
if (sendEvent) sendSelectionEvent();
}
/**
* Removes the widget selection.
*
* @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;
resetSelection();
// redraw old selection, if any
if (selectionEnd - selectionStart > 0) {
int length = content.getCharCount();
// 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);
}
if (sendEvent) {
sendSelectionEvent();
}
}
}
@Override
public Point computeSize (int wHint, int hHint, boolean changed) {
checkWidget();
int lineCount = (getStyle() & SWT.SINGLE) != 0 ? 1 : content.getLineCount();
int width = 0;
int height = 0;
if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
Display display = getDisplay();
int maxHeight = display.getClientArea().height;
for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
TextLayout layout = renderer.getTextLayout(lineIndex);
int wrapWidth = layout.getWidth();
if (wordWrap) layout.setWidth(wHint == 0 ? 1 : wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(1, wHint - leftMargin - rightMargin));
Rectangle rect = layout.getBounds();
height += rect.height;
width = Math.max(width, rect.width);
layout.setWidth(wrapWidth);
renderer.disposeTextLayout(layout);
if (isFixedLineHeight() && height > maxHeight) break;
}
if (isFixedLineHeight()) {
height = lineCount * renderer.getLineHeight();
}
}
// Use default values if no text is defined.
if (width == 0) width = DEFAULT_WIDTH;
if (height == 0) height = DEFAULT_HEIGHT;
if (wHint != SWT.DEFAULT) width = wHint;
if (hHint != SWT.DEFAULT) height = hHint;
int wTrim = getLeftMargin() + rightMargin + getCaretWidth();
int hTrim = topMargin + bottomMargin;
Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
return new Point (rect.width, rect.height);
}
/**
* Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
* <p>
* 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();
copySelection(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();
copySelection(clipboardType);
}
boolean copySelection(int type) {
if (type != DND.CLIPBOARD && type != DND.SELECTION_CLIPBOARD) return false;
try {
if (blockSelection && blockXLocation != -1) {
String text = getBlockSelectionText(PlatformLineDelimiter);
if (text.length() > 0) {
//TODO RTF support
TextTransfer plainTextTransfer = TextTransfer.getInstance();
Object[] data = new Object[]{text};
Transfer[] types = new Transfer[]{plainTextTransfer};
clipboard.setContents(data, types, type);
return true;
}
} else {
int length = selection.y - selection.x;
if (length > 0) {
setClipboardContent(selection.x, length, type);
return true;
}
}
} catch (SWTError error) {
// Copy to clipboard failed. This happens when another application
// is accessing the clipboard while we copy. Ignore the error.
// Rethrow all other errors. Fixes bug 17578.
if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
throw error;
}
}
return false;
}
/**
* Returns the alignment of the widget.
*
* @return the alignment
*
* @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>
*
* @see #getLineAlignment(int)
*
* @since 3.2
*/
public int getAlignment() {
checkWidget();
return alignment;
}
/**
* Returns the Always Show Scrollbars flag. True if the scrollbars are
* always shown even if they are not required. False if the scrollbars are only
* visible when some part of the content needs to be scrolled to be seen.
* The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
* horizontal and vertical directions.
*
* @return the Always Show Scrollbars flag value
*
* @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.8
*/
public boolean getAlwaysShowScrollBars() {
checkWidget();
return alwaysShowScroll;
}
int getAvailableHeightAbove(int height) {
int maxHeight = verticalScrollOffset;
if (maxHeight == -1) {
int lineIndex = topIndex - 1;
maxHeight = -topIndexY;
if (topIndexY > 0) {
maxHeight += renderer.getLineHeight(lineIndex--);
}
while (height > maxHeight && lineIndex >= 0) {
maxHeight += renderer.getLineHeight(lineIndex--);
}
}
return Math.min(height, maxHeight);
}
int getAvailableHeightBellow(int height) {
int partialBottomIndex = getPartialBottomIndex();
int topY = getLinePixel(partialBottomIndex);
int lineHeight = renderer.getLineHeight(partialBottomIndex);
int availableHeight = 0;
int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
if (topY + lineHeight > clientAreaHeight) {
availableHeight = lineHeight - (clientAreaHeight - topY);
}
int lineIndex = partialBottomIndex + 1;
int lineCount = content.getLineCount();
while (height > availableHeight && lineIndex < lineCount) {
availableHeight += renderer.getLineHeight(lineIndex++);
}
return Math.min(height, availableHeight);
}
/**
* Returns the color of the margins.
*
* @return the color of the margins.
* @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.5
*/
public Color getMarginColor() {
checkWidget();
return marginColor != null ? marginColor : getBackground();
}
/**
* Returns a string that uses only the line delimiter specified by the
* StyledTextContent implementation.
* <p>
* 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) {
int length = text.length();
if (length == 0) {
return text;
}
int crIndex = 0;
int lfIndex = 0;
int i = 0;
StringBuilder convertedText = new StringBuilder(length);
String delimiter = getLineDelimiter();
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 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();
}
boolean checkDragDetect(Event event) {
if (!isListening(SWT.DragDetect)) return false;
if (event.button != 1) return false;
if (blockSelection && blockXLocation != -1) {
Rectangle rect = getBlockSelectionRectangle();
if (rect.contains(event.x, event.y)) {
return dragDetect(event);
}
} else {
if (selection.x == selection.y) return false;
int offset = getOffsetAtPoint(event.x, event.y, null, true);
if (selection.x <= offset && offset < selection.y) {
return dragDetect(event);
}
}
return false;
}
/**
* 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);
if (IS_MAC) {
setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START);
setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END);
setKeyBinding(SWT.HOME, ST.TEXT_START);
setKeyBinding(SWT.END, ST.TEXT_END);
setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START);
setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END);
setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT);
setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS);
} else {
setKeyBinding(SWT.HOME, ST.LINE_START);
setKeyBinding(SWT.END, ST.LINE_END);
setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
}
setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
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);
// Selection
setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
if (IS_MAC) {
setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START);
setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END);
setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START);
setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END);
setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT);
setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS);
} else {
setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
}
setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
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);
// Modification
// Cut, Copy, Paste
setKeyBinding('X' | SWT.MOD1, ST.CUT);
setKeyBinding('C' | SWT.MOD1, ST.COPY);
setKeyBinding('V' | SWT.MOD1, ST.PASTE);
if (IS_MAC) {
setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT);
setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS);
setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT);
} else {
// 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();
}
int lineHeight = renderer.getLineHeight();
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.
*
* @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();
// Abort cut operation if copy to clipboard fails.
// Fixes bug 21030.
if (copySelection(DND.CLIPBOARD)) {
if (blockSelection && blockXLocation != -1) {
insertBlockSelectionText((char)0, SWT.NULL);
} else {
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) {
int caretLine = getCaretLine();
if (event.y > clientAreaHeight - bottomMargin && caretLine != content.getLineCount() - 1) {
doAutoScroll(SWT.DOWN, event.y - (clientAreaHeight - bottomMargin));
} else if (event.y < topMargin && caretLine != 0) {
doAutoScroll(SWT.UP, topMargin - event.y);
} else if (event.x < leftMargin && !wordWrap) {
doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
} else if (event.x > clientAreaWidth - rightMargin && !wordWrap) {
doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - rightMargin));
} else {
endAutoScroll();
}
}
/**
* Initiates autoscrolling.
*
* @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
*/
void doAutoScroll(int direction, int distance) {
autoScrollDistance = distance;
// If we're already autoscrolling in the given direction do nothing
if (autoScrollDirection == direction) {
return;
}
Runnable timer = null;
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() {
@Override
public void run() {
/* Bug 437357 - NPE in StyledText.getCaretLine
* StyledText.content is null at times, probably because the
* widget itself has been disposed.
*/
if (isDisposed()) return;
if (autoScrollDirection == SWT.UP) {
if (blockSelection) {
int verticalScrollOffset = getVerticalScrollOffset();
int y = blockYLocation - verticalScrollOffset;
int pixels = Math.max(-autoScrollDistance, -verticalScrollOffset);
if (pixels != 0) {
setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
scrollVertical(pixels, true);
}
} else {
doSelectionPageUp(autoScrollDistance);
}
display.timerExec(V_SCROLL_RATE, this);
}
}
};
autoScrollDirection = direction;
display.timerExec(V_SCROLL_RATE, timer);
} else if (direction == SWT.DOWN) {
timer = new Runnable() {
@Override
public void run() {
/* Bug 437357 - NPE in StyledText.getCaretLine
* StyledText.content is null at times, probably because the
* widget itself has been disposed.
*/
if (isDisposed()) return;
if (autoScrollDirection == SWT.DOWN) {
if (blockSelection) {
int verticalScrollOffset = getVerticalScrollOffset();
int y = blockYLocation - verticalScrollOffset;
int max = renderer.getHeight() - verticalScrollOffset - clientAreaHeight;
int pixels = Math.min(autoScrollDistance, Math.max(0,max));
if (pixels != 0) {
setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
scrollVertical(pixels, true);
}
} else {
doSelectionPageDown(autoScrollDistance);
}
display.timerExec(V_SCROLL_RATE, this);
}
}
};
autoScrollDirection = direction;
display.timerExec(V_SCROLL_RATE, timer);
} else if (direction == ST.COLUMN_NEXT) {
timer = new Runnable() {
@Override
public void run() {
/* Bug 437357 - NPE in StyledText.getCaretLine
* StyledText.content is null at times, probably because the
* widget itself has been disposed.
*/
if (isDisposed()) return;
if (autoScrollDirection == ST.COLUMN_NEXT) {
if (blockSelection) {
int x = blockXLocation - horizontalScrollOffset;
int max = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
int pixels = Math.min(autoScrollDistance, Math.max(0,max));
if (pixels != 0) {
setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
scrollHorizontal(pixels, true);
}
} else {
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() {
@Override
public void run() {
/* Bug 437357 - NPE in StyledText.getCaretLine
* StyledText.content is null at times, probably because the
* widget itself has been disposed.
*/
if (isDisposed()) return;
if (autoScrollDirection == ST.COLUMN_PREVIOUS) {
if (blockSelection) {
int x = blockXLocation - horizontalScrollOffset;
int pixels = Math.max(-autoScrollDistance, -horizontalScrollOffset);
if (pixels != 0) {
setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
scrollHorizontal(pixels, true);
}
} else {
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 lineIndex = content.getLineAtOffset(caretOffset);
int lineOffset = content.getOffsetAtLine(lineIndex);
if (caretOffset == lineOffset) {
lineOffset = content.getOffsetAtLine(lineIndex - 1);
event.start = lineOffset + content.getLine(lineIndex - 1).length();
event.end = caretOffset;
} else {
boolean isSurrogate = false;
String lineText = content.getLine(lineIndex);
char ch = lineText.charAt(caretOffset - lineOffset - 1);
if (0xDC00 <= ch && ch <= 0xDFFF) {
if (caretOffset - lineOffset - 2 >= 0) {
ch = lineText.charAt(caretOffset - lineOffset - 2);
isSurrogate = 0xD800 <= ch && ch <= 0xDBFF;
}
}
TextLayout layout = renderer.getTextLayout(lineIndex);
int start = layout.getPreviousOffset(caretOffset - lineOffset, isSurrogate ? SWT.MOVEMENT_CLUSTER : SWT.MOVEMENT_CHAR);
renderer.disposeTextLayout(layout);
event.start = start + lineOffset;
event.end = caretOffset;
}
sendKeyEvent(event);
}
}
void doBlockColumn(boolean next) {
if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
int x = blockXLocation - horizontalScrollOffset;
int y = blockYLocation - getVerticalScrollOffset();
int[] trailing = new int[1];
int offset = getOffsetAtPoint(x, y, trailing, true);
if (offset != -1) {
offset += trailing[0];
int lineIndex = content.getLineAtOffset(offset);
int newOffset;
if (next) {
newOffset = getClusterNext(offset, lineIndex);
} else {
newOffset = getClusterPrevious(offset, lineIndex);
}
offset = newOffset != offset ? newOffset : -1;
}
if (offset != -1) {
setBlockSelectionOffset(offset, true);
showCaret();
} else {
int width = next ? renderer.averageCharWidth : -renderer.averageCharWidth;
int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
setBlockSelectionLocation(x, y, true);
Rectangle rect = new Rectangle(x, y, 0, 0);
showLocation(rect, true);
}
}
void doBlockContentStartEnd(boolean end) {
if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
int offset = end ? content.getCharCount() : 0;
setBlockSelectionOffset(offset, true);
showCaret();
}
void doBlockWord(boolean next) {
if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
int x = blockXLocation - horizontalScrollOffset;
int y = blockYLocation - getVerticalScrollOffset();
int[] trailing = new int[1];
int offset = getOffsetAtPoint(x, y, trailing, true);
if (offset != -1) {
offset += trailing[0];
int lineIndex = content.getLineAtOffset(offset);
int lineOffset = content.getOffsetAtLine(lineIndex);
String lineText = content.getLine(lineIndex);
int lineLength = lineText.length();
int newOffset = offset;
if (next) {
if (offset < lineOffset + lineLength) {
newOffset = getWordNext(offset, SWT.MOVEMENT_WORD);
}
} else {
if (offset > lineOffset) {
newOffset = getWordPrevious(offset, SWT.MOVEMENT_WORD);
}
}
offset = newOffset != offset ? newOffset : -1;
}
if (offset != -1) {
setBlockSelectionOffset(offset, true);
showCaret();
} else {
int width = (next ? renderer.averageCharWidth : -renderer.averageCharWidth) * 6;
int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
setBlockSelectionLocation(x, y, true);
Rectangle rect = new Rectangle(x, y, 0, 0);
showLocation(rect, true);
}
}
void doBlockLineVertical(boolean up) {
if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
int y = blockYLocation - getVerticalScrollOffset();
int lineIndex = getLineIndex(y);
if (up) {
if (lineIndex > 0) {
y = getLinePixel(lineIndex - 1);
setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
if (y < topMargin) {
scrollVertical(y - topMargin, true);
}
}
} else {
int lineCount = content.getLineCount();
if (lineIndex + 1 < lineCount) {
y = getLinePixel(lineIndex + 2) - 1;
setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
int bottom = clientAreaHeight - bottomMargin;
if (y > bottom) {
scrollVertical(y - bottom, true);
}
}
}
}
void doBlockLineHorizontal(boolean end) {
if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
int x = blockXLocation - horizontalScrollOffset;
int y = blockYLocation - getVerticalScrollOffset();
int lineIndex = getLineIndex(y);
int lineOffset = content.getOffsetAtLine(lineIndex);
String lineText = content.getLine(lineIndex);
int lineLength = lineText.length();
int[] trailing = new int[1];
int offset = getOffsetAtPoint(x, y, trailing, true);
if (offset != -1) {
offset += trailing[0];
int newOffset = offset;
if (end) {
if (offset < lineOffset + lineLength) {
newOffset = lineOffset + lineLength;
}
} else {
if (offset > lineOffset) {
newOffset = lineOffset;
}
}
offset = newOffset != offset ? newOffset : -1;
} else {
if (!end) offset = lineOffset + lineLength;
}
if (offset != -1) {
setBlockSelectionOffset(offset, true);
showCaret();
} else {
int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
x = (end ? maxWidth : 0) - horizontalScrollOffset;
setBlockSelectionLocation(x, y, true);
Rectangle rect = new Rectangle(x, y, 0, 0);
showLocation(rect, true);
}
}
void doBlockSelection(boolean sendEvent) {
if (caretOffset > selectionAnchor) {
selection.x = selectionAnchor;
selection.y = caretOffset;
} else {
selection.x = caretOffset;
selection.y = selectionAnchor;
}
updateCaretVisibility();
setCaretLocation();
super.redraw();
if (sendEvent) {
sendSelectionEvent();
}
sendAccessibleTextCaretMoved();
}
/**
* Replaces the selection with the character or insert the character at the
* current caret position if no selection exists.
* <p>
* 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) {
if (blockSelection && blockXLocation != -1) {
insertBlockSelectionText(key, SWT.NULL);
return;
}
Event 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();
}
} else if (selection.x == selection.y && overwrite && key != TAB) {
// no selection and overwrite mode is on and the typed key is not a
// tab character (tabs are always inserted without overwriting)?
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) {
if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
return;
}
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();
setCaretOffset(length, SWT.DEFAULT);
showCaret();
}
}
/**
* Moves the caret in front of the first character of the widget content.
*/
void doContentStart() {
setCaretOffset(0, SWT.DEFAULT);
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.
*
* @see #doSelectionCursorPrevious
*/
void doCursorPrevious() {
if (selection.y - selection.x > 0) {
setCaretOffset(selection.x, OFFSET_LEADING);
showCaret();
} 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.
*
* @see #doSelectionCursorNext
*/
void doCursorNext() {
if (selection.y - selection.x > 0) {
setCaretOffset(selection.y, PREVIOUS_OFFSET_TRAILING);
showCaret();
} 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 = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
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 = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
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. Moves the caret to
* the end of the text if the caret already is on the last line.
*/
void doLineDown(boolean select) {
int caretLine = getCaretLine();
int lineCount = content.getLineCount();
int y = 0;
boolean lastLine = false;
if (isWordWrap()) {
int lineOffset = content.getOffsetAtLine(caretLine);
int offsetInLine = caretOffset - lineOffset;
TextLayout layout = renderer.getTextLayout(caretLine);
int lineIndex = getVisualLineIndex(layout, offsetInLine);
int layoutLineCount = layout.getLineCount();
if (lineIndex == layoutLineCount - 1) {
lastLine = caretLine == lineCount - 1;
caretLine++;
} else {
y = layout.getLineBounds(lineIndex + 1).y;
y++; // bug 485722: workaround for fractional line heights
}
renderer.disposeTextLayout(layout);
} else {
lastLine = caretLine == lineCount - 1;
caretLine++;
}
if (lastLine) {
setCaretOffset(content.getCharCount(), SWT.DEFAULT);
} else {
int[] alignment = new int[1];
int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
setCaretOffset(offset, alignment[0]);
}
int oldColumnX = columnX;
int oldHScrollOffset = horizontalScrollOffset;
if (select) {
setMouseWordSelectionAnchor();
// select first and then scroll to reduce flash when key
// repeat scrolls lots of lines
doSelection(ST.COLUMN_NEXT);
}
showCaret();
int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
columnX = oldColumnX + hScrollChange;
}
/**
* Moves the caret to the end of the line.
*/
void doLineEnd() {
int caretLine = getCaretLine();
int lineOffset = content.getOffsetAtLine(caretLine);
int lineEndOffset;
if (isWordWrap()) {
TextLayout layout = renderer.getTextLayout(caretLine);
int offsetInLine = caretOffset - lineOffset;
int lineIndex = getVisualLineIndex(layout, offsetInLine);
int[] offsets = layout.getLineOffsets();
lineEndOffset = lineOffset + offsets[lineIndex + 1];
renderer.disposeTextLayout(layout);
} else {
int lineLength = content.getLine(caretLine).length();
lineEndOffset = lineOffset + lineLength;
}
setCaretOffset(lineEndOffset, PREVIOUS_OFFSET_TRAILING);
showCaret();
}
/**
* Moves the caret to the beginning of the line.
*/
void doLineStart() {
int caretLine = getCaretLine();
int lineOffset = content.getOffsetAtLine(caretLine);
if (isWordWrap()) {
TextLayout layout = renderer.getTextLayout(caretLine);
int offsetInLine = caretOffset - lineOffset;
int lineIndex = getVisualLineIndex(layout, offsetInLine);
int[] offsets = layout.getLineOffsets();
lineOffset += offsets[lineIndex];
renderer.disposeTextLayout(layout);
}
setCaretOffset(lineOffset, OFFSET_LEADING);
showCaret();
}
/**
* 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. Moves the caret to
* the beginning of the document if it is already on the first line.
*/
void doLineUp(boolean select) {
int caretLine = getCaretLine(), y = 0;
boolean firstLine = false;
if (isWordWrap()) {
int lineOffset = content.getOffsetAtLine(caretLine);
int offsetInLine = caretOffset - lineOffset;
TextLayout layout = renderer.getTextLayout(caretLine);
int lineIndex = getVisualLineIndex(layout, offsetInLine);
if (lineIndex == 0) {
firstLine = caretLine == 0;
if (!firstLine) {
caretLine--;
y = renderer.getLineHeight(caretLine) - 1;
y--; // bug 485722: workaround for fractional line heights
}
} else {
y = layout.getLineBounds(lineIndex - 1).y;
y++; // bug 485722: workaround for fractional line heights
}
renderer.disposeTextLayout(layout);
} else {
firstLine = caretLine == 0;
caretLine--;
}
if (firstLine) {
setCaretOffset(0, SWT.DEFAULT);
} else {
int[] alignment = new int[1];
int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
setCaretOffset(offset, alignment[0]);
}
int oldColumnX = columnX;
int oldHScrollOffset = horizontalScrollOffset;
if (select) setMouseWordSelectionAnchor();
showCaret();
if (select) doSelection(ST.COLUMN_PREVIOUS);
int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
columnX = oldColumnX + hScrollChange;
}
void doMouseLinkCursor() {
Display display = getDisplay();
Point point = display.getCursorLocation();
point = display.map(null, this, point);
doMouseLinkCursor(point.x, point.y);
}
void doMouseLinkCursor(int x, int y) {
int offset = getOffsetAtPoint(x, y, null, true);
Display display = getDisplay();
Cursor newCursor = cursor;
if (renderer.hasLink(offset)) {
newCursor = display.getSystemCursor(SWT.CURSOR_HAND);
} else {
if (cursor == null) {
int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
newCursor = display.getSystemCursor(type);
}
}
if (newCursor != getCursor()) super.setCursor(newCursor);
}
/**
* Moves the caret to the specified location.
*
* @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 = getLineIndex(y);
updateCaretDirection = true;
if (blockSelection) {
x = Math.max(leftMargin, Math.min(x, clientAreaWidth - rightMargin));
y = Math.max(topMargin, Math.min(y, clientAreaHeight - bottomMargin));
if (doubleClickEnabled && clickCount > 1) {
boolean wordSelect = (clickCount & 1) == 0;
if (wordSelect) {
Point left = getPointAtOffset(doubleClickSelection.x);
int[] trailing = new int[1];
int offset = getOffsetAtPoint(x, y, trailing, true);
if (offset != -1) {
if (x > left.x) {
offset = getWordNext(offset + trailing[0], SWT.MOVEMENT_WORD_END);
setBlockSelectionOffset(doubleClickSelection.x, offset, true);
} else {
offset = getWordPrevious(offset + trailing[0], SWT.MOVEMENT_WORD_START);
setBlockSelectionOffset(doubleClickSelection.y, offset, true);
}
} else {
if (x > left.x) {
setBlockSelectionLocation(left.x, left.y, x, y, true);
} else {
Point right = getPointAtOffset(doubleClickSelection.y);
setBlockSelectionLocation(right.x, right.y, x, y, true);
}
}
} else {
setBlockSelectionLocation(blockXLocation, y, true);
}
return;
} else {
if (select) {
if (blockXLocation == -1) {
setBlockSelectionOffset(caretOffset, false);
}
} else {
clearBlockSelection(true, false);
}
int[] trailing = new int[1];
int offset = getOffsetAtPoint(x, y, trailing, true);
if (offset != -1) {
if (select) {
setBlockSelectionOffset(offset + trailing[0], true);
return;
}
} else {
if (isFixedLineHeight() && renderer.fixedPitch) {
int avg = renderer.averageCharWidth;
x = ((x + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
}
setBlockSelectionLocation(x, y, true);
return;
}
}
}
// 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;
}
int[] alignment = new int[1];
int newCaretOffset = getOffsetAtPoint(x, y, alignment);
int newCaretAlignemnt = alignment[0];
if (doubleClickEnabled && clickCount > 1) {
newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
}
int 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
boolean vchange = 0 <= y && y < clientAreaHeight || newCaretLine == 0 || newCaretLine == content.getLineCount() - 1;
boolean hchange = 0 <= x && x < clientAreaWidth || wordWrap || newCaretLine != content.getLineAtOffset(caretOffset);
if (vchange && hchange && (newCaretOffset != caretOffset || newCaretAlignemnt != caretAlignment)) {
setCaretOffset(newCaretOffset, newCaretAlignemnt);
if (select) doMouseSelection();
showCaret();
}
if (!select) {
setCaretOffset(newCaretOffset, newCaretAlignemnt);
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 selection on
* non-bidi platforms) the start offset of the word preceding 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) {
// 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 (0 <= x && x < clientAreaWidth) {
boolean wordSelect = (clickCount & 1) == 0;
if (caretOffset == selection.x) {
if (wordSelect) {
newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START);
} else {
newCaretOffset = content.getOffsetAtLine(line);
}
} else {
if (wordSelect) {
newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END);
} else {
int lineEnd = content.getCharCount();
if (line + 1 < content.getLineCount()) {
lineEnd = content.getOffsetAtLine(line + 1);
}
newCaretOffset = lineEnd;
}
}
}
return newCaretOffset;
}
/**
* Scrolls one page down so that the last line (truncated or whole)
* of the current page becomes the fully visible top line.
* <p>
* 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 height) {
if (isSingleLine()) return;
int oldColumnX = columnX;
int oldHScrollOffset = horizontalScrollOffset;
if (isFixedLineHeight()) {
int lineCount = content.getLineCount();
int caretLine = getCaretLine();
if (caretLine < lineCount - 1) {
int lineHeight = renderer.getLineHeight();
int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
int scrollLines = Math.min(lineCount - caretLine - 1, lines);
// ensure that scrollLines never gets negative and at least one
// line is scrolled. fixes bug 5602.
scrollLines = Math.max(1, scrollLines);
int[] alignment = new int[1];
int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines), alignment);
setCaretOffset(offset, alignment[0]);
if (select) {
doSelection(ST.COLUMN_NEXT);
}
// scroll one page down or to the bottom
int verticalMaximum = lineCount * getVerticalIncrement();
int pageSize = clientAreaHeight;
int verticalScrollOffset = getVerticalScrollOffset();
int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
if (scrollOffset + pageSize > verticalMaximum) {
scrollOffset = verticalMaximum - pageSize;
}
if (scrollOffset > verticalScrollOffset) {
scrollVertical(scrollOffset - verticalScrollOffset, true);
}
}
} else {
int lineCount = content.getLineCount();
int caretLine = getCaretLine();
int lineIndex, lineHeight;
if (height == -1) {
lineIndex = getPartialBottomIndex();
int topY = getLinePixel(lineIndex);
lineHeight = renderer.getLineHeight(lineIndex);
height = topY;
if (topY + lineHeight <= clientAreaHeight) {
height += lineHeight;
} else {
if (isWordWrap()) {
TextLayout layout = renderer.getTextLayout(lineIndex);
int y = clientAreaHeight - topY;
for (int i = 0; i < layout.getLineCount(); i++) {
Rectangle bounds = layout.getLineBounds(i);
if (bounds.contains(bounds.x, y)) {
height += bounds.y;
break;
}
}
renderer.disposeTextLayout(layout);
}
}
} else {
lineIndex = getLineIndex(height);
int topLineY = getLinePixel(lineIndex);
if (isWordWrap()) {
TextLayout layout = renderer.getTextLayout(lineIndex);
int y = height - topLineY;
for (int i = 0; i < layout.getLineCount(); i++) {
Rectangle bounds = layout.getLineBounds(i);
if (bounds.contains(bounds.x, y)) {
height = topLineY + bounds.y + bounds.height;
break;
}
}
renderer.disposeTextLayout(layout);
} else {
height = topLineY + renderer.getLineHeight(lineIndex);
}
}
int caretHeight = height;
if (isWordWrap()) {
TextLayout layout = renderer.getTextLayout(caretLine);
int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
lineIndex = getVisualLineIndex(layout, offsetInLine);
caretHeight += layout.getLineBounds(lineIndex).y;
renderer.disposeTextLayout(layout);
}
lineIndex = caretLine;
lineHeight = renderer.getLineHeight(lineIndex);
while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
caretHeight -= lineHeight;
lineHeight = renderer.getLineHeight(++lineIndex);
}
int[] alignment = new int[1];
int offset = getOffsetAtPoint(columnX, caretHeight, lineIndex, alignment);
setCaretOffset(offset, alignment[0]);
if (select) doSelection(ST.COLUMN_NEXT);
height = getAvailableHeightBellow(height);
scrollVertical(height, true);
if (height == 0) setCaretLocation();
}
showCaret();
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 bottomOffset;
if (isWordWrap()) {
int lineIndex = getPartialBottomIndex();
TextLayout layout = renderer.getTextLayout(lineIndex);
int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);