All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.swt.custom.StyledText Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2000, 2021 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  - bug 488172
 *     Stefan Xenos (Google) - bug 487254 - StyledText.getTopIndex() can return negative values
 *     Angelo Zerr  - Customize different line spacing of StyledText - Bug 522020
 *     Karsten Thoms  - bug 528746 add getOffsetAtPoint(Point)
 *******************************************************************************/
package org.eclipse.swt.custom;


import java.util.*;
import java.util.function.*;
import java.util.stream.*;

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:
 * 
    *
  • foreground color *
  • background color *
  • font style (bold, italic, bold-italic, regular) *
  • underline *
  • strikeout *
*

* In addition to text style attributes, the background color of a line may * be specified. *

* 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:

*
    *
  • getStyleRangeAtOffset(int) *
  • getStyleRanges() *
  • replaceStyleRanges(int,int,StyleRange[]) *
  • setStyleRange(StyleRange) *
  • setStyleRanges(StyleRange[]) *
*

* 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:

*
    *
  • getLineBackground(int) *
  • setLineBackground(int,int,Color) *
*

* 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. *

*
*
Styles:
FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP *
Events:
ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey, OrientationChange *
*

* IMPORTANT: This class is not intended to be subclassed. *

* * @see StyledText snippets * @see SWT Examples: CustomControlExample, TextEditor * @see Sample code and further information * @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.lineSeparator(); 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$ private static int getX(Point p) { return p.x; } // workaround p -> p.x lose typing and cannot be chained in comparing... static final Comparator SELECTION_COMPARATOR = Comparator.comparingInt(StyledText::getX).thenComparingInt(p -> p.y); 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 Caret[] carets; int[] caretOffsets = {0}; int caretAlignment; Point[] selection = { new Point(0, 0) }; // x and y are start and end caret offsets of selection (x <= y) int[] selectionAnchors = {0}; // position of selection anchor for the selection ranges. 0 based offset from beginning of text Point clipboardSelection; // x and y are start and end caret offsets of previous selection 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 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 Printing 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 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 = { new Point(0, 0) }; // x and y are start and end caret offsets of selection (x <= y) boolean mirrored; // indicates the printing gc should be mirrored int lineSpacing; int printMargin; /** * Creates an instance of Printing. * 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 = Arrays.copyOf(styledText.selection, styledText.selection.length); } 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 StyledTextContent 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 (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 (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 (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 StyledTextContent. * * @param original the StyledTextContent 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 PrintRenderer. */ 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[0].x); if (selection[0].y > 0) { endLine = content.getLineAtOffset(selection[0].y); } 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(); } } } /** * Constructs a new instance of this class given its parent * and a style value describing its behavior and appearance. *

* The style value is either one of the style constants defined in * class SWT which is applicable to instances of this * class, or must be built by bitwise OR'ing together * (that is, using the int "|" operator) two or more * of those SWT style constants. The class description * lists the style constants that are applicable to the class. * Style bits are also inherited from superclasses. *

* * @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
    *
  • ERROR_NULL_ARGUMENT - if the parent is null
  • *
* @exception SWTException
    *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent
  • *
