--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * 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:</p>
+ * <ul>
+ * <li>getStyleRangeAtOffset(int)
+ * <li>getStyleRanges()
+ * <li>replaceStyleRanges(int,int,StyleRange[])
+ * <li>setStyleRange(StyleRange)
+ * <li>setStyleRanges(StyleRange[])
+ * </ul>
+ * <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:</p>
+ * <ul>
+ * <li>getLineBackground(int)
+ * <li>setLineBackground(int,int,Color)
+ * </ul>
+ * <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>
+ * <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>
+ * 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 hasStyleWithVariableHeight = false;
+ boolean hasVerticalIndent = false;
+ boolean doubleClickEnabled = true; // see getDoubleClickEnabled
+ boolean overwrite = false; // insert/overwrite edit mode
+ int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default.
+ Map<Integer, Integer> keyActionMap = new HashMap<>();
+ Color background = null; // workaround for bug 4791
+ Color foreground = null; //
+ /** True if a non-default background color is set */
+ boolean customBackground;
+ /** True if a non-default foreground color is set */
+ boolean customForeground;
+ /** False iff the widget is disabled */
+ boolean enabled = true;
+ /** True iff the widget is in the midst of being enabled or disabled */
+ boolean insideSetEnableCall;
+ 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 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;
+ MouseNavigator mouseNavigator;
+ boolean middleClickPressed;
+
+ //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.
+ *
+ * @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.
+ *
+ * @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>.
+ *
+ * @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();
+ 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);
+ int index = layout.getLineCount() - 1;
+ while (index >= 0) {
+ Rectangle bounds = layout.getLineBounds(index);
+ if (y >= bounds.y + bounds.height) break;
+ index--;
+ }
+ if (index == -1 && lineIndex > 0) {
+ bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length();
+ } else {
+ bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1);
+ }
+ renderer.disposeTextLayout(layout);
+ } else {
+ int lineIndex = getBottomIndex();
+ bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length();
+ }
+ if (caretOffset < bottomOffset) {
+ setCaretOffset(bottomOffset, OFFSET_LEADING);
+ showCaret();
+ }
+ }
+}
+/**
+ * Moves the cursor to the beginning of the first fully visible line.
+ */
+void doPageStart() {
+ int topOffset;
+ if (isWordWrap()) {
+ int y, lineIndex;
+ if (topIndexY > 0) {
+ lineIndex = topIndex - 1;
+ y = renderer.getLineHeight(lineIndex) - topIndexY;
+ } else {
+ lineIndex = topIndex;
+ y = -topIndexY;
+ }
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ int index = 0;
+ int lineCount = layout.getLineCount();
+ while (index < lineCount) {
+ Rectangle bounds = layout.getLineBounds(index);
+ if (y <= bounds.y) break;
+ index++;
+ }
+ if (index == lineCount) {
+ topOffset = content.getOffsetAtLine(lineIndex + 1);
+ } else {
+ topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
+ }
+ renderer.disposeTextLayout(layout);
+ } else {
+ topOffset = content.getOffsetAtLine(topIndex);
+ }
+ if (caretOffset > topOffset) {
+ setCaretOffset(topOffset, OFFSET_LEADING);
+ showCaret();
+ }
+}
+/**
+ * Scrolls one page up so that the first line (truncated or whole)
+ * of the current page becomes the fully visible last line.
+ * The caret is scrolled the same number of lines so that its location
+ * relative to the top line remains the same. The exception is the beginning
+ * of the text where a full page scroll is not possible. In this case the
+ * caret is moved in front of the first character.
+ */
+void doPageUp(boolean select, int height) {
+ if (isSingleLine()) return;
+ int oldHScrollOffset = horizontalScrollOffset;
+ int oldColumnX = columnX;
+ if (isFixedLineHeight()) {
+ int caretLine = getCaretLine();
+ if (caretLine > 0) {
+ int lineHeight = renderer.getLineHeight();
+ int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
+ int scrollLines = Math.max(1, Math.min(caretLine, lines));
+ caretLine -= scrollLines;
+ int[] alignment = new int[1];
+ int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine), alignment);
+ setCaretOffset(offset, alignment[0]);
+ if (select) {
+ doSelection(ST.COLUMN_PREVIOUS);
+ }
+ int verticalScrollOffset = getVerticalScrollOffset();
+ int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
+ if (scrollOffset < verticalScrollOffset) {
+ scrollVertical(scrollOffset - verticalScrollOffset, true);
+ }
+ }
+ } else {
+ int caretLine = getCaretLine();
+ int lineHeight, lineIndex;
+ if (height == -1) {
+ if (topIndexY == 0) {
+ height = clientAreaHeight;
+ } else {
+ int y;
+ if (topIndex > 0) {
+ lineIndex = topIndex - 1;
+ lineHeight = renderer.getLineHeight(lineIndex);
+ height = clientAreaHeight - topIndexY;
+ y = lineHeight - topIndexY;
+ } else {
+ lineIndex = topIndex;
+ lineHeight = renderer.getLineHeight(lineIndex);
+ height = clientAreaHeight - (lineHeight + topIndexY);
+ y = -topIndexY;
+ }
+ if (isWordWrap()) {
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ for (int i = 0; i < layout.getLineCount(); i++) {
+ Rectangle bounds = layout.getLineBounds(i);
+ if (bounds.contains(bounds.x, y)) {
+ height += lineHeight - (bounds.y + bounds.height);
+ break;
+ }
+ }
+ renderer.disposeTextLayout(layout);
+ }
+ }
+ } else {
+ lineIndex = getLineIndex(clientAreaHeight - height);
+ int topLineY = getLinePixel(lineIndex);
+ if (isWordWrap()) {
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ int y = topLineY;
+ for (int i = 0; i < layout.getLineCount(); i++) {
+ Rectangle bounds = layout.getLineBounds(i);
+ if (bounds.contains(bounds.x, y)) {
+ height = clientAreaHeight - (topLineY + bounds.y);
+ break;
+ }
+ }
+ renderer.disposeTextLayout(layout);
+ } else {
+ height = clientAreaHeight - topLineY;
+ }
+ }
+ int caretHeight = height;
+ if (isWordWrap()) {
+ TextLayout layout = renderer.getTextLayout(caretLine);
+ int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
+ lineIndex = getVisualLineIndex(layout, offsetInLine);
+ caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
+ renderer.disposeTextLayout(layout);
+ }
+ lineIndex = caretLine;
+ lineHeight = renderer.getLineHeight(lineIndex);
+ while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
+ caretHeight -= lineHeight;
+ lineHeight = renderer.getLineHeight(--lineIndex);
+ }
+ lineHeight = renderer.getLineHeight(lineIndex);
+ int[] alignment = new int[1];
+ int offset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex, alignment);
+ setCaretOffset(offset, alignment[0]);
+ if (select) doSelection(ST.COLUMN_PREVIOUS);
+ height = getAvailableHeightAbove(height);
+ scrollVertical(-height, true);
+ if (height == 0) setCaretLocation();
+ }
+ showCaret();
+ int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+ columnX = oldColumnX + hScrollChange;
+}
+/**
+ * Updates the selection to extend to the current caret position.
+ */
+void doSelection(int direction) {
+ int redrawStart = -1;
+ int redrawEnd = -1;
+ if (selectionAnchor == -1) {
+ selectionAnchor = selection.x;
+ }
+ if (direction == ST.COLUMN_PREVIOUS) {
+ if (caretOffset < selection.x) {
+ // grow selection
+ redrawEnd = selection.x;
+ redrawStart = selection.x = caretOffset;
+ // check if selection has reversed direction
+ if (selection.y != selectionAnchor) {
+ redrawEnd = selection.y;
+ selection.y = selectionAnchor;
+ }
+ // test whether selection actually changed. Fixes 1G71EO1
+ } else if (selectionAnchor == selection.x && caretOffset < selection.y) {
+ // caret moved towards selection anchor (left side of selection).
+ // shrink selection
+ redrawEnd = selection.y;
+ redrawStart = selection.y = caretOffset;
+ }
+ } else {
+ if (caretOffset > selection.y) {
+ // grow selection
+ redrawStart = selection.y;
+ redrawEnd = selection.y = caretOffset;
+ // check if selection has reversed direction
+ if (selection.x != selectionAnchor) {
+ redrawStart = selection.x;
+ selection.x = selectionAnchor;
+ }
+ // test whether selection actually changed. Fixes 1G71EO1
+ } else if (selectionAnchor == selection.y && caretOffset > selection.x) {
+ // caret moved towards selection anchor (right side of selection).
+ // shrink selection
+ redrawStart = selection.x;
+ redrawEnd = selection.x = caretOffset;
+ }
+ }
+ if (redrawStart != -1 && redrawEnd != -1) {
+ internalRedrawRange(redrawStart, redrawEnd - redrawStart);
+ sendSelectionEvent();
+ }
+ sendAccessibleTextCaretMoved();
+}
+/**
+ * Moves the caret to the next character or to the beginning of the
+ * next line if the cursor is at the end of a line.
+ */
+void doSelectionCursorNext() {
+ int caretLine = getCaretLine();
+ int lineOffset = content.getOffsetAtLine(caretLine);
+ int offsetInLine = caretOffset - lineOffset;
+ int offset, alignment;
+ if (offsetInLine < content.getLine(caretLine).length()) {
+ TextLayout layout = renderer.getTextLayout(caretLine);
+ offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
+ int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
+ renderer.disposeTextLayout(layout);
+ offset = offsetInLine + lineOffset;
+ alignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
+ setCaretOffset(offset, alignment);
+ showCaret();
+ } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
+ caretLine++;
+ offset = content.getOffsetAtLine(caretLine);
+ alignment = PREVIOUS_OFFSET_TRAILING;
+ setCaretOffset(offset, alignment);
+ showCaret();
+ }
+}
+/**
+ * Moves the caret to the previous character or to the end of the previous
+ * line if the cursor is at the beginning of a line.
+ */
+void doSelectionCursorPrevious() {
+ int caretLine = getCaretLine();
+ int lineOffset = content.getOffsetAtLine(caretLine);
+ int offsetInLine = caretOffset - lineOffset;
+ if (offsetInLine > 0) {
+ int offset = getClusterPrevious(caretOffset, caretLine);
+ setCaretOffset(offset, OFFSET_LEADING);
+ showCaret();
+ } else if (caretLine > 0) {
+ caretLine--;
+ lineOffset = content.getOffsetAtLine(caretLine);
+ int offset = lineOffset + content.getLine(caretLine).length();
+ setCaretOffset(offset, OFFSET_LEADING);
+ showCaret();
+ }
+}
+/**
+ * Moves the caret one line down and to the same character offset relative
+ * to the beginning of the line. Moves the caret to the end of the new line
+ * if the new line is shorter than the character offset.
+ * Moves the caret to the end of the text if the caret already is on the
+ * last line.
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ */
+void doSelectionLineDown() {
+ int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+ doLineDown(true);
+ columnX = oldColumnX;
+}
+/**
+ * Moves the caret one line up and to the same character offset relative
+ * to the beginning of the line. Moves the caret to the end of the new line
+ * if the new line is shorter than the character offset.
+ * Moves the caret to the beginning of the document if it is already on the
+ * first line.
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ */
+void doSelectionLineUp() {
+ int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+ doLineUp(true);
+ columnX = oldColumnX;
+}
+/**
+ * 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><p>
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ * </p>
+ */
+void doSelectionPageDown(int pixels) {
+ int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+ doPageDown(true, pixels);
+ columnX = oldColumnX;
+}
+/**
+ * Scrolls one page up so that the first line (truncated or whole)
+ * of the current page becomes the fully visible last 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 beginning
+ * of the text where a full page scroll is not possible. In this case the
+ * caret is moved in front of the first character.
+ * </p><p>
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ * </p>
+ */
+void doSelectionPageUp(int pixels) {
+ int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+ doPageUp(true, pixels);
+ columnX = oldColumnX;
+}
+/**
+ * Moves the caret to the end of the next word .
+ */
+void doSelectionWordNext() {
+ int offset = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
+ // don't change caret position if in single line mode and the cursor
+ // would be on a different line. fixes 5673
+ if (!isSingleLine() ||
+ content.getLineAtOffset(caretOffset) == content.getLineAtOffset(offset)) {
+ // Force symmetrical movement for word next and previous. Fixes 14536
+ setCaretOffset(offset, OFFSET_LEADING);
+ showCaret();
+ }
+}
+/**
+ * Moves the caret to the start of the previous word.
+ */
+void doSelectionWordPrevious() {
+ int offset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
+ setCaretOffset(offset, OFFSET_LEADING);
+ showCaret();
+}
+/**
+ * Moves the caret one character to the left. Do not go to the previous line.
+ * When in a bidi locale and at a R2L character the caret is moved to the
+ * beginning of the R2L segment (visually right) and then one character to the
+ * left (visually left because it's now in a L2R segment).
+ */
+void doVisualPrevious() {
+ int offset = getClusterPrevious(caretOffset, getCaretLine());
+ setCaretOffset(offset, SWT.DEFAULT);
+ showCaret();
+}
+/**
+ * Moves the caret one character to the right. Do not go to the next line.
+ * When in a bidi locale and at a R2L character the caret is moved to the
+ * end of the R2L segment (visually left) and then one character to the
+ * right (visually right because it's now in a L2R segment).
+ */
+void doVisualNext() {
+ int offset = getClusterNext(caretOffset, getCaretLine());
+ setCaretOffset(offset, SWT.DEFAULT);
+ showCaret();
+}
+/**
+ * Moves the caret to the end of the next word.
+ * If a selection exists, move the caret to the end of the selection
+ * and remove the selection.
+ */
+void doWordNext() {
+ if (selection.y - selection.x > 0) {
+ setCaretOffset(selection.y, SWT.DEFAULT);
+ showCaret();
+ } else {
+ doSelectionWordNext();
+ }
+}
+/**
+ * Moves the caret to the start of the previous word.
+ * If a selection exists, move the caret to the start of the selection
+ * and remove the selection.
+ */
+void doWordPrevious() {
+ if (selection.y - selection.x > 0) {
+ setCaretOffset(selection.x, SWT.DEFAULT);
+ showCaret();
+ } else {
+ doSelectionWordPrevious();
+ }
+}
+/**
+ * Ends the autoscroll process.
+ */
+void endAutoScroll() {
+ autoScrollDirection = SWT.NULL;
+}
+@Override
+public Color getBackground() {
+ checkWidget();
+ if (background == null) {
+ return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+ }
+ return background;
+}
+/**
+ * Returns the baseline, in points.
+ *
+ * Note: this API should not be used if a StyleRange attribute causes lines to
+ * have different heights (i.e. different fonts, rise, etc).
+ *
+ * @return baseline the baseline
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 3.0
+ *
+ * @see #getBaseline(int)
+ */
+public int getBaseline() {
+ checkWidget();
+ return renderer.getBaseline();
+}
+/**
+ * Returns the baseline at the given offset, in points.
+ *
+ * @param offset the offset
+ *
+ * @return baseline the baseline
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public int getBaseline(int offset) {
+ checkWidget();
+ if (!(0 <= offset && offset <= content.getCharCount())) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ if (isFixedLineHeight()) {
+ return renderer.getBaseline();
+ }
+ int lineIndex = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
+ FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
+ renderer.disposeTextLayout(layout);
+ return metrics.getAscent() + metrics.getLeading();
+}
+/**
+ * Gets the BIDI coloring mode. When true the BIDI text display
+ * algorithm is applied to segments of text that are the same
+ * color.
+ *
+ * @return the current coloring mode
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @deprecated use BidiSegmentListener instead.
+ */
+@Deprecated
+public boolean getBidiColoring() {
+ checkWidget();
+ return bidiColoring;
+}
+/**
+ * Returns whether the widget is in block selection mode.
+ *
+ * @return true if widget is in block selection mode, false otherwise
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.5
+ */
+public boolean getBlockSelection() {
+ checkWidget();
+ return blockSelection;
+}
+Rectangle getBlockSelectionPosition() {
+ int firstLine = getLineIndex(blockYAnchor - getVerticalScrollOffset());
+ int lastLine = getLineIndex(blockYLocation - getVerticalScrollOffset());
+ if (firstLine > lastLine) {
+ int temp = firstLine;
+ firstLine = lastLine;
+ lastLine = temp;
+ }
+ int left = blockXAnchor;
+ int right = blockXLocation;
+ if (left > right) {
+ left = blockXLocation;
+ right = blockXAnchor;
+ }
+ return new Rectangle (left - horizontalScrollOffset, firstLine, right - horizontalScrollOffset, lastLine);
+}
+/**
+ * Returns the block selection bounds. The bounds is
+ * relative to the upper left corner of the document.
+ *
+ * @return the block selection bounds
+ *
+ * @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 Rectangle getBlockSelectionBounds() {
+ Rectangle rect;
+ if (blockSelection && blockXLocation != -1) {
+ rect = getBlockSelectionRectangle();
+ } else {
+ Point startPoint = getPointAtOffset(selection.x);
+ Point endPoint = getPointAtOffset(selection.y);
+ int height = getLineHeight(selection.y);
+ rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y);
+ if (selection.x == selection.y) {
+ rect.width = getCaretWidth();
+ }
+ }
+ rect.x += horizontalScrollOffset;
+ rect.y += getVerticalScrollOffset();
+ return rect;
+}
+Rectangle getBlockSelectionRectangle() {
+ Rectangle rect = getBlockSelectionPosition();
+ rect.y = getLinePixel(rect.y);
+ rect.width = rect.width - rect.x;
+ rect.height = getLinePixel(rect.height + 1) - rect.y;
+ return rect;
+}
+String getBlockSelectionText(String delimiter) {
+ Rectangle rect = getBlockSelectionPosition();
+ int firstLine = rect.y;
+ int lastLine = rect.height;
+ int left = rect.x;
+ int right = rect.width;
+ StringBuilder buffer = new StringBuilder();
+ for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
+ int start = getOffsetAtPoint(left, 0, lineIndex, null);
+ int end = getOffsetAtPoint(right, 0, lineIndex, null);
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ String text = content.getTextRange(start, end - start);
+ buffer.append(text);
+ if (lineIndex < lastLine) buffer.append(delimiter);
+ }
+ return buffer.toString();
+}
+/**
+ * Returns the index of the last fully visible line.
+ *
+ * @return index of the last fully visible line.
+ */
+int getBottomIndex() {
+ int bottomIndex;
+ if (isFixedLineHeight()) {
+ int lineCount = 1;
+ int lineHeight = renderer.getLineHeight();
+ if (lineHeight != 0) {
+ // calculate the number of lines that are fully visible
+ int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
+ lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
+ }
+ bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
+ } else {
+ int clientAreaHeight = this.clientAreaHeight - bottomMargin;
+ bottomIndex = getLineIndex(clientAreaHeight);
+ if (bottomIndex > 0) {
+ int linePixel = getLinePixel(bottomIndex);
+ int lineHeight = renderer.getLineHeight(bottomIndex);
+ if (linePixel + lineHeight > clientAreaHeight) {
+ if (getLinePixel(bottomIndex - 1) >= topMargin) {
+ bottomIndex--;
+ }
+ }
+ }
+ }
+ return bottomIndex;
+}
+/**
+ * Returns the bottom margin.
+ *
+ * @return the bottom margin.
+ * @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 int getBottomMargin() {
+ checkWidget();
+ return bottomMargin;
+}
+Rectangle getBoundsAtOffset(int offset) {
+ int lineIndex = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ String line = content.getLine(lineIndex);
+ Rectangle bounds;
+ if (line.length() != 0) {
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ int offsetInLine = Math.min (layout.getText().length(), Math.max (0, offset - lineOffset));
+ bounds = layout.getBounds(offsetInLine, offsetInLine);
+ if (getListeners(ST.LineGetSegments).length > 0 && caretAlignment == PREVIOUS_OFFSET_TRAILING && offsetInLine != 0) {
+ offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
+ Point point = layout.getLocation(offsetInLine, true);
+ bounds = new Rectangle (point.x, point.y, 0, bounds.height);
+ }
+ renderer.disposeTextLayout(layout);
+ } else {
+ bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
+ }
+ if (offset == caretOffset && !isWordWrap()) {
+ int lineEnd = lineOffset + line.length();
+ if (offset == lineEnd) {
+ bounds.width += getCaretWidth();
+ }
+ }
+ bounds.x += leftMargin - horizontalScrollOffset;
+ bounds.y += getLinePixel(lineIndex);
+ return bounds;
+}
+/**
+ * Returns the caret position relative to the start of the text.
+ *
+ * @return the caret position relative to the start of the text.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getCaretOffset() {
+ checkWidget();
+ return caretOffset;
+}
+/**
+ * Returns the caret width.
+ *
+ * @return the caret width, 0 if caret is null.
+ */
+int getCaretWidth() {
+ Caret caret = getCaret();
+ if (caret == null) return 0;
+ return caret.getSize().x;
+}
+Object getClipboardContent(int clipboardType) {
+ TextTransfer plainTextTransfer = TextTransfer.getInstance();
+ return clipboard.getContents(plainTextTransfer, clipboardType);
+}
+int getClusterNext(int offset, int lineIndex) {
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ offset -= lineOffset;
+ offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
+ offset += lineOffset;
+ renderer.disposeTextLayout(layout);
+ return offset;
+}
+int getClusterPrevious(int offset, int lineIndex) {
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ offset -= lineOffset;
+ offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
+ offset += lineOffset;
+ renderer.disposeTextLayout(layout);
+ return offset;
+}
+/**
+ * Returns the content implementation that is used for text storage.
+ *
+ * @return content the user defined content implementation that is used for
+ * text storage or the default content implementation if no user defined
+ * content implementation has been set.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public StyledTextContent getContent() {
+ checkWidget();
+ return content;
+}
+@Override
+public boolean getDragDetect () {
+ checkWidget ();
+ return dragDetect;
+}
+/**
+ * Returns whether the widget implements double click mouse behavior.
+ *
+ * @return true if double clicking a word selects the word, false if double clicks
+ * have the same effect as regular mouse clicks
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public boolean getDoubleClickEnabled() {
+ checkWidget();
+ return doubleClickEnabled;
+}
+/**
+ * Returns whether the widget content can be edited.
+ *
+ * @return true if content can be edited, false otherwise
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public boolean getEditable() {
+ checkWidget();
+ return editable;
+}
+@Override
+public Color getForeground() {
+ checkWidget();
+ if (foreground == null) {
+ return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+ }
+ return foreground;
+}
+/**
+ * Returns the horizontal scroll increment.
+ *
+ * @return horizontal scroll increment.
+ */
+int getHorizontalIncrement() {
+ return renderer.averageCharWidth;
+}
+/**
+ * Returns the horizontal scroll offset relative to the start of the line.
+ *
+ * @return horizontal scroll offset relative to the start of the line,
+ * measured in character increments starting at 0, if > 0 the content is scrolled
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getHorizontalIndex() {
+ checkWidget();
+ return horizontalScrollOffset / getHorizontalIncrement();
+}
+/**
+ * Returns the horizontal scroll offset relative to the start of the line.
+ *
+ * @return the horizontal scroll offset relative to the start of the line,
+ * measured in SWT logical point starting at 0, if > 0 the content is scrolled.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getHorizontalPixel() {
+ checkWidget();
+ return horizontalScrollOffset;
+}
+/**
+ * Returns the line indentation of the widget.
+ *
+ * @return the line indentation
+ *
+ * @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 #getLineIndent(int)
+ *
+ * @since 3.2
+ */
+public int getIndent() {
+ checkWidget();
+ return indent;
+}
+/**
+ * Returns whether the widget justifies lines.
+ *
+ * @return whether lines are justified
+ *
+ * @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 #getLineJustify(int)
+ *
+ * @since 3.2
+ */
+public boolean getJustify() {
+ checkWidget();
+ return justify;
+}
+/**
+ * Returns the action assigned to the key.
+ * Returns SWT.NULL if there is no action associated with the key.
+ *
+ * @param key a key code defined in SWT.java or a character.
+ * Optionally ORd with a state mask. Preferred state masks are one or more of
+ * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
+ * differences. However, there may be cases where using the specific state masks
+ * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
+ * @return one of the predefined actions defined in ST.java or SWT.NULL
+ * if there is no action associated with the key.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getKeyBinding(int key) {
+ checkWidget();
+ Integer action = keyActionMap.get(key);
+ return action == null ? SWT.NULL : action.intValue();
+}
+/**
+ * Gets the number of characters.
+ *
+ * @return number of characters in the widget
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getCharCount() {
+ checkWidget();
+ return content.getCharCount();
+}
+/**
+ * Returns the line at the given line index without delimiters.
+ * Index 0 is the first line of the content. When there are not
+ * any lines, getLine(0) is a valid call that answers an empty string.
+ * <p>
+ *
+ * @param lineIndex index of the line to return.
+ * @return the line text without delimiters
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
+ * </ul>
+ * @since 3.4
+ */
+public String getLine(int lineIndex) {
+ checkWidget();
+ if (lineIndex < 0 ||
+ (lineIndex > 0 && lineIndex >= content.getLineCount())) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ return content.getLine(lineIndex);
+}
+/**
+ * Returns the alignment of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line 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>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getAlignment()
+ *
+ * @since 3.2
+ */
+public int getLineAlignment(int index) {
+ checkWidget();
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return renderer.getLineAlignment(index, alignment);
+}
+/**
+ * Returns the line at the specified offset in the text
+ * where 0 < offset < getCharCount() so that getLineAtOffset(getCharCount())
+ * returns the line of the insert location.
+ *
+ * @param offset offset relative to the start of the content.
+ * 0 <= offset <= getCharCount()
+ * @return line at the specified offset in the text
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ */
+public int getLineAtOffset(int offset) {
+ checkWidget();
+ if (offset < 0 || offset > getCharCount()) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ return content.getLineAtOffset(offset);
+}
+/**
+ * Returns the background color of the line at the given index.
+ * Returns null if a LineBackgroundListener has been set or if no background
+ * color has been specified for the line. Should not be called if a
+ * LineBackgroundListener has been set since the listener maintains the
+ * line background colors.
+ *
+ * @param index the index of the line
+ * @return the background color of the line at the given index.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ */
+public Color getLineBackground(int index) {
+ checkWidget();
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return isListening(ST.LineGetBackground) ? null : renderer.getLineBackground(index, null);
+}
+/**
+ * Returns the bullet of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line bullet
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public Bullet getLineBullet(int index) {
+ checkWidget();
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return isListening(ST.LineGetStyle) ? null : renderer.getLineBullet(index, null);
+}
+/**
+ * Returns the line background data for the given line or null if
+ * there is none.
+ *
+ * @param lineOffset offset of the line start relative to the start
+ * of the content.
+ * @param line line to get line background data for
+ * @return line background data for the given line.
+ */
+StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
+ return sendLineEvent(ST.LineGetBackground, lineOffset, line);
+}
+/**
+ * Gets the number of text lines.
+ *
+ * @return the number of lines in the widget
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getLineCount() {
+ checkWidget();
+ return content.getLineCount();
+}
+/**
+ * Returns the number of lines that can be completely displayed in the
+ * widget client area.
+ *
+ * @return number of lines that can be completely displayed in the widget
+ * client area.
+ */
+int getLineCountWhole() {
+ if (isFixedLineHeight()) {
+ int lineHeight = renderer.getLineHeight();
+ return lineHeight != 0 ? clientAreaHeight / lineHeight : 1;
+ }
+ return getBottomIndex() - topIndex + 1;
+}
+/**
+ * Returns the line delimiter used for entering new lines by key down
+ * or paste operation.
+ *
+ * @return line delimiter used for entering new lines by key down
+ * or paste operation.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public String getLineDelimiter() {
+ checkWidget();
+ return content.getLineDelimiter();
+}
+/**
+ * Returns the line height.
+ * <p>
+ * Note: this API should not be used if a StyleRange attribute causes lines to
+ * have different heights (i.e. different fonts, rise, etc).
+ * </p>
+ *
+ * @return line height in points.
+ * @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 #getLineHeight(int)
+ */
+public int getLineHeight() {
+ checkWidget();
+ return renderer.getLineHeight();
+}
+/**
+ * Returns the line height at the given offset.
+ *
+ * @param offset the offset
+ *
+ * @return line height in points
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public int getLineHeight(int offset) {
+ checkWidget();
+ if (!(0 <= offset && offset <= content.getCharCount())) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ if (isFixedLineHeight()) {
+ return renderer.getLineHeight();
+ }
+ int lineIndex = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
+ int height = layout.getLineBounds(lineInParagraph).height;
+ renderer.disposeTextLayout(layout);
+ return height;
+}
+/**
+ * Returns the indentation of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line indentation
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getIndent()
+ *
+ * @since 3.2
+ */
+public int getLineIndent(int index) {
+ checkWidget();
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return isListening(ST.LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
+}
+/**
+ * Returns the vertical indentation of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line vertical indentation
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @since 3.109
+ */
+public int getLineVerticalIndent(int index) {
+ checkWidget();
+ if (index < 0 || index >= content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return isListening(ST.LineGetStyle) ? 0 : renderer.getLineVerticalIndent(index);
+}
+/**
+ * Returns whether the line at the given index is justified.
+ *
+ * @param index the index of the line
+ *
+ * @return whether the line is justified
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getJustify()
+ *
+ * @since 3.2
+ */
+public boolean getLineJustify(int index) {
+ checkWidget();
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return isListening(ST.LineGetStyle) ? false : renderer.getLineJustify(index, justify);
+}
+/**
+ * Returns the line spacing of the widget.
+ *
+ * @return the line spacing
+ *
+ * @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.2
+ */
+public int getLineSpacing() {
+ checkWidget();
+ return lineSpacing;
+}
+/**
+ * Returns the line style data for the given line or null if there is
+ * none.
+ * <p>
+ * If there is a LineStyleListener but it does not set any styles,
+ * the StyledTextEvent.styles field will be initialized to an empty
+ * array.
+ * </p>
+ *
+ * @param lineOffset offset of the line start relative to the start of
+ * the content.
+ * @param line line to get line styles for
+ * @return line style data for the given line. Styles may start before
+ * line start and end after line end
+ */
+StyledTextEvent getLineStyleData(int lineOffset, String line) {
+ return sendLineEvent(ST.LineGetStyle, lineOffset, line);
+}
+/**
+ * Returns the top SWT logical point, relative to the client area, of a given line.
+ * Clamps out of ranges index.
+ *
+ * @param lineIndex the line index, the max value is lineCount. If
+ * lineIndex == lineCount it returns the bottom SWT logical point of the last line.
+ * It means this function can be used to retrieve the bottom SWT logical point of any line.
+ *
+ * @return the top SWT logical point of a given line index
+ *
+ * @since 3.2
+ */
+public int getLinePixel(int lineIndex) {
+ checkWidget();
+ int lineCount = content.getLineCount();
+ lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
+ if (isFixedLineHeight()) {
+ int lineHeight = renderer.getLineHeight();
+ return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
+ }
+ if (lineIndex == topIndex) return topIndexY + topMargin;
+ int height = topIndexY;
+ if (lineIndex > topIndex) {
+ for (int i = topIndex; i < lineIndex; i++) {
+ height += renderer.getLineHeight(i);
+ }
+ } else {
+ for (int i = topIndex - 1; i >= lineIndex; i--) {
+ height -= renderer.getLineHeight(i);
+ }
+ }
+ return height + topMargin;
+}
+/**
+ * Returns the line index for a y, relative to the client area.
+ * The line index returned is always in the range 0..lineCount - 1.
+ *
+ * @param y the y-coordinate point
+ *
+ * @return the line index for a given y-coordinate point
+ *
+ * @since 3.2
+ */
+public int getLineIndex(int y) {
+ checkWidget();
+ y -= topMargin;
+ if (isFixedLineHeight()) {
+ int lineHeight = renderer.getLineHeight();
+ int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
+ int lineCount = content.getLineCount();
+ lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
+ return lineIndex;
+ }
+ if (y == topIndexY) return topIndex;
+ int line = topIndex;
+ if (y < topIndexY) {
+ while (y < topIndexY && line > 0) {
+ y += renderer.getLineHeight(--line);
+ }
+ } else {
+ int lineCount = content.getLineCount();
+ int lineHeight = renderer.getLineHeight(line);
+ while (y - lineHeight >= topIndexY && line < lineCount - 1) {
+ y -= lineHeight;
+ lineHeight = renderer.getLineHeight(++line);
+ }
+ }
+ return line;
+}
+/**
+ * Returns the tab stops of the line at the given <code>index</code>.
+ *
+ * @param index the index of the line
+ *
+ * @return the tab stops for the line
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getTabStops()
+ *
+ * @since 3.6
+ */
+public int[] getLineTabStops(int index) {
+ checkWidget();
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (isListening(ST.LineGetStyle)) return null;
+ int[] tabs = renderer.getLineTabStops(index, null);
+ if (tabs == null) tabs = this.tabs;
+ if (tabs == null) return new int [] {renderer.tabWidth};
+ int[] result = new int[tabs.length];
+ System.arraycopy(tabs, 0, result, 0, tabs.length);
+ return result;
+}
+/**
+ * Returns the wrap indentation of the line at the given <code>index</code>.
+ *
+ * @param index the index of the line
+ *
+ * @return the wrap indentation
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getWrapIndent()
+ *
+ * @since 3.6
+ */
+public int getLineWrapIndent(int index) {
+ checkWidget();
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return isListening(ST.LineGetStyle) ? 0 : renderer.getLineWrapIndent(index, wrapIndent);
+}
+/**
+ * Returns the left margin.
+ *
+ * @return the left margin.
+ * @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 int getLeftMargin() {
+ checkWidget();
+ return leftMargin - alignmentMargin;
+}
+/**
+ * Returns the x, y location of the upper left corner of the character
+ * bounding box at the specified offset in the text. The point is
+ * relative to the upper left corner of the widget client area.
+ *
+ * @param offset offset relative to the start of the content.
+ * 0 <= offset <= getCharCount()
+ * @return x, y location of the upper left corner of the character
+ * bounding box at the specified offset in the text.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ */
+public Point getLocationAtOffset(int offset) {
+ checkWidget();
+ if (offset < 0 || offset > getCharCount()) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ return getPointAtOffset(offset);
+}
+/**
+ * Returns <code>true</code> if the mouse navigator is enabled.
+ * When mouse navigator is enabled, the user can navigate through the widget by pressing the middle button and moving the cursor
+ *
+ * @return the mouse navigator's enabled state
+ *
+ * @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 #getEnabled
+ * @since 3.110
+ */
+public boolean getMouseNavigatorEnabled () {
+ checkWidget ();
+ return mouseNavigator != null;
+}
+/**
+ * Returns the character offset of the first character of the given line.
+ *
+ * @param lineIndex index of the line, 0 based relative to the first
+ * line in the content. 0 <= lineIndex < getLineCount(), except
+ * lineIndex may always be 0
+ * @return offset offset of the first character of the line, relative to
+ * the beginning of the document. The first character of the document is
+ * at offset 0.
+ * When there are not any lines, getOffsetAtLine(0) is a valid call that
+ * answers 0.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
+ * </ul>
+ * @since 2.0
+ */
+public int getOffsetAtLine(int lineIndex) {
+ checkWidget();
+ if (lineIndex < 0 ||
+ (lineIndex > 0 && lineIndex >= content.getLineCount())) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ return content.getOffsetAtLine(lineIndex);
+}
+/**
+ * Returns the offset of the character at the given location relative
+ * to the first character in the document.
+ * <p>
+ * The return value reflects the character offset that the caret will
+ * be placed at if a mouse click occurred at the specified location.
+ * If the x coordinate of the location is beyond the center of a character
+ * the returned offset will be behind the character.
+ * </p>
+ *
+ * @param point the origin of character bounding box relative to
+ * the origin of the widget client area.
+ * @return offset of the character at the given location relative
+ * to the first character in the document.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when point is null</li>
+ * <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
+ * </ul>
+ *
+ * @deprecated Use {@link #getOffsetAtPoint(Point)} instead for better performance
+ */
+@Deprecated
+public int getOffsetAtLocation(Point point) {
+ checkWidget();
+ if (point == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ int[] trailing = new int[1];
+ int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
+ if (offset == -1) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ return offset + trailing[0];
+}
+
+/**
+ * Returns the offset of the character at the given point relative
+ * to the first character in the document.
+ * <p>
+ * The return value reflects the character offset that the caret will
+ * be placed at if a mouse click occurred at the specified point.
+ * If the x coordinate of the point is beyond the center of a character
+ * the returned offset will be behind the character.
+ * </p>
+ * Note: This method is functionally similar to {@link #getOffsetAtLocation(Point)} except that
+ * it does not throw an exception when no character is found and thus performs faster.
+ *
+ * @param point the origin of character bounding box relative to
+ * the origin of the widget client area.
+ * @return offset of the character at the given point relative
+ * to the first character in the document.
+ * -1 when there is no character at the specified location.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when point is <code>null</code></li>
+ * </ul>
+ *
+ * @since 3.107
+ */
+public int getOffsetAtPoint(Point point) {
+ checkWidget();
+ if (point == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ int[] trailing = new int[1];
+ int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
+ return offset != -1 ? offset + trailing[0] : -1;
+}
+
+int getOffsetAtPoint(int x, int y, int[] alignment) {
+ int lineIndex = getLineIndex(y);
+ y -= getLinePixel(lineIndex);
+ return getOffsetAtPoint(x, y, lineIndex, alignment);
+}
+int getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment) {
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ x += horizontalScrollOffset - leftMargin;
+ int[] trailing = new int[1];
+ int offsetInLine = layout.getOffset(x, y, trailing);
+ if (alignment != null) alignment[0] = OFFSET_LEADING;
+ if (trailing[0] != 0) {
+ int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
+ int lineStart = layout.getLineOffsets()[lineInParagraph];
+ if (offsetInLine + trailing[0] == lineStart) {
+ offsetInLine += trailing[0];
+ if (alignment != null) alignment[0] = PREVIOUS_OFFSET_TRAILING;
+ } else {
+ String line = content.getLine(lineIndex);
+ int level = 0;
+ if (alignment != null) {
+ int offset = offsetInLine;
+ while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
+ if (offset == 0 && Character.isDigit(line.charAt(offset))) {
+ level = isMirrored() ? 1 : 0;
+ } else {
+ level = layout.getLevel(offset) & 0x1;
+ }
+ }
+ offsetInLine += trailing[0];
+ if (alignment != null) {
+ int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
+ if (level != trailingLevel) {
+ alignment[0] = PREVIOUS_OFFSET_TRAILING;
+ } else {
+ alignment[0] = OFFSET_LEADING;
+ }
+ }
+ }
+ }
+ renderer.disposeTextLayout(layout);
+ return offsetInLine + content.getOffsetAtLine(lineIndex);
+}
+int getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly) {
+ if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
+ return -1;
+ }
+ int bottomIndex = getPartialBottomIndex();
+ int height = getLinePixel(bottomIndex + 1);
+ if (inTextOnly && y > height) {
+ return -1;
+ }
+ int lineIndex = getLineIndex(y);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ x += horizontalScrollOffset - leftMargin;
+ y -= getLinePixel(lineIndex);
+ int offset = layout.getOffset(x, y, trailing);
+ Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
+ renderer.disposeTextLayout(layout);
+ if (inTextOnly && !(rect.x <= x && x <= rect.x + rect.width)) {
+ return -1;
+ }
+ return offset + lineOffset;
+}
+/**
+ * Returns the orientation of the receiver.
+ *
+ * @return the orientation style
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 2.1.2
+ */
+@Override
+public int getOrientation () {
+ return super.getOrientation ();
+}
+/**
+ * Returns the index of the last partially visible line.
+ *
+ * @return index of the last partially visible line.
+ */
+int getPartialBottomIndex() {
+ if (isFixedLineHeight()) {
+ int lineHeight = renderer.getLineHeight();
+ int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
+ return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
+ }
+ return getLineIndex(clientAreaHeight - bottomMargin);
+}
+/**
+ * Returns the index of the first partially visible line.
+ *
+ * @return index of the first partially visible line.
+ */
+int getPartialTopIndex() {
+ if (isFixedLineHeight()) {
+ int lineHeight = renderer.getLineHeight();
+ return getVerticalScrollOffset() / lineHeight;
+ }
+ return topIndexY <= 0 ? topIndex : topIndex - 1;
+}
+/**
+ * Returns the content in the specified range using the platform line
+ * delimiter to separate lines.
+ *
+ * @param writer the TextWriter to write line text into
+ * @return the content in the specified range using the platform line
+ * delimiter to separate lines as written by the specified TextWriter.
+ */
+String getPlatformDelimitedText(TextWriter writer) {
+ int end = writer.getStart() + writer.getCharCount();
+ int startLine = content.getLineAtOffset(writer.getStart());
+ int endLine = content.getLineAtOffset(end);
+ String endLineText = content.getLine(endLine);
+ int endLineOffset = content.getOffsetAtLine(endLine);
+
+ for (int i = startLine; i <= endLine; i++) {
+ writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
+ if (i < endLine) {
+ writer.writeLineDelimiter(PlatformLineDelimiter);
+ }
+ }
+ if (end > endLineOffset + endLineText.length()) {
+ writer.writeLineDelimiter(PlatformLineDelimiter);
+ }
+ writer.close();
+ return writer.toString();
+}
+/**
+ * Returns all the ranges of text that have an associated StyleRange.
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * <p>
+ * The ranges array contains start and length pairs. Each pair refers to
+ * the corresponding style in the styles array. For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
+ * </p>
+ *
+ * @return the ranges or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getStyleRanges(boolean)
+ */
+public int[] getRanges() {
+ checkWidget();
+ if (!isListening(ST.LineGetStyle)) {
+ int[] ranges = renderer.getRanges(0, content.getCharCount());
+ if (ranges != null) return ranges;
+ }
+ return new int[0];
+}
+/**
+ * Returns the ranges of text that have an associated StyleRange.
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * <p>
+ * The ranges array contains start and length pairs. Each pair refers to
+ * the corresponding style in the styles array. For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
+ * </p>
+ *
+ * @param start the start offset of the style ranges to return
+ * @param length the number of style ranges to return
+ *
+ * @return the ranges or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getStyleRanges(int, int, boolean)
+ */
+public int[] getRanges(int start, int length) {
+ checkWidget();
+ int contentLength = getCharCount();
+ int end = start + length;
+ if (start > end || start < 0 || end > contentLength) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ if (!isListening(ST.LineGetStyle)) {
+ int[] ranges = renderer.getRanges(start, length);
+ if (ranges != null) return ranges;
+ }
+ return new int[0];
+}
+/**
+ * Returns the right margin.
+ *
+ * @return the right margin.
+ * @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 int getRightMargin() {
+ checkWidget();
+ return rightMargin;
+}
+/**
+ * Returns the selection.
+ * <p>
+ * Text selections are specified in terms of caret positions. In a text
+ * widget that contains N characters, there are N+1 caret positions,
+ * ranging from 0..N
+ * </p>
+ *
+ * @return start and end of the selection, x is the offset of the first
+ * selected character, y is the offset after the last selected character.
+ * The selection values returned are visual (i.e., x will always always be
+ * <= y). To determine if a selection is right-to-left (RtoL) vs. left-to-right
+ * (LtoR), compare the caretOffset to the start and end of the selection
+ * (e.g., caretOffset == start of selection implies that the selection is RtoL).
+ * @see #getSelectionRange
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public Point getSelection() {
+ checkWidget();
+ return new Point(selection.x, selection.y);
+}
+/**
+ * Returns the selection.
+ *
+ * @return start and length of the selection, x is the offset of the
+ * first selected character, relative to the first character of the
+ * widget content. y is the length of the selection.
+ * The selection values returned are visual (i.e., length will always always be
+ * positive). To determine if a selection is right-to-left (RtoL) vs. left-to-right
+ * (LtoR), compare the caretOffset to the start and end of the selection
+ * (e.g., caretOffset == start of selection implies that the selection is RtoL).
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public Point getSelectionRange() {
+ checkWidget();
+ return new Point(selection.x, selection.y - selection.x);
+}
+/**
+ * Returns the ranges of text that are inside the block selection rectangle.
+ * <p>
+ * The ranges array contains start and length pairs. When the receiver is not
+ * in block selection mode the return arrays contains the start and length of
+ * the regular selection.
+ *
+ * @return the ranges array
+ *
+ * @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 int[] getSelectionRanges() {
+ checkWidget();
+ if (blockSelection && blockXLocation != -1) {
+ Rectangle rect = getBlockSelectionPosition();
+ int firstLine = rect.y;
+ int lastLine = rect.height;
+ int left = rect.x;
+ int right = rect.width;
+ int[] ranges = new int[(lastLine - firstLine + 1) * 2];
+ int index = 0;
+ for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
+ int start = getOffsetAtPoint(left, 0, lineIndex, null);
+ int end = getOffsetAtPoint(right, 0, lineIndex, null);
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ ranges[index++] = start;
+ ranges[index++] = end - start;
+ }
+ return ranges;
+ }
+ return new int[] {selection.x, selection.y - selection.x};
+}
+/**
+ * Returns the receiver's selection background color.
+ *
+ * @return the selection background color
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public Color getSelectionBackground() {
+ checkWidget();
+ if (selectionBackground == null) {
+ return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
+ }
+ return selectionBackground;
+}
+/**
+ * Gets the number of selected characters.
+ *
+ * @return the number of selected characters.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getSelectionCount() {
+ checkWidget();
+ if (blockSelection && blockXLocation != -1) {
+ return getBlockSelectionText(content.getLineDelimiter()).length();
+ }
+ return getSelectionRange().y;
+}
+/**
+ * Returns the receiver's selection foreground color.
+ *
+ * @return the selection foreground color
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public Color getSelectionForeground() {
+ checkWidget();
+ if (selectionForeground == null) {
+ return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
+ }
+ return selectionForeground;
+}
+/**
+ * Returns the selected text.
+ *
+ * @return selected text, or an empty String if there is no selection.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public String getSelectionText() {
+ checkWidget();
+ if (blockSelection && blockXLocation != -1) {
+ return getBlockSelectionText(content.getLineDelimiter());
+ }
+ return content.getTextRange(selection.x, selection.y - selection.x);
+}
+StyledTextEvent getBidiSegments(int lineOffset, String line) {
+ if (!isListening(ST.LineGetSegments)) {
+ if (!bidiColoring) return null;
+ StyledTextEvent event = new StyledTextEvent(content);
+ event.segments = getBidiSegmentsCompatibility(line, lineOffset);
+ return event;
+ }
+ StyledTextEvent event = sendLineEvent(ST.LineGetSegments, lineOffset, line);
+ if (event == null || event.segments == null || event.segments.length == 0) return null;
+ int lineLength = line.length();
+ int[] segments = event.segments;
+ if (segments[0] > lineLength) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ char[] segmentsChars = event.segmentsChars;
+ boolean hasSegmentsChars = segmentsChars != null;
+ for (int i = 1; i < segments.length; i++) {
+ if ((hasSegmentsChars ? segments[i] < segments[i - 1] : segments[i] <= segments[i - 1]) || segments[i] > lineLength) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ }
+ if (hasSegmentsChars && !visualWrap) {
+ for (int i= 0; i < segmentsChars.length; i++) {
+ if (segmentsChars[i] == '\n' || segmentsChars[i] == '\r') {
+ visualWrap = true;
+ break;
+ }
+ }
+ }
+ return event;
+}
+/**
+ * @see #getBidiSegments
+ * Supports deprecated setBidiColoring API. Remove when API is removed.
+ */
+int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
+ int lineLength = line.length();
+ StyleRange [] styles = null;
+ StyledTextEvent event = getLineStyleData(lineOffset, line);
+ if (event != null) {
+ styles = event.styles;
+ } else {
+ styles = renderer.getStyleRanges(lineOffset, lineLength, true);
+ }
+ if (styles == null || styles.length == 0) {
+ return new int[] {0, lineLength};
+ }
+ int k=0, count = 1;
+ while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) {
+ k++;
+ }
+ int[] offsets = new int[(styles.length - k) * 2 + 2];
+ for (int i = k; i < styles.length; i++) {
+ StyleRange style = styles[i];
+ int styleLineStart = Math.max(style.start - lineOffset, 0);
+ int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
+ styleLineEnd = Math.min (styleLineEnd, line.length ());
+ if (i > 0 && count > 1 &&
+ ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
+ (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
+ style.similarTo(styles[i-1])) {
+ offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
+ offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
+ } else {
+ if (styleLineStart > offsets[count - 1]) {
+ offsets[count] = styleLineStart;
+ count++;
+ }
+ offsets[count] = styleLineEnd;
+ count++;
+ }
+ }
+ // add offset for last non-colored segment in line, if any
+ if (lineLength > offsets[count-1]) {
+ offsets [count] = lineLength;
+ count++;
+ }
+ if (count == offsets.length) {
+ return offsets;
+ }
+ int [] result = new int [count];
+ System.arraycopy (offsets, 0, result, 0, count);
+ return result;
+}
+/**
+ * Returns the style range at the given offset.
+ * <p>
+ * Returns null if a LineStyleListener has been set or if a style is not set
+ * for the offset.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param offset the offset to return the style for.
+ * 0 <= offset < getCharCount() must be true.
+ * @return a StyleRange with start == offset and length == 1, indicating
+ * the style at the given offset. null if a LineStyleListener has been set
+ * or if a style is not set for the given offset.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
+ * </ul>
+ */
+public StyleRange getStyleRangeAtOffset(int offset) {
+ checkWidget();
+ if (offset < 0 || offset >= getCharCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (!isListening(ST.LineGetStyle)) {
+ StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
+ if (ranges != null) return ranges[0];
+ }
+ return null;
+}
+/**
+ * Returns the styles.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p><p>
+ * Note: Because a StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>getStyleRanges(boolean)</code>
+ * can be used to get the styles without the ranges.
+ * </p>
+ *
+ * @return the styles or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #getStyleRanges(boolean)
+ */
+public StyleRange[] getStyleRanges() {
+ checkWidget();
+ return getStyleRanges(0, content.getCharCount(), true);
+}
+/**
+ * Returns the styles.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p><p>
+ * Note: When <code>includeRanges</code> is true, the start and length
+ * fields of each StyleRange will be valid, however the StyleRange
+ * objects may need to be cloned. When <code>includeRanges</code> is
+ * false, <code>getRanges(int, int)</code> can be used to get the
+ * associated ranges.
+ * </p>
+ *
+ * @param includeRanges whether the start and length field of the StyleRanges should be set.
+ *
+ * @return the styles or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getRanges(int, int)
+ * @see #setStyleRanges(int[], StyleRange[])
+ */
+public StyleRange[] getStyleRanges(boolean includeRanges) {
+ checkWidget();
+ return getStyleRanges(0, content.getCharCount(), includeRanges);
+}
+/**
+ * Returns the styles for the given text range.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p><p>
+ * Note: Because the StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>getStyleRanges(int, int, boolean)</code>
+ * can be used to get the styles without the ranges.
+ * </p>
+ * @param start the start offset of the style ranges to return
+ * @param length the number of style ranges to return
+ *
+ * @return the styles or an empty array if a LineStyleListener has
+ * been set. The returned styles will reflect the given range. The first
+ * returned <code>StyleRange</code> will have a starting offset >= start
+ * and the last returned <code>StyleRange</code> will have an ending
+ * offset <= start + length - 1
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ *
+ * @see #getStyleRanges(int, int, boolean)
+ *
+ * @since 3.0
+ */
+public StyleRange[] getStyleRanges(int start, int length) {
+ checkWidget();
+ return getStyleRanges(start, length, true);
+}
+/**
+ * Returns the styles for the given text range.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p><p>
+ * Note: When <code>includeRanges</code> is true, the start and length
+ * fields of each StyleRange will be valid, however the StyleRange
+ * objects may need to be cloned. When <code>includeRanges</code> is
+ * false, <code>getRanges(int, int)</code> can be used to get the
+ * associated ranges.
+ * </p>
+ *
+ * @param start the start offset of the style ranges to return
+ * @param length the number of style ranges to return
+ * @param includeRanges whether the start and length field of the StyleRanges should be set.
+ *
+ * @return the styles or an empty array if a LineStyleListener has
+ * been set. The returned styles will reflect the given range. The first
+ * returned <code>StyleRange</code> will have a starting offset >= start
+ * and the last returned <code>StyleRange</code> will have an ending
+ * offset >= start + length - 1
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getRanges(int, int)
+ * @see #setStyleRanges(int[], StyleRange[])
+ */
+public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) {
+ checkWidget();
+ int contentLength = getCharCount();
+ int end = start + length;
+ if (start > end || start < 0 || end > contentLength) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ if (!isListening(ST.LineGetStyle)) {
+ StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
+ if (ranges != null) return ranges;
+ }
+ return new StyleRange[0];
+}
+/**
+ * Returns the tab width measured in characters.
+ *
+ * @return tab width measured in characters
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #getTabStops()
+ */
+public int getTabs() {
+ checkWidget();
+ return tabLength;
+}
+
+/**
+ * Returns the tab list of the receiver.
+ *
+ * @return the tab list
+ * @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.6
+ */
+public int[] getTabStops() {
+ checkWidget();
+ if (tabs == null) return new int [] {renderer.tabWidth};
+ int[] result = new int[tabs.length];
+ System.arraycopy(tabs, 0, result, 0, tabs.length);
+ return result;
+}
+
+/**
+ * Returns a copy of the widget content.
+ *
+ * @return copy of the widget content
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public String getText() {
+ checkWidget();
+ return content.getTextRange(0, getCharCount());
+}
+/**
+ * Returns the widget content between the two offsets.
+ *
+ * @param start offset of the first character in the returned String
+ * @param end offset of the last character in the returned String
+ * @return widget content starting at start and ending at end
+ * @see #getTextRange(int,int)
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ */
+public String getText(int start, int end) {
+ checkWidget();
+ int contentLength = getCharCount();
+ if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ return content.getTextRange(start, end - start + 1);
+}
+/**
+ * Returns the smallest bounding rectangle that includes the characters between two offsets.
+ *
+ * @param start offset of the first character included in the bounding box
+ * @param end offset of the last character included in the bounding box
+ * @return bounding box of the text between start and end
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ * @since 3.1
+ */
+public Rectangle getTextBounds(int start, int end) {
+ checkWidget();
+ int contentLength = getCharCount();
+ if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ int lineStart = content.getLineAtOffset(start);
+ int lineEnd = content.getLineAtOffset(end);
+ Rectangle rect;
+ int y = getLinePixel(lineStart);
+ int height = 0;
+ int left = 0x7fffffff, right = 0;
+ for (int i = lineStart; i <= lineEnd; i++) {
+ int lineOffset = content.getOffsetAtLine(i);
+ TextLayout layout = renderer.getTextLayout(i);
+ int length = layout.getText().length();
+ if (length > 0) {
+ if (i == lineStart) {
+ if (i == lineEnd) {
+ rect = layout.getBounds(start - lineOffset, end - lineOffset);
+ } else {
+ rect = layout.getBounds(start - lineOffset, length);
+ }
+ y += rect.y;
+ } else if (i == lineEnd) {
+ rect = layout.getBounds(0, end - lineOffset);
+ } else {
+ rect = layout.getBounds();
+ }
+ left = Math.min(left, rect.x);
+ right = Math.max(right, rect.x + rect.width);
+ height += rect.height;
+ } else {
+ height += renderer.getLineHeight();
+ }
+ renderer.disposeTextLayout(layout);
+ }
+ rect = new Rectangle (left, y, right-left, height);
+ rect.x += leftMargin - horizontalScrollOffset;
+ return rect;
+}
+/**
+ * Returns the widget content starting at start for length characters.
+ *
+ * @param start offset of the first character in the returned String
+ * @param length number of characters to return
+ * @return widget content starting at start and extending length characters.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
+ * </ul>
+ */
+public String getTextRange(int start, int length) {
+ checkWidget();
+ int contentLength = getCharCount();
+ int end = start + length;
+ if (start > end || start < 0 || end > contentLength) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ return content.getTextRange(start, length);
+}
+/**
+ * Returns the maximum number of characters that the receiver is capable of holding.
+ *
+ * @return the text limit
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getTextLimit() {
+ checkWidget();
+ return textLimit;
+}
+/**
+ * Gets the top index.
+ * <p>
+ * The top index is the index of the fully visible line that is currently
+ * at the top of the widget or the topmost partially visible line if no line is fully visible.
+ * The top index changes when the widget is scrolled. Indexing is zero based.
+ * </p>
+ *
+ * @return the index of the top line
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getTopIndex() {
+ checkWidget();
+ return topIndex;
+}
+/**
+ * Returns the top margin.
+ *
+ * @return the top margin.
+ * @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 int getTopMargin() {
+ checkWidget();
+ return topMargin;
+}
+/**
+ * Gets the top SWT logical point.
+ * <p>
+ * The top point is the SWT logical point position of the line that is
+ * currently at the top of the widget. The text widget can be scrolled by points
+ * by dragging the scroll thumb so that a partial line may be displayed at the top
+ * the widget. The top point changes when the widget is scrolled. The top point
+ * does not include the widget trimming.
+ * </p>
+ *
+ * @return SWT logical point position of the top line
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getTopPixel() {
+ checkWidget();
+ return getVerticalScrollOffset();
+}
+/**
+ * Returns the vertical scroll increment.
+ *
+ * @return vertical scroll increment.
+ */
+int getVerticalIncrement() {
+ return renderer.getLineHeight();
+}
+int getVerticalScrollOffset() {
+ if (verticalScrollOffset == -1) {
+ renderer.calculate(0, topIndex);
+ int height = 0;
+ for (int i = 0; i < topIndex; i++) {
+ height += renderer.getLineHeight(i);
+ }
+ height -= topIndexY;
+ verticalScrollOffset = height;
+ }
+ return verticalScrollOffset;
+}
+int getVisualLineIndex(TextLayout layout, int offsetInLine) {
+ int lineIndex = layout.getLineIndex(offsetInLine);
+ int[] offsets = layout.getLineOffsets();
+ Caret caret = getCaret();
+ if (caret != null && lineIndex != 0 && offsetInLine == offsets[lineIndex]) {
+ int lineY = layout.getLineBounds(lineIndex).y;
+ int caretY = caret.getLocation().y - getLinePixel(getCaretLine());
+ if (lineY > caretY) lineIndex--;
+ caretAlignment = OFFSET_LEADING;
+ }
+ return lineIndex;
+}
+int getCaretDirection() {
+ if (!isBidiCaret()) return SWT.DEFAULT;
+ if (ime.getCompositionOffset() != -1) return SWT.DEFAULT;
+ if (!updateCaretDirection && caretDirection != SWT.NULL) return caretDirection;
+ updateCaretDirection = false;
+ int caretLine = getCaretLine();
+ int lineOffset = content.getOffsetAtLine(caretLine);
+ String line = content.getLine(caretLine);
+ int offset = caretOffset - lineOffset;
+ int lineLength = line.length();
+ if (lineLength == 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT;
+ if (caretAlignment == PREVIOUS_OFFSET_TRAILING && offset > 0) offset--;
+ if (offset == lineLength && offset > 0) offset--;
+ while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
+ if (offset == 0 && Character.isDigit(line.charAt(offset))) {
+ return isMirrored() ? SWT.RIGHT : SWT.LEFT;
+ }
+ TextLayout layout = renderer.getTextLayout(caretLine);
+ int level = layout.getLevel(offset);
+ renderer.disposeTextLayout(layout);
+ return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT;
+}
+/*
+ * Returns the index of the line the caret is on.
+ */
+int getCaretLine() {
+ return content.getLineAtOffset(caretOffset);
+}
+int getWrapWidth () {
+ if (wordWrap && !isSingleLine()) {
+ int width = clientAreaWidth - leftMargin - rightMargin;
+ return width > 0 ? width : 1;
+ }
+ return -1;
+}
+int getWordNext (int offset, int movement) {
+ return getWordNext(offset, movement, false);
+}
+int getWordNext (int offset, int movement, boolean ignoreListener) {
+ int newOffset, lineOffset;
+ String lineText;
+ if (offset >= getCharCount()) {
+ newOffset = offset;
+ int lineIndex = content.getLineCount() - 1;
+ lineOffset = content.getOffsetAtLine(lineIndex);
+ lineText = content.getLine(lineIndex);
+ } else {
+ int lineIndex = content.getLineAtOffset(offset);
+ lineOffset = content.getOffsetAtLine(lineIndex);
+ lineText = content.getLine(lineIndex);
+ int lineLength = lineText.length();
+ if (offset >= lineOffset + lineLength) {
+ newOffset = content.getOffsetAtLine(lineIndex + 1);
+ } else {
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
+ renderer.disposeTextLayout(layout);
+ }
+ }
+ if (ignoreListener) return newOffset;
+ return sendWordBoundaryEvent(ST.WordNext, movement, offset, newOffset, lineText, lineOffset);
+}
+int getWordPrevious(int offset, int movement) {
+ return getWordPrevious(offset, movement, false);
+}
+int getWordPrevious(int offset, int movement, boolean ignoreListener) {
+ int newOffset, lineOffset;
+ String lineText;
+ if (offset <= 0) {
+ newOffset = 0;
+ int lineIndex = content.getLineAtOffset(newOffset);
+ lineOffset = content.getOffsetAtLine(lineIndex);
+ lineText = content.getLine(lineIndex);
+ } else {
+ int lineIndex = content.getLineAtOffset(offset);
+ lineOffset = content.getOffsetAtLine(lineIndex);
+ lineText = content.getLine(lineIndex);
+ if (offset == lineOffset) {
+ String nextLineText = content.getLine(lineIndex - 1);
+ int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
+ newOffset = nextLineOffset + nextLineText.length();
+ } else {
+ int layoutOffset = Math.min(offset - lineOffset, lineText.length());
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ newOffset = lineOffset + layout.getPreviousOffset(layoutOffset, movement);
+ renderer.disposeTextLayout(layout);
+ }
+ }
+ if (ignoreListener) return newOffset;
+ return sendWordBoundaryEvent(ST.WordPrevious, movement, offset, newOffset, lineText, lineOffset);
+}
+/**
+ * Returns whether the widget wraps lines.
+ *
+ * @return true if widget wraps lines, false otherwise
+ * @since 2.0
+ */
+public boolean getWordWrap() {
+ checkWidget();
+ return wordWrap;
+}
+/**
+ * Returns the wrap indentation of the widget.
+ *
+ * @return the wrap indentation
+ *
+ * @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 #getLineWrapIndent(int)
+ *
+ * @since 3.6
+ */
+public int getWrapIndent() {
+ checkWidget();
+ return wrapIndent;
+}
+/**
+ * Returns the location of the given offset.
+ * <p>
+ * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
+ * </p>
+ *
+ * @return location of the character at the given offset in the line.
+ */
+Point getPointAtOffset(int offset) {
+ int lineIndex = content.getLineAtOffset(offset);
+ String line = content.getLine(lineIndex);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ int offsetInLine = Math.max (0, offset - lineOffset);
+ int lineLength = line.length();
+ if (lineIndex < content.getLineCount() - 1) {
+ int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
+ if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
+ offsetInLine = lineLength;
+ }
+ }
+ Point point;
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ if (lineLength != 0 && offsetInLine <= lineLength) {
+ if (offsetInLine == lineLength) {
+ offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
+ point = layout.getLocation(offsetInLine, true);
+ } else {
+ switch (caretAlignment) {
+ case OFFSET_LEADING:
+ point = layout.getLocation(offsetInLine, false);
+ break;
+ case PREVIOUS_OFFSET_TRAILING:
+ default:
+ boolean lineBegin = offsetInLine == 0;
+ // If word wrap is enabled, we should also consider offsets
+ // of wrapped line parts as line begin and do NOT go back.
+ // This prevents clients to jump one line higher than
+ // expected, see bug 488172.
+ // Respect caretAlignment at the caretOffset, unless there's
+ // a non-empty selection, see bug 488172 comment 6.
+ if (wordWrap && !lineBegin && (offset != caretOffset || selection.x != selection.y)) {
+ int[] offsets = layout.getLineOffsets();
+ for (int i : offsets) {
+ if (i == offsetInLine) {
+ lineBegin = true;
+ break;
+ }
+ }
+ }
+ if (lineBegin) {
+ point = layout.getLocation(offsetInLine, false);
+ } else {
+ offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
+ point = layout.getLocation(offsetInLine, true);
+ }
+ break;
+ }
+ }
+ } else {
+ point = new Point(layout.getIndent(), layout.getVerticalIndent());
+ }
+ renderer.disposeTextLayout(layout);
+ point.x += leftMargin - horizontalScrollOffset;
+ point.y += getLinePixel(lineIndex);
+ return point;
+}
+/**
+ * Inserts a string. The old selection is replaced with the new text.
+ *
+ * @param string the string
+ * @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 string is null</li>
+ * </ul>
+ */
+public void insert(String string) {
+ checkWidget();
+ if (string == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ if (blockSelection) {
+ insertBlockSelectionText(string, false);
+ } else {
+ Point sel = getSelectionRange();
+ replaceTextRange(sel.x, sel.y, string);
+ }
+}
+int insertBlockSelectionText(String text, boolean fillWithSpaces) {
+ int lineCount = 1;
+ for (int i = 0; i < text.length(); i++) {
+ char ch = text.charAt(i);
+ if (ch == '\n' || ch == '\r') {
+ lineCount++;
+ if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
+ i++;
+ }
+ }
+ }
+ String[] lines = new String[lineCount];
+ int start = 0;
+ lineCount = 0;
+ for (int i = 0; i < text.length(); i++) {
+ char ch = text.charAt(i);
+ if (ch == '\n' || ch == '\r') {
+ lines[lineCount++] = text.substring(start, i);
+ if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
+ i++;
+ }
+ start = i + 1;
+ }
+ }
+ lines[lineCount++] = text.substring(start);
+ if (fillWithSpaces) {
+ int maxLength = 0;
+ for (int i = 0; i < lines.length; i++) {
+ int length = lines[i].length();
+ maxLength = Math.max(maxLength, length);
+ }
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i];
+ int length = line.length();
+ if (length < maxLength) {
+ int numSpaces = maxLength - length;
+ StringBuilder buffer = new StringBuilder(length + numSpaces);
+ buffer.append(line);
+ for (int j = 0; j < numSpaces; j++) buffer.append(' ');
+ lines[i] = buffer.toString();
+ }
+ }
+ }
+ int firstLine, lastLine, left, right;
+ if (blockXLocation != -1) {
+ Rectangle rect = getBlockSelectionPosition();
+ firstLine = rect.y;
+ lastLine = rect.height;
+ left = rect.x;
+ right = rect.width;
+ } else {
+ firstLine = lastLine = getCaretLine();
+ left = right = getPointAtOffset(caretOffset).x;
+ }
+ start = caretOffset;
+ int caretLine = getCaretLine();
+ int index = 0, lineIndex = firstLine;
+ while (lineIndex <= lastLine) {
+ String string = index < lineCount ? lines[index++] : "";
+ int lineStart = sendTextEvent(left, right, lineIndex, string, fillWithSpaces);
+ if (lineIndex == caretLine) start = lineStart;
+ lineIndex++;
+ }
+ while (index < lineCount) {
+ int lineStart = sendTextEvent(left, left, lineIndex, lines[index++], fillWithSpaces);
+ if (lineIndex == caretLine) start = lineStart;
+ lineIndex++;
+ }
+ return start;
+}
+void insertBlockSelectionText(char key, int action) {
+ if (key == SWT.CR || key == SWT.LF) return;
+ Rectangle rect = getBlockSelectionPosition();
+ int firstLine = rect.y;
+ int lastLine = rect.height;
+ int left = rect.x;
+ int right = rect.width;
+ int[] trailing = new int[1];
+ int offset = 0, delta = 0;
+ String text = key != 0 ? new String(new char[] {key}) : "";
+ int length = text.length();
+ for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
+ String line = content.getLine(lineIndex);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ int lineEndOffset = lineOffset + line.length();
+ int linePixel = getLinePixel(lineIndex);
+ int start = getOffsetAtPoint(left, linePixel, trailing, true);
+ boolean outOfLine = start == -1;
+ if (outOfLine) {
+ start = left < leftMargin ? lineOffset : lineEndOffset;
+ } else {
+ start += trailing[0];
+ }
+ int end = getOffsetAtPoint(right, linePixel, trailing, true);
+ if (end == -1) {
+ end = right < leftMargin ? lineOffset : lineEndOffset;
+ } else {
+ end += trailing[0];
+ }
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ if (start == end && !outOfLine) {
+ switch (action) {
+ case ST.DELETE_PREVIOUS:
+ if (start > lineOffset) start = getClusterPrevious(start, lineIndex);
+ break;
+ case ST.DELETE_NEXT:
+ if (end < lineEndOffset) end = getClusterNext(end, lineIndex);
+ break;
+ }
+ }
+ if (outOfLine) {
+ if (line.length() >= delta) {
+ delta = line.length();
+ offset = lineEndOffset + length;
+ }
+ } else {
+ offset = start + length;
+ delta = content.getCharCount();
+ }
+ Event event = new Event();
+ event.text = text;
+ event.start = start;
+ event.end = end;
+ sendKeyEvent(event);
+ }
+ int x = getPointAtOffset(offset).x;
+ int verticalScrollOffset = getVerticalScrollOffset();
+ setBlockSelectionLocation(x, blockYAnchor - verticalScrollOffset, x, blockYLocation - verticalScrollOffset, false);
+}
+/**
+ * Creates content change listeners and set the default content model.
+ */
+void installDefaultContent() {
+ textChangeListener = new TextChangeListener() {
+ @Override
+ public void textChanging(TextChangingEvent event) {
+ handleTextChanging(event);
+ }
+ @Override
+ public void textChanged(TextChangedEvent event) {
+ handleTextChanged(event);
+ }
+ @Override
+ public void textSet(TextChangedEvent event) {
+ handleTextSet(event);
+ }
+ };
+ content = new DefaultContent();
+ content.addTextChangeListener(textChangeListener);
+}
+/**
+ * Adds event listeners
+ */
+void installListeners() {
+ ScrollBar verticalBar = getVerticalBar();
+ ScrollBar horizontalBar = getHorizontalBar();
+
+ listener = event -> {
+ switch (event.type) {
+ case SWT.Dispose: handleDispose(event); break;
+ case SWT.KeyDown: handleKeyDown(event); break;
+ case SWT.KeyUp: handleKeyUp(event); break;
+ case SWT.MenuDetect: handleMenuDetect(event); break;
+ case SWT.MouseDown: handleMouseDown(event); break;
+ case SWT.MouseUp: handleMouseUp(event); break;
+ case SWT.MouseMove: handleMouseMove(event); break;
+ case SWT.Paint: handlePaint(event); break;
+ case SWT.Resize: handleResize(event); break;
+ case SWT.Traverse: handleTraverse(event); break;
+ }
+ };
+ addListener(SWT.Dispose, listener);
+ addListener(SWT.KeyDown, listener);
+ addListener(SWT.KeyUp, listener);
+ addListener(SWT.MenuDetect, listener);
+ addListener(SWT.MouseDown, listener);
+ addListener(SWT.MouseUp, listener);
+ addListener(SWT.MouseMove, listener);
+ addListener(SWT.Paint, listener);
+ addListener(SWT.Resize, listener);
+ addListener(SWT.Traverse, listener);
+ ime.addListener(SWT.ImeComposition, event -> {
+ if (!editable) {
+ event.doit = false;
+ event.start = 0;
+ event.end = 0;
+ event.text = "";
+ return;
+ }
+
+ switch (event.detail) {
+ case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break;
+ case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break;
+ case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break;
+ }
+ });
+ if (verticalBar != null) {
+ verticalBar.addListener(SWT.Selection, event -> handleVerticalScroll(event));
+ }
+ if (horizontalBar != null) {
+ horizontalBar.addListener(SWT.Selection, event -> handleHorizontalScroll(event));
+ }
+}
+void internalRedrawRange(int start, int length) {
+ if (length <= 0) return;
+ int end = start + length;
+ int startLine = content.getLineAtOffset(start);
+ int endLine = content.getLineAtOffset(end);
+ int partialBottomIndex = getPartialBottomIndex();
+ int partialTopIndex = getPartialTopIndex();
+ if (startLine > partialBottomIndex || endLine < partialTopIndex) {
+ return;
+ }
+ if (partialTopIndex > startLine) {
+ startLine = partialTopIndex;
+ start = 0;
+ } else {
+ start -= content.getOffsetAtLine(startLine);
+ }
+ if (partialBottomIndex < endLine) {
+ endLine = partialBottomIndex + 1;
+ end = 0;
+ } else {
+ end -= content.getOffsetAtLine(endLine);
+ }
+
+ TextLayout layout = renderer.getTextLayout(startLine);
+ int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
+ int[] offsets = layout.getLineOffsets();
+ int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length()));
+
+ /* Redraw end of line before start line if wrapped and start offset is first char */
+ if (isWordWrap() && startIndex > 0 && offsets[startIndex] == start) {
+ Rectangle rect = layout.getLineBounds(startIndex - 1);
+ rect.x = rect.width;
+ rect.width = clientAreaWidth - rightMargin - rect.x;
+ rect.x += lineX;
+ rect.y += startLineY;
+ super.redraw(rect.x, rect.y, rect.width, rect.height, false);
+ }
+
+ if (startLine == endLine) {
+ int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
+ if (startIndex == endIndex) {
+ /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
+ Rectangle rect = layout.getBounds(start, end - 1);
+ rect.x += lineX;
+ rect.y += startLineY;
+ super.redraw(rect.x, rect.y, rect.width, rect.height, false);
+ renderer.disposeTextLayout(layout);
+ return;
+ }
+ }
+
+ /* Redraw start line from the start offset to the end of client area */
+ Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1);
+ if (startRect.height == 0) {
+ Rectangle bounds = layout.getLineBounds(startIndex);
+ startRect.x = bounds.width;
+ startRect.y = bounds.y;
+ startRect.height = bounds.height;
+ }
+ startRect.x += lineX;
+ startRect.y += startLineY;
+ startRect.width = clientAreaWidth - rightMargin - startRect.x;
+ super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);
+
+ /* Redraw end line from the beginning of the line to the end offset */
+ if (startLine != endLine) {
+ renderer.disposeTextLayout(layout);
+ layout = renderer.getTextLayout(endLine);
+ offsets = layout.getLineOffsets();
+ }
+ int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
+ Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1);
+ if (endRect.height == 0) {
+ Rectangle bounds = layout.getLineBounds(endIndex);
+ endRect.y = bounds.y;
+ endRect.height = bounds.height;
+ }
+ endRect.x += lineX;
+ endRect.y += getLinePixel(endLine);
+ super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
+ renderer.disposeTextLayout(layout);
+
+ /* Redraw all lines in between start and end line */
+ int y = startRect.y + startRect.height;
+ if (endRect.y > y) {
+ super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
+ }
+}
+void handleCompositionOffset (Event event) {
+ int[] trailing = new int [1];
+ event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
+ event.count = trailing[0];
+}
+void handleCompositionSelection (Event event) {
+ if (event.start != event.end) {
+ int charCount = getCharCount();
+ event.start = Math.max(0, Math.min(event.start, charCount));
+ event.end = Math.max(0, Math.min(event.end, charCount));
+ if (event.text != null) {
+ setSelection(event.start, event.end);
+ } else {
+ event.text = getTextRange(event.start, event.end - event.start);
+ }
+ } else {
+ event.start = selection.x;
+ event.end = selection.y;
+ event.text = getSelectionText();
+ }
+}
+void handleCompositionChanged(Event event) {
+ String text = event.text;
+ int start = event.start;
+ int end = event.end;
+ int charCount = content.getCharCount();
+ start = Math.min(start, charCount);
+ end = Math.min(end, charCount);
+ int length = text.length();
+ if (length == ime.getCommitCount()) {
+ content.replaceTextRange(start, end - start, "");
+ setCaretOffset(ime.getCompositionOffset(), SWT.DEFAULT);
+ caretWidth = 0;
+ caretDirection = SWT.NULL;
+ } else {
+ content.replaceTextRange(start, end - start, text);
+ int alignment = SWT.DEFAULT;
+ if (ime.getWideCaret()) {
+ start = ime.getCompositionOffset();
+ int lineIndex = getCaretLine();
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width;
+ renderer.disposeTextLayout(layout);
+ alignment = OFFSET_LEADING;
+ }
+ setCaretOffset(ime.getCaretOffset(), alignment);
+ }
+ resetSelection();
+ showCaret();
+}
+/**
+ * Frees resources.
+ */
+void handleDispose(Event event) {
+ removeListener(SWT.Dispose, listener);
+ notifyListeners(SWT.Dispose, event);
+ event.type = SWT.None;
+
+ clipboard.dispose();
+ if (renderer != null) {
+ renderer.dispose();
+ renderer = null;
+ }
+ if (content != null) {
+ content.removeTextChangeListener(textChangeListener);
+ content = null;
+ }
+ if (defaultCaret != null) {
+ defaultCaret.dispose();
+ defaultCaret = null;
+ }
+ if (leftCaretBitmap != null) {
+ leftCaretBitmap.dispose();
+ leftCaretBitmap = null;
+ }
+ if (rightCaretBitmap != null) {
+ rightCaretBitmap.dispose();
+ rightCaretBitmap = null;
+ }
+ if (isBidiCaret()) {
+ BidiUtil.removeLanguageListener(this);
+ }
+ selectionBackground = null;
+ selectionForeground = null;
+ marginColor = null;
+ textChangeListener = null;
+ selection = null;
+ doubleClickSelection = null;
+ keyActionMap = null;
+ background = null;
+ foreground = null;
+ clipboard = null;
+ tabs = null;
+}
+/**
+ * Scrolls the widget horizontally.
+ */
+void handleHorizontalScroll(Event event) {
+ int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
+ scrollHorizontal(scrollPixel, false);
+}
+/**
+ * If an action has been registered for the key stroke execute the action.
+ * Otherwise, if a character has been entered treat it as new content.
+ *
+ * @param event keyboard event
+ */
+void handleKey(Event event) {
+ int action;
+ caretAlignment = PREVIOUS_OFFSET_TRAILING;
+ if (event.keyCode != 0) {
+ // special key pressed (e.g., F1)
+ action = getKeyBinding(event.keyCode | event.stateMask);
+ } else {
+ // character key pressed
+ action = getKeyBinding(event.character | event.stateMask);
+ if (action == SWT.NULL) {
+ // see if we have a control character
+ if ((event.stateMask & SWT.CTRL) != 0 && event.character <= 31) {
+ // get the character from the CTRL+char sequence, the control
+ // key subtracts 64 from the value of the key that it modifies
+ int c = event.character + 64;
+ action = getKeyBinding(c | event.stateMask);
+ }
+ }
+ }
+ if (action == SWT.NULL) {
+ boolean ignore = false;
+
+ if (IS_MAC) {
+ // Ignore accelerator key combinations (we do not want to
+ // insert a character in the text in this instance).
+ ignore = (event.stateMask & (SWT.COMMAND | SWT.CTRL)) != 0;
+ } else {
+ // Ignore accelerator key combinations (we do not want to
+ // insert a character in the text in this instance). Don't
+ // ignore CTRL+ALT combinations since that is the Alt Gr
+ // key on some keyboards. See bug 20953.
+ ignore = event.stateMask == SWT.ALT ||
+ event.stateMask == SWT.CTRL ||
+ event.stateMask == (SWT.ALT | SWT.SHIFT) ||
+ event.stateMask == (SWT.CTRL | SWT.SHIFT);
+ }
+ // -ignore anything below SPACE except for line delimiter keys and tab.
+ // -ignore DEL
+ if (!ignore && event.character > 31 && event.character != SWT.DEL ||
+ event.character == SWT.CR || event.character == SWT.LF ||
+ event.character == TAB) {
+ doContent(event.character);
+ update();
+ }
+ } else {
+ invokeAction(action);
+ }
+}
+/**
+ * If a VerifyKey listener exists, verify that the key that was entered
+ * should be processed.
+ *
+ * @param event keyboard event
+ */
+void handleKeyDown(Event event) {
+ if (clipboardSelection == null) {
+ clipboardSelection = new Point(selection.x, selection.y);
+ }
+ newOrientation = SWT.NONE;
+ event.stateMask &= SWT.MODIFIER_MASK;
+
+ Event verifyEvent = new Event();
+ verifyEvent.character = event.character;
+ verifyEvent.keyCode = event.keyCode;
+ verifyEvent.keyLocation = event.keyLocation;
+ verifyEvent.stateMask = event.stateMask;
+ verifyEvent.doit = event.doit;
+ notifyListeners(ST.VerifyKey, verifyEvent);
+ if (verifyEvent.doit) {
+ if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL && event.keyCode == SWT.SHIFT && isBidiCaret()) {
+ newOrientation = event.keyLocation == SWT.LEFT ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT;
+ }
+ handleKey(event);
+ }
+}
+/**
+ * Update the Selection Clipboard.
+ *
+ * @param event keyboard event
+ */
+void handleKeyUp(Event event) {
+ if (clipboardSelection != null) {
+ if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) {
+ copySelection(DND.SELECTION_CLIPBOARD);
+ }
+ }
+ clipboardSelection = null;
+
+ if (newOrientation != SWT.NONE) {
+ if (newOrientation != getOrientation()) {
+ Event e = new Event();
+ e.doit = true;
+ notifyListeners(SWT.OrientationChange, e);
+ if (e.doit) {
+ setOrientation(newOrientation);
+ }
+ }
+ newOrientation = SWT.NONE;
+ }
+}
+/**
+ * Update the event location for focus-based context menu triggers.
+ *
+ * @param event menu detect event
+ */
+void handleMenuDetect(Event event) {
+ if (event.detail == SWT.MENU_KEYBOARD) {
+ Point point = getDisplay().map(this, null, getPointAtOffset(caretOffset));
+ event.x = point.x;
+ event.y = point.y + getLineHeight(caretOffset);
+ }
+}
+/**
+ * Updates the caret location and selection if mouse button 1 has been
+ * pressed.
+ */
+void handleMouseDown(Event event) {
+ //force focus (object support)
+ forceFocus();
+
+ //drag detect
+ if (dragDetect && checkDragDetect(event)) return;
+
+ //paste clipboard selection
+ if (event.button == 2) {
+ // On GTK, if mouseNavigator is enabled we have to distinguish a short middle-click (to paste content) from
+ // a long middle-click (mouse navigation started)
+ if (IS_GTK && mouseNavigator != null) {
+ middleClickPressed = true;
+ getDisplay().timerExec(200, ()->{
+ boolean click = middleClickPressed;
+ middleClickPressed = false;
+ if (click && mouseNavigator !=null) {
+ mouseNavigator.onMouseDown(event);
+ } else {
+ pasteOnMiddleClick(event);
+ }
+ });
+ return;
+ } else {
+ pasteOnMiddleClick(event);
+ }
+ }
+
+ //set selection
+ if ((event.button != 1) || (IS_MAC && (event.stateMask & SWT.MOD4) != 0)) {
+ return;
+ }
+ clickCount = event.count;
+ if (clickCount == 1) {
+ boolean select = (event.stateMask & SWT.MOD2) != 0;
+ doMouseLocationChange(event.x, event.y, select);
+ } else {
+ if (doubleClickEnabled) {
+ boolean wordSelect = (clickCount & 1) == 0;
+ int offset = getOffsetAtPoint(event.x, event.y, null);
+ int lineIndex = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ if (wordSelect) {
+ int min = blockSelection ? lineOffset : 0;
+ int max = blockSelection ? lineOffset + content.getLine(lineIndex).length() : content.getCharCount();
+ int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
+ int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END));
+ setSelection(start, end - start, false, true);
+ sendSelectionEvent();
+ } else {
+ if (blockSelection) {
+ setBlockSelectionLocation(leftMargin, event.y, clientAreaWidth - rightMargin, event.y, true);
+ } else {
+ int lineEnd = content.getCharCount();
+ if (lineIndex + 1 < content.getLineCount()) {
+ lineEnd = content.getOffsetAtLine(lineIndex + 1);
+ }
+ setSelection(lineOffset, lineEnd - lineOffset, false, false);
+ sendSelectionEvent();
+ }
+ }
+ doubleClickSelection = new Point(selection.x, selection.y);
+ showCaret();
+ }
+ }
+}
+/**
+ * Updates the caret location and selection if mouse button 1 is pressed
+ * during the mouse move.
+ */
+void handleMouseMove(Event event) {
+ if (clickCount > 0) {
+ update();
+ doAutoScroll(event);
+ doMouseLocationChange(event.x, event.y, true);
+ }
+ if (renderer.hasLinks) {
+ doMouseLinkCursor(event.x, event.y);
+ }
+}
+/**
+ * Autoscrolling ends when the mouse button is released.
+ */
+void handleMouseUp(Event event) {
+ middleClickPressed = false;
+ clickCount = 0;
+ endAutoScroll();
+ if (event.button == 1) {
+ copySelection(DND.SELECTION_CLIPBOARD);
+ }
+}
+/**
+ * Renders the invalidated area specified in the paint event.
+ *
+ * @param event paint event
+ */
+void handlePaint(Event event) {
+ if (event.width == 0 || event.height == 0) return;
+ if (clientAreaWidth == 0 || clientAreaHeight == 0) return;
+
+ int startLine = getLineIndex(event.y);
+ int y = getLinePixel(startLine);
+ int endY = event.y + event.height;
+ GC gc = event.gc;
+ Color background = getBackground();
+ Color foreground = getForeground();
+ if (endY > 0) {
+ int lineCount = isSingleLine() ? 1 : content.getLineCount();
+ int x = leftMargin - horizontalScrollOffset;
+ for (int i = startLine; y < endY && i < lineCount; i++) {
+ y += renderer.drawLine(i, x, y, gc, background, foreground);
+ }
+ if (y < endY) {
+ gc.setBackground(background);
+ drawBackground(gc, 0, y, clientAreaWidth, endY - y);
+ }
+ }
+ if (blockSelection && blockXLocation != -1) {
+ gc.setBackground(getSelectionBackground());
+ Rectangle rect = getBlockSelectionRectangle();
+ gc.drawRectangle(rect.x, rect.y, Math.max(1, rect.width - 1), Math.max(1, rect.height - 1));
+ gc.setAdvanced(true);
+ if (gc.getAdvanced()) {
+ gc.setAlpha(100);
+ gc.fillRectangle(rect);
+ gc.setAdvanced(false);
+ }
+ }
+
+ // fill the margin background
+ gc.setBackground(marginColor != null ? marginColor : background);
+ if (topMargin > 0) {
+ drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
+ }
+ if (bottomMargin > 0) {
+ drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
+ }
+ if (leftMargin - alignmentMargin > 0) {
+ drawBackground(gc, 0, 0, leftMargin - alignmentMargin, clientAreaHeight);
+ }
+ if (rightMargin > 0) {
+ drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
+ }
+}
+/**
+ * Recalculates the scroll bars. Rewraps all lines when in word
+ * wrap mode.
+ *
+ * @param event resize event
+ */
+void handleResize(Event event) {
+ int oldHeight = clientAreaHeight;
+ int oldWidth = clientAreaWidth;
+ Rectangle clientArea = getClientArea();
+ clientAreaHeight = clientArea.height;
+ clientAreaWidth = clientArea.width;
+ if (!alwaysShowScroll && ignoreResize != 0) return;
+
+ redrawMargins(oldHeight, oldWidth);
+ if (wordWrap) {
+ if (oldWidth != clientAreaWidth) {
+ renderer.reset(0, content.getLineCount());
+ verticalScrollOffset = -1;
+ renderer.calculateIdle();
+ super.redraw();
+ }
+ if (oldHeight != clientAreaHeight) {
+ if (oldHeight == 0) topIndexY = 0;
+ setScrollBars(true);
+ }
+ setCaretLocation();
+ } else {
+ renderer.calculateClientArea();
+ setScrollBars(true);
+ claimRightFreeSpace();
+ // StyledText allows any value for horizontalScrollOffset when clientArea is zero
+ // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
+ if (clientAreaWidth != 0) {
+ ScrollBar horizontalBar = getHorizontalBar();
+ if (horizontalBar != null && horizontalBar.getVisible()) {
+ if (horizontalScrollOffset != horizontalBar.getSelection()) {
+ horizontalBar.setSelection(horizontalScrollOffset);
+ horizontalScrollOffset = horizontalBar.getSelection();
+ }
+ }
+ }
+ }
+ updateCaretVisibility();
+ claimBottomFreeSpace();
+ setAlignment();
+ //TODO FIX TOP INDEX DURING RESIZE
+// if (oldHeight != clientAreaHeight || wordWrap) {
+// calculateTopIndex(0);
+// }
+}
+/**
+ * Updates the caret position and selection and the scroll bars to reflect
+ * the content change.
+ */
+void handleTextChanged(TextChangedEvent event) {
+ int offset = ime.getCompositionOffset();
+ if (offset != -1 && lastTextChangeStart < offset) {
+ ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount);
+ }
+ int firstLine = content.getLineAtOffset(lastTextChangeStart);
+ resetCache(firstLine, 0);
+ if (!isFixedLineHeight() && topIndex > firstLine) {
+ topIndex = firstLine;
+ 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
+ + ", lastTextChangeStart = " + lastTextChangeStart
+ + ", content.getClass() = " + content.getClass()
+ );
+ topIndex = 0;
+ }
+ topIndexY = 0;
+ super.redraw();
+ } else {
+ int lastLine = firstLine + lastTextChangeNewLineCount;
+ int firstLineTop = getLinePixel(firstLine);
+ int newLastLineBottom = getLinePixel(lastLine + 1);
+ if (lastLineBottom != newLastLineBottom) {
+ super.redraw();
+ } else {
+ super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
+ redrawLinesBullet(renderer.redrawLines);
+ }
+ }
+ renderer.redrawLines = null;
+ // update selection/caret location after styles have been changed.
+ // otherwise any text measuring could be incorrect
+ //
+ // also, this needs to be done after all scrolling. Otherwise,
+ // selection redraw would be flushed during scroll which is wrong.
+ // in some cases new text would be drawn in scroll source area even
+ // though the intent is to scroll it.
+ if (!(blockSelection && blockXLocation != -1)) {
+ updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
+ }
+ if (lastTextChangeReplaceLineCount > 0 || wordWrap || visualWrap) {
+ claimBottomFreeSpace();
+ }
+ if (lastTextChangeReplaceCharCount > 0) {
+ claimRightFreeSpace();
+ }
+
+ sendAccessibleTextChanged(lastTextChangeStart, lastTextChangeNewCharCount, 0);
+ lastCharCount += lastTextChangeNewCharCount;
+ lastCharCount -= lastTextChangeReplaceCharCount;
+ setAlignment();
+}
+/**
+ * Updates the screen to reflect a pending content change.
+ *
+ * @param event .start the start offset of the change
+ * @param event .newText text that is going to be inserted or empty String
+ * if no text will be inserted
+ * @param event .replaceCharCount length of text that is going to be replaced
+ * @param event .newCharCount length of text that is going to be inserted
+ * @param event .replaceLineCount number of lines that are going to be replaced
+ * @param event .newLineCount number of new lines that are going to be inserted
+ */
+void handleTextChanging(TextChangingEvent event) {
+ if (event.replaceCharCount < 0) {
+ event.start += event.replaceCharCount;
+ event.replaceCharCount *= -1;
+ }
+ lastTextChangeStart = event.start;
+ lastTextChangeNewLineCount = event.newLineCount;
+ lastTextChangeNewCharCount = event.newCharCount;
+ lastTextChangeReplaceLineCount = event.replaceLineCount;
+ lastTextChangeReplaceCharCount = event.replaceCharCount;
+ int lineIndex = content.getLineAtOffset(event.start);
+ int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
+ int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
+ lastLineBottom = destY;
+ if (srcY < 0 && destY < 0) {
+ lastLineBottom += srcY - destY;
+ verticalScrollOffset += destY - srcY;
+ calculateTopIndex(destY - srcY);
+ setScrollBars(true);
+ } else {
+ scrollText(srcY, destY);
+ }
+ sendAccessibleTextChanged(lastTextChangeStart, 0, lastTextChangeReplaceCharCount);
+ renderer.textChanging(event);
+
+ // Update the caret offset if it is greater than the length of the content.
+ // This is necessary since style range API may be called between the
+ // handleTextChanging and handleTextChanged events and this API sets the
+ // caretOffset.
+ int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
+ if (caretOffset > newEndOfText) setCaretOffset(newEndOfText, SWT.DEFAULT);
+}
+/**
+ * Called when the widget content is set programmatically, overwriting
+ * the old content. Resets the caret position, selection and scroll offsets.
+ * Recalculates the content width and scroll bars. Redraws the widget.
+ *
+ * @param event text change event.
+ */
+void handleTextSet(TextChangedEvent event) {
+ reset();
+ int newCharCount = getCharCount();
+ sendAccessibleTextChanged(0, newCharCount, lastCharCount);
+ lastCharCount = newCharCount;
+ setAlignment();
+}
+/**
+ * Called when a traversal key is pressed.
+ * Allow tab next traversal to occur when the widget is in single
+ * line mode or in multi line and non-editable mode .
+ * When in editable multi line mode we want to prevent the tab
+ * traversal and receive the tab key event instead.
+ *
+ * @param event the event
+ */
+void handleTraverse(Event event) {
+ switch (event.detail) {
+ case SWT.TRAVERSE_ESCAPE:
+ case SWT.TRAVERSE_PAGE_NEXT:
+ case SWT.TRAVERSE_PAGE_PREVIOUS:
+ event.doit = true;
+ break;
+ case SWT.TRAVERSE_RETURN:
+ case SWT.TRAVERSE_TAB_NEXT:
+ case SWT.TRAVERSE_TAB_PREVIOUS:
+ if ((getStyle() & SWT.SINGLE) != 0) {
+ event.doit = true;
+ } else {
+ if (!editable || (event.stateMask & SWT.MODIFIER_MASK) != 0) {
+ event.doit = true;
+ }
+ }
+ break;
+ }
+}
+/**
+ * Scrolls the widget vertically.
+ */
+void handleVerticalScroll(Event event) {
+ int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
+ scrollVertical(scrollPixel, false);
+}
+/**
+ * Add accessibility support for the widget.
+ */
+void initializeAccessible() {
+ acc = getAccessible();
+
+ accAdapter = new AccessibleAdapter() {
+ @Override
+ public void getName (AccessibleEvent e) {
+ String name = null;
+ String text = getAssociatedLabel ();
+ if (text != null) {
+ name = stripMnemonic (text);
+ }
+ e.result = name;
+ }
+ @Override
+ public void getHelp(AccessibleEvent e) {
+ e.result = getToolTipText();
+ }
+ @Override
+ public void getKeyboardShortcut(AccessibleEvent e) {
+ String shortcut = null;
+ String text = getAssociatedLabel ();
+ if (text != null) {
+ char mnemonic = _findMnemonic (text);
+ if (mnemonic != '\0') {
+ shortcut = "Alt+"+mnemonic; //$NON-NLS-1$
+ }
+ }
+ e.result = shortcut;
+ }
+ };
+ acc.addAccessibleListener(accAdapter);
+
+ accTextExtendedAdapter = new AccessibleTextExtendedAdapter() {
+ @Override
+ public void getCaretOffset(AccessibleTextEvent e) {
+ e.offset = StyledText.this.getCaretOffset();
+ }
+ @Override
+ public void setCaretOffset(AccessibleTextEvent e) {
+ StyledText.this.setCaretOffset(e.offset);
+ e.result = ACC.OK;
+ }
+ @Override
+ public void getSelectionRange(AccessibleTextEvent e) {
+ Point selection = StyledText.this.getSelectionRange();
+ e.offset = selection.x;
+ e.length = selection.y;
+ }
+ @Override
+ public void addSelection(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ Point point = st.getSelection();
+ if (point.x == point.y) {
+ int end = e.end;
+ if (end == -1) end = st.getCharCount();
+ st.setSelection(e.start, end);
+ e.result = ACC.OK;
+ }
+ }
+ @Override
+ public void getSelection(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ if (st.blockSelection && st.blockXLocation != -1) {
+ Rectangle rect = st.getBlockSelectionPosition();
+ int lineIndex = rect.y + e.index;
+ int linePixel = st.getLinePixel(lineIndex);
+ e.ranges = getRanges(rect.x, linePixel, rect.width, linePixel);
+ if (e.ranges.length > 0) {
+ e.start = e.ranges[0];
+ e.end = e.ranges[e.ranges.length - 1];
+ }
+ } else {
+ if (e.index == 0) {
+ Point point = st.getSelection();
+ e.start = point.x;
+ e.end = point.y;
+ if (e.start > e.end) {
+ int temp = e.start;
+ e.start = e.end;
+ e.end = temp;
+ }
+ }
+ }
+ }
+ @Override
+ public void getSelectionCount(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ if (st.blockSelection && st.blockXLocation != -1) {
+ Rectangle rect = st.getBlockSelectionPosition();
+ e.count = rect.height - rect.y + 1;
+ } else {
+ Point point = st.getSelection();
+ e.count = point.x == point.y ? 0 : 1;
+ }
+ }
+ @Override
+ public void removeSelection(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ if (e.index == 0) {
+ if (st.blockSelection) {
+ st.clearBlockSelection(true, false);
+ } else {
+ st.clearSelection(false);
+ }
+ e.result = ACC.OK;
+ }
+ }
+ @Override
+ public void setSelection(AccessibleTextEvent e) {
+ if (e.index != 0) return;
+ StyledText st = StyledText.this;
+ Point point = st.getSelection();
+ if (point.x == point.y) return;
+ int end = e.end;
+ if (end == -1) end = st.getCharCount();
+ st.setSelection(e.start, end);
+ e.result = ACC.OK;
+ }
+ @Override
+ public void getCharacterCount(AccessibleTextEvent e) {
+ e.count = StyledText.this.getCharCount();
+ }
+ @Override
+ public void getOffsetAtPoint(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ Point point = new Point (e.x, e.y);
+ Display display = st.getDisplay();
+ point = display.map(null, st, point);
+ e.offset = st.getOffsetAtPoint(point.x, point.y, null, true);
+ }
+ @Override
+ public void getTextBounds(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ int start = e.start;
+ int end = e.end;
+ int contentLength = st.getCharCount();
+ start = Math.max(0, Math.min(start, contentLength));
+ end = Math.max(0, Math.min(end, contentLength));
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ int startLine = st.getLineAtOffset(start);
+ int endLine = st.getLineAtOffset(end);
+ Rectangle[] rects = new Rectangle[endLine - startLine + 1];
+ Rectangle bounds = null;
+ int index = 0;
+ Display display = st.getDisplay();
+ for (int lineIndex = startLine; lineIndex <= endLine; lineIndex++) {
+ Rectangle rect = new Rectangle(0, 0, 0, 0);
+ rect.y = st.getLinePixel(lineIndex);
+ rect.height = st.renderer.getLineHeight(lineIndex);
+ if (lineIndex == startLine) {
+ rect.x = st.getPointAtOffset(start).x;
+ } else {
+ rect.x = st.leftMargin - st.horizontalScrollOffset;
+ }
+ if (lineIndex == endLine) {
+ rect.width = st.getPointAtOffset(end).x - rect.x;
+ } else {
+ TextLayout layout = st.renderer.getTextLayout(lineIndex);
+ rect.width = layout.getBounds().width - rect.x;
+ st.renderer.disposeTextLayout(layout);
+ }
+ rects [index++] = rect = display.map(st, null, rect);
+ if (bounds == null) {
+ bounds = new Rectangle(rect.x, rect.y, rect.width, rect.height);
+ } else {
+ bounds.add(rect);
+ }
+ }
+ e.rectangles = rects;
+ if (bounds != null) {
+ e.x = bounds.x;
+ e.y = bounds.y;
+ e.width = bounds.width;
+ e.height = bounds.height;
+ }
+ }
+ int[] getRanges(int left, int top, int right, int bottom) {
+ StyledText st = StyledText.this;
+ int lineStart = st.getLineIndex(top);
+ int lineEnd = st.getLineIndex(bottom);
+ int count = lineEnd - lineStart + 1;
+ int[] ranges = new int [count * 2];
+ int index = 0;
+ for (int lineIndex = lineStart; lineIndex <= lineEnd; lineIndex++) {
+ String line = st.content.getLine(lineIndex);
+ int lineOffset = st.content.getOffsetAtLine(lineIndex);
+ int lineEndOffset = lineOffset + line.length();
+ int linePixel = st.getLinePixel(lineIndex);
+ int start = st.getOffsetAtPoint(left, linePixel, null, true);
+ if (start == -1) {
+ start = left < st.leftMargin ? lineOffset : lineEndOffset;
+ }
+ int[] trailing = new int[1];
+ int end = st.getOffsetAtPoint(right, linePixel, trailing, true);
+ if (end == -1) {
+ end = right < st.leftMargin ? lineOffset : lineEndOffset;
+ } else {
+ end += trailing[0];
+ }
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ ranges[index++] = start;
+ ranges[index++] = end;
+ }
+ return ranges;
+ }
+ @Override
+ public void getRanges(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ Point point = new Point (e.x, e.y);
+ Display display = st.getDisplay();
+ point = display.map(null, st, point);
+ e.ranges = getRanges(point.x, point.y, point.x + e.width, point.y + e.height);
+ if (e.ranges.length > 0) {
+ e.start = e.ranges[0];
+ e.end = e.ranges[e.ranges.length - 1];
+ }
+ }
+ @Override
+ public void getText(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ int start = e.start;
+ int end = e.end;
+ int contentLength = st.getCharCount();
+ if (end == -1) end = contentLength;
+ start = Math.max(0, Math.min(start, contentLength));
+ end = Math.max(0, Math.min(end, contentLength));
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ int count = e.count;
+ switch (e.type) {
+ case ACC.TEXT_BOUNDARY_ALL:
+ //nothing to do
+ break;
+ case ACC.TEXT_BOUNDARY_CHAR: {
+ int newCount = 0;
+ if (count > 0) {
+ while (count-- > 0) {
+ int newEnd = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
+ if (newEnd == contentLength) break;
+ if (newEnd == end) break;
+ end = newEnd;
+ newCount++;
+ }
+ start = end;
+ end = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
+ } else {
+ while (count++ < 0) {
+ int newStart = st.getWordPrevious(start, SWT.MOVEMENT_CLUSTER);
+ if (newStart == start) break;
+ start = newStart;
+ newCount--;
+ }
+ end = st.getWordNext(start, SWT.MOVEMENT_CLUSTER);
+ }
+ count = newCount;
+ break;
+ }
+ case ACC.TEXT_BOUNDARY_WORD: {
+ int newCount = 0;
+ if (count > 0) {
+ while (count-- > 0) {
+ int newEnd = st.getWordNext(end, SWT.MOVEMENT_WORD_START, true);
+ if (newEnd == end) break;
+ newCount++;
+ end = newEnd;
+ }
+ start = end;
+ end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
+ } else {
+ if (st.getWordPrevious(Math.min(start + 1, contentLength), SWT.MOVEMENT_WORD_START, true) == start) {
+ //start is a word start already
+ count++;
+ }
+ while (count <= 0) {
+ int newStart = st.getWordPrevious(start, SWT.MOVEMENT_WORD_START, true);
+ if (newStart == start) break;
+ count++;
+ start = newStart;
+ if (count != 0) newCount--;
+ }
+ if (count <= 0 && start == 0) {
+ end = start;
+ } else {
+ end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
+ }
+ }
+ count = newCount;
+ break;
+ }
+ case ACC.TEXT_BOUNDARY_LINE:
+ //TODO implement line
+ case ACC.TEXT_BOUNDARY_PARAGRAPH:
+ case ACC.TEXT_BOUNDARY_SENTENCE: {
+ int offset = count > 0 ? end : start;
+ int lineIndex = st.getLineAtOffset(offset) + count;
+ lineIndex = Math.max(0, Math.min(lineIndex, st.getLineCount() - 1));
+ start = st.getOffsetAtLine(lineIndex);
+ String line = st.getLine(lineIndex);
+ end = start + line.length();
+ count = lineIndex - st.getLineAtOffset(offset);
+ break;
+ }
+ }
+ e.start = start;
+ e.end = end;
+ e.count = count;
+ e.result = st.content.getTextRange(start, end - start);
+ }
+ @Override
+ public void getVisibleRanges(AccessibleTextEvent e) {
+ e.ranges = getRanges(leftMargin, topMargin, clientAreaWidth - rightMargin, clientAreaHeight - bottomMargin);
+ if (e.ranges.length > 0) {
+ e.start = e.ranges[0];
+ e.end = e.ranges[e.ranges.length - 1];
+ }
+ }
+ @Override
+ public void scrollText(AccessibleTextEvent e) {
+ StyledText st = StyledText.this;
+ int topPixel = getTopPixel(), horizontalPixel = st.getHorizontalPixel();
+ switch (e.type) {
+ case ACC.SCROLL_TYPE_ANYWHERE:
+ case ACC.SCROLL_TYPE_TOP_LEFT:
+ case ACC.SCROLL_TYPE_LEFT_EDGE:
+ case ACC.SCROLL_TYPE_TOP_EDGE: {
+ Rectangle rect = st.getBoundsAtOffset(e.start);
+ if (e.type != ACC.SCROLL_TYPE_TOP_EDGE) {
+ horizontalPixel = horizontalPixel + rect.x - st.leftMargin;
+ }
+ if (e.type != ACC.SCROLL_TYPE_LEFT_EDGE) {
+ topPixel = topPixel + rect.y - st.topMargin;
+ }
+ break;
+ }
+ case ACC.SCROLL_TYPE_BOTTOM_RIGHT:
+ case ACC.SCROLL_TYPE_BOTTOM_EDGE:
+ case ACC.SCROLL_TYPE_RIGHT_EDGE: {
+ Rectangle rect = st.getBoundsAtOffset(e.end - 1);
+ if (e.type != ACC.SCROLL_TYPE_BOTTOM_EDGE) {
+ horizontalPixel = horizontalPixel - st.clientAreaWidth + rect.x + rect.width + st.rightMargin;
+ }
+ if (e.type != ACC.SCROLL_TYPE_RIGHT_EDGE) {
+ topPixel = topPixel - st.clientAreaHeight + rect.y +rect.height + st.bottomMargin;
+ }
+ break;
+ }
+ case ACC.SCROLL_TYPE_POINT: {
+ Point point = new Point(e.x, e.y);
+ Display display = st.getDisplay();
+ point = display.map(null, st, point);
+ Rectangle rect = st.getBoundsAtOffset(e.start);
+ topPixel = topPixel - point.y + rect.y;
+ horizontalPixel = horizontalPixel - point.x + rect.x;
+ break;
+ }
+ }
+ st.setTopPixel(topPixel);
+ st.setHorizontalPixel(horizontalPixel);
+ e.result = ACC.OK;
+ }
+ };
+ acc.addAccessibleTextListener(accTextExtendedAdapter);
+
+ accEditableTextListener = new AccessibleEditableTextListener() {
+ @Override
+ public void setTextAttributes(AccessibleTextAttributeEvent e) {
+ // This method must be implemented by the application
+ e.result = ACC.OK;
+ }
+ @Override
+ public void replaceText(AccessibleEditableTextEvent e) {
+ StyledText st = StyledText.this;
+ st.replaceTextRange(e.start, e.end - e.start, e.string);
+ e.result = ACC.OK;
+ }
+ @Override
+ public void pasteText(AccessibleEditableTextEvent e) {
+ StyledText st = StyledText.this;
+ st.setSelection(e.start);
+ st.paste();
+ e.result = ACC.OK;
+ }
+ @Override
+ public void cutText(AccessibleEditableTextEvent e) {
+ StyledText st = StyledText.this;
+ st.setSelection(e.start, e.end);
+ st.cut();
+ e.result = ACC.OK;
+ }
+ @Override
+ public void copyText(AccessibleEditableTextEvent e) {
+ StyledText st = StyledText.this;
+ st.setSelection(e.start, e.end);
+ st.copy();
+ e.result = ACC.OK;
+ }
+ };
+ acc.addAccessibleEditableTextListener(accEditableTextListener);
+
+ accAttributeAdapter = new AccessibleAttributeAdapter() {
+ @Override
+ public void getAttributes(AccessibleAttributeEvent e) {
+ StyledText st = StyledText.this;
+ e.leftMargin = st.getLeftMargin();
+ e.topMargin = st.getTopMargin();
+ e.rightMargin = st.getRightMargin();
+ e.bottomMargin = st.getBottomMargin();
+ e.tabStops = st.getTabStops();
+ e.justify = st.getJustify();
+ e.alignment = st.getAlignment();
+ e.indent = st.getIndent();
+ }
+ @Override
+ public void getTextAttributes(AccessibleTextAttributeEvent e) {
+ StyledText st = StyledText.this;
+ int contentLength = st.getCharCount();
+ if (!isListening(ST.LineGetStyle) && st.renderer.styleCount == 0) {
+ e.start = 0;
+ e.end = contentLength;
+ e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
+ return;
+ }
+ int offset = Math.max(0, Math.min(e.offset, contentLength - 1));
+ int lineIndex = st.getLineAtOffset(offset);
+ int lineOffset = st.getOffsetAtLine(lineIndex);
+ int lineCount = st.getLineCount();
+ offset = offset - lineOffset;
+
+ TextLayout layout = st.renderer.getTextLayout(lineIndex);
+ int lineLength = layout.getText().length();
+ if (lineLength > 0) {
+ e.textStyle = layout.getStyle(Math.max(0, Math.min(offset, lineLength - 1)));
+ }
+
+ // If no override info available, use defaults. Don't supply default colors, though.
+ if (e.textStyle == null) {
+ e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
+ } else {
+ if (e.textStyle.foreground == null || e.textStyle.background == null || e.textStyle.font == null) {
+ TextStyle textStyle = new TextStyle(e.textStyle);
+ if (textStyle.foreground == null) textStyle.foreground = st.foreground;
+ if (textStyle.background == null) textStyle.background = st.background;
+ if (textStyle.font == null) textStyle.font = st.getFont();
+ e.textStyle = textStyle;
+ }
+ }
+
+ //offset at line delimiter case
+ if (offset >= lineLength) {
+ e.start = lineOffset + lineLength;
+ if (lineIndex + 1 < lineCount) {
+ e.end = st.getOffsetAtLine(lineIndex + 1);
+ } else {
+ e.end = contentLength;
+ }
+ return;
+ }
+
+ int[] ranges = layout.getRanges();
+ st.renderer.disposeTextLayout(layout);
+ int index = 0;
+ int end = 0;
+ while (index < ranges.length) {
+ int styleStart = ranges[index++];
+ int styleEnd = ranges[index++];
+ if (styleStart <= offset && offset <= styleEnd) {
+ e.start = lineOffset + styleStart;
+ e.end = lineOffset + styleEnd + 1;
+ return;
+ }
+ if (styleStart > offset) {
+ e.start = lineOffset + end;
+ e.end = lineOffset + styleStart;
+ return;
+ }
+ end = styleEnd + 1;
+ }
+ if (index == ranges.length) {
+ e.start = lineOffset + end;
+ if (lineIndex + 1 < lineCount) {
+ e.end = st.getOffsetAtLine(lineIndex + 1);
+ } else {
+ e.end = contentLength;
+ }
+ }
+ }
+ };
+ acc.addAccessibleAttributeListener(accAttributeAdapter);
+
+ accControlAdapter = new AccessibleControlAdapter() {
+ @Override
+ public void getRole(AccessibleControlEvent e) {
+ e.detail = ACC.ROLE_TEXT;
+ }
+ @Override
+ public void getState(AccessibleControlEvent e) {
+ int state = 0;
+ if (isEnabled()) state |= ACC.STATE_FOCUSABLE;
+ if (isFocusControl()) state |= ACC.STATE_FOCUSED;
+ if (!isVisible()) state |= ACC.STATE_INVISIBLE;
+ if (!getEditable()) state |= ACC.STATE_READONLY;
+ if (isSingleLine()) state |= ACC.STATE_SINGLELINE;
+ else state |= ACC.STATE_MULTILINE;
+ e.detail = state;
+ }
+ @Override
+ public void getValue(AccessibleControlEvent e) {
+ e.result = StyledText.this.getText();
+ }
+ };
+ acc.addAccessibleControlListener(accControlAdapter);
+
+ addListener(SWT.FocusIn, event -> acc.setFocus(ACC.CHILDID_SELF));
+}
+
+@Override
+public void dispose() {
+ /*
+ * Note: It is valid to attempt to dispose a widget more than once.
+ * Added check for this.
+ */
+ if (!isDisposed()) {
+ acc.removeAccessibleControlListener(accControlAdapter);
+ acc.removeAccessibleAttributeListener(accAttributeAdapter);
+ acc.removeAccessibleEditableTextListener(accEditableTextListener);
+ acc.removeAccessibleTextListener(accTextExtendedAdapter);
+ acc.removeAccessibleListener(accAdapter);
+ }
+ super.dispose();
+}
+
+/*
+ * Return the Label immediately preceding the receiver in the z-order,
+ * or null if none.
+ */
+String getAssociatedLabel () {
+ Control[] siblings = getParent ().getChildren ();
+ for (int i = 0; i < siblings.length; i++) {
+ if (siblings [i] == StyledText.this) {
+ if (i > 0) {
+ Control sibling = siblings [i-1];
+ if (sibling instanceof Label) return ((Label) sibling).getText();
+ if (sibling instanceof CLabel) return ((CLabel) sibling).getText();
+ }
+ break;
+ }
+ }
+ return null;
+}
+String stripMnemonic (String string) {
+ int index = 0;
+ int length = string.length ();
+ do {
+ while ((index < length) && (string.charAt (index) != '&')) index++;
+ if (++index >= length) return string;
+ if (string.charAt (index) != '&') {
+ return string.substring(0, index-1) + string.substring(index, length);
+ }
+ index++;
+ } while (index < length);
+ return string;
+}
+/*
+ * Return the lowercase of the first non-'&' character following
+ * an '&' character in the given string. If there are no '&'
+ * characters in the given string, return '\0'.
+ */
+char _findMnemonic (String string) {
+ if (string == null) return '\0';
+ int index = 0;
+ int length = string.length ();
+ do {
+ while (index < length && string.charAt (index) != '&') index++;
+ if (++index >= length) return '\0';
+ if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index));
+ index++;
+ } while (index < length);
+ return '\0';
+}
+/**
+ * Executes the action.
+ *
+ * @param action one of the actions defined in ST.java
+ */
+public void invokeAction(int action) {
+ checkWidget();
+ if (blockSelection && invokeBlockAction(action)) return;
+ updateCaretDirection = true;
+ switch (action) {
+ // Navigation
+ case ST.LINE_UP:
+ doLineUp(false);
+ clearSelection(true);
+ break;
+ case ST.LINE_DOWN:
+ doLineDown(false);
+ clearSelection(true);
+ break;
+ case ST.LINE_START:
+ doLineStart();
+ clearSelection(true);
+ break;
+ case ST.LINE_END:
+ doLineEnd();
+ clearSelection(true);
+ break;
+ case ST.COLUMN_PREVIOUS:
+ doCursorPrevious();
+ clearSelection(true);
+ break;
+ case ST.COLUMN_NEXT:
+ doCursorNext();
+ clearSelection(true);
+ break;
+ case ST.PAGE_UP:
+ doPageUp(false, -1);
+ clearSelection(true);
+ break;
+ case ST.PAGE_DOWN:
+ doPageDown(false, -1);
+ clearSelection(true);
+ break;
+ case ST.WORD_PREVIOUS:
+ doWordPrevious();
+ clearSelection(true);
+ break;
+ case ST.WORD_NEXT:
+ doWordNext();
+ clearSelection(true);
+ break;
+ case ST.TEXT_START:
+ doContentStart();
+ clearSelection(true);
+ break;
+ case ST.TEXT_END:
+ doContentEnd();
+ clearSelection(true);
+ break;
+ case ST.WINDOW_START:
+ doPageStart();
+ clearSelection(true);
+ break;
+ case ST.WINDOW_END:
+ doPageEnd();
+ clearSelection(true);
+ break;
+ // Selection
+ case ST.SELECT_LINE_UP:
+ doSelectionLineUp();
+ break;
+ case ST.SELECT_ALL:
+ selectAll();
+ break;
+ case ST.SELECT_LINE_DOWN:
+ doSelectionLineDown();
+ break;
+ case ST.SELECT_LINE_START:
+ doLineStart();
+ doSelection(ST.COLUMN_PREVIOUS);
+ break;
+ case ST.SELECT_LINE_END:
+ doLineEnd();
+ doSelection(ST.COLUMN_NEXT);
+ break;
+ case ST.SELECT_COLUMN_PREVIOUS:
+ doSelectionCursorPrevious();
+ doSelection(ST.COLUMN_PREVIOUS);
+ break;
+ case ST.SELECT_COLUMN_NEXT:
+ doSelectionCursorNext();
+ doSelection(ST.COLUMN_NEXT);
+ break;
+ case ST.SELECT_PAGE_UP:
+ doSelectionPageUp(-1);
+ break;
+ case ST.SELECT_PAGE_DOWN:
+ doSelectionPageDown(-1);
+ break;
+ case ST.SELECT_WORD_PREVIOUS:
+ doSelectionWordPrevious();
+ doSelection(ST.COLUMN_PREVIOUS);
+ break;
+ case ST.SELECT_WORD_NEXT:
+ doSelectionWordNext();
+ doSelection(ST.COLUMN_NEXT);
+ break;
+ case ST.SELECT_TEXT_START:
+ doContentStart();
+ doSelection(ST.COLUMN_PREVIOUS);
+ break;
+ case ST.SELECT_TEXT_END:
+ doContentEnd();
+ doSelection(ST.COLUMN_NEXT);
+ break;
+ case ST.SELECT_WINDOW_START:
+ doPageStart();
+ doSelection(ST.COLUMN_PREVIOUS);
+ break;
+ case ST.SELECT_WINDOW_END:
+ doPageEnd();
+ doSelection(ST.COLUMN_NEXT);
+ break;
+ // Modification
+ case ST.CUT:
+ cut();
+ break;
+ case ST.COPY:
+ copy();
+ break;
+ case ST.PASTE:
+ paste();
+ break;
+ case ST.DELETE_PREVIOUS:
+ doBackspace();
+ break;
+ case ST.DELETE_NEXT:
+ doDelete();
+ break;
+ case ST.DELETE_WORD_PREVIOUS:
+ doDeleteWordPrevious();
+ break;
+ case ST.DELETE_WORD_NEXT:
+ doDeleteWordNext();
+ break;
+ // Miscellaneous
+ case ST.TOGGLE_OVERWRITE:
+ overwrite = !overwrite; // toggle insert/overwrite mode
+ break;
+ case ST.TOGGLE_BLOCKSELECTION:
+ setBlockSelection(!blockSelection);
+ break;
+ }
+}
+/**
+* Returns true if an action should not be performed when block
+* selection in active
+*/
+boolean invokeBlockAction(int action) {
+ switch (action) {
+ // Navigation
+ case ST.LINE_UP:
+ case ST.LINE_DOWN:
+ case ST.LINE_START:
+ case ST.LINE_END:
+ case ST.COLUMN_PREVIOUS:
+ case ST.COLUMN_NEXT:
+ case ST.PAGE_UP:
+ case ST.PAGE_DOWN:
+ case ST.WORD_PREVIOUS:
+ case ST.WORD_NEXT:
+ case ST.TEXT_START:
+ case ST.TEXT_END:
+ case ST.WINDOW_START:
+ case ST.WINDOW_END:
+ clearBlockSelection(false, false);
+ return false;
+ // Selection
+ case ST.SELECT_LINE_UP:
+ doBlockLineVertical(true);
+ return true;
+ case ST.SELECT_LINE_DOWN:
+ doBlockLineVertical(false);
+ return true;
+ case ST.SELECT_LINE_START:
+ doBlockLineHorizontal(false);
+ return true;
+ case ST.SELECT_LINE_END:
+ doBlockLineHorizontal(true);
+ return false;
+ case ST.SELECT_COLUMN_PREVIOUS:
+ doBlockColumn(false);
+ return true;
+ case ST.SELECT_COLUMN_NEXT:
+ doBlockColumn(true);
+ return true;
+ case ST.SELECT_WORD_PREVIOUS:
+ doBlockWord(false);
+ return true;
+ case ST.SELECT_WORD_NEXT:
+ doBlockWord(true);
+ return true;
+ case ST.SELECT_ALL:
+ return false;
+ case ST.SELECT_TEXT_START:
+ doBlockContentStartEnd(false);
+ break;
+ case ST.SELECT_TEXT_END:
+ doBlockContentStartEnd(true);
+ break;
+ case ST.SELECT_PAGE_UP:
+ case ST.SELECT_PAGE_DOWN:
+ case ST.SELECT_WINDOW_START:
+ case ST.SELECT_WINDOW_END:
+ //blocked actions
+ return true;
+ // Modification
+ case ST.CUT:
+ case ST.COPY:
+ case ST.PASTE:
+ return false;
+ case ST.DELETE_PREVIOUS:
+ case ST.DELETE_NEXT:
+ if (blockXLocation != -1) {
+ insertBlockSelectionText((char)0, action);
+ return true;
+ }
+ return false;
+ case ST.DELETE_WORD_PREVIOUS:
+ case ST.DELETE_WORD_NEXT:
+ //blocked actions
+ return blockXLocation != -1;
+ }
+ return false;
+}
+boolean isBidiCaret() {
+ return BidiUtil.isBidiPlatform();
+}
+boolean isFixedLineHeight() {
+ return !isWordWrap() && lineSpacing == 0 && renderer.lineSpacingProvider == null && !hasStyleWithVariableHeight && !hasVerticalIndent;
+}
+/**
+ * Returns whether the given offset is inside a multi byte line delimiter.
+ * Example:
+ * "Line1\r\n" isLineDelimiter(5) == false but isLineDelimiter(6) == true
+ *
+ * @return true if the given offset is inside a multi byte line delimiter.
+ * false if the given offset is before or after a line delimiter.
+ */
+boolean isLineDelimiter(int offset) {
+ int line = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = offset - lineOffset;
+ // offsetInLine will be greater than line length if the line
+ // delimiter is longer than one character and the offset is set
+ // in between parts of the line delimiter.
+ return offsetInLine > content.getLine(line).length();
+}
+/**
+ * Returns whether the widget is mirrored (right oriented/right to left
+ * writing order).
+ *
+ * @return isMirrored true=the widget is right oriented, false=the widget
+ * is left oriented
+ */
+boolean isMirrored() {
+ return (getStyle() & SWT.MIRRORED) != 0;
+}
+/**
+ * Returns <code>true</code> if any text in the widget is selected,
+ * and <code>false</code> otherwise.
+ *
+ * @return the text selection state
+ * @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.103
+ */
+public boolean isTextSelected() {
+ checkWidget();
+ if (blockSelection && blockXLocation != -1) {
+ Rectangle rect = getBlockSelectionPosition();
+ return !rect.isEmpty();
+ }
+ return selection.y != selection.x;
+}
+/**
+ * Returns whether the widget can have only one line.
+ *
+ * @return true if widget can have only one line, false if widget can have
+ * multiple lines
+ */
+boolean isSingleLine() {
+ return (getStyle() & SWT.SINGLE) != 0;
+}
+
+/**
+ * Sends the specified verify event, replace/insert text as defined by
+ * the event and send a modify event.
+ *
+ * @param event the text change event.
+ * <ul>
+ * <li>event.start - the replace start offset</li>
+ * <li>event.end - the replace end offset</li>
+ * <li>event.text - the new text</li>
+ * </ul>
+ * @param updateCaret whether or not he caret should be set behind
+ * the new text
+ */
+void modifyContent(Event event, boolean updateCaret) {
+ event.doit = true;
+ notifyListeners(SWT.Verify, event);
+ if (event.doit) {
+ StyledTextEvent styledTextEvent = null;
+ int replacedLength = event.end - event.start;
+ if (isListening(ST.ExtendedModify)) {
+ styledTextEvent = new StyledTextEvent(content);
+ styledTextEvent.start = event.start;
+ styledTextEvent.end = event.start + event.text.length();
+ styledTextEvent.text = content.getTextRange(event.start, replacedLength);
+ }
+ if (updateCaret) {
+ //Fix advancing flag for delete/backspace key on direction boundary
+ if (event.text.length() == 0) {
+ int lineIndex = content.getLineAtOffset(event.start);
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ int levelStart = layout.getLevel(event.start - lineOffset);
+ int lineIndexEnd = content.getLineAtOffset(event.end);
+ if (lineIndex != lineIndexEnd) {
+ renderer.disposeTextLayout(layout);
+ lineOffset = content.getOffsetAtLine(lineIndexEnd);
+ layout = renderer.getTextLayout(lineIndexEnd);
+ }
+ int levelEnd = layout.getLevel(event.end - lineOffset);
+ renderer.disposeTextLayout(layout);
+ if (levelStart != levelEnd) {
+ caretAlignment = PREVIOUS_OFFSET_TRAILING;
+ } else {
+ caretAlignment = OFFSET_LEADING;
+ }
+ }
+ }
+ content.replaceTextRange(event.start, replacedLength, event.text);
+ // set the caret position prior to sending the modify event.
+ // fixes 1GBB8NJ
+ if (updateCaret && !(blockSelection && blockXLocation != -1)) {
+ // always update the caret location. fixes 1G8FODP
+ setSelection(event.start + event.text.length(), 0, true, false);
+ showCaret();
+ }
+ notifyListeners(SWT.Modify, event);
+ if (isListening(ST.ExtendedModify)) {
+ notifyListeners(ST.ExtendedModify, styledTextEvent);
+ }
+ }
+}
+void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) {
+ if (isListening(ST.PaintObject)) {
+ StyledTextEvent event = new StyledTextEvent (content) ;
+ event.gc = gc;
+ event.x = x;
+ event.y = y;
+ event.ascent = ascent;
+ event.descent = descent;
+ event.style = style;
+ event.bullet = bullet;
+ event.bulletIndex = bulletIndex;
+ notifyListeners(ST.PaintObject, event);
+ }
+}
+/**
+ * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
+ * clipboard or, if there is no selection, inserts the text at the current
+ * caret offset. If the widget has the SWT.SINGLE style and the
+ * clipboard text contains more than one line, only the first line without
+ * line delimiters is inserted in the widget.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void paste(){
+ checkWidget();
+ String text = (String) getClipboardContent(DND.CLIPBOARD);
+ if (text != null && text.length() > 0) {
+ if (blockSelection) {
+ boolean fillWithSpaces = isFixedLineHeight() && renderer.fixedPitch;
+ int offset = insertBlockSelectionText(text, fillWithSpaces);
+ setCaretOffset(offset, SWT.DEFAULT);
+ clearBlockSelection(true, true);
+ setCaretLocation();
+ return;
+ }
+ Event event = new Event();
+ event.start = selection.x;
+ event.end = selection.y;
+ String delimitedText = getModelDelimitedText(text);
+ if (textLimit > 0) {
+ int uneditedTextLength = getCharCount() - (selection.y - selection.x);
+ if ((uneditedTextLength + delimitedText.length()) > textLimit) {
+ int endIndex = textLimit - uneditedTextLength;
+ delimitedText = delimitedText.substring(0, Math.max(endIndex, 0));
+ }
+ }
+ event.text = delimitedText;
+ sendKeyEvent(event);
+ }
+}
+private void pasteOnMiddleClick(Event event) {
+ String text = (String)getClipboardContent(DND.SELECTION_CLIPBOARD);
+ if (text != null && text.length() > 0) {
+ // position cursor
+ doMouseLocationChange(event.x, event.y, false);
+ // insert text
+ Event e = new Event();
+ e.start = selection.x;
+ e.end = selection.y;
+ e.text = getModelDelimitedText(text);
+ sendKeyEvent(e);
+ }
+}
+/**
+ * Prints the widget's text to the default printer.
+ *
+ * @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 print() {
+ checkWidget();
+ Printer printer = new Printer();
+ StyledTextPrintOptions options = new StyledTextPrintOptions();
+ options.printTextForeground = true;
+ options.printTextBackground = true;
+ options.printTextFontStyle = true;
+ options.printLineBackground = true;
+ new Printing(this, printer, options).run();
+ printer.dispose();
+}
+/**
+ * Returns a runnable that will print the widget's text
+ * to the specified printer.
+ * <p>
+ * The runnable may be run in a non-UI thread.
+ * </p>
+ *
+ * @param printer the printer to print to
+ *
+ * @return a <code>Runnable</code> for printing the receiver's text
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when printer is null</li>
+ * </ul>
+ */
+public Runnable print(Printer printer) {
+ checkWidget();
+ if (printer == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ StyledTextPrintOptions options = new StyledTextPrintOptions();
+ options.printTextForeground = true;
+ options.printTextBackground = true;
+ options.printTextFontStyle = true;
+ options.printLineBackground = true;
+ return print(printer, options);
+}
+/**
+ * Returns a runnable that will print the widget's text
+ * to the specified printer.
+ * <p>
+ * The runnable may be run in a non-UI thread.
+ * </p>
+ *
+ * @param printer the printer to print to
+ * @param options print options to use during printing
+ *
+ * @return a <code>Runnable</code> for printing the receiver's text
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
+ * </ul>
+ * @since 2.1
+ */
+public Runnable print(Printer printer, StyledTextPrintOptions options) {
+ checkWidget();
+ if (printer == null || options == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ return new Printing(this, printer, options);
+}
+/**
+ * Causes the entire bounds of the receiver to be marked
+ * as needing to be redrawn. The next time a paint request
+ * is processed, the control will be completely painted.
+ * <p>
+ * Recalculates the content width for all lines in the bounds.
+ * When a <code>LineStyleListener</code> is used a redraw call
+ * is the only notification to the widget that styles have changed
+ * and that the content width may have changed.
+ * </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>
+ *
+ * @see Control#update()
+ */
+@Override
+public void redraw() {
+ super.redraw();
+ int itemCount = getPartialBottomIndex() - topIndex + 1;
+ renderer.reset(topIndex, itemCount);
+ renderer.calculate(topIndex, itemCount);
+ setScrollBars(false);
+ doMouseLinkCursor();
+}
+/**
+ * Causes the rectangular area of the receiver specified by
+ * the arguments to be marked as needing to be redrawn.
+ * The next time a paint request is processed, that area of
+ * the receiver will be painted. If the <code>all</code> flag
+ * is <code>true</code>, any children of the receiver which
+ * intersect with the specified area will also paint their
+ * intersecting areas. If the <code>all</code> flag is
+ * <code>false</code>, the children will not be painted.
+ * <p>
+ * Marks the content width of all lines in the specified rectangle
+ * as unknown. Recalculates the content width of all visible lines.
+ * When a <code>LineStyleListener</code> is used a redraw call
+ * is the only notification to the widget that styles have changed
+ * and that the content width may have changed.
+ * </p>
+ *
+ * @param x the x coordinate of the area to draw
+ * @param y the y coordinate of the area to draw
+ * @param width the width of the area to draw
+ * @param height the height of the area to draw
+ * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see Control#update()
+ */
+@Override
+public void redraw(int x, int y, int width, int height, boolean all) {
+ super.redraw(x, y, width, height, all);
+ if (height > 0) {
+ int firstLine = getLineIndex(y);
+ int lastLine = getLineIndex(y + height);
+ resetCache(firstLine, lastLine - firstLine + 1);
+ doMouseLinkCursor();
+ }
+}
+void redrawLines(int startLine, int lineCount, boolean bottomChanged) {
+ // do nothing if redraw range is completely invisible
+ int endLine = startLine + lineCount - 1;
+ int partialBottomIndex = getPartialBottomIndex();
+ int partialTopIndex = getPartialTopIndex();
+ if (startLine > partialBottomIndex || endLine < partialTopIndex) {
+ return;
+ }
+ // only redraw visible lines
+ if (startLine < partialTopIndex) {
+ startLine = partialTopIndex;
+ }
+ if (endLine > partialBottomIndex) {
+ endLine = partialBottomIndex;
+ }
+ int redrawTop = getLinePixel(startLine);
+ int redrawBottom = getLinePixel(endLine + 1);
+ if (bottomChanged) redrawBottom = clientAreaHeight - bottomMargin;
+ int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
+ super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
+}
+void redrawLinesBullet (int[] redrawLines) {
+ if (redrawLines == null) return;
+ int topIndex = getPartialTopIndex();
+ int bottomIndex = getPartialBottomIndex();
+ for (int i = 0; i < redrawLines.length; i++) {
+ int lineIndex = redrawLines[i];
+ if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue;
+ int width = -1;
+ Bullet bullet = renderer.getLineBullet(lineIndex, null);
+ if (bullet != null) {
+ StyleRange style = bullet.style;
+ GlyphMetrics metrics = style.metrics;
+ width = metrics.width;
+ }
+ if (width == -1) width = getClientArea().width;
+ int height = renderer.getLineHeight(lineIndex);
+ int y = getLinePixel(lineIndex);
+ super.redraw(0, y, width, height, false);
+ }
+}
+void redrawMargins(int oldHeight, int oldWidth) {
+ /* Redraw the old or new right/bottom margin if needed */
+ if (oldWidth != clientAreaWidth) {
+ if (rightMargin > 0) {
+ int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
+ super.redraw(x, 0, rightMargin, oldHeight, false);
+ }
+ }
+ if (oldHeight != clientAreaHeight) {
+ if (bottomMargin > 0) {
+ int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
+ super.redraw(0, y, oldWidth, bottomMargin, false);
+ }
+ }
+}
+/**
+ * Redraws the specified text range.
+ *
+ * @param start offset of the first character to redraw
+ * @param length number of characters to redraw
+ * @param clearBackground true if the background should be cleared as
+ * part of the redraw operation. If true, the entire redraw range will
+ * be cleared before anything is redrawn. If the redraw range includes
+ * the last character of a line (i.e., the entire line is redrawn) the
+ * line is cleared all the way to the right border of the widget.
+ * The redraw operation will be faster and smoother if clearBackground
+ * is set to false. Whether or not the flag can be set to false depends
+ * on the type of change that has taken place. If font styles or
+ * background colors for the redraw range have changed, clearBackground
+ * should be set to true. If only foreground colors have changed for
+ * the redraw range, clearBackground can be set to false.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ */
+public void redrawRange(int start, int length, boolean clearBackground) {
+ checkWidget();
+ int end = start + length;
+ int contentLength = content.getCharCount();
+ if (start > end || start < 0 || end > contentLength) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ int firstLine = content.getLineAtOffset(start);
+ int lastLine = content.getLineAtOffset(end);
+ resetCache(firstLine, lastLine - firstLine + 1);
+ internalRedrawRange(start, length);
+ doMouseLinkCursor();
+}
+/**
+ * Removes the specified bidirectional segment listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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 2.0
+ */
+public void removeBidiSegmentListener(BidiSegmentListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.LineGetSegments, listener);
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Removes the specified caret listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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 removeCaretListener(CaretListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.CaretMoved, listener);
+}
+/**
+ * Removes the specified extended modify listener.
+ *
+ * @param extendedModifyListener the listener which should no longer be notified
+ *
+ * @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 removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
+ checkWidget();
+ if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.ExtendedModify, extendedModifyListener);
+}
+/**
+ * Removes the specified line background listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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 removeLineBackgroundListener(LineBackgroundListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.LineGetBackground, listener);
+}
+/**
+ * Removes the specified line style listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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 removeLineStyleListener(LineStyleListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.LineGetStyle, listener);
+ setCaretLocation();
+}
+/**
+ * Removes the specified modify listener.
+ *
+ * @param modifyListener the listener which should no longer be notified
+ *
+ * @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 removeModifyListener(ModifyListener modifyListener) {
+ checkWidget();
+ if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(SWT.Modify, modifyListener);
+}
+/**
+ * Removes the specified listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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
+ */
+public void removePaintObjectListener(PaintObjectListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.PaintObject, listener);
+}
+/**
+ * Removes the listener from the collection of listeners who will
+ * be notified when the user changes the receiver's selection.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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 #addSelectionListener
+ */
+public void removeSelectionListener(SelectionListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(SWT.Selection, listener);
+}
+/**
+ * Removes the specified verify listener.
+ *
+ * @param verifyListener the listener which should no longer be notified
+ *
+ * @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 removeVerifyListener(VerifyListener verifyListener) {
+ checkWidget();
+ if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(SWT.Verify, verifyListener);
+}
+/**
+ * Removes the specified key verify listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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 removeVerifyKeyListener(VerifyKeyListener listener) {
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.VerifyKey, listener);
+}
+/**
+ * Removes the specified word movement listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @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 #addWordMovementListener
+ *
+ * @since 3.3
+ */
+
+public void removeWordMovementListener(MovementListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ST.WordNext, listener);
+ removeListener(ST.WordPrevious, listener);
+}
+/**
+ * Replaces the styles in the given range with new styles. This method
+ * effectively deletes the styles in the given range and then adds the
+ * the new styles.
+ * <p>
+ * Note: Because a StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
+ * can be used to share styles and reduce memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param start offset of first character where styles will be deleted
+ * @param length length of the range to delete styles in
+ * @param ranges StyleRange objects containing the new style information.
+ * The ranges should not overlap and should be within the specified start
+ * and length. The style rendering is undefined if the ranges do overlap
+ * or are ill-defined. Must not be null.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
+ * <li>ERROR_NULL_ARGUMENT when ranges is null</li>
+ * </ul>
+ *
+ * @since 2.0
+ *
+ * @see #setStyleRanges(int, int, int[], StyleRange[])
+ */
+public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ setStyleRanges(start, length, null, ranges, false);
+}
+/**
+ * Replaces the given text range with new text.
+ * If the widget has the SWT.SINGLE style and "text" contains more than
+ * one line, only the first line is rendered but the text is stored
+ * unchanged. A subsequent call to getText will return the same text
+ * that was set. Note that only a single line of text should be set when
+ * the SWT.SINGLE style is used.
+ * <p>
+ * <b>NOTE:</b> During the replace operation the current selection is
+ * changed as follows:
+ * </p>
+ * <ul>
+ * <li>selection before replaced text: selection unchanged
+ * <li>selection after replaced text: adjust the selection so that same text
+ * remains selected
+ * <li>selection intersects replaced text: selection is cleared and caret
+ * is placed after inserted text
+ * </ul>
+ *
+ * @param start offset of first character to replace
+ * @param length number of characters to replace. Use 0 to insert text
+ * @param text new text. May be empty to delete text.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
+ * <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
+ * Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li>
+ * <li>ERROR_NULL_ARGUMENT when string is null</li>
+ * </ul>
+ */
+public void replaceTextRange(int start, int length, String text) {
+ checkWidget();
+ if (text == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ int contentLength = getCharCount();
+ int end = start + length;
+ if (start > end || start < 0 || end > contentLength) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ Event event = new Event();
+ event.start = start;
+ event.end = end;
+ event.text = text;
+ modifyContent(event, false);
+}
+/**
+ * Resets the caret position, selection and scroll offsets. Recalculate
+ * the content width and scroll bars. Redraw the widget.
+ */
+void reset() {
+ ScrollBar verticalBar = getVerticalBar();
+ ScrollBar horizontalBar = getHorizontalBar();
+ setCaretOffset(0, SWT.DEFAULT);
+ topIndex = 0;
+ topIndexY = 0;
+ verticalScrollOffset = 0;
+ horizontalScrollOffset = 0;
+ resetSelection();
+ renderer.setContent(content);
+ if (verticalBar != null) {
+ verticalBar.setSelection(0);
+ }
+ if (horizontalBar != null) {
+ horizontalBar.setSelection(0);
+ }
+ resetCache(0, 0);
+ setCaretLocation();
+ super.redraw();
+}
+void resetBidiData() {
+ caretDirection = SWT.NULL;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ keyActionMap.clear();
+ createKeyBindings();
+ super.redraw();
+}
+void resetCache(SortedSet<Integer> lines) {
+ if (lines == null || lines.isEmpty()) return;
+ int maxLineIndex = renderer.maxWidthLineIndex;
+ renderer.reset(lines);
+ renderer.calculateClientArea();
+ if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
+ renderer.calculate(maxLineIndex, 1);
+ }
+ setScrollBars(true);
+ if (!isFixedLineHeight()) {
+ if (topIndex > lines.iterator().next()) {
+ verticalScrollOffset = -1;
+ }
+ renderer.calculateIdle();
+ }
+}
+void resetCache(int firstLine, int count) {
+ int maxLineIndex = renderer.maxWidthLineIndex;
+ renderer.reset(firstLine, count);
+ renderer.calculateClientArea();
+ if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
+ renderer.calculate(maxLineIndex, 1);
+ }
+ setScrollBars(true);
+ if (!isFixedLineHeight()) {
+ if (topIndex > firstLine) {
+ verticalScrollOffset = -1;
+ }
+ renderer.calculateIdle();
+ }
+}
+/**
+ * Resets the selection.
+ */
+void resetSelection() {
+ selection.x = selection.y = caretOffset;
+ selectionAnchor = -1;
+ sendAccessibleTextCaretMoved();
+}
+
+@Override
+public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) {
+ super.scroll(destX, destY, x, y, width, height, false);
+ if (all) {
+ int deltaX = destX - x, deltaY = destY - y;
+ Control[] children = getChildren();
+ for (int i=0; i<children.length; i++) {
+ Control child = children[i];
+ Rectangle rect = child.getBounds();
+ child.setLocation(rect.x + deltaX, rect.y + deltaY);
+ }
+ }
+}
+
+/**
+ * Scrolls the widget horizontally.
+ *
+ * @param pixels number of SWT logical points to scroll, > 0 = scroll left,
+ * < 0 scroll right
+ * @param adjustScrollBar
+ * true= the scroll thumb will be moved to reflect the new scroll offset.
+ * false = the scroll thumb will not be moved
+ * @return
+ * true=the widget was scrolled
+ * false=the widget was not scrolled, the given offset is not valid.
+ */
+boolean scrollHorizontal(int pixels, boolean adjustScrollBar) {
+ if (pixels == 0) return false;
+ if (wordWrap) return false;
+ ScrollBar horizontalBar = getHorizontalBar();
+ if (horizontalBar != null && adjustScrollBar) {
+ horizontalBar.setSelection(horizontalScrollOffset + pixels);
+ }
+ int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
+ if (pixels > 0) {
+ int sourceX = leftMargin + pixels;
+ int scrollWidth = clientAreaWidth - sourceX - rightMargin;
+ if (scrollWidth > 0) {
+ scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
+ }
+ if (sourceX > scrollWidth) {
+ super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
+ }
+ } else {
+ int destinationX = leftMargin - pixels;
+ int scrollWidth = clientAreaWidth - destinationX - rightMargin;
+ if (scrollWidth > 0) {
+ scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
+ }
+ if (destinationX > scrollWidth) {
+ super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
+ }
+ }
+ horizontalScrollOffset += pixels;
+ setCaretLocation();
+ return true;
+}
+/**
+ * Scrolls the widget vertically.
+ *
+ * @param pixel the new vertical scroll offset
+ * @param adjustScrollBar
+ * true= the scroll thumb will be moved to reflect the new scroll offset.
+ * false = the scroll thumb will not be moved
+ * @return
+ * true=the widget was scrolled
+ * false=the widget was not scrolled
+ */
+boolean scrollVertical(int pixels, boolean adjustScrollBar) {
+ if (pixels == 0) {
+ return false;
+ }
+ if (verticalScrollOffset != -1) {
+ ScrollBar verticalBar = getVerticalBar();
+ if (verticalBar != null && adjustScrollBar) {
+ verticalBar.setSelection(verticalScrollOffset + pixels);
+ }
+ int deltaY = 0;
+ if (pixels > 0) {
+ int sourceY = topMargin + pixels;
+ int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
+ if (scrollHeight > 0) {
+ deltaY = -pixels;
+ }
+ } else {
+ int destinationY = topMargin - pixels;
+ int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
+ if (scrollHeight > 0) {
+ deltaY = -pixels;
+ }
+ }
+ Control[] children = getChildren();
+ for (int i=0; i<children.length; i++) {
+ Control child = children[i];
+ Rectangle rect = child.getBounds();
+ child.setLocation(rect.x, rect.y + deltaY);
+ }
+ verticalScrollOffset += pixels;
+ calculateTopIndex(pixels);
+ super.redraw();
+ } else {
+ calculateTopIndex(pixels);
+ super.redraw();
+ }
+ setCaretLocation();
+ return true;
+}
+void scrollText(int srcY, int destY) {
+ if (srcY == destY) return;
+ int deltaY = destY - srcY;
+ int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
+ if (deltaY > 0) {
+ scrollHeight = clientAreaHeight - srcY - bottomMargin;
+ } else {
+ scrollHeight = clientAreaHeight - destY - bottomMargin;
+ }
+ scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
+ if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
+ super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
+ }
+ if ((0 < destY + scrollHeight) && (topMargin > destY)) {
+ super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
+ }
+ if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
+ super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
+ }
+ if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
+ super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
+ }
+}
+void sendAccessibleTextCaretMoved() {
+ if (caretOffset != accCaretOffset) {
+ accCaretOffset = caretOffset;
+ getAccessible().textCaretMoved(caretOffset);
+ }
+}
+void sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount) {
+ Accessible accessible = getAccessible();
+ if (replaceCharCount != 0) {
+ accessible.textChanged(ACC.TEXT_DELETE, start, replaceCharCount);
+ }
+ if (newCharCount != 0) {
+ accessible.textChanged(ACC.TEXT_INSERT, start, newCharCount);
+ }
+}
+/**
+ * Selects all the text.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void selectAll() {
+ checkWidget();
+ if (blockSelection) {
+ renderer.calculate(0, content.getLineCount());
+ setScrollBars(false);
+ int verticalScrollOffset = getVerticalScrollOffset();
+ int left = leftMargin - horizontalScrollOffset;
+ int top = topMargin - verticalScrollOffset;
+ int right = renderer.getWidth() - rightMargin - horizontalScrollOffset;
+ int bottom = renderer.getHeight() - bottomMargin - verticalScrollOffset;
+ setBlockSelectionLocation(left, top, right, bottom, false);
+ return;
+ }
+ setSelection(0, Math.max(getCharCount(),0));
+}
+/**
+ * Replaces/inserts text as defined by the event.
+ *
+ * @param event the text change event.
+ * <ul>
+ * <li>event.start - the replace start offset</li>
+ * <li>event.end - the replace end offset</li>
+ * <li>event.text - the new text</li>
+ * </ul>
+ */
+void sendKeyEvent(Event event) {
+ if (editable) {
+ modifyContent(event, true);
+ }
+}
+/**
+ * Returns a StyledTextEvent that can be used to request data such
+ * as styles and background color for a line.
+ * <p>
+ * The specified line may be a visual (wrapped) line if in word
+ * wrap mode. The returned object will always be for a logical
+ * (unwrapped) line.
+ * </p>
+ *
+ * @param lineOffset offset of the line. This may be the offset of
+ * a visual line if the widget is in word wrap mode.
+ * @param line line text. This may be the text of a visual line if
+ * the widget is in word wrap mode.
+ * @return StyledTextEvent that can be used to request line data
+ * for the given line.
+ */
+StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
+ StyledTextEvent event = null;
+ if (isListening(eventType)) {
+ event = new StyledTextEvent(content);
+ event.detail = lineOffset;
+ event.text = line;
+ event.alignment = alignment;
+ event.indent = indent;
+ event.wrapIndent = wrapIndent;
+ event.justify = justify;
+ notifyListeners(eventType, event);
+ }
+ return event;
+}
+/**
+ * Sends the specified selection event.
+ */
+void sendSelectionEvent() {
+ getAccessible().textSelectionChanged();
+ Event event = new Event();
+ event.x = selection.x;
+ event.y = selection.y;
+ notifyListeners(SWT.Selection, event);
+}
+int sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces) {
+ int lineWidth = 0, start, end;
+ StringBuilder buffer = new StringBuilder();
+ if (lineIndex < content.getLineCount()) {
+ int[] trailing = new int[1];
+ start = getOffsetAtPoint(left, getLinePixel(lineIndex), trailing, true);
+ if (start == -1) {
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ int lineLegth = content.getLine(lineIndex).length();
+ start = end = lineOffset + lineLegth;
+ if (fillWithSpaces) {
+ TextLayout layout = renderer.getTextLayout(lineIndex);
+ lineWidth = layout.getBounds().width;
+ renderer.disposeTextLayout(layout);
+ }
+ } else {
+ start += trailing[0];
+ end = left == right ? start : getOffsetAtPoint(right, 0, lineIndex, null);
+ fillWithSpaces = false;
+ }
+ } else {
+ start = end = content.getCharCount();
+ buffer.append(content.getLineDelimiter());
+ }
+ if (start > end) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ if (fillWithSpaces) {
+ int spacesWidth = left - lineWidth + horizontalScrollOffset - leftMargin;
+ int spacesCount = spacesWidth / renderer.averageCharWidth;
+ for (int i = 0; i < spacesCount; i++) {
+ buffer.append(' ');
+ }
+ }
+ buffer.append(text);
+ Event event = new Event();
+ event.start = start;
+ event.end = end;
+ event.text = buffer.toString();
+ sendKeyEvent(event);
+ return event.start + event.text.length();
+}
+int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) {
+ if (isListening(eventType)) {
+ StyledTextEvent event = new StyledTextEvent(content);
+ event.detail = lineOffset;
+ event.text = lineText;
+ event.count = movement;
+ event.start = offset;
+ event.end = newOffset;
+ notifyListeners(eventType, event);
+ offset = event.end;
+ if (offset != newOffset) {
+ int length = getCharCount();
+ if (offset < 0) {
+ offset = 0;
+ } else if (offset > length) {
+ offset = length;
+ } else {
+ if (isLineDelimiter(offset)) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ }
+ }
+ return offset;
+ }
+ return newOffset;
+}
+void setAlignment() {
+ if ((getStyle() & SWT.SINGLE) == 0) return;
+ int alignment = renderer.getLineAlignment(0, this.alignment);
+ int newAlignmentMargin = 0;
+ if (alignment != SWT.LEFT) {
+ renderer.calculate(0, 1);
+ int width = renderer.getWidth() - alignmentMargin;
+ newAlignmentMargin = clientAreaWidth - width;
+ if (newAlignmentMargin < 0) newAlignmentMargin = 0;
+ if (alignment == SWT.CENTER) newAlignmentMargin /= 2;
+ }
+ if (alignmentMargin != newAlignmentMargin) {
+ leftMargin -= alignmentMargin;
+ leftMargin += newAlignmentMargin;
+ alignmentMargin = newAlignmentMargin;
+ resetCache(0, 1);
+ setCaretLocation();
+ super.redraw();
+ }
+}
+/**
+ * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>,
+ * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines.
+ * <p>
+ * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
+ * in order to stabilize the right edge before setting alignment.
+ * </p>
+ *
+ * @param alignment the new 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 #setLineAlignment(int, int, int)
+ *
+ * @since 3.2
+ */
+public void setAlignment(int alignment) {
+ checkWidget();
+ alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
+ if (alignment == 0 || this.alignment == alignment) return;
+ this.alignment = alignment;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ setAlignment();
+ super.redraw();
+}
+/**
+ * Set the Always Show Scrollbars flag. True if the scrollbars are
+ * always shown even if they are not required (default value). 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.
+ *
+ * @param show true to show the scrollbars even when not required, false to show scrollbars only when required
+ *
+ * @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 void setAlwaysShowScrollBars(boolean show) {
+ checkWidget();
+ if (show == alwaysShowScroll) return;
+ alwaysShowScroll = show;
+ setScrollBars(true);
+}
+/**
+ * @see Control#setBackground(Color)
+ */
+@Override
+public void setBackground(Color color) {
+ checkWidget();
+ boolean backgroundDisabled = false;
+ if (!this.enabled && color == null) {
+ if (background != null) {
+ Color disabledBg = getDisplay().getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND);
+ if (background.equals(disabledBg)) {
+ return;
+ } else {
+ color = new Color (getDisplay(), disabledBg.getRGBA());
+ backgroundDisabled = true;
+ }
+ }
+ }
+ customBackground = color != null && !this.insideSetEnableCall && !backgroundDisabled;
+ background = color;
+ super.setBackground(color);
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets the block selection mode.
+ *
+ * @param blockSelection true=enable block selection, false=disable block selection
+ *
+ * @since 3.5
+ */
+public void setBlockSelection(boolean blockSelection) {
+ checkWidget();
+ if ((getStyle() & SWT.SINGLE) != 0) return;
+ if (blockSelection == this.blockSelection) return;
+ if (wordWrap) return;
+ this.blockSelection = blockSelection;
+ if (cursor == null) {
+ Display display = getDisplay();
+ int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
+ super.setCursor(display.getSystemCursor(type));
+ }
+ if (blockSelection) {
+ int start = selection.x;
+ int end = selection.y;
+ if (start != end) {
+ setBlockSelectionOffset(start, end, false);
+ }
+ } else {
+ clearBlockSelection(false, false);
+ }
+}
+/**
+ * Sets the block selection bounds. The bounds is
+ * relative to the upper left corner of the document.
+ *
+ * @param rect the new bounds for the block selection
+ *
+ * @see #setBlockSelectionBounds(int, int, int, int)
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when point is null</li>
+ * </ul>
+ *
+ * @since 3.5
+ */
+public void setBlockSelectionBounds(Rectangle rect) {
+ checkWidget();
+ if (rect == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ setBlockSelectionBounds(rect.x, rect.y, rect.width, rect.height);
+}
+/**
+ * Sets the block selection bounds. The bounds is
+ * relative to the upper left corner of the document.
+ *
+ * @param x the new x coordinate for the block selection
+ * @param y the new y coordinate for the block selection
+ * @param width the new width for the block selection
+ * @param height the new height for the block selection
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.5
+ */
+public void setBlockSelectionBounds(int x, int y, int width, int height) {
+ checkWidget();
+ int verticalScrollOffset = getVerticalScrollOffset();
+ if (!blockSelection) {
+ x -= horizontalScrollOffset;
+ y -= verticalScrollOffset;
+ int start = getOffsetAtPoint(x, y, null);
+ int end = getOffsetAtPoint(x+width-1, y+height-1, null);
+ setSelection(start, end - start, false, false);
+ setCaretLocation();
+ return;
+ }
+ int minY = topMargin;
+ int minX = leftMargin;
+ int maxY = renderer.getHeight() - bottomMargin;
+ int maxX = Math.max(clientAreaWidth, renderer.getWidth()) - rightMargin;
+ int anchorX = Math.max(minX, Math.min(maxX, x)) - horizontalScrollOffset;
+ int anchorY = Math.max(minY, Math.min(maxY, y)) - verticalScrollOffset;
+ int locationX = Math.max(minX, Math.min(maxX, x + width)) - horizontalScrollOffset;
+ int locationY = Math.max(minY, Math.min(maxY, y + height - 1)) - verticalScrollOffset;
+ if (isFixedLineHeight() && renderer.fixedPitch) {
+ int avg = renderer.averageCharWidth;
+ anchorX = ((anchorX - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
+ locationX = ((locationX + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
+ }
+ setBlockSelectionLocation(anchorX, anchorY, locationX, locationY, false);
+}
+void setBlockSelectionLocation (int x, int y, boolean sendEvent) {
+ int verticalScrollOffset = getVerticalScrollOffset();
+ blockXLocation = x + horizontalScrollOffset;
+ blockYLocation = y + verticalScrollOffset;
+ int[] alignment = new int[1];
+ int offset = getOffsetAtPoint(x, y, alignment);
+ setCaretOffset(offset, alignment[0]);
+ if (blockXAnchor == -1) {
+ blockXAnchor = blockXLocation;
+ blockYAnchor = blockYLocation;
+ selectionAnchor = caretOffset;
+ }
+ doBlockSelection(sendEvent);
+}
+void setBlockSelectionLocation (int anchorX, int anchorY, int x, int y, boolean sendEvent) {
+ int verticalScrollOffset = getVerticalScrollOffset();
+ blockXAnchor = anchorX + horizontalScrollOffset;
+ blockYAnchor = anchorY + verticalScrollOffset;
+ selectionAnchor = getOffsetAtPoint(anchorX, anchorY, null);
+ setBlockSelectionLocation(x, y, sendEvent);
+}
+void setBlockSelectionOffset (int offset, boolean sendEvent) {
+ Point point = getPointAtOffset(offset);
+ int verticalScrollOffset = getVerticalScrollOffset();
+ blockXLocation = point.x + horizontalScrollOffset;
+ blockYLocation = point.y + verticalScrollOffset;
+ setCaretOffset(offset, SWT.DEFAULT);
+ if (blockXAnchor == -1) {
+ blockXAnchor = blockXLocation;
+ blockYAnchor = blockYLocation;
+ selectionAnchor = caretOffset;
+ }
+ doBlockSelection(sendEvent);
+}
+void setBlockSelectionOffset (int anchorOffset, int offset, boolean sendEvent) {
+ int verticalScrollOffset = getVerticalScrollOffset();
+ Point anchorPoint = getPointAtOffset(anchorOffset);
+ blockXAnchor = anchorPoint.x + horizontalScrollOffset;
+ blockYAnchor = anchorPoint.y + verticalScrollOffset;
+ selectionAnchor = anchorOffset;
+ setBlockSelectionOffset(offset, sendEvent);
+}
+/**
+ * Sets the receiver's caret. Set the caret's height and location.
+ *
+ * @param caret the new caret for the receiver
+ *
+ * @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>
+ */
+@Override
+public void setCaret(Caret caret) {
+ checkWidget ();
+ super.setCaret(caret);
+ caretDirection = SWT.NULL;
+ if (caret != null) {
+ setCaretLocation();
+ }
+}
+/**
+ * Sets the BIDI coloring mode. When true the BIDI text display
+ * algorithm is applied to segments of text that are the same
+ * color.
+ *
+ * @param mode the new coloring mode
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @deprecated use BidiSegmentListener instead.
+ */
+@Deprecated
+public void setBidiColoring(boolean mode) {
+ checkWidget();
+ bidiColoring = mode;
+}
+/**
+ * Sets the bottom margin.
+ *
+ * @param bottomMargin the bottom margin.
+ * @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 void setBottomMargin (int bottomMargin) {
+ checkWidget();
+ setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
+}
+/**
+ * Moves the Caret to the current caret offset.
+ */
+void setCaretLocation() {
+ Point newCaretPos = getPointAtOffset(caretOffset);
+ setCaretLocation(newCaretPos, getCaretDirection());
+}
+void setCaretLocation(final Point location, int direction) {
+ Caret caret = getCaret();
+ if (caret != null) {
+ final boolean isDefaultCaret = caret == defaultCaret;
+ final StyleRange styleAtOffset = content.getCharCount() > 0 ?
+ (caretOffset < content.getCharCount() ?
+ getStyleRangeAtOffset(caretOffset) :
+ getStyleRangeAtOffset(content.getCharCount() - 1)) : // caret after last char: use last char style
+ null;
+ final int caretLine = getCaretLine();
+
+ int graphicalLineHeight = getLineHeight();
+ final int lineStartOffset = getOffsetAtLine(caretLine);
+ int graphicalLineFirstOffset = lineStartOffset;
+ final int lineEndOffset = lineStartOffset + getLine(caretLine).length();
+ int graphicalLineLastOffset = lineEndOffset;
+ if (caretLine < getLineCount() && renderer.getLineHeight(caretLine) != getLineHeight()) { // word wrap, metrics, styles...
+ graphicalLineHeight = getLineHeight(caretOffset);
+ final Rectangle characterBounds = getBoundsAtOffset(caretOffset);
+ graphicalLineFirstOffset = getOffsetAtPoint(new Point(leftMargin, characterBounds.y));
+ graphicalLineLastOffset = getOffsetAtPoint(new Point(leftMargin, characterBounds.y + graphicalLineHeight)) - 1;
+ if (graphicalLineLastOffset < graphicalLineFirstOffset) {
+ graphicalLineLastOffset = getCharCount();
+ }
+ }
+
+ int caretHeight = getLineHeight();
+ boolean isTextAlignedAtBottom = true;
+ if (graphicalLineFirstOffset >= 0) {
+ for (StyleRange style : getStyleRanges(graphicalLineFirstOffset, graphicalLineLastOffset - graphicalLineFirstOffset)) {
+ isTextAlignedAtBottom &= (
+ (style.font == null || Objects.equals(style.font, getFont())) &&
+ style.rise >= 0 &&
+ (style.metrics == null || style.metrics.descent <= 0)
+ );
+ }
+ }
+ if (!isTextAlignedAtBottom || (styleAtOffset != null && styleAtOffset.isVariableHeight())) {
+ if (isDefaultCaret) {
+ direction = SWT.DEFAULT;
+ caretHeight = graphicalLineHeight;
+ } else {
+ caretHeight = caret.getSize().y;
+ }
+ }
+ if (isTextAlignedAtBottom && caretHeight < graphicalLineHeight) {
+ location.y += (graphicalLineHeight - caretHeight);
+ }
+
+ int imageDirection = direction;
+ if (isMirrored()) {
+ if (imageDirection == SWT.LEFT) {
+ imageDirection = SWT.RIGHT;
+ } else if (imageDirection == SWT.RIGHT) {
+ imageDirection = SWT.LEFT;
+ }
+ }
+ if (isDefaultCaret && imageDirection == SWT.RIGHT) {
+ location.x -= (caret.getSize().x - 1);
+ }
+ if (isDefaultCaret) {
+ caret.setBounds(location.x, location.y, caretWidth, caretHeight);
+ } else {
+ caret.setLocation(location);
+ }
+ if (direction != caretDirection) {
+ caretDirection = direction;
+ if (isDefaultCaret) {
+ if (imageDirection == SWT.DEFAULT) {
+ defaultCaret.setImage(null);
+ } else if (imageDirection == SWT.LEFT) {
+ defaultCaret.setImage(leftCaretBitmap);
+ } else if (imageDirection == SWT.RIGHT) {
+ defaultCaret.setImage(rightCaretBitmap);
+ }
+ }
+ if (caretDirection == SWT.LEFT) {
+ BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
+ } else if (caretDirection == SWT.RIGHT) {
+ BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
+ }
+ }
+ updateCaretVisibility();
+ }
+ columnX = location.x;
+}
+/**
+ * Sets the caret offset.
+ *
+ * @param offset caret offset, relative to the first character in the text.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the offset is inside a multi byte line
+ * delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setCaretOffset(int offset) {
+ checkWidget();
+ int length = getCharCount();
+ if (length > 0 && offset != caretOffset) {
+ if (offset < 0) {
+ offset = 0;
+ } else if (offset > length) {
+ offset = length;
+ } else {
+ if (isLineDelimiter(offset)) {
+ // offset is inside a multi byte line delimiter. This is an
+ // illegal operation and an exception is thrown. Fixes 1GDKK3R
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ }
+ setCaretOffset(offset, PREVIOUS_OFFSET_TRAILING);
+ // clear the selection if the caret is moved.
+ // don't notify listeners about the selection change.
+ if (blockSelection) {
+ clearBlockSelection(true, false);
+ } else {
+ clearSelection(false);
+ }
+ }
+ setCaretLocation();
+}
+void setCaretOffset(int offset, int alignment) {
+ if (caretOffset != offset) {
+ caretOffset = offset;
+ if (isListening(ST.CaretMoved)) {
+ StyledTextEvent event = new StyledTextEvent(content);
+ event.end = caretOffset;
+ notifyListeners(ST.CaretMoved, event);
+ }
+ }
+ if (alignment != SWT.DEFAULT) {
+ caretAlignment = alignment;
+ }
+}
+/**
+ * Copies the specified text range to the clipboard. The text will be placed
+ * in the clipboard in plain text format and RTF format.
+ *
+ * @param start start index of the text
+ * @param length length of text to place in clipboard
+ *
+ * @exception SWTError
+ * @see org.eclipse.swt.dnd.Clipboard#setContents
+ */
+void setClipboardContent(int start, int length, int clipboardType) throws SWTError {
+ if (clipboardType == DND.SELECTION_CLIPBOARD && !IS_GTK) return;
+ TextTransfer plainTextTransfer = TextTransfer.getInstance();
+ TextWriter plainTextWriter = new TextWriter(start, length);
+ String plainText = getPlatformDelimitedText(plainTextWriter);
+ Object[] data;
+ Transfer[] types;
+ if (clipboardType == DND.SELECTION_CLIPBOARD) {
+ data = new Object[]{plainText};
+ types = new Transfer[]{plainTextTransfer};
+ } else {
+ RTFTransfer rtfTransfer = RTFTransfer.getInstance();
+ RTFWriter rtfWriter = new RTFWriter(start, length);
+ String rtfText = getPlatformDelimitedText(rtfWriter);
+ data = new Object[]{rtfText, plainText};
+ types = new Transfer[]{rtfTransfer, plainTextTransfer};
+ }
+ clipboard.setContents(data, types, clipboardType);
+}
+/**
+ * Sets the content implementation to use for text storage.
+ *
+ * @param newContent StyledTextContent implementation to use for text storage.
+ * @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 setContent(StyledTextContent newContent) {
+ checkWidget();
+ if (newContent == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ if (content != null) {
+ content.removeTextChangeListener(textChangeListener);
+ }
+ content = newContent;
+ content.addTextChangeListener(textChangeListener);
+ reset();
+}
+/**
+ * Sets the receiver's cursor to the cursor specified by the
+ * argument. Overridden to handle the null case since the
+ * StyledText widget uses an ibeam as its default cursor.
+ *
+ * @see Control#setCursor(Cursor)
+ */
+@Override
+public void setCursor (Cursor cursor) {
+ checkWidget();
+ if (cursor != null && cursor.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ this.cursor = cursor;
+ if (cursor == null) {
+ Display display = getDisplay();
+ int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
+ super.setCursor(display.getSystemCursor(type));
+ } else {
+ super.setCursor(cursor);
+ }
+}
+/**
+ * Sets whether the widget implements double click mouse behavior.
+ *
+ * @param enable if true double clicking a word selects the word, if false
+ * double clicks have the same effect as regular mouse clicks.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setDoubleClickEnabled(boolean enable) {
+ checkWidget();
+ doubleClickEnabled = enable;
+}
+@Override
+public void setDragDetect (boolean dragDetect) {
+ checkWidget ();
+ this.dragDetect = dragDetect;
+}
+/**
+ * Sets whether the widget content can be edited.
+ *
+ * @param editable if true content can be edited, if false content can not be
+ * edited
+ * @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 setEditable(boolean editable) {
+ checkWidget();
+ this.editable = editable;
+}
+@Override
+public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ Display display = getDisplay();
+ this.enabled = enabled;
+ this.insideSetEnableCall = true;
+ try {
+ if (enabled) {
+ if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
+ if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
+ } else {
+ if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND));
+ if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND));
+ }
+ }
+ finally {
+ this.insideSetEnableCall = false;
+ }
+}
+/**
+ * Sets a new font to render text with.
+ * <p>
+ * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
+ * and the same baseline as regular fonts.
+ * </p>
+ *
+ * @param font new font
+ * @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>
+ */
+@Override
+public void setFont(Font font) {
+ checkWidget();
+ int oldLineHeight = renderer.getLineHeight();
+ super.setFont(font);
+ renderer.setFont(getFont(), tabLength);
+ // keep the same top line visible. fixes 5815
+ if (isFixedLineHeight()) {
+ int lineHeight = renderer.getLineHeight();
+ if (lineHeight != oldLineHeight) {
+ int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
+ scrollVertical(vscroll, true);
+ }
+ }
+ resetCache(0, content.getLineCount());
+ claimBottomFreeSpace();
+ calculateScrollBars();
+ if (isBidiCaret()) createCaretBitmaps();
+ caretDirection = SWT.NULL;
+ setCaretLocation();
+ super.redraw();
+}
+@Override
+public void setForeground(Color color) {
+ checkWidget();
+ boolean foregroundDisabled = false;
+ if (!this.enabled && color == null) {
+ if (foreground != null) {
+ Color disabledFg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND);
+ if (foreground.equals(disabledFg)) {
+ return;
+ } else {
+ color = new Color (getDisplay(), disabledFg.getRGBA());
+ foregroundDisabled = true;
+ }
+ }
+ }
+ customForeground = color != null && !this.insideSetEnableCall && !foregroundDisabled;
+ foreground = color;
+ super.setForeground(color);
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets the horizontal scroll offset relative to the start of the line.
+ * Do nothing if there is no text set.
+ * <p>
+ * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
+ * widget.
+ * </p>
+ *
+ * @param offset horizontal scroll offset relative to the start
+ * of the line, measured in character increments starting at 0, if
+ * equal to 0 the content is not scrolled, if > 0 = the content is scrolled.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setHorizontalIndex(int offset) {
+ checkWidget();
+ if (getCharCount() == 0) {
+ return;
+ }
+ if (offset < 0) {
+ offset = 0;
+ }
+ offset *= getHorizontalIncrement();
+ // allow any value if client area width is unknown or 0.
+ // offset will be checked in resize handler.
+ // don't use isVisible since width is known even if widget
+ // is temporarily invisible
+ if (clientAreaWidth > 0) {
+ int width = renderer.getWidth();
+ // prevent scrolling if the content fits in the client area.
+ // align end of longest line with right border of client area
+ // if offset is out of range.
+ if (offset > width - clientAreaWidth) {
+ offset = Math.max(0, width - clientAreaWidth);
+ }
+ }
+ scrollHorizontal(offset - horizontalScrollOffset, true);
+}
+/**
+ * Sets the horizontal SWT logical point offset relative to the start of the line.
+ * Do nothing if there is no text set.
+ * <p>
+ * <b>NOTE:</b> The horizontal SWT logical point offset is reset to 0 when new text
+ * is set in the widget.
+ * </p>
+ *
+ * @param pixel horizontal SWT logical point offset relative to the start
+ * of the line.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.0
+ */
+public void setHorizontalPixel(int pixel) {
+ checkWidget();
+ if (getCharCount() == 0) {
+ return;
+ }
+ if (pixel < 0) {
+ pixel = 0;
+ }
+ // allow any value if client area width is unknown or 0.
+ // offset will be checked in resize handler.
+ // don't use isVisible since width is known even if widget
+ // is temporarily invisible
+ if (clientAreaWidth > 0) {
+ int width = renderer.getWidth();
+ // prevent scrolling if the content fits in the client area.
+ // align end of longest line with right border of client area
+ // if offset is out of range.
+ if (pixel > width - clientAreaWidth) {
+ pixel = Math.max(0, width - clientAreaWidth);
+ }
+ }
+ scrollHorizontal(pixel - horizontalScrollOffset, true);
+}
+/**
+ * Sets the line indentation of the widget.
+ * <p>
+ * It is the amount of blank space, in points, at the beginning of each line.
+ * When a line wraps in several lines only the first one is indented.
+ * </p>
+ *
+ * @param indent the new indent
+ *
+ * @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 #setLineIndent(int, int, int)
+ *
+ * @since 3.2
+ */
+public void setIndent(int indent) {
+ checkWidget();
+ if (this.indent == indent || indent < 0) return;
+ this.indent = indent;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets whether the widget should justify lines.
+ *
+ * @param justify whether lines should be justified
+ *
+ * @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 #setLineJustify(int, int, boolean)
+ *
+ * @since 3.2
+ */
+public void setJustify(boolean justify) {
+ checkWidget();
+ if (this.justify == justify) return;
+ this.justify = justify;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Maps a key to an action.
+ * <p>
+ * One action can be associated with N keys. However, each key can only
+ * have one action (key:action is N:1 relation).
+ * </p>
+ *
+ * @param key a key code defined in SWT.java or a character.
+ * Optionally ORd with a state mask. Preferred state masks are one or more of
+ * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
+ * differences. However, there may be cases where using the specific state masks
+ * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
+ * @param action one of the predefined actions defined in ST.java.
+ * Use SWT.NULL to remove a key binding.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setKeyBinding(int key, int action) {
+ checkWidget();
+ int modifierValue = key & SWT.MODIFIER_MASK;
+ int keyInt = key & SWT.KEY_MASK;
+ char keyChar = (char)keyInt;
+ /**
+ * Bug 440535: Make sure the key getting mapped to letter is in defiened
+ * character range and filter out incorrect int to char typecasting. For
+ * Example: SWT.KEYPAD_CR int gets wrongly type-cast to char letter 'p'
+ */
+ if (Character.isDefined(keyInt) && Character.isLetter(keyChar)) {
+ // make the keybinding case insensitive by adding it
+ // in its upper and lower case form
+ char ch = Character.toUpperCase(keyChar);
+ int newKey = ch | modifierValue;
+ if (action == SWT.NULL) {
+ keyActionMap.remove(newKey);
+ } else {
+ keyActionMap.put(newKey, action);
+ }
+ ch = Character.toLowerCase(keyChar);
+ newKey = ch | modifierValue;
+ if (action == SWT.NULL) {
+ keyActionMap.remove(newKey);
+ } else {
+ keyActionMap.put(newKey, action);
+ }
+ } else {
+ if (action == SWT.NULL) {
+ keyActionMap.remove(key);
+ } else {
+ keyActionMap.put(key, action);
+ }
+ }
+}
+/**
+ * Sets the left margin.
+ *
+ * @param leftMargin the left margin.
+ * @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 void setLeftMargin (int leftMargin) {
+ checkWidget();
+ setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
+}
+/**
+ * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>,
+ * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>.
+ * <p>
+ * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
+ * in order to stabilize the right edge before setting alignment.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ *
+ * @param startLine first line the alignment is applied to, 0 based
+ * @param lineCount number of lines the alignment applies to.
+ * @param alignment line 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>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setAlignment(int)
+ * @since 3.2
+ */
+public void setLineAlignment(int startLine, int lineCount, int alignment) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+
+ renderer.setLineAlignment(startLine, lineCount, alignment);
+ resetCache(startLine, lineCount);
+ redrawLines(startLine, lineCount, false);
+ int caretLine = getCaretLine();
+ if (startLine <= caretLine && caretLine < startLine + lineCount) {
+ setCaretLocation();
+ }
+ setAlignment();
+}
+/**
+ * Sets the background color of the specified lines.
+ * <p>
+ * The background color is drawn for the width of the widget. All
+ * line background colors are discarded when setText is called.
+ * The text background color if defined in a StyleRange overlays the
+ * line background color.
+ * </p><p>
+ * Should not be called if a LineBackgroundListener has been set since the
+ * listener maintains the line backgrounds.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the color is applied to, 0 based
+ * @param lineCount number of lines the color applies to.
+ * @param background line background color
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ */
+public void setLineBackground(int startLine, int lineCount, Color background) {
+ checkWidget();
+ if (isListening(ST.LineGetBackground)) return;
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (background != null) {
+ renderer.setLineBackground(startLine, lineCount, background);
+ } else {
+ renderer.clearLineBackground(startLine, lineCount);
+ }
+ redrawLines(startLine, lineCount, false);
+}
+/**
+ * Sets the bullet of the specified lines.
+ * <p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the bullet is applied to, 0 based
+ * @param lineCount number of lines the bullet applies to.
+ * @param bullet line bullet
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @since 3.2
+ */
+public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ int oldBottom = getLinePixel(startLine + lineCount);
+ renderer.setLineBullet(startLine, lineCount, bullet);
+ resetCache(startLine, lineCount);
+ int newBottom = getLinePixel(startLine + lineCount);
+ redrawLines(startLine, lineCount, oldBottom != newBottom);
+ int caretLine = getCaretLine();
+ if (startLine <= caretLine && caretLine < startLine + lineCount) {
+ setCaretLocation();
+ }
+}
+/**
+ * Returns true if StyledText is in word wrap mode and false otherwise.
+ *
+ * @return true if StyledText is in word wrap mode and false otherwise.
+ */
+boolean isWordWrap() {
+ return wordWrap || visualWrap;
+}
+/**
+ * Sets the indent of the specified lines.
+ * <p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the indent is applied to, 0 based
+ * @param lineCount number of lines the indent applies to.
+ * @param indent line indent
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setIndent(int)
+ * @since 3.2
+ */
+public void setLineIndent(int startLine, int lineCount, int indent) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ int oldBottom = getLinePixel(startLine + lineCount);
+ renderer.setLineIndent(startLine, lineCount, indent);
+ resetCache(startLine, lineCount);
+ int newBottom = getLinePixel(startLine + lineCount);
+ redrawLines(startLine, lineCount, oldBottom != newBottom);
+ int caretLine = getCaretLine();
+ if (startLine <= caretLine && caretLine < startLine + lineCount) {
+ setCaretLocation();
+ }
+}
+
+/**
+ * Sets the vertical indent of the specified lines.
+ * <p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p><p>
+ * Setting both line spacing and vertical indent on a line would result in the
+ * spacing and indent add up for the line.
+ * </p>
+ *
+ * @param lineIndex line index the vertical indent is applied to, 0 based
+ * @param verticalLineIndent vertical line indent
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line index is invalid</li>
+ * </ul>
+ * @since 3.109
+ */
+public void setLineVerticalIndent(int lineIndex, int verticalLineIndent) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (lineIndex < 0 || lineIndex >= content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (verticalLineIndent == renderer.getLineVerticalIndent(lineIndex)) {
+ return;
+ }
+ int oldBottom = getLinePixel(lineIndex + 1);
+ if (oldBottom <= getClientArea().height) {
+ verticalScrollOffset = -1;
+ }
+ renderer.setLineVerticalIndent(lineIndex, verticalLineIndent);
+ hasVerticalIndent = verticalLineIndent != 0 || renderer.hasVerticalIndent();
+ resetCache(lineIndex, 1);
+ int newBottom = getLinePixel(lineIndex + 1);
+ redrawLines(lineIndex, 1, oldBottom != newBottom);
+ int caretLine = getCaretLine();
+ if (lineIndex <= caretLine && caretLine < lineIndex + 1) {
+ setCaretLocation();
+ }
+}
+
+/**
+ * Sets the justify of the specified lines.
+ * <p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the justify is applied to, 0 based
+ * @param lineCount number of lines the justify applies to.
+ * @param justify true if lines should be justified
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setJustify(boolean)
+ * @since 3.2
+ */
+public void setLineJustify(int startLine, int lineCount, boolean justify) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+
+ renderer.setLineJustify(startLine, lineCount, justify);
+ resetCache(startLine, lineCount);
+ redrawLines(startLine, lineCount, false);
+ int caretLine = getCaretLine();
+ if (startLine <= caretLine && caretLine < startLine + lineCount) {
+ setCaretLocation();
+ }
+}
+/**
+ * Sets the line spacing of the widget. The line spacing applies for all lines.
+ * In the case of #setLineSpacingProvider(StyledTextLineSpacingProvider) is customized,
+ * the line spacing are applied only for the lines which are not managed by {@link StyledTextLineSpacingProvider}.
+ *
+ * @param lineSpacing the line spacing
+ * @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 #setLineSpacingProvider(StyledTextLineSpacingProvider)
+ * @since 3.2
+ */
+public void setLineSpacing(int lineSpacing) {
+ checkWidget();
+ if (this.lineSpacing == lineSpacing || lineSpacing < 0) return;
+ this.lineSpacing = lineSpacing;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets the line spacing provider of the widget. The line spacing applies for some lines with customized spacing
+ * or reset the customized spacing if the argument is null.
+ *
+ * @param lineSpacingProvider the line spacing provider (or null)
+ * @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 #setLineSpacingProvider(StyledTextLineSpacingProvider)
+ * @since 3.107
+ */
+public void setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider) {
+ checkWidget();
+ boolean wasFixedLineHeight = isFixedLineHeight();
+ if (renderer.getLineSpacingProvider() == null && lineSpacingProvider == null
+ || (renderer.getLineSpacingProvider() != null
+ && renderer.getLineSpacingProvider().equals(lineSpacingProvider)))
+ return;
+ renderer.setLineSpacingProvider(lineSpacingProvider);
+ // reset lines cache if needed
+ if (lineSpacingProvider == null) {
+ if (!wasFixedLineHeight) {
+ resetCache(0, content.getLineCount());
+ }
+ } else {
+ if (wasFixedLineHeight) {
+ int firstLine = -1;
+ for (int i = 0; i < content.getLineCount(); i++) {
+ Integer lineSpacing = lineSpacingProvider.getLineSpacing(i);
+ if (lineSpacing != null && lineSpacing > 0) {
+ // there is a custom line spacing, set StyledText as variable line height mode
+ // reset only the line size
+ renderer.reset(i, 1);
+ if (firstLine == -1) {
+ firstLine = i;
+ }
+ }
+ }
+ if (firstLine != -1) {
+ // call reset cache for the first line which have changed to recompute scrollbars
+ resetCache(firstLine, 0);
+ }
+ }
+ }
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets the tab stops of the specified lines.
+ * <p>
+ * Should not be called if a <code>LineStyleListener</code> has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the justify is applied to, 0 based
+ * @param lineCount number of lines the justify applies to.
+ * @param tabStops tab stops
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setTabStops(int[])
+ * @since 3.6
+ */
+public void setLineTabStops(int startLine, int lineCount, int[] tabStops) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (tabStops != null) {
+ int pos = 0;
+ int[] newTabs = new int[tabStops.length];
+ for (int i = 0; i < tabStops.length; i++) {
+ if (tabStops[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ newTabs[i] = pos = tabStops[i];
+ }
+ renderer.setLineTabStops(startLine, lineCount, newTabs);
+ } else {
+ renderer.setLineTabStops(startLine, lineCount, null);
+ }
+ resetCache(startLine, lineCount);
+ redrawLines(startLine, lineCount, false);
+ int caretLine = getCaretLine();
+ if (startLine <= caretLine && caretLine < startLine + lineCount) {
+ setCaretLocation();
+ }
+}
+/**
+ * Sets the wrap indent of the specified lines.
+ * <p>
+ * Should not be called if a <code>LineStyleListener</code> has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the wrap indent is applied to, 0 based
+ * @param lineCount number of lines the wrap indent applies to.
+ * @param wrapIndent line wrap indent
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setWrapIndent(int)
+ * @since 3.6
+ */
+public void setLineWrapIndent(int startLine, int lineCount, int wrapIndent) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ int oldBottom = getLinePixel(startLine + lineCount);
+ renderer.setLineWrapIndent(startLine, lineCount, wrapIndent);
+ resetCache(startLine, lineCount);
+ int newBottom = getLinePixel(startLine + lineCount);
+ redrawLines(startLine, lineCount, oldBottom != newBottom);
+ int caretLine = getCaretLine();
+ if (startLine <= caretLine && caretLine < startLine + lineCount) {
+ setCaretLocation();
+ }
+}
+
+/**
+ * Sets the color of the margins.
+ *
+ * @param color the new color (or null)
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</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>
+ *
+ * @since 3.5
+ */
+public void setMarginColor(Color color) {
+ checkWidget();
+ if (color != null && color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ marginColor = color;
+ super.redraw();
+}
+/**
+ * Sets the margins.
+ *
+ * @param leftMargin the left margin.
+ * @param topMargin the top margin.
+ * @param rightMargin the right margin.
+ * @param bottomMargin the bottom margin.
+ * @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 void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
+ checkWidget();
+ this.leftMargin = Math.max(0, leftMargin) + alignmentMargin;
+ this.topMargin = Math.max(0, topMargin);
+ this.rightMargin = Math.max(0, rightMargin);
+ this.bottomMargin = Math.max(0, bottomMargin);
+ resetCache(0, content.getLineCount());
+ setScrollBars(true);
+ setCaretLocation();
+ setAlignment();
+ super.redraw();
+}
+/**
+ * Sets the enabled state of the mouse navigator. When the mouse navigator is enabled, the user can navigate through the widget
+ * by pressing the middle button and moving the cursor.
+ *
+ * @param enabled if true, the mouse navigator is enabled, if false the mouse navigator is deactivated
+ * @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.110
+ */
+public void setMouseNavigatorEnabled(boolean enabled) {
+ checkWidget();
+ if ((enabled && mouseNavigator != null) || (!enabled && mouseNavigator == null)) {
+ return;
+ }
+ if (enabled) {
+ mouseNavigator = new MouseNavigator(this);
+ } else {
+ mouseNavigator.dispose();
+ mouseNavigator = null;
+ }
+}
+/**
+ * Flips selection anchor based on word selection direction.
+ */
+void setMouseWordSelectionAnchor() {
+ if (doubleClickEnabled && clickCount > 1) {
+ if (caretOffset < doubleClickSelection.x) {
+ selectionAnchor = doubleClickSelection.y;
+ } else if (caretOffset > doubleClickSelection.y) {
+ selectionAnchor = doubleClickSelection.x;
+ }
+ }
+}
+/**
+ * Sets the orientation of the receiver, which must be one
+ * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
+ *
+ * @param orientation new orientation style
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 2.1.2
+ */
+@Override
+public void setOrientation(int orientation) {
+ int oldOrientation = getOrientation();
+ super.setOrientation(orientation);
+ int newOrientation = getOrientation();
+ if (oldOrientation != newOrientation) {
+ resetBidiData();
+ }
+}
+/**
+ * Sets the right margin.
+ *
+ * @param rightMargin the right margin.
+ * @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 void setRightMargin (int rightMargin) {
+ checkWidget();
+ setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
+}
+void setScrollBar(ScrollBar bar, int clientArea, int maximum, int margin) {
+ int inactive = 1;
+ if (clientArea < maximum) {
+ bar.setMaximum(maximum - margin);
+ bar.setThumb(clientArea - margin);
+ bar.setPageIncrement(clientArea - margin);
+ if (!alwaysShowScroll) bar.setVisible(true);
+ } else if (bar.getThumb() != inactive || bar.getMaximum() != inactive) {
+ bar.setValues(bar.getSelection(), bar.getMinimum(), inactive, inactive, bar.getIncrement(), inactive);
+ }
+}
+/**
+ * Adjusts the maximum and the page size of the scroll bars to
+ * reflect content width/length changes.
+ *
+ * @param vertical indicates if the vertical scrollbar also needs to be set
+ */
+void setScrollBars(boolean vertical) {
+ ignoreResize++;
+ if (!isFixedLineHeight() || !alwaysShowScroll) vertical = true;
+ ScrollBar verticalBar = vertical ? getVerticalBar() : null;
+ ScrollBar horizontalBar = getHorizontalBar();
+ int oldHeight = clientAreaHeight;
+ int oldWidth = clientAreaWidth;
+ if (!alwaysShowScroll) {
+ if (verticalBar != null) verticalBar.setVisible(false);
+ if (horizontalBar != null) horizontalBar.setVisible(false);
+ }
+ if (verticalBar != null) {
+ setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
+ }
+ if (horizontalBar != null && !wordWrap) {
+ setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
+ if (!alwaysShowScroll && horizontalBar.getVisible() && verticalBar != null) {
+ setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
+ if (verticalBar.getVisible()) {
+ setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
+ }
+ }
+ }
+ if (!alwaysShowScroll) {
+ redrawMargins(oldHeight, oldWidth);
+ }
+ ignoreResize--;
+}
+/**
+ * Sets the selection to the given position and scrolls it into view. Equivalent to setSelection(start,start).
+ *
+ * @param start new caret position
+ * @see #setSelection(int,int)
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelection(int start) {
+ // checkWidget test done in setSelectionRange
+ setSelection(start, start);
+}
+/**
+ * Sets the selection and scrolls it into view.
+ * <p>
+ * Indexing is zero based. Text selections are specified in terms of
+ * caret positions. In a text widget that contains N characters, there are
+ * N+1 caret positions, ranging from 0..N
+ * </p>
+ *
+ * @param point x=selection start offset, y=selection end offset
+ * The caret will be placed at the selection start when x > y.
+ * @see #setSelection(int,int)
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when point is null</li>
+ * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelection(Point point) {
+ checkWidget();
+ if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ setSelection(point.x, point.y);
+}
+/**
+ * Sets the receiver's selection background color to the color specified
+ * by the argument, or to the default system color for the control
+ * if the argument is null.
+ *
+ * @param color the new color (or null)
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</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>
+ * @since 2.1
+ */
+public void setSelectionBackground (Color color) {
+ checkWidget ();
+ if (color != null) {
+ if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ selectionBackground = color;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets the receiver's selection foreground color to the color specified
+ * by the argument, or to the default system color for the control
+ * if the argument is null.
+ * <p>
+ * Note that this is a <em>HINT</em>. Some platforms do not allow the application
+ * to change the selection foreground color.
+ * </p>
+ * @param color the new color (or null)
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</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>
+ * @since 2.1
+ */
+public void setSelectionForeground (Color color) {
+ checkWidget ();
+ if (color != null) {
+ if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ selectionForeground = color;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets the selection and scrolls it into view.
+ * <p>
+ * Indexing is zero based. Text selections are specified in terms of
+ * caret positions. In a text widget that contains N characters, there are
+ * N+1 caret positions, ranging from 0..N
+ * </p>
+ *
+ * @param start selection start offset. The caret will be placed at the
+ * selection start when start > end.
+ * @param end selection end offset
+ * @see #setSelectionRange(int,int)
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelection(int start, int end) {
+ setSelectionRange(start, end - start);
+ showSelection();
+}
+/**
+ * Sets the selection.
+ * <p>
+ * The new selection may not be visible. Call showSelection to scroll
+ * the selection into view.
+ * </p>
+ *
+ * @param start offset of the first selected character, start >= 0 must be true.
+ * @param length number of characters to select, 0 <= start + length
+ * <= getCharCount() must be true.
+ * A negative length places the caret at the selection start.
+ * @param sendEvent a Selection event is sent when set to true and when
+ * the selection is reset.
+ */
+void setSelection(int start, int length, boolean sendEvent, boolean doBlock) {
+ int end = start + length;
+ if (start > end) {
+ int temp = end;
+ end = start;
+ start = temp;
+ }
+ // is the selection range different or is the selection direction
+ // different?
+ if (selection.x != start || selection.y != end ||
+ (length > 0 && selectionAnchor != selection.x) ||
+ (length < 0 && selectionAnchor != selection.y)) {
+ if (blockSelection && doBlock) {
+ if (length < 0) {
+ setBlockSelectionOffset(end, start, sendEvent);
+ } else {
+ setBlockSelectionOffset(start, end, sendEvent);
+ }
+ } else {
+ int oldStart = selection.x;
+ int oldLength = selection.y - selection.x;
+ int charCount = content.getCharCount();
+ // called internally to remove selection after text is removed
+ // therefore make sure redraw range is valid.
+ int redrawX = Math.min(selection.x, charCount);
+ int redrawY = Math.min(selection.y, charCount);
+ if (length < 0) {
+ selectionAnchor = selection.y = end;
+ selection.x = start;
+ setCaretOffset(start, PREVIOUS_OFFSET_TRAILING);
+ } else {
+ selectionAnchor = selection.x = start;
+ selection.y = end;
+ setCaretOffset(end, PREVIOUS_OFFSET_TRAILING);
+ }
+ redrawX = Math.min(redrawX, selection.x);
+ redrawY = Math.max(redrawY, selection.y);
+ if (redrawY - redrawX > 0) {
+ internalRedrawRange(redrawX, redrawY - redrawX);
+ }
+ if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) {
+ sendSelectionEvent();
+ }
+ sendAccessibleTextCaretMoved();
+ }
+ }
+}
+/**
+ * Sets the selection.
+ * <p>
+ * The new selection may not be visible. Call showSelection to scroll the selection
+ * into view. A negative length places the caret at the visual start of the selection.
+ * </p>
+ *
+ * @param start offset of the first selected character
+ * @param length number of characters to select
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelectionRange(int start, int length) {
+ checkWidget();
+ int contentLength = getCharCount();
+ start = Math.max(0, Math.min (start, contentLength));
+ int end = start + length;
+ if (end < 0) {
+ length = -start;
+ } else {
+ if (end > contentLength) length = contentLength - start;
+ }
+ if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
+ // the start offset or end offset of the selection range is inside a
+ // multi byte line delimiter. This is an illegal operation and an exception
+ // is thrown. Fixes 1GDKK3R
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ setSelection(start, length, false, true);
+ setCaretLocation();
+}
+/**
+ * Adds the specified style.
+ * <p>
+ * The new style overwrites existing styles for the specified range.
+ * Existing style ranges are adjusted if they partially overlap with
+ * the new style. To clear an individual style, call setStyleRange
+ * with a StyleRange that has null attributes.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param range StyleRange object containing the style information.
+ * Overwrites the old style in the given range. May be null to delete
+ * all styles.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li>
+ * </ul>
+ */
+public void setStyleRange(StyleRange range) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (range != null) {
+ if (range.isUnstyled()) {
+ setStyleRanges(range.start, range.length, null, null, false);
+ } else {
+ setStyleRanges(range.start, 0, null, new StyleRange[]{range}, false);
+ }
+ } else {
+ setStyleRanges(0, 0, null, null, true);
+ }
+}
+/**
+ * Clears the styles in the range specified by <code>start</code> and
+ * <code>length</code> and adds the new styles.
+ * <p>
+ * The ranges array contains start and length pairs. Each pair refers to
+ * the corresponding style in the styles array. For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2]. The range fields within each StyleRange are ignored.
+ * If ranges or styles is null, the specified range is cleared.
+ * </p><p>
+ * Note: It is expected that the same instance of a StyleRange will occur
+ * multiple times within the styles array, reducing memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param start offset of first character where styles will be deleted
+ * @param length length of the range to delete styles in
+ * @param ranges the array of ranges. The ranges must not overlap and must be in order.
+ * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
+ *
+ * @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 an element in the styles array is null</li>
+ * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
+ * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
+ * <li>ERROR_INVALID_RANGE when a range overlaps</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (ranges == null || styles == null) {
+ setStyleRanges(start, length, null, null, false);
+ } else {
+ setStyleRanges(start, length, ranges, styles, false);
+ }
+}
+/**
+ * Sets styles to be used for rendering the widget content.
+ * <p>
+ * All styles in the widget will be replaced with the given set of ranges and styles.
+ * The ranges array contains start and length pairs. Each pair refers to
+ * the corresponding style in the styles array. For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2]. The range fields within each StyleRange are ignored.
+ * If either argument is null, the styles are cleared.
+ * </p><p>
+ * Note: It is expected that the same instance of a StyleRange will occur
+ * multiple times within the styles array, reducing memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param ranges the array of ranges. The ranges must not overlap and must be in order.
+ * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
+ *
+ * @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 an element in the styles array is null</li>
+ * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
+ * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
+ * <li>ERROR_INVALID_RANGE when a range overlaps</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public void setStyleRanges(int[] ranges, StyleRange[] styles) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (ranges == null || styles == null) {
+ setStyleRanges(0, 0, null, null, true);
+ } else {
+ setStyleRanges(0, 0, ranges, styles, true);
+ }
+}
+void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) {
+ int charCount = content.getCharCount();
+ if (reset) {
+ start = 0;
+ length = charCount;
+ }
+ int[] formerRanges = getRanges(start, length);
+ StyleRange[] formerStyles = getStyleRanges(start, length);
+ int end = start + length;
+ final boolean wasFixedLineHeight = isFixedLineHeight();
+ if (start > end || start < 0) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ if (styles != null) {
+ if (end > charCount) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ if (ranges != null) {
+ if (ranges.length != styles.length << 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ int lastOffset = 0;
+ for (int i = 0; i < styles.length; i ++) {
+ if (styles[i] == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ int rangeStart, rangeLength;
+ if (ranges != null) {
+ rangeStart = ranges[i << 1];
+ rangeLength = ranges[(i << 1) + 1];
+ } else {
+ rangeStart = styles[i].start;
+ rangeLength = styles[i].length;
+ }
+ if (rangeLength < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ if (lastOffset > rangeStart) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ hasStyleWithVariableHeight |= styles[i].isVariableHeight();
+ lastOffset = rangeStart + rangeLength;
+ }
+ }
+ int rangeStart = start, rangeEnd = end;
+ if (styles != null && styles.length > 0) {
+ if (ranges != null) {
+ rangeStart = ranges[0];
+ rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
+ } else {
+ rangeStart = styles[0].start;
+ rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
+ }
+ }
+
+ // This needs to happen before new styles are applied
+ int expectedBottom = 0;
+ if (!isFixedLineHeight() && !reset) {
+ int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
+ int partialTopIndex = getPartialTopIndex();
+ int partialBottomIndex = getPartialBottomIndex();
+ if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
+ expectedBottom = getLinePixel(lineEnd + 1);
+ }
+ }
+ if (reset) {
+ renderer.setStyleRanges(null, null);
+ } else {
+ renderer.updateRanges(start, length, length);
+ }
+ if (styles != null && styles.length > 0) {
+ renderer.setStyleRanges(ranges, styles);
+ }
+
+ // re-evaluate variable height with all styles (including new ones)
+ hasStyleWithVariableHeight = false;
+ for (StyleRange style : getStyleRanges(false)) {
+ hasStyleWithVariableHeight = style.isVariableHeight();
+ if (hasStyleWithVariableHeight) break;
+ }
+
+ SortedSet<Integer> modifiedLines = computeModifiedLines(formerRanges, formerStyles, ranges, styles);
+ resetCache(modifiedLines);
+ if (reset) {
+ super.redraw();
+ } else {
+ int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
+ int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
+ int partialTopIndex = getPartialTopIndex();
+ int partialBottomIndex = getPartialBottomIndex();
+ if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
+ int top = 0;
+ int bottom = clientAreaHeight;
+ if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
+ top = Math.max(0, getLinePixel(lineStart));
+ }
+ if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
+ bottom = getLinePixel(lineEnd + 1);
+ }
+ if (!(wasFixedLineHeight && isFixedLineHeight()) && bottom != expectedBottom) {
+ bottom = clientAreaHeight;
+ }
+ super.redraw(0, top, clientAreaWidth, bottom - top, false);
+ }
+ }
+ int oldColumnX = columnX;
+ setCaretLocation();
+ columnX = oldColumnX;
+ doMouseLinkCursor();
+}
+
+/**
+ *
+ * @param referenceRanges former ranges, sorted by order and without overlapping, typically returned {@link #getRanges(int, int)}
+ * @param referenceStyles
+ * @param newRanges former ranges, sorted by order and without overlapping
+ * @param newStyles
+ * @return
+ */
+private SortedSet<Integer> computeModifiedLines(int[] referenceRanges, StyleRange[] referenceStyles, int[] newRanges, StyleRange[] newStyles) {
+ if (referenceStyles == null) {
+ referenceStyles = new StyleRange[0];
+ }
+ if (referenceRanges == null) {
+ referenceRanges = createRanges(referenceStyles);
+ }
+ if (newStyles == null) {
+ newStyles = new StyleRange[0];
+ }
+ if (newRanges == null) {
+ newRanges = createRanges(newStyles);
+ }
+ if (referenceRanges.length != 2 * referenceStyles.length) {
+ throw new IllegalArgumentException();
+ }
+ if (newRanges.length != 2 * newStyles.length) {
+ throw new IllegalArgumentException();
+ }
+ SortedSet<Integer> res = new TreeSet<>();
+ int referenceRangeIndex = 0;
+ int newRangeIndex = 0;
+ StyleRange defaultStyle = new StyleRange();
+ defaultStyle.foreground = this.foreground;
+ defaultStyle.background = this.background;
+ defaultStyle.font = getFont();
+ int currentOffset = referenceRanges.length > 0 ? referenceRanges[0] : Integer.MAX_VALUE;
+ if (newRanges.length > 0) {
+ currentOffset = Math.min(currentOffset, newRanges[0]);
+ }
+ while (currentOffset < content.getCharCount() && (referenceRangeIndex < referenceStyles.length || newRangeIndex < newRanges.length)) {
+ int nextMilestoneOffset = Integer.MAX_VALUE; // next new range start/end after current offset
+
+ while (referenceRangeIndex < referenceStyles.length && endRangeOffset(referenceRanges, referenceRangeIndex) <= currentOffset) {
+ referenceRangeIndex++;
+ }
+ StyleRange referenceStyleAtCurrentOffset = defaultStyle;
+ if (isInRange(referenceRanges, referenceRangeIndex, currentOffset)) { // has styling
+ referenceStyleAtCurrentOffset = referenceStyles[referenceRangeIndex];
+ nextMilestoneOffset = endRangeOffset(referenceRanges, referenceRangeIndex);
+ } else if (referenceRangeIndex < referenceStyles.length) { // no range, default styling
+ nextMilestoneOffset = referenceRanges[2 * referenceRangeIndex]; // beginning of next range
+ }
+
+ while (newRangeIndex < newStyles.length && endRangeOffset(newRanges, newRangeIndex) <= currentOffset) {
+ newRangeIndex++;
+ }
+ StyleRange newStyleAtCurrentOffset = defaultStyle;
+ if (isInRange(newRanges, newRangeIndex, currentOffset)) {
+ newStyleAtCurrentOffset = newStyles[newRangeIndex];
+ nextMilestoneOffset = Math.min(nextMilestoneOffset, endRangeOffset(newRanges, newRangeIndex));
+ } else if (newRangeIndex < newStyles.length) {
+ nextMilestoneOffset = Math.min(nextMilestoneOffset, newRanges[2 * newRangeIndex]);
+ }
+
+ if (!referenceStyleAtCurrentOffset.similarTo(newStyleAtCurrentOffset)) {
+ int fromLine = getLineAtOffset(currentOffset);
+ int toLine = getLineAtOffset(nextMilestoneOffset - 1);
+ for (int line = fromLine; line <= toLine; line++) {
+ res.add(line);
+ }
+ currentOffset = toLine + 1 < getLineCount() ? getOffsetAtLine(toLine + 1) : content.getCharCount();
+ } else {
+ currentOffset = nextMilestoneOffset;
+ }
+ }
+ return res;
+}
+private int[] createRanges(StyleRange[] referenceStyles) {
+ int[] referenceRanges;
+ referenceRanges = new int[2 * referenceStyles.length];
+ for (int i = 0; i < referenceStyles.length; i++) {
+ referenceRanges[2 * i] = referenceStyles[i].start;
+ referenceRanges[2 * i + 1] = referenceStyles[i].length;
+ }
+ return referenceRanges;
+}
+
+private boolean isInRange(int[] ranges, int styleIndex, int offset) {
+ if (ranges == null || ranges.length == 0 || styleIndex < 0 || 2 * styleIndex + 1 > ranges.length) {
+ return false;
+ }
+ int start = ranges[2 * styleIndex];
+ int length = ranges[2 * styleIndex + 1];
+ return offset >= start && offset < start + length;
+}
+
+/**
+ * The offset on which the range ends (excluded)
+ * @param ranges
+ * @param styleIndex
+ * @return
+ */
+private int endRangeOffset(int[] ranges, int styleIndex) {
+ if (styleIndex < 0 || 2 * styleIndex > ranges.length) {
+ throw new IllegalArgumentException();
+ }
+ int start = ranges[2 * styleIndex];
+ int length = ranges[2 * styleIndex + 1];
+ return start + length;
+}
+
+/**
+ * Sets styles to be used for rendering the widget content. All styles
+ * in the widget will be replaced with the given set of styles.
+ * <p>
+ * Note: Because a StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
+ * can be used to share styles and reduce memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param ranges StyleRange objects containing the style information.
+ * The ranges should not overlap. The style rendering is undefined if
+ * the ranges do overlap. Must not be null. The styles need to be in order.
+ * @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 the list of ranges is null</li>
+ * <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
+ * </ul>
+ *
+ * @see #setStyleRanges(int[], StyleRange[])
+ */
+public void setStyleRanges(StyleRange[] ranges) {
+ checkWidget();
+ if (isListening(ST.LineGetStyle)) return;
+ if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ setStyleRanges(0, 0, null, ranges, true);
+}
+/**
+ * Sets the tab width.
+ *
+ * @param tabs tab width measured in characters.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #setTabStops(int[])
+ */
+public void setTabs(int tabs) {
+ checkWidget();
+ tabLength = tabs;
+ renderer.setFont(null, tabs);
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+
+/**
+ * Sets the receiver's tab list. Each value in the tab list specifies
+ * the space in points from the origin of the document to the respective
+ * tab stop. The last tab stop width is repeated continuously.
+ *
+ * @param tabs the new tab list (or null)
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT - if a tab stop is negative or less than the previous stop in the list</li>
+ * </ul>
+ *
+ * @see StyledText#getTabStops()
+ *
+ * @since 3.6
+ */
+public void setTabStops(int [] tabs) {
+ checkWidget();
+ if (tabs != null) {
+ int pos = 0;
+ int[] newTabs = new int[tabs.length];
+ for (int i = 0; i < tabs.length; i++) {
+ if (tabs[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ newTabs[i] = pos = tabs[i];
+ }
+ this.tabs = newTabs;
+ } else {
+ this.tabs = null;
+ }
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+
+/**
+ * Sets the widget content.
+ * If the widget has the SWT.SINGLE style and "text" contains more than
+ * one line, only the first line is rendered but the text is stored
+ * unchanged. A subsequent call to getText will return the same text
+ * that was set.
+ * <p>
+ * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE
+ * style is used.
+ * </p>
+ *
+ * @param text new widget content. Replaces existing content. Line styles
+ * that were set using StyledText API are discarded. The
+ * current selection is also discarded.
+ * @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 string is null</li>
+ * </ul>
+ */
+public void setText(String text) {
+ checkWidget();
+ if (text == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ Event event = new Event();
+ event.start = 0;
+ event.end = getCharCount();
+ event.text = text;
+ event.doit = true;
+ notifyListeners(SWT.Verify, event);
+ if (event.doit) {
+ StyledTextEvent styledTextEvent = null;
+ if (isListening(ST.ExtendedModify)) {
+ styledTextEvent = new StyledTextEvent(content);
+ styledTextEvent.start = event.start;
+ styledTextEvent.end = event.start + event.text.length();
+ styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
+ }
+ content.setText(event.text);
+ notifyListeners(SWT.Modify, event);
+ if (styledTextEvent != null) {
+ notifyListeners(ST.ExtendedModify, styledTextEvent);
+ }
+ }
+}
+
+/**
+ * Sets the base text direction (a.k.a. "paragraph direction") of the receiver,
+ * which must be one of the constants <code>SWT.LEFT_TO_RIGHT</code> or
+ * <code>SWT.RIGHT_TO_LEFT</code>.
+ * <p>
+ * <code>setOrientation</code> would override this value with the text direction
+ * that is consistent with the new orientation.
+ * </p>
+ * <p>
+ * <b>Warning</b>: This API is currently only implemented on Windows.
+ * It doesn't set the base text direction on GTK and Cocoa.
+ * </p>
+ *
+ * @param textDirection the base text direction style
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SWT#FLIP_TEXT_DIRECTION
+ */
+@Override
+public void setTextDirection(int textDirection) {
+ checkWidget();
+ int oldStyle = getStyle();
+ super.setTextDirection(textDirection);
+ if (isAutoDirection () || oldStyle != getStyle()) {
+ resetBidiData();
+ }
+}
+
+/**
+ * Sets the text limit to the specified number of characters.
+ * <p>
+ * The text limit specifies the amount of text that
+ * the user can type into the widget.
+ * </p>
+ *
+ * @param limit the new text limit.
+ * @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_CANNOT_BE_ZERO when limit is 0</li>
+ * </ul>
+ */
+public void setTextLimit(int limit) {
+ checkWidget();
+ if (limit == 0) {
+ SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
+ }
+ textLimit = limit;
+}
+/**
+ * Sets the top index. Do nothing if there is no text set.
+ * <p>
+ * The top index is the index of the line that is currently at the top
+ * of the widget. The top index changes when the widget is scrolled.
+ * Indexing starts from zero.
+ * Note: The top index is reset to 0 when new text is set in the widget.
+ * </p>
+ *
+ * @param topIndex new top index. Must be between 0 and
+ * getLineCount() - fully visible lines per page. If no lines are fully
+ * visible the maximum value is getLineCount() - 1. An out of range
+ * index will be adjusted accordingly.
+ * @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 setTopIndex(int topIndex) {
+ checkWidget();
+ if (getCharCount() == 0) {
+ return;
+ }
+ int lineCount = content.getLineCount(), pixel;
+ if (isFixedLineHeight()) {
+ int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
+ if (topIndex < 0) {
+ topIndex = 0;
+ } else if (topIndex > lineCount - pageSize) {
+ topIndex = lineCount - pageSize;
+ }
+ pixel = getLinePixel(topIndex);
+ } else {
+ topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
+ pixel = getLinePixel(topIndex);
+ if (pixel > 0) {
+ pixel = getAvailableHeightBellow(pixel);
+ } else {
+ pixel = getAvailableHeightAbove(pixel);
+ }
+ }
+ scrollVertical(pixel, true);
+}
+/**
+ * Sets the top margin.
+ *
+ * @param topMargin the top margin.
+ * @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 void setTopMargin (int topMargin) {
+ checkWidget();
+ setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
+}
+/**
+ * Sets the top SWT logical point offset. Do nothing if there is no text set.
+ * <p>
+ * The top point offset is the vertical SWT logical point offset of the widget. The
+ * widget is scrolled so that the given SWT logical point position is at the top.
+ * The top index is adjusted to the corresponding top line.
+ * Note: The top point is reset to 0 when new text is set in the widget.
+ * </p>
+ *
+ * @param pixel new top point offset. Must be between 0 and
+ * (getLineCount() - visible lines per page) / getLineHeight()). An out
+ * of range offset will be adjusted accordingly.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.0
+ */
+public void setTopPixel(int pixel) {
+ checkWidget();
+ if (getCharCount() == 0) {
+ return;
+ }
+ if (pixel < 0) pixel = 0;
+ int lineCount = content.getLineCount();
+ int height = clientAreaHeight - topMargin - bottomMargin;
+ int verticalOffset = getVerticalScrollOffset();
+ if (isFixedLineHeight()) {
+ int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
+ if (pixel > maxTopPixel) pixel = maxTopPixel;
+ pixel -= verticalOffset;
+ } else {
+ pixel -= verticalOffset;
+ if (pixel > 0) {
+ pixel = getAvailableHeightBellow(pixel);
+ }
+ }
+ scrollVertical(pixel, true);
+}
+/**
+ * Sets whether the widget wraps lines.
+ * <p>
+ * This overrides the creation style bit SWT.WRAP.
+ * </p>
+ *
+ * @param wrap true=widget wraps lines, false=widget does not wrap lines
+ * @since 2.0
+ */
+public void setWordWrap(boolean wrap) {
+ checkWidget();
+ if ((getStyle() & SWT.SINGLE) != 0) return;
+ if (wordWrap == wrap) return;
+ if (wordWrap && blockSelection) setBlockSelection(false);
+ wordWrap = wrap;
+ resetCache(0, content.getLineCount());
+ horizontalScrollOffset = 0;
+ ScrollBar horizontalBar = getHorizontalBar();
+ if (horizontalBar != null) {
+ horizontalBar.setVisible(!wordWrap);
+ }
+ setScrollBars(true);
+ setCaretLocation();
+ super.redraw();
+}
+/**
+ * Sets the wrap line indentation of the widget.
+ * <p>
+ * It is the amount of blank space, in points, at the beginning of each wrapped line.
+ * When a line wraps in several lines all the lines but the first one is indented
+ * by this amount.
+ * </p>
+ *
+ * @param wrapIndent the new wrap indent
+ *
+ * @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 #setLineWrapIndent(int, int, int)
+ *
+ * @since 3.6
+ */
+public void setWrapIndent(int wrapIndent) {
+ checkWidget();
+ if (this.wrapIndent == wrapIndent || wrapIndent < 0) return;
+ this.wrapIndent = wrapIndent;
+ resetCache(0, content.getLineCount());
+ setCaretLocation();
+ super.redraw();
+}
+boolean showLocation(Rectangle rect, boolean scrollPage) {
+ boolean scrolled = false;
+ if (rect.y < topMargin) {
+ scrolled = scrollVertical(rect.y - topMargin, true);
+ } else if (rect.y + rect.height > clientAreaHeight - bottomMargin) {
+ if (clientAreaHeight - topMargin - bottomMargin <= 0) {
+ scrolled = scrollVertical(rect.y - topMargin, true);
+ } else {
+ scrolled = scrollVertical(rect.y + rect.height - (clientAreaHeight - bottomMargin), true);
+ }
+ }
+ int width = clientAreaWidth - rightMargin - leftMargin;
+ if (width > 0) {
+ int minScroll = scrollPage ? width / 4 : 0;
+ if (rect.x < leftMargin) {
+ int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
+ int maxScroll = horizontalScrollOffset;
+ scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
+ } else if (rect.x + rect.width > (clientAreaWidth - rightMargin)) {
+ int scrollWidth = Math.max(rect.x + rect.width - (clientAreaWidth - rightMargin), minScroll);
+ int maxScroll = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
+ scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
+ }
+ }
+ return scrolled;
+}
+/**
+ * Sets the caret location and scrolls the caret offset into view.
+ */
+void showCaret() {
+ Rectangle bounds = getBoundsAtOffset(caretOffset);
+ if (!showLocation(bounds, true)) {
+ setCaretLocation();
+ }
+}
+/**
+ * Scrolls the selection into view.
+ * <p>
+ * The end of the selection will be scrolled into view.
+ * Note that if a right-to-left selection exists, the end of the selection is
+ * the visual beginning of the selection (i.e., where the caret is located).
+ * </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 showSelection() {
+ checkWidget();
+ // is selection from right-to-left?
+ boolean rightToLeft = caretOffset == selection.x;
+ int startOffset, endOffset;
+ if (rightToLeft) {
+ startOffset = selection.y;
+ endOffset = selection.x;
+ } else {
+ startOffset = selection.x;
+ endOffset = selection.y;
+ }
+
+ Rectangle startBounds = getBoundsAtOffset(startOffset);
+ Rectangle endBounds = getBoundsAtOffset(endOffset);
+
+ // can the selection be fully displayed within the widget's visible width?
+ int w = clientAreaWidth - leftMargin - rightMargin;
+ boolean selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
+ if (selectionFits) {
+ // show as much of the selection as possible by first showing
+ // the start of the selection
+ if (showLocation(startBounds, false)) {
+ // endX value could change if showing startX caused a scroll to occur
+ endBounds = getBoundsAtOffset(endOffset);
+ }
+ // the character at endOffset is not part of the selection
+ endBounds.width = endOffset == caretOffset ? getCaretWidth() : 0;
+ showLocation(endBounds, false);
+ } else {
+ // just show the end of the selection since the selection start
+ // will not be visible
+ showLocation(endBounds, true);
+ }
+}
+void updateCaretVisibility() {
+ Caret caret = getCaret();
+ if (caret != null) {
+ if (blockSelection && blockXLocation != -1) {
+ caret.setVisible(false);
+ } else {
+ Point location = caret.getLocation();
+ Point size = caret.getSize();
+ boolean visible =
+ topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin &&
+ leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin;
+ caret.setVisible(visible);
+ }
+ }
+}
+/**
+ * Updates the selection and caret position depending on the text change.
+ * <p>
+ * If the selection intersects with the replaced text, the selection is
+ * reset and the caret moved to the end of the new text.
+ * If the selection is behind the replaced text it is moved so that the
+ * same text remains selected. If the selection is before the replaced text
+ * it is left unchanged.
+ * </p>
+ *
+ * @param startOffset offset of the text change
+ * @param replacedLength length of text being replaced
+ * @param newLength length of new text
+ */
+void updateSelection(int startOffset, int replacedLength, int newLength) {
+ if (selection.y <= startOffset) {
+ // selection ends before text change
+ if (isWordWrap()) setCaretLocation();
+ return;
+ }
+ if (selection.x < startOffset) {
+ // clear selection fragment before text change
+ internalRedrawRange(selection.x, startOffset - selection.x);
+ }
+ if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
+ // clear selection fragment after text change.
+ // do this only when the selection is actually affected by the
+ // change. Selection is only affected if it intersects the change (1GDY217).
+ int netNewLength = newLength - replacedLength;
+ int redrawStart = startOffset + newLength;
+ internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
+ }
+ if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
+ // selection intersects replaced text. set caret behind text change
+ setSelection(startOffset + newLength, 0, true, false);
+ } else {
+ // move selection to keep same text selected
+ setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true, false);
+ }
+ setCaretLocation();
+}
+}