org.eclipse.swt.custom.StyledText Maven / Gradle / Ivy
Show all versions of org.eclipse.swt.gtk.linux.ppc64le Show documentation
/*******************************************************************************
* 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();
}
}