* * @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; setCaretLocations(Arrays.stream(caretOffsets).mapToObj(this::getPointAtOffset).toArray(Point[]::new), 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ 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. *

* 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. *

* * @param listener the listener * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* @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()); setCaretLocations(); super.redraw(); } /** * Adds a caret listener. CaretEvent is sent when the caret offset changes. * * @param listener the listener * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ 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)); setCaretLocations(); } /** * Adds a modify listener. A Modify event is sent by the widget when the widget text * has changed. * * @param modifyListener the listener * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void addModifyListener(ModifyListener modifyListener) { addTypedListener(modifyListener, SWT.Modify); } /** * 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* * @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. *

* When widgetSelected is called, the event x and y fields contain * the start and end caret indices of the selection[0]. 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. *

* widgetDefaultSelected is not called for StyledTexts. *

* * @param listener the listener which should be notified when the user changes the receiver's selection * @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT - if the listener is null
  • *
* @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @see SelectionListener * @see #removeSelectionListener * @see SelectionEvent */ public void addSelectionListener(SelectionListener listener) { addTypedListener(listener, SWT.Selection); } /** * 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void addVerifyListener(VerifyListener verifyListener) { addTypedListener(verifyListener, SWT.Verify); } /** * 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ 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; topIndexY = getLinePixel(topIndex); 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.getCachedLineHeight(lineIndex++); } if (lineIndex < lineCount && -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) { topIndex = lineIndex; topIndexY = -delta; } else { topIndex = lineIndex - 1; topIndexY = -renderer.getCachedLineHeight(topIndex) - delta; } } else { delta -= topIndexY; int lineIndex = topIndex; while (lineIndex > 0) { int lineHeight = renderer.getCachedLineHeight(lineIndex - 1); if (delta + lineHeight > 0) break; delta += lineHeight; lineIndex--; } if (lineIndex == 0 || -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) { topIndex = lineIndex; topIndexY = - delta; } else { topIndex = lineIndex - 1; topIndexY = - renderer.getCachedLineHeight(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[0].x; int selectionEnd = selection[0].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 DND.CLIPBOARD clipboard. *

* The text will be put on the clipboard in plain text, HTML, and RTF formats. * The DND.CLIPBOARD clipboard is used for data that is * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or * by menu action. *

* * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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, HTML, and RTF formats. *

* The clipboardType is one of the clipboard constants defined in class * DND. The DND.CLIPBOARD clipboard is * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) * or by menu action. The DND.SELECTION_CLIPBOARD * clipboard is used for data that is transferred by selecting text and pasting * with the middle mouse button. *

* * @param clipboardType indicates the type of clipboard * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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 if (getSelectionRanges().length > 2) { StringBuilder text = new StringBuilder(); int[] ranges = getSelectionRanges(); for (int i = 0; i < ranges.length; i += 2) { int offset = ranges[i]; int length = ranges[i + 1]; text.append(length == 0 ? "" : getText(offset, offset + length - 1)); text.append(PlatformLineDelimiter); } text.delete(text.length() - PlatformLineDelimiter.length(), text.length()); if (text.length() > 0) { //TODO RTF support clipboard.setContents(new Object[]{text.toString()}, new Transfer[]{TextTransfer.getInstance()}, type); return true; } } else { int length = selection[0].y - selection[0].x; if (length > 0) { setClipboardContent(selection[0].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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*R * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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. *

* Returns only the first line if the widget has the SWT.SINGLE style. *

* * @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[0].x == selection[0].y) return false; int offset = getOffsetAtPoint(event.x, event.y, null, true); if (selection[0].x <= offset && offset < selection[0].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, HTML, and RTF formats. * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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 = getFirstCaretLine(); 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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) { // at least one range to delete for (int i = selection.length - 1; i >= 0; i--) { // from bottom to top to avoid moving ranges Point sel = selection[i]; event.start = sel.x; event.end = sel.y; sendKeyEvent(event); } } else { for (int i = caretOffsets.length - 1; i >= 0; i--) { // reverse loop, process from bottom to top int caretOffset = caretOffsets[i]; 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(caretOffsets[0], 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(caretOffsets[0], false); int offset = end ? content.getCharCount() : 0; setBlockSelectionOffset(offset, true); showCaret(); } void doBlockWord(boolean next) { if (blockXLocation == -1) setBlockSelectionOffset(caretOffsets[0], 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(caretOffsets[0], 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(caretOffsets[0], 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 (caretOffsets[0] > selectionAnchors[0]) { selection[0].x = selectionAnchors[0]; selection[0].y = caretOffsets[0]; } else { selection[0].x = caretOffsets[0]; selection[0].y = selectionAnchors[0]; } updateCaretVisibility(); setCaretLocations(); 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. *

* If a carriage return was typed replace it with the line break character * used by the widget on this platform. *

* * @param key the character typed by the user */ void doContent(final char key) { if (blockSelection && blockXLocation != -1) { insertBlockSelectionText(key, SWT.NULL); return; } for (int i = selection.length - 1; i >= 0; i--) { Point sel = selection[i]; Event event = new Event(); event.start = sel.x; event.end = sel.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 (sel.x == sel.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(); setCaretOffsets(new int[] {length}, SWT.DEFAULT); showCaret(); } } /** * Moves the caret in front of the first character of the widget content. */ void doContentStart() { setCaretOffsets(new int[] {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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) { setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.x).toArray(), 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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) { setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.y).toArray(), 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 (Arrays.stream(selection).anyMatch(sel -> sel.x != sel.y)) { for (Point sel : selection) { event.start = sel.x; event.end = sel.y; sendKeyEvent(event); } } else { for (int i = caretOffsets.length - 1; i >= 0; i--) { int caretOffset = caretOffsets[i]; 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 (Arrays.stream(selection).anyMatch(sel -> sel.x != sel.y)) { // if a selection exists, treat the as if // only the delete key was pressed doDelete(); } else { for (int i = caretOffsets.length - 1; i >= 0; i--) { int caretOffset = caretOffsets[i]; 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 (Arrays.stream(selection).anyMatch(sel -> sel.x != sel.y)) { // if a selection exists, treat as if // only the backspace key was pressed doBackspace(); } else { for (int i = caretOffsets.length - 1; i >= 0; i--) { int caretOffset = caretOffsets[i]; 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[] newCaretOffsets = new int[caretOffsets.length]; int lineCount = content.getLineCount(); int[] alignment = new int[1]; for (int i = 0; i < caretOffsets.length; i++) { int caretOffset = caretOffsets[i]; int caretLine = content.getLineAtOffset(caretOffset); int x = caretOffsets.length == 1 ? columnX : getPointAtOffset(caretOffset).x; 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) { newCaretOffsets[i] = content.getCharCount(); } else { newCaretOffsets[i] = getOffsetAtPoint(x, y, caretLine, alignment); } } boolean hitLastLine = content.getLineAtOffset(newCaretOffsets[newCaretOffsets.length - 1]) == lineCount - 1; setCaretOffsets(newCaretOffsets, hitLastLine ? SWT.DEFAULT : 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[] newOffsets = new int[caretOffsets.length]; for (int i = 0; i < caretOffsets.length; i++) { int caretOffset = caretOffsets[i]; int caretLine = content.getLineAtOffset(caretOffset); 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; } newOffsets[i] = lineEndOffset; } setCaretOffsets(newOffsets, PREVIOUS_OFFSET_TRAILING); showCaret(); } /** * Moves the caret to the beginning of the line. */ void doLineStart() { int[] newCaretOffsets = new int[caretOffsets.length]; for (int i = 0; i < caretOffsets.length; i++) { int caretOffset = caretOffsets[i]; int caretLine = content.getLineAtOffset(caretOffset); 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); } newCaretOffsets[i] = lineOffset; } setCaretOffsets(newCaretOffsets, 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[] newCaretOffsets = new int[caretOffsets.length]; int[] alignment = new int[1]; for (int i = 0; i < caretOffsets.length; i++) { int caretOffset = caretOffsets[i]; int caretLine = content.getLineAtOffset(caretOffset); int x = caretOffsets.length == 1 ? columnX : getPointAtOffset(caretOffset).x; int 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) { newCaretOffsets[i] = 0; } else { newCaretOffsets[i] = getOffsetAtPoint(x, y, caretLine, alignment); } } setCaretOffsets(newCaretOffsets, newCaretOffsets[0] == 0 ? SWT.DEFAULT : 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(caretOffsets[0], 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(caretOffsets[0]); if (vchange && hchange && (newCaretOffset != caretOffsets[0] || newCaretAlignemnt != caretAlignment)) { setCaretOffsets(new int[] {newCaretOffset}, newCaretAlignemnt); if (select) doMouseSelection(); showCaret(); } if (!select) { setCaretOffsets(new int[] {newCaretOffset}, newCaretAlignemnt); clearSelection(true); } } /** * Updates the selection based on the caret position */ void doMouseSelection() { if (caretOffsets[0] <= selection[0].x || (caretOffsets[0] > selection[0].x && caretOffsets[0] < selection[0].y && selectionAnchors[0] == selection[0].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 < selectionAnchors[0] && selectionAnchors[0] == selection[0].x) { selectionAnchors[0] = doubleClickSelection.y; } else if (newCaretOffset > selectionAnchors[0] && selectionAnchors[0] == selection[0].y) { selectionAnchors[0] = doubleClickSelection.x; } if (0 <= x && x < clientAreaWidth) { boolean wordSelect = (clickCount & 1) == 0; if (caretOffsets[0] == selection[0].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. *

* 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. *

* * @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 = getFirstCaretLine(); 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); setCaretOffsets(new int[] {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 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()) { for (int caretOffset : caretOffsets) { int caretLine = content.getLineAtOffset(caretOffset); TextLayout layout = renderer.getTextLayout(caretLine); int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine); lineIndex = getVisualLineIndex(layout, offsetInLine); caretHeight += layout.getLineBounds(lineIndex).y; renderer.disposeTextLayout(layout); } } lineIndex = getFirstCaretLine(); 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); setCaretOffsets(new int[] {offset}, alignment[0]); if (select) doSelection(ST.COLUMN_NEXT); height = getAvailableHeightBellow(height); scrollVertical(height, true); if (height == 0) setCaretLocations(); } 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 if (caretOffsets.length == 1) { // pageEnd doesn't make sense with multi-carets 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 (caretOffsets[0] < bottomOffset) { setCaretOffsets(new int[] {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 (caretOffsets[0] > topOffset) { setCaretOffsets(new int[] {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 = getFirstCaretLine(); 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); setCaretOffsets(new int[] {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 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()) { for (int caretOffset : caretOffsets) { int caretLine = content.getLineAtOffset(caretOffset); 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 = getFirstCaretLine(); 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); setCaretOffsets(new int[] {offset}, alignment[0]); if (select) doSelection(ST.COLUMN_PREVIOUS); height = getAvailableHeightAbove(height); scrollVertical(-height, true); if (height == 0) setCaretLocations(); } showCaret(); int hScrollChange = oldHScrollOffset - horizontalScrollOffset; columnX = oldColumnX + hScrollChange; } /** * Updates the selection to extend to the current caret position. */ void doSelection(int direction) { if (caretOffsets.length != selection.length) { return; } if (selectionAnchors.length != selection.length) { selectionAnchors = new int[selection.length]; Arrays.fill(selectionAnchors, -1); } boolean selectionChanged = false; Point[] newSelection = Arrays.stream(selection).map(p -> new Point(p.x, p.y)).toArray(Point[]::new); boolean[] caretAtBeginning = new boolean[newSelection.length]; for (int i = 0; i < caretOffsets.length; i++) { int caretOffset = caretOffsets[i]; Point currentSelection = newSelection[i]; int selectionAnchor = selectionAnchors[i]; if (selectionAnchor == -1) { selectionAnchor = selectionAnchors[i] = currentSelection.x; } int redrawStart = -1; int redrawEnd = -1; if (direction == ST.COLUMN_PREVIOUS) { if (caretOffset < currentSelection.x) { caretAtBeginning[i] = true; // grow selection redrawEnd = currentSelection.x; redrawStart = currentSelection.x = caretOffset; // check if selection has reversed direction if (currentSelection.y != selectionAnchor) { redrawEnd = currentSelection.y; currentSelection.y = selectionAnchor; } // test whether selection actually changed. Fixes 1G71EO1 } else if (selectionAnchor == currentSelection.x && caretOffset < currentSelection.y) { // caret moved towards selection anchor (left side of selection). // shrink selection redrawEnd = currentSelection.y; redrawStart = currentSelection.y = caretOffset; } } else { if (caretOffset > currentSelection.y) { // grow selection redrawStart = currentSelection.y; redrawEnd = currentSelection.y = caretOffset; // check if selection has reversed direction if (currentSelection.x != selectionAnchor) { redrawStart = currentSelection.x; currentSelection.x = selectionAnchor; } // test whether selection actually changed. Fixes 1G71EO1 } else if (selectionAnchor == currentSelection.y && caretOffset > currentSelection.x) { // caret moved towards selection anchor (right side of selection). // shrink selection caretAtBeginning[i] = true; redrawStart = currentSelection.x; redrawEnd = currentSelection.x = caretOffset; } } if (redrawStart != -1 && redrawEnd != -1) { internalRedrawRange(redrawStart, redrawEnd - redrawStart); selectionChanged = true; } } if (selectionChanged) { int[] regions = new int[newSelection.length * 2]; for (int i = 0; i < newSelection.length; i++) { Point p = newSelection[i]; if (caretAtBeginning[i]) { regions[2 * i] = p.y; regions[2 * i + 1] = p.x - p.y; } else { regions[2 * i] = p.x; regions[2 * i + 1] = p.y - p.x; } } setSelection(regions, false, blockSelection); 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[] newCarets = Arrays.copyOf(caretOffsets, caretOffsets.length); int newAlignment = Integer.MIN_VALUE; for (int i = 0; i < caretOffsets.length; i++) { int caretOffset = caretOffsets[i]; int caretLine = content.getLineAtOffset(caretOffset); int lineOffset = content.getOffsetAtLine(caretLine); int offsetInLine = caretOffset - lineOffset; int offset; 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; newAlignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING; newCarets[i] = offset; } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) { caretLine++; offset = content.getOffsetAtLine(caretLine); newAlignment = PREVIOUS_OFFSET_TRAILING; newCarets[i] = offset; } } if (newAlignment > Integer.MIN_VALUE) { setCaretOffsets(newCarets, newAlignment); 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[] newCarets = Arrays.copyOf(caretOffsets, caretOffsets.length); for (int i = 0; i < caretOffsets.length; i++) { int caretOffset = caretOffsets[i]; int caretLine = content.getLineAtOffset(caretOffset); int lineOffset = content.getOffsetAtLine(caretLine); int offsetInLine = caretOffset - lineOffset; if (offsetInLine > 0) { newCarets[i] = getClusterPrevious(caretOffset, caretLine); } else if (caretLine > 0) { caretLine--; lineOffset = content.getOffsetAtLine(caretLine); newCarets[i] = lineOffset + content.getLine(caretLine).length(); } } if (!Arrays.equals(caretOffsets, newCarets)) { setCaretOffsets(newCarets, 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(caretOffsets[0]).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(caretOffsets[0]).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. *

* 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. *

* 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 doSelectionPageDown(int pixels) { int oldColumnX = columnX = getPointAtOffset(caretOffsets[0]).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. *

* 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. *

* 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 doSelectionPageUp(int pixels) { if (caretOffsets.length > 1) { // operation doesn't make sense for multi-carets return; } int oldColumnX = columnX = getPointAtOffset(caretOffsets[0]).x; doPageUp(true, pixels); columnX = oldColumnX; } /** * Moves the caret to the end of the next word . */ void doSelectionWordNext() { int[] offsets = Arrays.stream(caretOffsets).map(offset -> getWordNext(offset, SWT.MOVEMENT_WORD)).toArray(); // don't change caret position if in single line mode and the cursor // would be on a different line. fixes 5673 if (!isSingleLine()) { // Force symmetrical movement for word next and previous. Fixes 14536 setCaretOffsets(offsets, OFFSET_LEADING); showCaret(); } else { int[] linesForCurrentCarets = Arrays.stream(caretOffsets).map(offset -> content.getLineAtOffset(offset)).toArray(); int[] linesForNewCarets = Arrays.stream(offsets).map(offset -> content.getLineAtOffset(offset)).toArray(); if (Arrays.equals(linesForCurrentCarets, linesForNewCarets)) { // Force symmetrical movement for word next and previous. Fixes 14536 setCaretOffsets(offsets, OFFSET_LEADING); showCaret(); } } } /** * Moves the caret to the start of the previous word. */ void doSelectionWordPrevious() { setCaretOffsets(Arrays.stream(caretOffsets).map(offset -> getWordPrevious(offset, SWT.MOVEMENT_WORD)).toArray(), 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() { setCaretOffsets(Arrays.stream(caretOffsets).map(offset -> getClusterPrevious(offset, content.getLineAtOffset(offset))).toArray(), 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() { setCaretOffsets(Arrays.stream(caretOffsets).map(offset -> getClusterNext(offset, content.getLineAtOffset(offset))).toArray(), 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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) { setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.y).toArray(), 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 (Arrays.stream(selection).anyMatch(p -> p.x != p.y)) { setCaretOffsets(Arrays.stream(selection).mapToInt(sel -> sel.x).toArray(), 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @since 3.5 */ public Rectangle getBlockSelectionBounds() { Rectangle rect; if (blockSelection && blockXLocation != -1) { rect = getBlockSelectionRectangle(); } else { Point startPoint = getPointAtOffset(selection[0].x); Point endPoint = getPointAtOffset(selection[0].y); int height = getLineHeight(selection[0].y); rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y); if (selection[0].x == selection[0].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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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 (Arrays.binarySearch(caretOffsets, offset) >= 0 && !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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public int getCaretOffset() { checkWidget(); return caretOffsets[0]; } /** * 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public int getHorizontalPixel() { checkWidget(); return horizontalScrollOffset; } /** * Returns the line indentation of the widget. * * @return the line indentation * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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. * * @param lineIndex index of the line to return. * @return the line text without delimiters * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())
  • *
* @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public String getLineDelimiter() { checkWidget(); return content.getLineDelimiter(); } /** * Returns the line height. *

* Note: this API should not be used if a StyleRange attribute causes lines to * have different heights (i.e. different fonts, rise, etc). *

* * @return line height in points. * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @since 3.2 */ public int getLineSpacing() { checkWidget(); return lineSpacing; } /** * Returns the line style data for the given line or null if there is * none. *

* If there is a LineStyleListener but it does not set any styles, * the StyledTextEvent.styles field will be initialized to an empty * array. *

* * @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 index. * * @param index the index of the line * * @return the tab stops for the line * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
* * @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 index. * * @param index the index of the line * * @return the wrap indentation * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the index is invalid
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())
  • *
*/ public Point getLocationAtOffset(int offset) { checkWidget(); if (offset < 0 || offset > getCharCount()) { SWT.error(SWT.ERROR_INVALID_RANGE); } return getPointAtOffset(offset); } /** * Returns true 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())
  • *
* @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. *

* 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. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when point is null
  • *
  • ERROR_INVALID_ARGUMENT when there is no character at the specified location
  • *
* * @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. *

* 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. *

* 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when point is null
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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. *

* 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 getStyleRanges(int, int, boolean). *

* * @return the ranges or an empty array if a LineStyleListener has been set. * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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. *

* 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 getStyleRanges(int, int, boolean). *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE if start or length are outside the widget content
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @since 3.5 */ public int getRightMargin() { checkWidget(); return rightMargin; } /** * Returns the selection. *

* 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 *

*

* It is usually better to use {@link #getSelectionRanges()} which better * support multiple selection and carets and block selection. *

* * @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 #getSelectionRanges * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public Point getSelection() { checkWidget(); return new Point(selection[0].x, selection[0].y); } /** * Returns the selection. *

* It is usually better to use {@link #getSelectionRanges()} which better * support multiple selection and carets and block 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @see #getSelectionRanges */ public Point getSelectionRange() { checkWidget(); return new Point(selection[0].x, selection[0].y - selection[0].x); } /** * Returns the selected ranges of text. * If block is enabled, return the ranges that are inside the block selection rectangle. *

* 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 selections. * * @return the ranges array * * @exception SWTException

    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @since 3.5 * @see #setSelectionRanges(int[]) */ 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; } int[] res = new int[2 * selection.length]; int index = 0; for (Point p : selection) { res[index++] = p.x; res[index++] = p.y - p.x; } return res; } /** * Returns the receiver's selection background color. * * @return the selection background color * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public int getSelectionCount() { checkWidget(); if (blockSelection && blockXLocation != -1) { return getBlockSelectionText(content.getLineDelimiter()).length(); } return Arrays.stream(selection).collect(Collectors.summingInt(sel -> sel.y - sel.x)); } /** * Returns the receiver's selection foreground color. * * @return the selection foreground color * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public String getSelectionText() { checkWidget(); if (blockSelection && blockXLocation != -1) { return getBlockSelectionText(content.getLineDelimiter()); } return Arrays.stream(selection).map(sel -> content.getTextRange(sel.x, sel.y - sel.x)).collect(Collectors.joining()); } 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 (char segmentsChar : segmentsChars) { if (segmentsChar == '\n' || segmentsChar == '\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. *

* 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. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the offset is invalid
  • *
*/ 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. *

* 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. *

* 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, getStyleRanges(boolean) * can be used to get the styles without the ranges. *

* * @return the styles or an empty array if a LineStyleListener has been set. * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @see #getStyleRanges(boolean) */ public StyleRange[] getStyleRanges() { checkWidget(); return getStyleRanges(0, content.getCharCount(), true); } /** * Returns the styles. *

* 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. *

* Note: When includeRanges is true, the start and length * fields of each StyleRange will be valid, however the StyleRange * objects may need to be cloned. When includeRanges is * false, getRanges(int, int) can be used to get the * associated ranges. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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. *

* 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. *

* 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, getStyleRanges(int, int, boolean) * can be used to get the styles without the ranges. *

* @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 StyleRange will have a starting offset >= start * and the last returned StyleRange will have an ending * offset <= start + length - 1 * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when start and/or end are outside the widget content
  • *
* * @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. *

* 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. *

* Note: When includeRanges is true, the start and length * fields of each StyleRange will be valid, however the StyleRange * objects may need to be cloned. When includeRanges is * false, getRanges(int, int) can be used to get the * associated ranges. *

* * @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 StyleRange will have a starting offset >= start * and the last returned StyleRange will have an ending * offset >= start + length - 1 * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when start and/or end are outside the widget content
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @see #getTabStops() */ public int getTabs() { checkWidget(); return tabLength; } /** * Returns the tab list of the receiver. * * @return the tab list * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when start and/or end are outside the widget content
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when start and/or end are outside the widget content
  • *
* @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 = Integer.MAX_VALUE, 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); } if (left == Integer.MAX_VALUE) { left = 0; } 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when start and/or length are outside the widget content
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public int getTextLimit() { checkWidget(); return textLimit; } /** * Gets the top index. *

* 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. *

* * @return the index of the top line * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public int getTopIndex() { checkWidget(); return topIndex; } /** * Returns the top margin. * * @return the top margin. * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @since 3.5 */ public int getTopMargin() { checkWidget(); return topMargin; } /** * Gets the top SWT logical point. *

* 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. *

* * @return SWT logical point position of the top line * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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.getCachedLineHeight(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(getFirstCaretLine()); 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 = getFirstCaretLine(); int lineOffset = content.getOffsetAtLine(caretLine); String line = content.getLine(caretLine); int offset = caretOffsets[0] - 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 first/top caret is on. */ int getFirstCaretLine() { return content.getLineAtOffset(caretOffsets[0]); } 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @see #getLineWrapIndent(int) * * @since 3.6 */ public int getWrapIndent() { checkWidget(); return wrapIndent; } /** * Returns the location of the given offset. *

* NOTE: Does not return correct values for true italic fonts (vs. slanted fonts). *

* * @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 && (Arrays.binarySearch(caretOffsets, offset) < 0 || Arrays.stream(selection).allMatch(p -> p.x == p.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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when string is null
  • *
*/ 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 (String line : lines) { int length = line.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 = getFirstCaretLine(); left = right = getPointAtOffset(caretOffsets[0]).x; } start = caretOffsets[0]; int caretLine = getFirstCaretLine(); 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, this::handleVerticalScroll); } if (horizontalBar != null) { horizontalBar.addListener(SWT.Selection, this::handleHorizontalScroll); } } 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[0].x; event.end = selection[0].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, ""); setCaretOffsets(new int[] {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(); for (int caretOffset : caretOffsets) { int lineIndex = content.getLineAtOffset(caretOffset); 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; } setCaretOffsets(new int[] {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 (carets != null) { for (Caret caret : carets) { if (caret != null) { caret.dispose(); } } carets = 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[0].x, selection[0].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[0].x || clipboardSelection.y != selection[0].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(caretOffsets[0])); event.x = point.x; event.y = point.y + getLineHeight(caretOffsets[0]); } } /** * 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; boolean addSelection = (event.stateMask & SWT.MOD3) != 0; if (clickCount == 1) { if (addSelection && !blockSelection) { int offset = getOffsetAtPoint(event.x, event.y, null); addSelection(offset, 0); sendSelectionEvent(); } else { boolean expandSelection = (event.stateMask & SWT.MOD2) != 0; doMouseLocationChange(event.x, event.y, expandSelection); } } 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) { String line = content.getLine(lineIndex); int lineLength = line.length(); int min = blockSelection ? lineOffset : 0; int max = blockSelection ? lineOffset + lineLength : content.getCharCount(); final Point offsetPoint = getPointAtOffset(offset); if (event.x > offsetPoint.x && offset < Math.min(max, lineOffset + lineLength) // Not beyond EOL && !Character.isWhitespace(line.charAt(offset - lineOffset))) { // Not on whitespace offset++; } int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START)); int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END)); int[] regions = new int[2]; if (addSelection) { int[] current = getSelectionRanges(); regions = Arrays.copyOf(current, current.length + 2); } regions[regions.length - 2] = start; regions[regions.length - 1] = end - start; setSelection(regions, 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); } int[] regions = new int[2]; if (addSelection) { int[] current = getSelectionRanges(); regions = Arrays.copyOf(current, current.length + 2); } regions[regions.length - 2] = lineOffset; regions[regions.length - 1] = lineEnd - lineOffset; setSelection(regions, false, false); sendSelectionEvent(); } } doubleClickSelection = new Point(selection[0].x, selection[0].y); showCaret(); } } } void addSelection(int offset, int length) { int[] ranges = getSelectionRanges(); ranges = Arrays.copyOf(ranges, ranges.length + 2); ranges[ranges.length - 2] = offset; ranges[ranges.length - 1] = length; setSelection(ranges, true, true); } /** * 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; final int endY = event.y + event.height; GC gc = event.gc; Color background = getBackground(); Color foreground = getForeground(); if (endY > 0) { final int startLine = getLineIndex(event.y); final int endLine = isSingleLine() ? 1 : content.getLineCount(); final int x = leftMargin - horizontalScrollOffset; int y = getLinePixel(startLine); y += renderer.drawLines(startLine, endLine, x, y, endY, 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); } } if(carets != null) { for (int i = 1; i < carets.length; i++) { //skip 1st caret that's already drawn Caret caret = carets[i]; if (caret.isVisible()) { if (caret.getImage() != null) { gc.drawImage(caret.getImage(), caret.getBounds().x, caret.getBounds().y); } else { gc.drawRectangle(caret.getBounds().x, caret.getBounds().y, caret.getBounds().width, getLineHeight(caretOffsets[i])); } } } } // 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); } setCaretLocations(); } 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) { // updating the compositionOffset of the ongoing IME if last IMEs text is deleted meanwhile // for example 1.insert IME text 2.open IME menu 3. ctrl+backspace => 4. commit IME would result in IllegalArgumentException int compositionOffset = ime.getCaretOffset() + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount; // workaround: while ongoing IME non-ime text is deleted it may result in too big compositionOffset // for example 1.insert IME text 2.insert space 3.open IME menu 4. ctrl+backspace twice => 5. commit IME would result in IllegalArgumentException compositionOffset= Math.min(compositionOffset, getCharCount()); ime.setCompositionOffset(compositionOffset); } 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; int tooBigOffsets = 0; while (tooBigOffsets < caretOffsets.length && caretOffsets[caretOffsets.length - 1 - tooBigOffsets] > newEndOfText) { tooBigOffsets++; } if (tooBigOffsets != 0) { int[] newCaretOffsets = Arrays.copyOf(caretOffsets, caretOffsets.length - tooBigOffsets + 1); newCaretOffsets[newCaretOffsets.length - 1] = newEndOfText; setCaretOffsets(newCaretOffsets, 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)); addListener(SWT.Dispose, event -> { // Issue 448: A 3rd party program that uses Accessible may // cache obtained connection and query it even after StyledText // is disposed. Disconnect all listeners to avoid querying a // disposed control. acc.removeAccessibleControlListener(accControlAdapter); acc.removeAccessibleAttributeListener(accAttributeAdapter); acc.removeAccessibleEditableTextListener(accEditableTextListener); acc.removeAccessibleTextListener(accTextExtendedAdapter); acc.removeAccessibleListener(accAdapter); }); } /* * 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 true if any text in the widget is selected, * and false otherwise. * * @return the text selection state * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @since 3.103 */ public boolean isTextSelected() { checkWidget(); if (blockSelection && blockXLocation != -1) { Rectangle rect = getBlockSelectionPosition(); return !rect.isEmpty(); } return Arrays.stream(selection).anyMatch(range -> range.x != range.y); } /** * 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. *
    *
  • event.start - the replace start offset
  • *
  • event.end - the replace end offset
  • *
  • event.text - the new text
  • *
* @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(Arrays.stream(selection).map(sel -> { if (sel.y < event.start || sel.x > event.end) { return sel; } else { // current range edited return new Point(event.start + event.text.length(), event.start + event.text.length()); } }).flatMapToInt(p -> IntStream.of(p.x, p.y - p.x)).toArray(), 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 DND.CLIPBOARD * 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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); setCaretOffsets(new int[] {offset}, SWT.DEFAULT); clearBlockSelection(true, true); setCaretLocations(); return; } else if (getSelectionRanges().length / 2 > 1) { // multi selection insertMultiSelectionText(text); setCaretLocations(); return; } Event event = new Event(); event.start = selection[0].x; event.end = selection[0].y; String delimitedText = getModelDelimitedText(text); if (textLimit > 0) { int uneditedTextLength = getCharCount() - (selection[0].y - selection[0].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 insertMultiSelectionText(String text) { String[] blocks = text.split(PlatformLineDelimiter); int[] ranges = getSelectionRanges(); for (int i = ranges.length / 2 - 1; i >= 0; i --) { int offset = ranges[2 * i]; int length = ranges[2 * i + 1]; String toPaste = blocks.length > i ? blocks[i] : blocks[blocks.length - 1]; Event event = new Event(); event.start = offset; event.end = offset + length; event.text = toPaste; 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[0].x; e.end = selection[0].y; e.text = getModelDelimitedText(text); sendKeyEvent(e); } } /** * Prints the widget's text to the default printer. * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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. *

* The runnable may be run in a non-UI thread. *

* * @param printer the printer to print to * * @return a Runnable for printing the receiver's text * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when printer is null
  • *
*/ 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. *

* The runnable may be run in a non-UI thread. *

* * @param printer the printer to print to * @param options print options to use during printing * * @return a Runnable for printing the receiver's text * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when printer or options is null
  • *
* @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. *

* Recalculates the content width for all lines in the bounds. * When a LineStyleListener is used a redraw call * is the only notification to the widget that styles have changed * and that the content width may have changed. *

* * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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 all flag * is true, any children of the receiver which * intersect with the specified area will also paint their * intersecting areas. If the all flag is * false, the children will not be painted. *

* Marks the content width of all lines in the specified rectangle * as unknown. Recalculates the content width of all visible lines. * When a LineStyleListener is used a redraw call * is the only notification to the widget that styles have changed * and that the content width may have changed. *

* * @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 true if children should redraw, and false otherwise * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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 redrawLine : redrawLines) { int lineIndex = redrawLine; 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when start and/or end are outside the widget content
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* * @since 2.0 */ public void removeBidiSegmentListener(BidiSegmentListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(ST.LineGetSegments, listener); resetCache(0, content.getLineCount()); setCaretLocations(); super.redraw(); } /** * Removes the specified caret listener. * * @param listener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* * @since 3.5 */ public void removeCaretListener(CaretListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(ST.CaretMoved, listener); } /** * Removes the specified extended modify listener. * * @param extendedModifyListener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) { checkWidget(); if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(ST.ExtendedModify, extendedModifyListener); } /** * Removes the specified line background listener. * * @param listener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void removeLineBackgroundListener(LineBackgroundListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(ST.LineGetBackground, listener); } /** * Removes the specified line style listener. * * @param listener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void removeLineStyleListener(LineStyleListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(ST.LineGetStyle, listener); setCaretLocations(); } /** * Removes the specified modify listener. * * @param modifyListener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void removeModifyListener(ModifyListener modifyListener) { checkWidget(); if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(SWT.Modify, modifyListener); } /** * Removes the specified listener. * * @param listener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* @since 3.2 */ public void removePaintObjectListener(PaintObjectListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(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
    *
  • ERROR_NULL_ARGUMENT - if the listener is null
  • *
* @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @see SelectionListener * @see #addSelectionListener */ public void removeSelectionListener(SelectionListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(SWT.Selection, listener); } /** * Removes the specified verify listener. * * @param verifyListener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void removeVerifyListener(VerifyListener verifyListener) { checkWidget(); if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(SWT.Verify, verifyListener); } /** * Removes the specified key verify listener. * * @param listener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ public void removeVerifyKeyListener(VerifyKeyListener listener) { if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(ST.VerifyKey, listener); } /** * Removes the specified word movement listener. * * @param listener the listener which should no longer be notified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
* * @see MovementEvent * @see MovementListener * @see #addWordMovementListener * * @since 3.3 */ public void removeWordMovementListener(MovementListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeTypedListener(ST.WordNext, listener); removeTypedListener(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. *

* 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, setStyleRanges(int, int, int[], StyleRange[]) * can be used to share styles and reduce memory usage. *

* Should not be called if a LineStyleListener has been set since the * listener maintains the styles. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())
  • *
  • ERROR_NULL_ARGUMENT when ranges is null
  • *
* * @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. *

* NOTE: During the replace operation the current selection is * changed as follows: *

*
    *
  • selection before replaced text: selection unchanged *
  • selection after replaced text: adjust the selection so that same text * remains selected *
  • selection intersects replaced text: selection is cleared and caret * is placed after inserted text *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())
  • *
  • 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
  • *
  • ERROR_NULL_ARGUMENT when string is null
  • *
*/ 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(); setCaretOffsets(new int[] {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); setCaretLocations(); super.redraw(); } void resetBidiData() { caretDirection = SWT.NULL; resetCache(0, content.getLineCount()); setCaretLocations(); keyActionMap.clear(); createKeyBindings(); super.redraw(); } void resetCache(SortedSet 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 = Arrays.stream(caretOffsets).mapToObj(offset -> new Point(offset, offset)).toArray(Point[]::new); selectionAnchors = Arrays.copyOf(caretOffsets, caretOffsets.length); 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; for (Control child : getChildren()) { 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; setCaretLocations(); 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 (Control child : children) { Rectangle rect = child.getBounds(); child.setLocation(rect.x, rect.y + deltaY); } verticalScrollOffset += pixels; calculateTopIndex(pixels); super.redraw(); } else { calculateTopIndex(pixels); super.redraw(); } setCaretLocations(); 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 (Arrays.stream(caretOffsets).noneMatch(caretOffset -> caretOffset == accCaretOffset)) { accCaretOffset = caretOffsets[0]; getAccessible().textCaretMoved(caretOffsets[0]); } } 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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. *
    *
  • event.start - the replace start offset
  • *
  • event.end - the replace end offset
  • *
  • event.text - the new text
  • *
*/ 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. *

* 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. *

* * @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[0].x; event.y = selection[selection.length - 1].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); setCaretLocations(); super.redraw(); } } /** * Sets the alignment of the widget. The argument should be one of SWT.LEFT, * SWT.CENTER or SWT.RIGHT. The alignment applies for all lines. *

* Note that if SWT.MULTI is set, then SWT.WRAP must also be set * in order to stabilize the right edge before setting alignment. *

* * @param alignment the new alignment * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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()); setCaretLocations(); 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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 (disabledBg.getRGBA()); backgroundDisabled = true; } } } customBackground = color != null && !this.insideSetEnableCall && !backgroundDisabled; background = color; super.setBackground(color); resetCache(0, content.getLineCount()); setCaretLocations(); 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[0].x; int end = selection[0].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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when point is null
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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(new int[] {start, end - start}, false, false); setCaretLocations(); 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); setCaretOffsets(new int[] {offset}, alignment[0]); if (blockXAnchor == -1) { blockXAnchor = blockXLocation; blockYAnchor = blockYLocation; selectionAnchors[0] = caretOffsets[0]; } doBlockSelection(sendEvent); } void setBlockSelectionLocation (int anchorX, int anchorY, int x, int y, boolean sendEvent) { int verticalScrollOffset = getVerticalScrollOffset(); blockXAnchor = anchorX + horizontalScrollOffset; blockYAnchor = anchorY + verticalScrollOffset; selectionAnchors[0] = 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; setCaretOffsets(new int[] {offset}, SWT.DEFAULT); if (blockXAnchor == -1) { blockXAnchor = blockXLocation; blockYAnchor = blockYLocation; selectionAnchors[0] = caretOffsets[0]; } 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; selectionAnchors[0] = 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ @Override public void setCaret(Caret caret) { checkWidget (); super.setCaret(caret); caretDirection = SWT.NULL; if (caret != null) { setCaretLocations(); if(carets != null) { for (int i = 1; i < carets.length; i++) { carets[i].dispose(); } } carets = new Caret[] {caret}; } else { carets = null; } } /** * 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @deprecated use BidiSegmentListener instead. */ @Deprecated public void setBidiColoring(boolean mode) { checkWidget(); bidiColoring = mode; } /** * Sets the bottom margin. * * @param bottomMargin the bottom margin. * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @since 3.5 */ public void setBottomMargin (int bottomMargin) { checkWidget(); setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin); } /** * Moves the Caret to the current caret offset. */ void setCaretLocations() { Point[] newCaretPos = Arrays.stream(caretOffsets).mapToObj(this::getPointAtOffset).toArray(Point[]::new); setCaretLocations(newCaretPos, getCaretDirection()); } void setCaretLocations(final Point[] locations, int direction) { Caret firstCaret = getCaret(); if (firstCaret != null) { if (carets == null || carets.length == 0) { carets = new Caret[] { firstCaret }; } final boolean isDefaultCaret = firstCaret == defaultCaret; if (locations.length > carets.length) { int formerCaretCount = carets.length; carets = Arrays.copyOf(carets, locations.length); for (int i = formerCaretCount; i < carets.length; i++) { carets[i] = new Caret(this, firstCaret.getStyle()); carets[i].setImage(firstCaret.getImage()); carets[i].setFont(firstCaret.getFont()); carets[i].setSize(firstCaret.getSize()); } } else if (locations.length < carets.length) { for (int i = locations.length; i < carets.length; i++) { carets[i].dispose(); } carets = Arrays.copyOf(carets, locations.length); } for (int i = Math.min(caretOffsets.length, locations.length)-1; i>=0; i--) { // reverse order, seee bug 579028#c7 final Caret caret = carets[i]; final int caretOffset = caretOffsets[i]; final Point location = locations[i]; 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 = content.getLineAtOffset(caretOffset); 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(); super.redraw(); } columnX = locations[0].x; } /** * Sets the caret offset. * * @param offset caret offset, relative to the first character in the text. * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • 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) *
*/ public void setCaretOffset(int offset) { checkWidget(); int length = getCharCount(); if (length > 0 && !Arrays.equals(caretOffsets, new int[] {offset})) { 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); } } setCaretOffsets(new int[] {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); } } setCaretLocations(); } void setCaretOffsets(int[] newOffsets, int alignment) { if (newOffsets.length > 1) { newOffsets = Arrays.stream(newOffsets).distinct().sorted().toArray(); } if (!Arrays.equals(caretOffsets, newOffsets)) { caretOffsets = newOffsets; if (isListening(ST.CaretMoved)) { StyledTextEvent event = new StyledTextEvent(content); event.end = caretOffsets[caretOffsets.length - 1]; 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, HTML, and RTF formats. * * @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 { try { RTFTransfer rtfTransfer = RTFTransfer.getInstance(); RTFWriter rtfWriter = new RTFWriter(this, start, length); String rtfText = getPlatformDelimitedText(rtfWriter); HTMLTransfer htmlTransfer = HTMLTransfer.getInstance(); HTMLWriter htmlWriter = new HTMLWriter(this, start, length, content); String htmlText = getPlatformDelimitedText(htmlWriter); htmlText = "" +htmlText; //cause extra memory pressure to fail fast instead of failing in HTMLTransfer.javaToNative() data = new Object[]{rtfText, htmlText, plainText}; types = new Transfer[]{rtfTransfer, htmlTransfer, plainTextTransfer}; } catch (OutOfMemoryError oome) { // Adding RTF and HTML text may increase size by factor > 15 // fall back: copy plain text data = new Object[] { plainText }; types = new Transfer[] { plainTextTransfer }; clipboard.setContents(data, types, clipboardType); OutOfMemoryError customOome = new OutOfMemoryError( "Out of Memory: Copied only plain text (" + plainText.lines().count() + " lines)."); customOome.initCause(oome); // Still throw as it is likely that other threads silently failed too, but at least copied text is not lost throw customOome; } } 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when listener is null
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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 && editable) { if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND)); if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); } else if(!enabled) { if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND)); if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND)); } else if(!editable) { if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND)); if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); } } finally { this.insideSetEnableCall = false; } } @Override public boolean setFocus() { boolean focusGained = super.setFocus(); if (focusGained && hasMultipleCarets()) { // Multiple carets need to update their drawing. See bug 579179 setCaretLocations(); } return focusGained; } private boolean hasMultipleCarets() { return carets != null && carets.length > 1; } /** * See {@link TextLayout#setFixedLineMetrics} * * @since 3.125 */ public void setFixedLineMetrics(FontMetrics metrics) { renderer.setFixedLineMetrics (metrics); } /** * Sets a new font to render text with. *

* NOTE: Italic fonts are not supported unless they have no overhang * and the same baseline as regular fonts. *

* * @param font new font * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ @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; setCaretLocations(); 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 (disabledFg.getRGBA()); foregroundDisabled = true; } } } customForeground = color != null && !this.insideSetEnableCall && !foregroundDisabled; foreground = color; super.setForeground(color); resetCache(0, content.getLineCount()); setCaretLocations(); super.redraw(); } /** * Sets the horizontal scroll offset relative to the start of the line. * Do nothing if there is no text set. *

* NOTE: The horizontal index is reset to 0 when new text is set in the * widget. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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. *

* NOTE: The horizontal SWT logical point offset is reset to 0 when new text * is set in the widget. *

* * @param pixel horizontal SWT logical point offset relative to the start * of the line. * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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. *

* 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. *

* * @param indent the new indent * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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()); setCaretLocations(); super.redraw(); } /** * Sets whether the widget should justify lines. * * @param justify whether lines should be justified * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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()); setCaretLocations(); super.redraw(); } /** * Maps a key to an action. *

* One action can be associated with N keys. However, each key can only * have one action (key:action is N:1 relation). *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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 SWT.LEFT, * SWT.CENTER or SWT.RIGHT. *

* Note that if SWT.MULTI is set, then SWT.WRAP must also be set * in order to stabilize the right edge before setting alignment. *

* Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. *

* 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. *

* 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line range is invalid
  • *
* @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); if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) { setCaretLocations(); } setAlignment(); } /** * Sets the background color of the specified lines. *

* 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. *

* Should not be called if a LineBackgroundListener has been set since the * listener maintains the line backgrounds. *

* 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. *

* 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 color is applied to, 0 based * @param lineCount number of lines the color applies to. * @param background line background color * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line range is invalid
  • *
*/ 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. *

* Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. *

* 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. *

* 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 bullet is applied to, 0 based * @param lineCount number of lines the bullet applies to. * @param bullet line bullet * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line range is invalid
  • *
* @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); if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) { setCaretLocations(); } } /** * 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. *

* Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. *

* 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. *

* 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 indent is applied to, 0 based * @param lineCount number of lines the indent applies to. * @param indent line indent * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line range is invalid
  • *
* @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); if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) { setCaretLocations(); } } /** * Sets the vertical indent of the specified lines. *

* Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. *

* 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. *

* 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. *

* Setting both line spacing and vertical indent on a line would result in the * spacing and indent add up for the line. *

* * @param lineIndex line index the vertical indent is applied to, 0 based * @param verticalLineIndent vertical line indent * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line index is invalid
  • *
* @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); } int previousVerticalIndent = renderer.getLineVerticalIndent(lineIndex); if (verticalLineIndent == previousVerticalIndent) { return; } int initialTopPixel = getTopPixel(); int initialTopIndex = getPartialTopIndex(); int initialBottomIndex = getPartialBottomIndex(); int verticalIndentDiff = verticalLineIndent - previousVerticalIndent; renderer.setLineVerticalIndent(lineIndex, verticalLineIndent); this.hasVerticalIndent = verticalLineIndent != 0 || renderer.hasVerticalIndent(); ScrollBar verticalScrollbar = getVerticalBar(); if (lineIndex < initialTopIndex) { verticalScrollOffset += verticalIndentDiff; // just change value, don't actually scroll/redraw if (verticalScrollbar != null) { verticalScrollbar.setSelection(verticalScrollOffset); verticalScrollbar.setMaximum(verticalScrollbar.getMaximum() + verticalIndentDiff); } } else if (lineIndex > initialBottomIndex) { if (verticalScrollbar != null) { verticalScrollbar.setMaximum(verticalScrollbar.getMaximum() + verticalIndentDiff); } } else { resetCache(lineIndex, 1); if((initialTopIndex == 0) && (initialBottomIndex == (content.getLineCount() - 1))) { // not scrollable editor setCaretLocations(); redrawLines(lineIndex, getBottomIndex() - lineIndex + 1, true); } else if (getFirstCaretLine() >= initialTopIndex && getFirstCaretLine() <= initialBottomIndex) { // caret line with caret mustn't move if (getFirstCaretLine() < lineIndex) { redrawLines(lineIndex, getPartialBottomIndex() - lineIndex + 1, true); } else { setTopPixel(initialTopPixel + verticalIndentDiff); } } else { // move as few lines as possible if (lineIndex - getTopIndex() < getBottomIndex() - lineIndex) { setTopPixel(initialTopPixel + verticalIndentDiff); } else { // redraw below redrawLines(lineIndex, getPartialBottomIndex() - lineIndex + 1, true); } } setScrollBars(true); } } /** * Sets the justify of the specified lines. *

* Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. *

* 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. *

* 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 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line range is invalid
  • *
* @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); if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) { setCaretLocations(); } } /** * 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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()); setCaretLocations(); 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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); } } } setCaretLocations(); super.redraw(); } /** * Sets the tab stops of the specified lines. *

* Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. *

* 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. *

* 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 justify is applied to, 0 based * @param lineCount number of lines the justify applies to. * @param tabStops tab stops * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line range is invalid
  • *
* @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); if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) { setCaretLocations(); } } /** * Sets the wrap indent of the specified lines. *

* Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. *

* 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. *

* 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 wrap indent is applied to, 0 based * @param lineCount number of lines the wrap indent applies to. * @param wrapIndent line wrap indent * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when the specified line range is invalid
  • *
* @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); if (Arrays.stream(caretOffsets).map(content::getLineAtOffset).anyMatch(caretLine -> startLine <= caretLine && caretLine < startLine + lineCount)) { setCaretLocations(); } } /** * Sets the color of the margins. * * @param color the new color (or null) * @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT - if the argument has been disposed
  • *
* @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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); setCaretLocations(); 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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 (caretOffsets[0] < doubleClickSelection.x) { selectionAnchors[0] = doubleClickSelection.y; } else if (caretOffsets[0] > doubleClickSelection.y) { selectionAnchors[0] = doubleClickSelection.x; } } } /** * Sets the orientation of the receiver, which must be one * of the constants SWT.LEFT_TO_RIGHT or SWT.RIGHT_TO_LEFT. * * @param orientation new orientation style * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • 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) *
*/ public void setSelection(int start) { // checkWidget test done in setSelectionRange setSelection(start, start); } /** * Sets the selection and scrolls it into view. *

* 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 *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when point is null
  • *
  • 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) *
*/ 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
    *
  • ERROR_INVALID_ARGUMENT - if the argument has been disposed
  • *
* @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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()); setCaretLocations(); 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. *

* Note that this is a HINT. Some platforms do not allow the application * to change the selection foreground color. *

* @param color the new color (or null) * * @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT - if the argument has been disposed
  • *
* @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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()); setCaretLocations(); super.redraw(); } /** * Sets the selection and scrolls it into view. *

* 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 *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • 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) *
*/ public void setSelection(int start, int end) { setSelectionRange(start, end - start); showSelection(); } /** * Sets the selection. *

* The new selection may not be visible. Call showSelection to scroll * the selection into view. *

* * @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[] regions, boolean sendEvent, boolean doBlock) { if (regions.length == 2 && selection.length == 1) { // single range before/after int start = regions[0]; int length = regions[1]; int end = start + length; if (start > end) { int temp = end; end = start; start = temp; } int selectionAnchor = selectionAnchors[0]; // is the selection range different or is the selection direction // different? if (selection[0].x != start || selection[0].y != end || (length > 0 && selectionAnchor != selection[0].x) || (length < 0 && selectionAnchor != selection[0].y)) { if (blockSelection && doBlock) { if (length < 0) { setBlockSelectionOffset(end, start, sendEvent); } else { setBlockSelectionOffset(start, end, sendEvent); } } else { int oldStart = selection[0].x; int oldLength = selection[0].y - selection[0].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[0].x, charCount); int redrawY = Math.min(selection[0].y, charCount); if (length < 0) { selectionAnchors[0] = selectionAnchor = selection[0].y = end; selection[0].x = start; setCaretOffsets(new int[] {start}, PREVIOUS_OFFSET_TRAILING); } else { selectionAnchors[0] = selectionAnchor = selection[0].x = start; selection[0].y = end; setCaretOffsets(new int[] {end}, PREVIOUS_OFFSET_TRAILING); } redrawX = Math.min(redrawX, selection[0].x); redrawY = Math.max(redrawY, selection[0].y); if (redrawY - redrawX > 0) { internalRedrawRange(redrawX, redrawY - redrawX); } if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) { sendSelectionEvent(); } sendAccessibleTextCaretMoved(); } } } else if (!blockSelection || !doBlock) { boolean caretAtEndOfSelection = regions[1] > 0; int charCount = content.getCharCount(); Point[] newRanges = new Point[regions.length / 2]; for (int i = 0; i < regions.length; i += 2) { int start = regions[i]; int length = regions[i + 1]; int end = start + length; if (start > end) { int temp = end; end = start; start = temp; } newRanges[i / 2] = new Point(start, end); } Arrays.sort(newRanges, SELECTION_COMPARATOR); // merge contiguous ranges int newRangeIndex = 0; for (Point range : newRanges) { if (newRangeIndex > 0) { Point previousRange = newRanges[newRangeIndex - 1]; if (previousRange.y >= range.x) { previousRange.y = Math.max(previousRange.y, range.y); } else { newRanges[newRangeIndex] = range; newRangeIndex++; } } else { newRanges[newRangeIndex] = range; newRangeIndex++; } } Point[] toRedraw = new Point[newRangeIndex + selection.length]; System.arraycopy(newRanges, 0, toRedraw, 0, newRangeIndex); System.arraycopy(selection, 0, toRedraw, newRangeIndex, selection.length); Arrays.sort(toRedraw, SELECTION_COMPARATOR); Point[] formerSelection = selection; selection = Arrays.copyOf(newRanges, newRangeIndex); Point currentToRedraw = null; for (Point p : toRedraw) { if (currentToRedraw == null) { currentToRedraw = new Point(p.x, p.y); } else if (currentToRedraw.y >= p.x - 1) { // expand if necessary currentToRedraw = new Point(currentToRedraw.x, Math.max(p.y, currentToRedraw.y)); } else { currentToRedraw = new Point(Math.max(0, currentToRedraw.x), Math.min(charCount, currentToRedraw.y)); internalRedrawRange(currentToRedraw.x, currentToRedraw.y - currentToRedraw.x); currentToRedraw = null; } } if (currentToRedraw != null) { currentToRedraw = new Point(Math.max(0, currentToRedraw.x), Math.min(charCount, currentToRedraw.y)); internalRedrawRange(currentToRedraw.x, currentToRedraw.y - currentToRedraw.x); } if (!caretAtEndOfSelection) { selectionAnchors = Arrays.stream(selection).mapToInt(p -> p.y).toArray(); setCaretOffsets(Arrays.stream(selection).mapToInt(p -> p.x).toArray(), PREVIOUS_OFFSET_TRAILING); } else { selectionAnchors = Arrays.stream(selection).mapToInt(p -> p.x).toArray(); setCaretOffsets(Arrays.stream(selection).mapToInt(p -> p.y).toArray(), PREVIOUS_OFFSET_TRAILING); } setCaretLocations(); if (sendEvent && !Arrays.equals(formerSelection, selection)) { sendSelectionEvent(); } sendAccessibleTextCaretMoved(); } } /** * Sets the selection. *

* 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. *

* * @param start offset of the first selected character * @param length number of characters to select * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • 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) *
*/ public void setSelectionRange(int start, int length) { setSelectionRanges(new int[] { start, length }); } /** * Sets the selected locations/ranges. *

* 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. *

* * @param ranges an array of offset/length pairs. * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT when either the start or the end of one selection range is inside a * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
  • *
  • ERROR_INVALID_ARGUMENT when ranges are null or size isn't valid (not pair)
  • *
* @see #getSelectionRanges() * @since 3.117 */ public void setSelectionRanges(int[] ranges) { checkWidget(); int contentLength = getCharCount(); if (ranges.length % 2 != 0) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } int[] fixedRanges = Arrays.copyOf(ranges, ranges.length); for (int i = 0; i < ranges.length; i+=2) { int start = ranges[i]; start = Math.max(0, Math.min(start, contentLength)); int length = ranges[i + 1]; 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); } fixedRanges[i] = start; fixedRanges[i + 1] = length; } setSelection(fixedRanges, false, true); setCaretLocations(); } /** * Adds the specified style. *

* 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. *

* Should not be called if a LineStyleListener has been set since the * listener maintains the styles. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())
  • *
*/ 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 start and * length and adds the new 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 ranges or styles is null, the specified range is cleared. *

* Note: It is expected that the same instance of a StyleRange will occur * multiple times within the styles array, reducing memory usage. *

* Should not be called if a LineStyleListener has been set since the * listener maintains the styles. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when an element in the styles array is null
  • *
  • ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)
  • *
  • ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)
  • *
  • ERROR_INVALID_RANGE when a range overlaps
  • *
* * @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. *

* 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. *

* Note: It is expected that the same instance of a StyleRange will occur * multiple times within the styles array, reducing memory usage. *

* Should not be called if a LineStyleListener has been set since the * listener maintains the styles. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when an element in the styles array is null
  • *
  • ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)
  • *
  • ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)
  • *
  • ERROR_INVALID_RANGE when a range overlaps
  • *
* * @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 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; setCaretLocations(); columnX = oldColumnX; doMouseLinkCursor(); } /** * * @param referenceRanges former ranges, sorted by order and without overlapping, typically returned {@link #getRanges(int, int)} * @param newRanges former ranges, sorted by order and without overlapping */ private SortedSet 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 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) */ 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. *

* 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, setStyleRanges(int[], StyleRange[]) * can be used to share styles and reduce memory usage. *

* Should not be called if a LineStyleListener has been set since the * listener maintains the styles. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when the list of ranges is null
  • *
  • ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())
  • *
* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @see #setTabStops(int[]) */ public void setTabs(int tabs) { checkWidget(); tabLength = tabs; renderer.setFont(null, tabs); resetCache(0, content.getLineCount()); setCaretLocations(); 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_INVALID_ARGUMENT - if a tab stop is negative or less than the previous stop in the list
  • *
* * @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()); setCaretLocations(); 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. *

* Note: Only a single line of text should be set when the SWT.SINGLE * style is used. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_NULL_ARGUMENT when string is null
  • *
*/ 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 SWT.LEFT_TO_RIGHT or * SWT.RIGHT_TO_LEFT. *

* setOrientation would override this value with the text direction * that is consistent with the new orientation. *

*

* Warning: This API is currently only implemented on Windows. * It doesn't set the base text direction on GTK and Cocoa. *

* * @param textDirection the base text direction style * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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. *

* The text limit specifies the amount of text that * the user can type into the widget. *

* * @param limit the new text limit. * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @exception IllegalArgumentException
    *
  • ERROR_CANNOT_BE_ZERO when limit is 0
  • *
*/ 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. *

* 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. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ 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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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. *

* 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. *

* * @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
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* @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. *

* This overrides the creation style bit SWT.WRAP. *

* * @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); setCaretLocations(); super.redraw(); } /** * Sets the wrap line indentation of the widget. *

* 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. *

* * @param wrapIndent the new wrap indent * * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
* * @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()); setCaretLocations(); 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(caretOffsets[0]); if (!showLocation(bounds, true) || (carets != null && caretOffsets.length != carets.length)) { setCaretLocations(); } } /** * Scrolls the selection into view. *

* 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). *

* * @exception SWTException
    *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • *
*/ public void showSelection() { checkWidget(); // is selection from right-to-left? boolean rightToLeft = caretOffsets[0] == selection[0].x; int startOffset, endOffset; if (rightToLeft) { startOffset = selection[0].y; endOffset = selection[0].x; } else { startOffset = selection[0].x; endOffset = selection[0].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 == caretOffsets[0] ? 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 (carets == null || carets.length == 0) { carets = new Caret[] { caret }; } if (blockSelection && blockXLocation != -1) { Arrays.stream(carets).forEach(c -> c.setVisible(false)); } else { Arrays.stream(carets).forEach(c -> { Point location = c.getLocation(); Point size = c.getSize(); boolean visible = topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin && leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin; c.setVisible(visible); }); } } } /** * Updates the selection and caret position depending on the text change. *

* 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. *

* * @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[selection.length - 1].y <= startOffset) { // selection ends before text change if (isWordWrap()) setCaretLocations(); return; } // clear selection fragment before text change Arrays.stream(selection) .filter(sel -> sel.y > startOffset) .filter(sel -> sel.x < startOffset) .forEach(sel -> internalRedrawRange(sel.x, startOffset - sel.x)); Arrays.stream(selection) .filter(sel -> sel.y > startOffset) .filter(sel -> sel.y > startOffset + replacedLength && sel.x < startOffset + replacedLength) .forEach(sel -> { // 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, sel.y + netNewLength - redrawStart); }); setSelection(Arrays.stream(selection).map(sel -> { if (sel.y <= startOffset) { return sel; } else if (sel.x == startOffset && sel.y == startOffset + replacedLength) { return new Point(startOffset + newLength, startOffset + newLength); } else if (sel.y > startOffset && sel.x < startOffset + replacedLength) { // selection intersects replaced text. set caret behind text change return new Point(startOffset + newLength, startOffset + newLength); } else { // move selection to keep same text selected int x = sel.x + newLength - replacedLength; int y = sel.x + newLength - replacedLength + (sel.y - sel.x); return new Point(x < 0 ? 0 : x, y < 0 ? 0 : y); } }).flatMapToInt(p -> IntStream.of(p.x, p.y - p.x)) .toArray(), true, false); setCaretLocations(); } /** * The method accepts a StyledText and a callback which takes * all the carets of the StyledText as the argument and executes it. * The caret is refreshed after the execution of the callback. * * @param styledText the StyledText to get the carets from * @param caretUpdater the callback which works with the carets * * @noreference This method is not intended to be referenced by clients. */ public static void updateAndRefreshCarets(StyledText styledText, Consumer caretUpdater) { caretUpdater.accept(styledText.getCaret()); caretUpdater.accept(styledText.defaultCaret); for (Caret caret : styledText.carets) { caretUpdater.accept(caret); } styledText.updateCaretVisibility(); styledText.setCaretLocations(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy