Java tutorial
/******************************************************************************* * Copyright (c) 2000, 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Andrey Loskutov <loskutov@gmx.de> - bug 488172 * Stefan Xenos (Google) - bug 487254 - StyledText.getTopIndex() can return negative values * Angelo Zerr <angelo.zerr@gmail.com> - Customize different line spacing of StyledText - Bug 522020 * Karsten Thoms <thoms@itemis.de> - bug 528746 add getOffsetAtPoint(Point) *******************************************************************************/ package org.eclipse.swt.custom; import java.util.*; import java.util.List; import org.eclipse.swt.*; import org.eclipse.swt.accessibility.*; import org.eclipse.swt.dnd.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.printing.*; import org.eclipse.swt.widgets.*; /** * A StyledText is an editable user interface object that displays lines * of text. The following style attributes can be defined for the text: * <ul> * <li>foreground color * <li>background color * <li>font style (bold, italic, bold-italic, regular) * <li>underline * <li>strikeout * </ul> * <p> * In addition to text style attributes, the background color of a line may * be specified. * </p><p> * There are two ways to use this widget when specifying text style information. * You may use the API that is defined for StyledText or you may define your own * LineStyleListener. If you define your own listener, you will be responsible * for maintaining the text style information for the widget. IMPORTANT: You may * not define your own listener and use the StyledText API. The following * StyledText API is not supported if you have defined a LineStyleListener:</p> * <ul> * <li>getStyleRangeAtOffset(int) * <li>getStyleRanges() * <li>replaceStyleRanges(int,int,StyleRange[]) * <li>setStyleRange(StyleRange) * <li>setStyleRanges(StyleRange[]) * </ul> * <p> * There are two ways to use this widget when specifying line background colors. * You may use the API that is defined for StyledText or you may define your own * LineBackgroundListener. If you define your own listener, you will be responsible * for maintaining the line background color information for the widget. * IMPORTANT: You may not define your own listener and use the StyledText API. * The following StyledText API is not supported if you have defined a * LineBackgroundListener:</p> * <ul> * <li>getLineBackground(int) * <li>setLineBackground(int,int,Color) * </ul> * <p> * The content implementation for this widget may also be user-defined. To do so, * you must implement the StyledTextContent interface and use the StyledText API * setContent(StyledTextContent) to initialize the widget. * </p> * <dl> * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey, OrientationChange * </dl> * <p> * IMPORTANT: This class is <em>not</em> intended to be subclassed. * </p> * * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a> * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a> * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> * @noextend This class is not intended to be subclassed by clients. */ public class StyledText extends Canvas { static final char TAB = '\t'; static final String PlatformLineDelimiter = System.getProperty("line.separator"); static final int BIDI_CARET_WIDTH = 3; static final int DEFAULT_WIDTH = 64; static final int DEFAULT_HEIGHT = 64; static final int V_SCROLL_RATE = 50; static final int H_SCROLL_RATE = 10; static final int PREVIOUS_OFFSET_TRAILING = 0; static final int OFFSET_LEADING = 1; static final String STYLEDTEXT_KEY = "org.eclipse.swt.internal.cocoa.styledtext"; //$NON-NLS-1$ Color selectionBackground; // selection background color Color selectionForeground; // selection foreground color StyledTextContent content; // native content (default or user specified) StyledTextRenderer renderer; Listener listener; TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent int verticalScrollOffset = 0; // pixel based int horizontalScrollOffset = 0; // pixel based boolean alwaysShowScroll = true; int ignoreResize = 0; int topIndex = 0; // top visible line int topIndexY; int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new visible lines during Resize callback int clientAreaWidth = 0; // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated int tabLength = 4; // number of characters in a tab int[] tabs; int leftMargin; int topMargin; int rightMargin; int bottomMargin; Color marginColor; int columnX; // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935 int caretOffset; int caretAlignment; Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection (x <= y) Point clipboardSelection; // x and y are start and end caret offsets of previous selection int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text Point doubleClickSelection; // selection after last mouse double click boolean editable = true; boolean wordWrap = false; // text is wrapped automatically boolean visualWrap = false; // process line breaks inside logical lines (inserted by BidiSegmentEvent) boolean doubleClickEnabled = true; // see getDoubleClickEnabled boolean overwrite = false; // insert/overwrite edit mode int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default. Map<Integer, Integer> keyActionMap = new HashMap<>(); Color background = null; // workaround for bug 4791 Color foreground = null; // Clipboard clipboard; int clickCount; int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left) int autoScrollDistance = 0; int lastTextChangeStart; // cache data of the int lastTextChangeNewLineCount; // last text changing int lastTextChangeNewCharCount; // event for use in the int lastTextChangeReplaceLineCount; // text changed handler int lastTextChangeReplaceCharCount; int lastCharCount = 0; int lastLineBottom; // the bottom pixel of the last line been replaced boolean bidiColoring = false; // apply the BIDI algorithm on text segments of the same color Image leftCaretBitmap = null; Image rightCaretBitmap = null; int caretDirection = SWT.NULL; int caretWidth = 0; Caret defaultCaret = null; boolean updateCaretDirection = true; boolean fixedLineHeight; boolean dragDetect = true; IME ime; Cursor cursor; int alignment; boolean justify; int indent, wrapIndent; int lineSpacing; int alignmentMargin; int newOrientation = SWT.NONE; int accCaretOffset; Accessible acc; AccessibleControlAdapter accControlAdapter; AccessibleAttributeAdapter accAttributeAdapter; AccessibleEditableTextListener accEditableTextListener; AccessibleTextExtendedAdapter accTextExtendedAdapter; AccessibleAdapter accAdapter; MouseNavigator mouseNavigator; boolean middleClickPressed; //block selection boolean blockSelection; int blockXAnchor = -1, blockYAnchor = -1; int blockXLocation = -1, blockYLocation = -1; final static boolean IS_MAC, IS_GTK; static { String platform = SWT.getPlatform(); IS_MAC = "cocoa".equals(platform); IS_GTK = "gtk".equals(platform); } /** * The Printing class implements printing of a range of text. * An instance of <code>Printing</code> is returned in the * StyledText#print(Printer) API. The run() method may be * invoked from any thread. */ static class Printing implements Runnable { final static int LEFT = 0; // left aligned header/footer segment final static int CENTER = 1; // centered header/footer segment final static int RIGHT = 2; // right aligned header/footer segment Printer printer; StyledTextRenderer printerRenderer; StyledTextPrintOptions printOptions; Rectangle clientArea; FontData fontData; Font printerFont; Map<Resource, Resource> resources; int tabLength; GC gc; // printer GC int pageWidth; // width of a printer page in pixels int startPage; // first page to print int endPage; // last page to print int scope; // scope of print job int startLine; // first (wrapped) line to print int endLine; // last (wrapped) line to print boolean singleLine; // widget single line mode Point selection = null; // selected text boolean mirrored; // indicates the printing gc should be mirrored int lineSpacing; int printMargin; /** * Creates an instance of <code>Printing</code>. * Copies the widget content and rendering data that needs * to be requested from listeners. * </p> * @param parent StyledText widget to print. * @param printer printer device to print on. * @param printOptions print options */ Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) { this.printer = printer; this.printOptions = printOptions; this.mirrored = (styledText.getStyle() & SWT.MIRRORED) != 0; singleLine = styledText.isSingleLine(); startPage = 1; endPage = Integer.MAX_VALUE; PrinterData data = printer.getPrinterData(); scope = data.scope; if (scope == PrinterData.PAGE_RANGE) { startPage = data.startPage; endPage = data.endPage; if (endPage < startPage) { int temp = endPage; endPage = startPage; startPage = temp; } } else if (scope == PrinterData.SELECTION) { selection = styledText.getSelectionRange(); } printerRenderer = new StyledTextRenderer(printer, null); printerRenderer.setContent(copyContent(styledText.getContent())); cacheLineData(styledText); } /** * Caches all line data that needs to be requested from a listener. * </p> * @param printerContent <code>StyledTextContent</code> to request * line data for. */ void cacheLineData(StyledText styledText) { StyledTextRenderer renderer = styledText.renderer; renderer.copyInto(printerRenderer); fontData = styledText.getFont().getFontData()[0]; tabLength = styledText.tabLength; int lineCount = printerRenderer.lineCount; if (styledText.isListening(ST.LineGetBackground) || (styledText.isListening(ST.LineGetSegments)) || styledText.isListening(ST.LineGetStyle)) { StyledTextContent content = printerRenderer.content; for (int i = 0; i < lineCount; i++) { String line = content.getLine(i); int lineOffset = content.getOffsetAtLine(i); StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line); if (event != null && event.lineBackground != null) { printerRenderer.setLineBackground(i, 1, event.lineBackground); } event = styledText.getBidiSegments(lineOffset, line); if (event != null) { printerRenderer.setLineSegments(i, 1, event.segments); printerRenderer.setLineSegmentChars(i, 1, event.segmentsChars); } event = styledText.getLineStyleData(lineOffset, line); if (event != null) { printerRenderer.setLineIndent(i, 1, event.indent); printerRenderer.setLineAlignment(i, 1, event.alignment); printerRenderer.setLineJustify(i, 1, event.justify); printerRenderer.setLineBullet(i, 1, event.bullet); StyleRange[] styles = event.styles; if (styles != null && styles.length > 0) { printerRenderer.setStyleRanges(event.ranges, styles); } } } } Point screenDPI = styledText.getDisplay().getDPI(); Point printerDPI = printer.getDPI(); resources = new HashMap<>(); for (int i = 0; i < lineCount; i++) { Color color = printerRenderer.getLineBackground(i, null); if (color != null) { if (printOptions.printLineBackground) { Color printerColor = (Color) resources.get(color); if (printerColor == null) { printerColor = new Color(printer, color.getRGB()); resources.put(color, printerColor); } printerRenderer.setLineBackground(i, 1, printerColor); } else { printerRenderer.setLineBackground(i, 1, null); } } int indent = printerRenderer.getLineIndent(i, 0); if (indent != 0) { printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x); } } StyleRange[] styles = printerRenderer.styles; for (int i = 0; i < printerRenderer.styleCount; i++) { StyleRange style = styles[i]; Font font = style.font; if (style.font != null) { Font printerFont = (Font) resources.get(font); if (printerFont == null) { printerFont = new Font(printer, font.getFontData()); resources.put(font, printerFont); } style.font = printerFont; } Color color = style.foreground; if (color != null) { Color printerColor = (Color) resources.get(color); if (printOptions.printTextForeground) { if (printerColor == null) { printerColor = new Color(printer, color.getRGB()); resources.put(color, printerColor); } style.foreground = printerColor; } else { style.foreground = null; } } color = style.background; if (color != null) { Color printerColor = (Color) resources.get(color); if (printOptions.printTextBackground) { if (printerColor == null) { printerColor = new Color(printer, color.getRGB()); resources.put(color, printerColor); } style.background = printerColor; } else { style.background = null; } } if (!printOptions.printTextFontStyle) { style.fontStyle = SWT.NORMAL; } style.rise = style.rise * printerDPI.y / screenDPI.y; GlyphMetrics metrics = style.metrics; if (metrics != null) { metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y; metrics.descent = metrics.descent * printerDPI.y / screenDPI.y; metrics.width = metrics.width * printerDPI.x / screenDPI.x; } } lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y; if (printOptions.printLineNumbers) { printMargin = 3 * printerDPI.x / screenDPI.x; } } /** * Copies the text of the specified <code>StyledTextContent</code>. * </p> * @param original the <code>StyledTextContent</code> to copy. */ StyledTextContent copyContent(StyledTextContent original) { StyledTextContent printerContent = new DefaultContent(); int insertOffset = 0; for (int i = 0; i < original.getLineCount(); i++) { int insertEndOffset; if (i < original.getLineCount() - 1) { insertEndOffset = original.getOffsetAtLine(i + 1); } else { insertEndOffset = original.getCharCount(); } printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset)); insertOffset = insertEndOffset; } return printerContent; } /** * Disposes of the resources and the <code>PrintRenderer</code>. */ void dispose() { if (gc != null) { gc.dispose(); gc = null; } if (resources != null) { for (Resource resource : resources.values()) { resource.dispose(); } resources = null; } if (printerFont != null) { printerFont.dispose(); printerFont = null; } if (printerRenderer != null) { printerRenderer.dispose(); printerRenderer = null; } } void init() { Rectangle trim = printer.computeTrim(0, 0, 0, 0); Point dpi = printer.getDPI(); printerFont = new Font(printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL); clientArea = printer.getClientArea(); pageWidth = clientArea.width; // one inch margin around text clientArea.x = dpi.x + trim.x; clientArea.y = dpi.y + trim.y; clientArea.width -= (clientArea.x + trim.width); clientArea.height -= (clientArea.y + trim.height); int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT; gc = new GC(printer, style); gc.setFont(printerFont); printerRenderer.setFont(printerFont, tabLength); int lineHeight = printerRenderer.getLineHeight(); if (printOptions.header != null) { clientArea.y += lineHeight * 2; clientArea.height -= lineHeight * 2; } if (printOptions.footer != null) { clientArea.height -= lineHeight * 2; } // TODO not wrapped StyledTextContent content = printerRenderer.content; startLine = 0; endLine = singleLine ? 0 : content.getLineCount() - 1; if (scope == PrinterData.PAGE_RANGE) { int pageSize = clientArea.height / lineHeight;//WRONG startLine = (startPage - 1) * pageSize; } else if (scope == PrinterData.SELECTION) { startLine = content.getLineAtOffset(selection.x); if (selection.y > 0) { endLine = content.getLineAtOffset(selection.x + selection.y - 1); } else { endLine = startLine - 1; } } } /** * Prints the lines in the specified page range. */ void print() { Color background = gc.getBackground(); Color foreground = gc.getForeground(); int paintY = clientArea.y; int paintX = clientArea.x; int width = clientArea.width; int page = startPage; int pageBottom = clientArea.y + clientArea.height; int orientation = gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT); TextLayout printLayout = null; if (printOptions.printLineNumbers || printOptions.header != null || printOptions.footer != null) { printLayout = new TextLayout(printer); printLayout.setFont(printerFont); } if (printOptions.printLineNumbers) { int numberingWidth = 0; int count = endLine - startLine + 1; String[] lineLabels = printOptions.lineLabels; if (lineLabels != null) { for (int i = startLine; i < Math.min(count, lineLabels.length); i++) { if (lineLabels[i] != null) { printLayout.setText(lineLabels[i]); int lineWidth = printLayout.getBounds().width; numberingWidth = Math.max(numberingWidth, lineWidth); } } } else { StringBuilder buffer = new StringBuilder("0"); while ((count /= 10) > 0) buffer.append("0"); printLayout.setText(buffer.toString()); numberingWidth = printLayout.getBounds().width; } numberingWidth += printMargin; if (numberingWidth > width) numberingWidth = width; paintX += numberingWidth; width -= numberingWidth; } for (int i = startLine; i <= endLine && page <= endPage; i++) { if (paintY == clientArea.y) { printer.startPage(); printDecoration(page, true, printLayout); } TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing); Color lineBackground = printerRenderer.getLineBackground(i, background); int paragraphBottom = paintY + layout.getBounds().height; if (paragraphBottom <= pageBottom) { //normal case, the whole paragraph fits in the current page printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); paintY = paragraphBottom; } else { int lineCount = layout.getLineCount(); while (paragraphBottom > pageBottom && lineCount > 0) { lineCount--; paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing(); } if (lineCount == 0) { //the whole paragraph goes to the next page printDecoration(page, false, printLayout); printer.endPage(); page++; if (page <= endPage) { printer.startPage(); printDecoration(page, true, printLayout); paintY = clientArea.y; printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); paintY += layout.getBounds().height; } } else { //draw paragraph top in the current page and paragraph bottom in the next int height = paragraphBottom - paintY; gc.setClipping(clientArea.x, paintY, clientArea.width, height); printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); gc.setClipping((Rectangle) null); printDecoration(page, false, printLayout); printer.endPage(); page++; if (page <= endPage) { printer.startPage(); printDecoration(page, true, printLayout); paintY = clientArea.y - height; int layoutHeight = layout.getBounds().height; gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height); printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); gc.setClipping((Rectangle) null); paintY += layoutHeight; } } } printerRenderer.disposeTextLayout(layout); } if (page <= endPage && paintY > clientArea.y) { // close partial page printDecoration(page, false, printLayout); printer.endPage(); } if (printLayout != null) printLayout.dispose(); } /** * Print header or footer decorations. * * @param page page number to print, if specified in the StyledTextPrintOptions header or footer. * @param header true = print the header, false = print the footer */ void printDecoration(int page, boolean header, TextLayout layout) { String text = header ? printOptions.header : printOptions.footer; if (text == null) return; int lastSegmentIndex = 0; for (int i = 0; i < 3; i++) { int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex); String segment; if (segmentIndex == -1) { segment = text.substring(lastSegmentIndex); printDecorationSegment(segment, i, page, header, layout); break; } else { segment = text.substring(lastSegmentIndex, segmentIndex); printDecorationSegment(segment, i, page, header, layout); lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length(); } } } /** * Print one segment of a header or footer decoration. * Headers and footers have three different segments. * One each for left aligned, centered, and right aligned text. * * @param segment decoration segment to print * @param alignment alignment of the segment. 0=left, 1=center, 2=right * @param page page number to print, if specified in the decoration segment. * @param header true = print the header, false = print the footer */ void printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout) { int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG); if (pageIndex != -1) { int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length(); StringBuilder buffer = new StringBuilder(segment.substring(0, pageIndex)); buffer.append(page); buffer.append(segment.substring(pageIndex + pageTagLength)); segment = buffer.toString(); } if (segment.length() > 0) { layout.setText(segment); int segmentWidth = layout.getBounds().width; int segmentHeight = printerRenderer.getLineHeight(); int drawX = 0, drawY; if (alignment == LEFT) { drawX = clientArea.x; } else if (alignment == CENTER) { drawX = (pageWidth - segmentWidth) / 2; } else if (alignment == RIGHT) { drawX = clientArea.x + clientArea.width - segmentWidth; } if (header) { drawY = clientArea.y - segmentHeight * 2; } else { drawY = clientArea.y + clientArea.height + segmentHeight; } layout.draw(gc, drawX, drawY); } } void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) { if (background != null) { Rectangle rect = layout.getBounds(); gc.setBackground(background); gc.fillRectangle(x, y, rect.width, rect.height); // int lineCount = layout.getLineCount(); // for (int i = 0; i < lineCount; i++) { // Rectangle rect = layout.getLineBounds(i); // rect.x += paintX; // rect.y += paintY + layout.getSpacing(); // rect.width = width;//layout bounds // gc.fillRectangle(rect); // } } if (printOptions.printLineNumbers) { FontMetrics metrics = layout.getLineMetrics(0); printLayout.setAscent(metrics.getAscent() + metrics.getLeading()); printLayout.setDescent(metrics.getDescent()); String[] lineLabels = printOptions.lineLabels; if (lineLabels != null) { if (0 <= index && index < lineLabels.length && lineLabels[index] != null) { printLayout.setText(lineLabels[index]); } else { printLayout.setText(""); } } else { printLayout.setText(String.valueOf(index)); } int paintX = x - printMargin - printLayout.getBounds().width; printLayout.draw(gc, paintX, y); printLayout.setAscent(-1); printLayout.setDescent(-1); } gc.setForeground(foreground); layout.draw(gc, x, y); } /** * Starts a print job and prints the pages specified in the constructor. */ @Override public void run() { String jobName = printOptions.jobName; if (jobName == null) { jobName = "Printing"; } if (printer.startJob(jobName)) { init(); print(); dispose(); printer.endJob(); } } } /** * The <code>RTFWriter</code> class is used to write widget content as * rich text. The implementation complies with the RTF specification * version 1.5. * <p> * toString() is guaranteed to return a valid RTF string only after * close() has been called. * </p><p> * Whole and partial lines and line breaks can be written. Lines will be * formatted using the styles queried from the LineStyleListener, if * set, or those set directly in the widget. All styles are applied to * the RTF stream like they are rendered by the widget. In addition, the * widget font name and size is used for the whole text. * </p> */ class RTFWriter extends TextWriter { static final int DEFAULT_FOREGROUND = 0; static final int DEFAULT_BACKGROUND = 1; List<Color> colorTable; List<Font> fontTable; /** * Creates a RTF writer that writes content starting at offset "start" * in the document. <code>start</code> and <code>length</code>can be set to specify partial * lines. * * @param start start offset of content to write, 0 based from * beginning of document * @param length length of content to write */ public RTFWriter(int start, int length) { super(start, length); colorTable = new ArrayList<>(); fontTable = new ArrayList<>(); colorTable.add(getForeground()); colorTable.add(getBackground()); fontTable.add(getFont()); } /** * Closes the RTF writer. Once closed no more content can be written. * <b>NOTE:</b> <code>toString()</code> does not return a valid RTF string until * <code>close()</code> has been called. */ @Override public void close() { if (!isClosed()) { writeHeader(); write("\n}}\0"); super.close(); } } /** * Returns the index of the specified color in the RTF color table. * * @param color the color * @param defaultIndex return value if color is null * @return the index of the specified color in the RTF color table * or "defaultIndex" if "color" is null. */ int getColorIndex(Color color, int defaultIndex) { if (color == null) return defaultIndex; int index = colorTable.indexOf(color); if (index == -1) { index = colorTable.size(); colorTable.add(color); } return index; } /** * Returns the index of the specified color in the RTF color table. * * @param color the color * @param defaultIndex return value if color is null * @return the index of the specified color in the RTF color table * or "defaultIndex" if "color" is null. */ int getFontIndex(Font font) { int index = fontTable.indexOf(font); if (index == -1) { index = fontTable.size(); fontTable.add(font); } return index; } /** * Appends the specified segment of "string" to the RTF data. * Copy from <code>start</code> up to, but excluding, <code>end</code>. * * @param string string to copy a segment from. Must not contain * line breaks. Line breaks should be written using writeLineDelimiter() * @param start start offset of segment. 0 based. * @param end end offset of segment */ void write(String string, int start, int end) { for (int index = start; index < end; index++) { char ch = string.charAt(index); if (ch > 0x7F) { // write the sub string from the last escaped character // to the current one. Fixes bug 21698. if (index > start) { write(string.substring(start, index)); } write("\\u"); write(Integer.toString((short) ch)); write('?'); // ANSI representation (1 byte long, \\uc1) start = index + 1; } else if (ch == '}' || ch == '{' || ch == '\\') { // write the sub string from the last escaped character // to the current one. Fixes bug 21698. if (index > start) { write(string.substring(start, index)); } write('\\'); write(ch); start = index + 1; } } // write from the last escaped character to the end. // Fixes bug 21698. if (start < end) { write(string.substring(start, end)); } } /** * Writes the RTF header including font table and color table. */ void writeHeader() { StringBuilder header = new StringBuilder(); FontData fontData = getFont().getFontData()[0]; header.append("{\\rtf1\\ansi"); // specify code page, necessary for copy to work in bidi // systems that don't support Unicode RTF. String cpg = System.getProperty("file.encoding").toLowerCase(); if (cpg.startsWith("cp") || cpg.startsWith("ms")) { cpg = cpg.substring(2, cpg.length()); header.append("\\ansicpg"); header.append(cpg); } header.append("\\uc1\\deff0{\\fonttbl{\\f0\\fnil "); header.append(fontData.getName()); header.append(";"); for (int i = 1; i < fontTable.size(); i++) { header.append("\\f"); header.append(i); header.append(" "); FontData fd = fontTable.get(i).getFontData()[0]; header.append(fd.getName()); header.append(";"); } header.append("}}\n{\\colortbl"); for (int i = 0; i < colorTable.size(); i++) { Color color = colorTable.get(i); header.append("\\red"); header.append(color.getRed()); header.append("\\green"); header.append(color.getGreen()); header.append("\\blue"); header.append(color.getBlue()); header.append(";"); } // some RTF readers ignore the deff0 font tag. Explicitly // set the font for the whole document to work around this. header.append("}\n{\\f0\\fs"); // font size is specified in half points header.append(fontData.getHeight() * 2); header.append(" "); write(header.toString(), 0); } /** * Appends the specified line text to the RTF data. Lines will be formatted * using the styles queried from the LineStyleListener, if set, or those set * directly in the widget. * * @param line line text to write as RTF. Must not contain line breaks * Line breaks should be written using writeLineDelimiter() * @param lineOffset offset of the line. 0 based from the start of the * widget document. Any text occurring before the start offset or after the * end offset specified during object creation is ignored. * @exception SWTException <ul> * <li>ERROR_IO when the writer is closed.</li> * </ul> */ @Override public void writeLine(String line, int lineOffset) { if (isClosed()) { SWT.error(SWT.ERROR_IO); } int lineIndex = content.getLineAtOffset(lineOffset); int lineAlignment, lineIndent; boolean lineJustify; int[] ranges; StyleRange[] styles; StyledTextEvent event = getLineStyleData(lineOffset, line); if (event != null) { lineAlignment = event.alignment; lineIndent = event.indent; lineJustify = event.justify; ranges = event.ranges; styles = event.styles; } else { lineAlignment = renderer.getLineAlignment(lineIndex, alignment); lineIndent = renderer.getLineIndent(lineIndex, indent); lineJustify = renderer.getLineJustify(lineIndex, justify); ranges = renderer.getRanges(lineOffset, line.length()); styles = renderer.getStyleRanges(lineOffset, line.length(), false); } if (styles == null) styles = new StyleRange[0]; Color lineBackground = renderer.getLineBackground(lineIndex, null); event = getLineBackgroundData(lineOffset, line); if (event != null && event.lineBackground != null) lineBackground = event.lineBackground; writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify); } /** * Appends the specified line delimiter to the RTF data. * * @param lineDelimiter line delimiter to write as RTF. * @exception SWTException <ul> * <li>ERROR_IO when the writer is closed.</li> * </ul> */ @Override public void writeLineDelimiter(String lineDelimiter) { if (isClosed()) { SWT.error(SWT.ERROR_IO); } write(lineDelimiter, 0, lineDelimiter.length()); write("\\par "); } /** * Appends the specified line text to the RTF data. * <p> * Use the colors and font styles specified in "styles" and "lineBackground". * Formatting is written to reflect the text rendering by the text widget. * Style background colors take precedence over the line background color. * Background colors are written using the \chshdng0\chcbpat tag (vs. the \cb tag). * </p> * * @param line line text to write as RTF. Must not contain line breaks * Line breaks should be written using writeLineDelimiter() * @param lineOffset offset of the line. 0 based from the start of the * widget document. Any text occurring before the start offset or after the * end offset specified during object creation is ignored. * @param styles styles to use for formatting. Must not be null. * @param lineBackground line background color to use for formatting. * May be null. */ void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify) { int lineLength = line.length(); int startOffset = getStart(); int writeOffset = startOffset - lineOffset; if (writeOffset >= lineLength) return; int lineIndex = Math.max(0, writeOffset); write("\\fi"); write(indent); switch (alignment) { case SWT.LEFT: write("\\ql"); break; case SWT.CENTER: write("\\qc"); break; case SWT.RIGHT: write("\\qr"); break; } if (justify) write("\\qj"); write(" "); if (lineBackground != null) { write("{\\chshdng0\\chcbpat"); write(getColorIndex(lineBackground, DEFAULT_BACKGROUND)); write(" "); } int endOffset = startOffset + super.getCharCount(); int lineEndOffset = Math.min(lineLength, endOffset - lineOffset); for (int i = 0; i < styles.length; i++) { StyleRange style = styles[i]; int start, end; if (ranges != null) { start = ranges[i << 1] - lineOffset; end = start + ranges[(i << 1) + 1]; } else { start = style.start - lineOffset; end = start + style.length; } // skip over partial first line if (end < writeOffset) { continue; } // style starts beyond line end or RTF write end if (start >= lineEndOffset) { break; } // write any unstyled text if (lineIndex < start) { // copy to start of style // style starting beyond end of write range or end of line // is guarded against above. write(line, lineIndex, start); lineIndex = start; } // write styled text write("{\\cf"); write(getColorIndex(style.foreground, DEFAULT_FOREGROUND)); int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND); if (colorIndex != DEFAULT_BACKGROUND) { write("\\chshdng0\\chcbpat"); write(colorIndex); } int fontStyle = style.fontStyle; Font font = style.font; if (font != null) { int fontIndex = getFontIndex(font); write("\\f"); write(fontIndex); FontData fontData = font.getFontData()[0]; write("\\fs"); write(fontData.getHeight() * 2); fontStyle = fontData.getStyle(); } if ((fontStyle & SWT.BOLD) != 0) { write("\\b"); } if ((fontStyle & SWT.ITALIC) != 0) { write("\\i"); } if (style.underline) { write("\\ul"); } if (style.strikeout) { write("\\strike"); } write(" "); // copy to end of style or end of write range or end of line int copyEnd = Math.min(end, lineEndOffset); // guard against invalid styles and let style processing continue copyEnd = Math.max(copyEnd, lineIndex); write(line, lineIndex, copyEnd); if ((fontStyle & SWT.BOLD) != 0) { write("\\b0"); } if ((style.fontStyle & SWT.ITALIC) != 0) { write("\\i0"); } if (style.underline) { write("\\ul0"); } if (style.strikeout) { write("\\strike0"); } write("}"); lineIndex = copyEnd; } // write unstyled text at the end of the line if (lineIndex < lineEndOffset) { write(line, lineIndex, lineEndOffset); } if (lineBackground != null) write("}"); } } /** * The <code>TextWriter</code> class is used to write widget content to * a string. Whole and partial lines and line breaks can be written. To write * partial lines, specify the start and length of the desired segment * during object creation. * <p> * </b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close() * has been called. * </p> */ class TextWriter { private StringBuilder buffer; private int startOffset; // offset of first character that will be written private int endOffset; // offset of last character that will be written. // 0 based from the beginning of the widget text. private boolean isClosed = false; /** * Creates a writer that writes content starting at offset "start" * in the document. <code>start</code> and <code>length</code> can be set to specify partial lines. * * @param start start offset of content to write, 0 based from beginning of document * @param length length of content to write */ public TextWriter(int start, int length) { buffer = new StringBuilder(length); startOffset = start; endOffset = start + length; } /** * Closes the writer. Once closed no more content can be written. * <b>NOTE:</b> <code>toString()</code> is not guaranteed to return a valid string unless * the writer is closed. */ public void close() { if (!isClosed) { isClosed = true; } } /** * Returns the number of characters to write. * @return the integer number of characters to write */ public int getCharCount() { return endOffset - startOffset; } /** * Returns the offset where writing starts. 0 based from the start of * the widget text. Used to write partial lines. * @return the integer offset where writing starts */ public int getStart() { return startOffset; } /** * Returns whether the writer is closed. * @return a boolean specifying whether or not the writer is closed */ public boolean isClosed() { return isClosed; } /** * Returns the string. <code>close()</code> must be called before <code>toString()</code> * is guaranteed to return a valid string. * * @return the string */ @Override public String toString() { return buffer.toString(); } /** * Appends the given string to the data. */ void write(String string) { buffer.append(string); } /** * Inserts the given string to the data at the specified offset. * <p> * Do nothing if "offset" is < 0 or > getCharCount() * </p> * * @param string text to insert * @param offset offset in the existing data to insert "string" at. */ void write(String string, int offset) { if (offset < 0 || offset > buffer.length()) { return; } buffer.insert(offset, string); } /** * Appends the given int to the data. */ void write(int i) { buffer.append(i); } /** * Appends the given character to the data. */ void write(char i) { buffer.append(i); } /** * Appends the specified line text to the data. * * @param line line text to write. Must not contain line breaks * Line breaks should be written using writeLineDelimiter() * @param lineOffset offset of the line. 0 based from the start of the * widget document. Any text occurring before the start offset or after the * end offset specified during object creation is ignored. * @exception SWTException <ul> * <li>ERROR_IO when the writer is closed.</li> * </ul> */ public void writeLine(String line, int lineOffset) { if (isClosed) { SWT.error(SWT.ERROR_IO); } int writeOffset = startOffset - lineOffset; int lineLength = line.length(); int lineIndex; if (writeOffset >= lineLength) { return; // whole line is outside write range } else if (writeOffset > 0) { lineIndex = writeOffset; // line starts before write start } else { lineIndex = 0; } int copyEnd = Math.min(lineLength, endOffset - lineOffset); if (lineIndex < copyEnd) { write(line.substring(lineIndex, copyEnd)); } } /** * Appends the specified line delimiter to the data. * * @param lineDelimiter line delimiter to write * @exception SWTException <ul> * <li>ERROR_IO when the writer is closed.</li> * </ul> */ public void writeLineDelimiter(String lineDelimiter) { if (isClosed) { SWT.error(SWT.ERROR_IO); } write(lineDelimiter); } } /** * Constructs a new instance of this class given its parent * and a style value describing its behavior and appearance. * <p> * The style value is either one of the style constants defined in * class <code>SWT</code> which is applicable to instances of this * class, or must be built by <em>bitwise OR</em>'ing together * (that is, using the <code>int</code> "|" operator) two or more * of those <code>SWT</code> style constants. The class description * lists the style constants that are applicable to the class. * Style bits are also inherited from superclasses. * </p> * * @param parent a widget which will be the parent of the new instance (cannot be null) * @param style the style of widget to construct * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> * </ul> * * @see SWT#FULL_SELECTION * @see SWT#MULTI * @see SWT#READ_ONLY * @see SWT#SINGLE * @see SWT#WRAP * @see #getStyle */ public StyledText(Composite parent, int style) { super(parent, checkStyle(style)); // set the fg in the OS to ensure that these are the same as StyledText, necessary // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses super.setForeground(getForeground()); super.setDragDetect(false); Display display = getDisplay(); fixedLineHeight = true; if ((style & SWT.READ_ONLY) != 0) { setEditable(false); } leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1 : 0; if ((style & SWT.SINGLE) != 0 && (style & SWT.BORDER) != 0) { leftMargin = topMargin = rightMargin = bottomMargin = 2; } alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER); if (alignment == 0) alignment = SWT.LEFT; clipboard = new Clipboard(display); installDefaultContent(); renderer = new StyledTextRenderer(getDisplay(), this); renderer.setContent(content); renderer.setFont(getFont(), tabLength); ime = new IME(this, SWT.NONE); defaultCaret = new Caret(this, SWT.NONE); if ((style & SWT.WRAP) != 0) { setWordWrap(true); } if (isBidiCaret()) { createCaretBitmaps(); Runnable runnable = () -> { int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT; if (direction == caretDirection) return; if (getCaret() != defaultCaret) return; Point newCaretPos = getPointAtOffset(caretOffset); setCaretLocation(newCaretPos, direction); }; BidiUtil.addLanguageListener(this, runnable); } setCaret(defaultCaret); calculateScrollBars(); createKeyBindings(); super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM)); installListeners(); initializeAccessible(); setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this)); if (IS_MAC) setData(STYLEDTEXT_KEY); } /** * Adds an extended modify listener. An ExtendedModify event is sent by the * widget when the widget text has changed. * * @param extendedModifyListener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) { checkWidget(); if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); StyledTextListener typedListener = new StyledTextListener(extendedModifyListener); addListener(ST.ExtendedModify, typedListener); } /** * Adds a bidirectional segment listener. * <p> * A BidiSegmentEvent is sent * whenever a line of text is measured or rendered. You can * specify text ranges in the line that should be treated as if they * had a different direction than the surrounding text. * This may be used when adjacent segments of right-to-left text should * not be reordered relative to each other. * E.g., multiple Java string literals in a right-to-left language * should generally remain in logical order to each other, that is, the * way they are stored. * </p> * * @param listener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * @see BidiSegmentEvent * @since 2.0 */ public void addBidiSegmentListener(BidiSegmentListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(ST.LineGetSegments, new StyledTextListener(listener)); resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Adds a caret listener. CaretEvent is sent when the caret offset changes. * * @param listener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * * @since 3.5 */ public void addCaretListener(CaretListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(ST.CaretMoved, new StyledTextListener(listener)); } /** * Adds a line background listener. A LineGetBackground event is sent by the * widget to determine the background color for a line. * * @param listener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void addLineBackgroundListener(LineBackgroundListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (!isListening(ST.LineGetBackground)) { renderer.clearLineBackground(0, content.getLineCount()); } addListener(ST.LineGetBackground, new StyledTextListener(listener)); } /** * Adds a line style listener. A LineGetStyle event is sent by the widget to * determine the styles for a line. * * @param listener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void addLineStyleListener(LineStyleListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (!isListening(ST.LineGetStyle)) { setStyleRanges(0, 0, null, null, true); renderer.clearLineStyle(0, content.getLineCount()); } addListener(ST.LineGetStyle, new StyledTextListener(listener)); setCaretLocation(); } /** * Adds a modify listener. A Modify event is sent by the widget when the widget text * has changed. * * @param modifyListener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void addModifyListener(ModifyListener modifyListener) { checkWidget(); if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(SWT.Modify, new TypedListener(modifyListener)); } /** * Adds a paint object listener. A paint object event is sent by the widget when an object * needs to be drawn. * * @param listener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * * @since 3.2 * * @see PaintObjectListener * @see PaintObjectEvent */ public void addPaintObjectListener(PaintObjectListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(ST.PaintObject, new StyledTextListener(listener)); } /** * Adds a selection listener. A Selection event is sent by the widget when the * user changes the selection. * <p> * When <code>widgetSelected</code> is called, the event x and y fields contain * the start and end caret indices of the selection. The selection values returned are visual * (i.e., x will always always be <= y). * No event is sent when the caret is moved while the selection length is 0. * </p><p> * <code>widgetDefaultSelected</code> is not called for StyledTexts. * </p> * * @param listener the listener which should be notified when the user changes the receiver's selection * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see SelectionListener * @see #removeSelectionListener * @see SelectionEvent */ public void addSelectionListener(SelectionListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(SWT.Selection, new TypedListener(listener)); } /** * Adds a verify key listener. A VerifyKey event is sent by the widget when a key * is pressed. The widget ignores the key press if the listener sets the doit field * of the event to false. * * @param listener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void addVerifyKeyListener(VerifyKeyListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(ST.VerifyKey, new StyledTextListener(listener)); } /** * Adds a verify listener. A Verify event is sent by the widget when the widget text * is about to change. The listener can set the event text and the doit field to * change the text that is set in the widget or to force the widget to ignore the * text change. * * @param verifyListener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void addVerifyListener(VerifyListener verifyListener) { checkWidget(); if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(SWT.Verify, new TypedListener(verifyListener)); } /** * Adds a word movement listener. A movement event is sent when the boundary * of a word is needed. For example, this occurs during word next and word * previous actions. * * @param movementListener the listener * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * * @see MovementEvent * @see MovementListener * @see #removeWordMovementListener * * @since 3.3 */ public void addWordMovementListener(MovementListener movementListener) { checkWidget(); if (movementListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); addListener(ST.WordNext, new StyledTextListener(movementListener)); addListener(ST.WordPrevious, new StyledTextListener(movementListener)); } /** * Appends a string to the text at the end of the widget. * * @param string the string to be appended * @see #replaceTextRange(int,int,String) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void append(String string) { checkWidget(); if (string == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } int lastChar = Math.max(getCharCount(), 0); replaceTextRange(lastChar, 0, string); } /** * Calculates the scroll bars */ void calculateScrollBars() { ScrollBar horizontalBar = getHorizontalBar(); ScrollBar verticalBar = getVerticalBar(); setScrollBars(true); if (verticalBar != null) { verticalBar.setIncrement(getVerticalIncrement()); } if (horizontalBar != null) { horizontalBar.setIncrement(getHorizontalIncrement()); } } /** * Calculates the top index based on the current vertical scroll offset. * The top index is the index of the topmost fully visible line or the * topmost partially visible line if no line is fully visible. * The top index starts at 0. */ void calculateTopIndex(int delta) { int oldDelta = delta; int oldTopIndex = topIndex; int oldTopIndexY = topIndexY; if (isFixedLineHeight()) { int verticalIncrement = getVerticalIncrement(); if (verticalIncrement == 0) { return; } topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement); // Set top index to partially visible top line if no line is fully // visible but at least some of the widget client area is visible. // Fixes bug 15088. if (topIndex > 0) { if (clientAreaHeight > 0) { int bottomPixel = getVerticalScrollOffset() + clientAreaHeight; int fullLineTopPixel = topIndex * verticalIncrement; int fullLineVisibleHeight = bottomPixel - fullLineTopPixel; // set top index to partially visible line if no line fully fits in // client area or if space is available but not used (the latter should // never happen because we use claimBottomFreeSpace) if (fullLineVisibleHeight < verticalIncrement) { topIndex = getVerticalScrollOffset() / verticalIncrement; } } else if (topIndex >= content.getLineCount()) { topIndex = content.getLineCount() - 1; } } } else { if (delta >= 0) { delta -= topIndexY; int lineIndex = topIndex; int lineCount = content.getLineCount(); while (lineIndex < lineCount) { if (delta <= 0) break; delta -= renderer.getLineHeight(lineIndex++); } if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) { topIndex = lineIndex; topIndexY = -delta; } else { topIndex = lineIndex - 1; topIndexY = -renderer.getLineHeight(topIndex) - delta; } } else { delta -= topIndexY; int lineIndex = topIndex; while (lineIndex > 0) { int lineHeight = renderer.getLineHeight(lineIndex - 1); if (delta + lineHeight > 0) break; delta += lineHeight; lineIndex--; } if (lineIndex == 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) { topIndex = lineIndex; topIndexY = -delta; } else { topIndex = lineIndex - 1; topIndexY = -renderer.getLineHeight(topIndex) - delta; } } } if (topIndex < 0) { // TODO: This logging is in place to determine why topIndex is getting set to negative values. // It should be deleted once we fix the root cause of this issue. See bug 487254 for details. System.err.println("StyledText: topIndex was " + topIndex + ", isFixedLineHeight() = " + isFixedLineHeight() + ", delta = " + delta + ", content.getLineCount() = " + content.getLineCount() + ", clientAreaHeight = " + clientAreaHeight + ", oldTopIndex = " + oldTopIndex + ", oldTopIndexY = " + oldTopIndexY + ", getVerticalScrollOffset = " + getVerticalScrollOffset() + ", oldDelta = " + oldDelta + ", getVerticalIncrement() = " + getVerticalIncrement()); topIndex = 0; } if (topIndex != oldTopIndex || oldTopIndexY != topIndexY) { int width = renderer.getWidth(); renderer.calculateClientArea(); if (width != renderer.getWidth()) { setScrollBars(false); } } } /** * Hides the scroll bars if widget is created in single line mode. */ static int checkStyle(int style) { if ((style & SWT.SINGLE) != 0) { style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI); } else { style |= SWT.MULTI; if ((style & SWT.WRAP) != 0) { style &= ~SWT.H_SCROLL; } } style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND; /* Clear SWT.CENTER to avoid the conflict with SWT.EMBEDDED */ return style & ~SWT.CENTER; } /** * Scrolls down the text to use new space made available by a resize or by * deleted lines. */ void claimBottomFreeSpace() { if (ime.getCompositionOffset() != -1) return; if (isFixedLineHeight()) { int newVerticalOffset = Math.max(0, renderer.getHeight() - clientAreaHeight); if (newVerticalOffset < getVerticalScrollOffset()) { scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true); } } else { int bottomIndex = getPartialBottomIndex(); int height = getLinePixel(bottomIndex + 1); if (clientAreaHeight > height) { scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true); } } } /** * Scrolls text to the right to use new space made available by a resize. */ void claimRightFreeSpace() { int newHorizontalOffset = Math.max(0, renderer.getWidth() - clientAreaWidth); if (newHorizontalOffset < horizontalScrollOffset) { // item is no longer drawn past the right border of the client area // align the right end of the item with the right border of the // client area (window is scrolled right). scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true); } } void clearBlockSelection(boolean reset, boolean sendEvent) { if (reset) resetSelection(); blockXAnchor = blockYAnchor = -1; blockXLocation = blockYLocation = -1; caretDirection = SWT.NULL; updateCaretVisibility(); super.redraw(); if (sendEvent) sendSelectionEvent(); } /** * Removes the widget selection. * * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset. */ void clearSelection(boolean sendEvent) { int selectionStart = selection.x; int selectionEnd = selection.y; resetSelection(); // redraw old selection, if any if (selectionEnd - selectionStart > 0) { int length = content.getCharCount(); // called internally to remove selection after text is removed // therefore make sure redraw range is valid. int redrawStart = Math.min(selectionStart, length); int redrawEnd = Math.min(selectionEnd, length); if (redrawEnd - redrawStart > 0) { internalRedrawRange(redrawStart, redrawEnd - redrawStart); } if (sendEvent) { sendSelectionEvent(); } } } @Override public Point computeSize(int wHint, int hHint, boolean changed) { checkWidget(); int lineCount = (getStyle() & SWT.SINGLE) != 0 ? 1 : content.getLineCount(); int width = 0; int height = 0; if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) { Display display = getDisplay(); int maxHeight = display.getClientArea().height; for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) { TextLayout layout = renderer.getTextLayout(lineIndex); int wrapWidth = layout.getWidth(); if (wordWrap) layout.setWidth(wHint == 0 ? 1 : wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(1, wHint - leftMargin - rightMargin)); Rectangle rect = layout.getBounds(); height += rect.height; width = Math.max(width, rect.width); layout.setWidth(wrapWidth); renderer.disposeTextLayout(layout); if (isFixedLineHeight() && height > maxHeight) break; } if (isFixedLineHeight()) { height = lineCount * renderer.getLineHeight(); } } // Use default values if no text is defined. if (width == 0) width = DEFAULT_WIDTH; if (height == 0) height = DEFAULT_HEIGHT; if (wHint != SWT.DEFAULT) width = wHint; if (hHint != SWT.DEFAULT) height = hHint; int wTrim = getLeftMargin() + rightMargin + getCaretWidth(); int hTrim = topMargin + bottomMargin; Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim); return new Point(rect.width, rect.height); } /** * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard. * <p> * The text will be put on the clipboard in plain text format and RTF format. * The <code>DND.CLIPBOARD</code> clipboard is used for data that is * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or * by menu action. * </p> * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void copy() { checkWidget(); copySelection(DND.CLIPBOARD); } /** * Copies the selected text to the specified clipboard. The text will be put in the * clipboard in plain text format and RTF format. * <p> * The clipboardType is one of the clipboard constants defined in class * <code>DND</code>. The <code>DND.CLIPBOARD</code> clipboard is * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) * or by menu action. The <code>DND.SELECTION_CLIPBOARD</code> * clipboard is used for data that is transferred by selecting text and pasting * with the middle mouse button. * </p> * * @param clipboardType indicates the type of clipboard * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.1 */ public void copy(int clipboardType) { checkWidget(); copySelection(clipboardType); } boolean copySelection(int type) { if (type != DND.CLIPBOARD && type != DND.SELECTION_CLIPBOARD) return false; try { if (blockSelection && blockXLocation != -1) { String text = getBlockSelectionText(PlatformLineDelimiter); if (text.length() > 0) { //TODO RTF support TextTransfer plainTextTransfer = TextTransfer.getInstance(); Object[] data = new Object[] { text }; Transfer[] types = new Transfer[] { plainTextTransfer }; clipboard.setContents(data, types, type); return true; } } else { int length = selection.y - selection.x; if (length > 0) { setClipboardContent(selection.x, length, type); return true; } } } catch (SWTError error) { // Copy to clipboard failed. This happens when another application // is accessing the clipboard while we copy. Ignore the error. // Rethrow all other errors. Fixes bug 17578. if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) { throw error; } } return false; } /** * Returns the alignment of the widget. * * @return the alignment * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getLineAlignment(int) * * @since 3.2 */ public int getAlignment() { checkWidget(); return alignment; } /** * Returns the Always Show Scrollbars flag. True if the scrollbars are * always shown even if they are not required. False if the scrollbars are only * visible when some part of the content needs to be scrolled to be seen. * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the * horizontal and vertical directions. * * @return the Always Show Scrollbars flag value * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.8 */ public boolean getAlwaysShowScrollBars() { checkWidget(); return alwaysShowScroll; } int getAvailableHeightAbove(int height) { int maxHeight = verticalScrollOffset; if (maxHeight == -1) { int lineIndex = topIndex - 1; maxHeight = -topIndexY; if (topIndexY > 0) { maxHeight += renderer.getLineHeight(lineIndex--); } while (height > maxHeight && lineIndex >= 0) { maxHeight += renderer.getLineHeight(lineIndex--); } } return Math.min(height, maxHeight); } int getAvailableHeightBellow(int height) { int partialBottomIndex = getPartialBottomIndex(); int topY = getLinePixel(partialBottomIndex); int lineHeight = renderer.getLineHeight(partialBottomIndex); int availableHeight = 0; int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin; if (topY + lineHeight > clientAreaHeight) { availableHeight = lineHeight - (clientAreaHeight - topY); } int lineIndex = partialBottomIndex + 1; int lineCount = content.getLineCount(); while (height > availableHeight && lineIndex < lineCount) { availableHeight += renderer.getLineHeight(lineIndex++); } return Math.min(height, availableHeight); } /** * Returns the color of the margins. * * @return the color of the margins. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public Color getMarginColor() { checkWidget(); return marginColor != null ? marginColor : getBackground(); } /** * Returns a string that uses only the line delimiter specified by the * StyledTextContent implementation. * <p> * Returns only the first line if the widget has the SWT.SINGLE style. * </p> * * @param text the text that may have line delimiters that don't * match the model line delimiter. Possible line delimiters * are CR ('\r'), LF ('\n'), CR/LF ("\r\n") * @return the converted text that only uses the line delimiter * specified by the model. Returns only the first line if the widget * has the SWT.SINGLE style. */ String getModelDelimitedText(String text) { int length = text.length(); if (length == 0) { return text; } int crIndex = 0; int lfIndex = 0; int i = 0; StringBuilder convertedText = new StringBuilder(length); String delimiter = getLineDelimiter(); while (i < length) { if (crIndex != -1) { crIndex = text.indexOf(SWT.CR, i); } if (lfIndex != -1) { lfIndex = text.indexOf(SWT.LF, i); } if (lfIndex == -1 && crIndex == -1) { // no more line breaks? break; } else if ((crIndex < lfIndex && crIndex != -1) || lfIndex == -1) { convertedText.append(text.substring(i, crIndex)); if (lfIndex == crIndex + 1) { // CR/LF combination? i = lfIndex + 1; } else { i = crIndex + 1; } } else { // LF occurs before CR! convertedText.append(text.substring(i, lfIndex)); i = lfIndex + 1; } if (isSingleLine()) { break; } convertedText.append(delimiter); } // copy remaining text if any and if not in single line mode or no // text copied thus far (because there only is one line) if (i < length && (!isSingleLine() || convertedText.length() == 0)) { convertedText.append(text.substring(i)); } return convertedText.toString(); } boolean checkDragDetect(Event event) { if (!isListening(SWT.DragDetect)) return false; if (event.button != 1) return false; if (blockSelection && blockXLocation != -1) { Rectangle rect = getBlockSelectionRectangle(); if (rect.contains(event.x, event.y)) { return dragDetect(event); } } else { if (selection.x == selection.y) return false; int offset = getOffsetAtPoint(event.x, event.y, null, true); if (selection.x <= offset && offset < selection.y) { return dragDetect(event); } } return false; } /** * Creates default key bindings. */ void createKeyBindings() { int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT; int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT; // Navigation setKeyBinding(SWT.ARROW_UP, ST.LINE_UP); setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN); if (IS_MAC) { setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START); setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END); setKeyBinding(SWT.HOME, ST.TEXT_START); setKeyBinding(SWT.END, ST.TEXT_END); setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START); setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END); setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT); setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS); } else { setKeyBinding(SWT.HOME, ST.LINE_START); setKeyBinding(SWT.END, ST.LINE_END); setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START); setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END); setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT); setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS); } setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP); setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN); setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START); setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END); setKeyBinding(nextKey, ST.COLUMN_NEXT); setKeyBinding(previousKey, ST.COLUMN_PREVIOUS); // Selection setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP); setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN); if (IS_MAC) { setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START); setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END); setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START); setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END); setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START); setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END); setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT); setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS); } else { setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START); setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END); setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START); setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END); setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT); setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS); } setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP); setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN); setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START); setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END); setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT); setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS); // Modification // Cut, Copy, Paste setKeyBinding('X' | SWT.MOD1, ST.CUT); setKeyBinding('C' | SWT.MOD1, ST.COPY); setKeyBinding('V' | SWT.MOD1, ST.PASTE); if (IS_MAC) { setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT); setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS); setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT); } else { // Cut, Copy, Paste Wordstar style setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT); setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY); setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE); } setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS); setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS); setKeyBinding(SWT.DEL, ST.DELETE_NEXT); setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS); setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT); // Miscellaneous setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE); } /** * Create the bitmaps to use for the caret in bidi mode. This * method only needs to be called upon widget creation and when the * font changes (the caret bitmap height needs to match font height). */ void createCaretBitmaps() { int caretWidth = BIDI_CARET_WIDTH; Display display = getDisplay(); if (leftCaretBitmap != null) { if (defaultCaret != null && leftCaretBitmap.equals(defaultCaret.getImage())) { defaultCaret.setImage(null); } leftCaretBitmap.dispose(); } int lineHeight = renderer.getLineHeight(); leftCaretBitmap = new Image(display, caretWidth, lineHeight); GC gc = new GC(leftCaretBitmap); gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); gc.fillRectangle(0, 0, caretWidth, lineHeight); gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE)); gc.drawLine(0, 0, 0, lineHeight); gc.drawLine(0, 0, caretWidth - 1, 0); gc.drawLine(0, 1, 1, 1); gc.dispose(); if (rightCaretBitmap != null) { if (defaultCaret != null && rightCaretBitmap.equals(defaultCaret.getImage())) { defaultCaret.setImage(null); } rightCaretBitmap.dispose(); } rightCaretBitmap = new Image(display, caretWidth, lineHeight); gc = new GC(rightCaretBitmap); gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); gc.fillRectangle(0, 0, caretWidth, lineHeight); gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE)); gc.drawLine(caretWidth - 1, 0, caretWidth - 1, lineHeight); gc.drawLine(0, 0, caretWidth - 1, 0); gc.drawLine(caretWidth - 1, 1, 1, 1); gc.dispose(); } /** * Moves the selected text to the clipboard. The text will be put in the * clipboard in plain text format and RTF format. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void cut() { checkWidget(); // Abort cut operation if copy to clipboard fails. // Fixes bug 21030. if (copySelection(DND.CLIPBOARD)) { if (blockSelection && blockXLocation != -1) { insertBlockSelectionText((char) 0, SWT.NULL); } else { doDelete(); } } } /** * A mouse move event has occurred. See if we should start autoscrolling. If * the move position is outside of the client area, initiate autoscrolling. * Otherwise, we've moved back into the widget so end autoscrolling. */ void doAutoScroll(Event event) { int caretLine = getCaretLine(); if (event.y > clientAreaHeight - bottomMargin && caretLine != content.getLineCount() - 1) { doAutoScroll(SWT.DOWN, event.y - (clientAreaHeight - bottomMargin)); } else if (event.y < topMargin && caretLine != 0) { doAutoScroll(SWT.UP, topMargin - event.y); } else if (event.x < leftMargin && !wordWrap) { doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x); } else if (event.x > clientAreaWidth - rightMargin && !wordWrap) { doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - rightMargin)); } else { endAutoScroll(); } } /** * Initiates autoscrolling. * * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS */ void doAutoScroll(int direction, int distance) { autoScrollDistance = distance; // If we're already autoscrolling in the given direction do nothing if (autoScrollDirection == direction) { return; } Runnable timer = null; final Display display = getDisplay(); // Set a timer that will simulate the user pressing and holding // down a cursor key (i.e., arrowUp, arrowDown). if (direction == SWT.UP) { timer = new Runnable() { @Override public void run() { /* Bug 437357 - NPE in StyledText.getCaretLine * StyledText.content is null at times, probably because the * widget itself has been disposed. */ if (isDisposed()) return; if (autoScrollDirection == SWT.UP) { if (blockSelection) { int verticalScrollOffset = getVerticalScrollOffset(); int y = blockYLocation - verticalScrollOffset; int pixels = Math.max(-autoScrollDistance, -verticalScrollOffset); if (pixels != 0) { setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true); scrollVertical(pixels, true); } } else { doSelectionPageUp(autoScrollDistance); } display.timerExec(V_SCROLL_RATE, this); } } }; autoScrollDirection = direction; display.timerExec(V_SCROLL_RATE, timer); } else if (direction == SWT.DOWN) { timer = new Runnable() { @Override public void run() { /* Bug 437357 - NPE in StyledText.getCaretLine * StyledText.content is null at times, probably because the * widget itself has been disposed. */ if (isDisposed()) return; if (autoScrollDirection == SWT.DOWN) { if (blockSelection) { int verticalScrollOffset = getVerticalScrollOffset(); int y = blockYLocation - verticalScrollOffset; int max = renderer.getHeight() - verticalScrollOffset - clientAreaHeight; int pixels = Math.min(autoScrollDistance, Math.max(0, max)); if (pixels != 0) { setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true); scrollVertical(pixels, true); } } else { doSelectionPageDown(autoScrollDistance); } display.timerExec(V_SCROLL_RATE, this); } } }; autoScrollDirection = direction; display.timerExec(V_SCROLL_RATE, timer); } else if (direction == ST.COLUMN_NEXT) { timer = new Runnable() { @Override public void run() { /* Bug 437357 - NPE in StyledText.getCaretLine * StyledText.content is null at times, probably because the * widget itself has been disposed. */ if (isDisposed()) return; if (autoScrollDirection == ST.COLUMN_NEXT) { if (blockSelection) { int x = blockXLocation - horizontalScrollOffset; int max = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth; int pixels = Math.min(autoScrollDistance, Math.max(0, max)); if (pixels != 0) { setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true); scrollHorizontal(pixels, true); } } else { doVisualNext(); setMouseWordSelectionAnchor(); doMouseSelection(); } display.timerExec(H_SCROLL_RATE, this); } } }; autoScrollDirection = direction; display.timerExec(H_SCROLL_RATE, timer); } else if (direction == ST.COLUMN_PREVIOUS) { timer = new Runnable() { @Override public void run() { /* Bug 437357 - NPE in StyledText.getCaretLine * StyledText.content is null at times, probably because the * widget itself has been disposed. */ if (isDisposed()) return; if (autoScrollDirection == ST.COLUMN_PREVIOUS) { if (blockSelection) { int x = blockXLocation - horizontalScrollOffset; int pixels = Math.max(-autoScrollDistance, -horizontalScrollOffset); if (pixels != 0) { setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true); scrollHorizontal(pixels, true); } } else { doVisualPrevious(); setMouseWordSelectionAnchor(); doMouseSelection(); } display.timerExec(H_SCROLL_RATE, this); } } }; autoScrollDirection = direction; display.timerExec(H_SCROLL_RATE, timer); } } /** * Deletes the previous character. Delete the selected text if any. * Move the caret in front of the deleted text. */ void doBackspace() { Event event = new Event(); event.text = ""; if (selection.x != selection.y) { event.start = selection.x; event.end = selection.y; sendKeyEvent(event); } else if (caretOffset > 0) { int lineIndex = content.getLineAtOffset(caretOffset); int lineOffset = content.getOffsetAtLine(lineIndex); if (caretOffset == lineOffset) { lineOffset = content.getOffsetAtLine(lineIndex - 1); event.start = lineOffset + content.getLine(lineIndex - 1).length(); event.end = caretOffset; } else { boolean isSurrogate = false; String lineText = content.getLine(lineIndex); char ch = lineText.charAt(caretOffset - lineOffset - 1); if (0xDC00 <= ch && ch <= 0xDFFF) { if (caretOffset - lineOffset - 2 >= 0) { ch = lineText.charAt(caretOffset - lineOffset - 2); isSurrogate = 0xD800 <= ch && ch <= 0xDBFF; } } TextLayout layout = renderer.getTextLayout(lineIndex); int start = layout.getPreviousOffset(caretOffset - lineOffset, isSurrogate ? SWT.MOVEMENT_CLUSTER : SWT.MOVEMENT_CHAR); renderer.disposeTextLayout(layout); event.start = start + lineOffset; event.end = caretOffset; } sendKeyEvent(event); } } void doBlockColumn(boolean next) { if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); int x = blockXLocation - horizontalScrollOffset; int y = blockYLocation - getVerticalScrollOffset(); int[] trailing = new int[1]; int offset = getOffsetAtPoint(x, y, trailing, true); if (offset != -1) { offset += trailing[0]; int lineIndex = content.getLineAtOffset(offset); int newOffset; if (next) { newOffset = getClusterNext(offset, lineIndex); } else { newOffset = getClusterPrevious(offset, lineIndex); } offset = newOffset != offset ? newOffset : -1; } if (offset != -1) { setBlockSelectionOffset(offset, true); showCaret(); } else { int width = next ? renderer.averageCharWidth : -renderer.averageCharWidth; int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth()); x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset; setBlockSelectionLocation(x, y, true); Rectangle rect = new Rectangle(x, y, 0, 0); showLocation(rect, true); } } void doBlockContentStartEnd(boolean end) { if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); int offset = end ? content.getCharCount() : 0; setBlockSelectionOffset(offset, true); showCaret(); } void doBlockWord(boolean next) { if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); int x = blockXLocation - horizontalScrollOffset; int y = blockYLocation - getVerticalScrollOffset(); int[] trailing = new int[1]; int offset = getOffsetAtPoint(x, y, trailing, true); if (offset != -1) { offset += trailing[0]; int lineIndex = content.getLineAtOffset(offset); int lineOffset = content.getOffsetAtLine(lineIndex); String lineText = content.getLine(lineIndex); int lineLength = lineText.length(); int newOffset = offset; if (next) { if (offset < lineOffset + lineLength) { newOffset = getWordNext(offset, SWT.MOVEMENT_WORD); } } else { if (offset > lineOffset) { newOffset = getWordPrevious(offset, SWT.MOVEMENT_WORD); } } offset = newOffset != offset ? newOffset : -1; } if (offset != -1) { setBlockSelectionOffset(offset, true); showCaret(); } else { int width = (next ? renderer.averageCharWidth : -renderer.averageCharWidth) * 6; int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth()); x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset; setBlockSelectionLocation(x, y, true); Rectangle rect = new Rectangle(x, y, 0, 0); showLocation(rect, true); } } void doBlockLineVertical(boolean up) { if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); int y = blockYLocation - getVerticalScrollOffset(); int lineIndex = getLineIndex(y); if (up) { if (lineIndex > 0) { y = getLinePixel(lineIndex - 1); setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true); if (y < topMargin) { scrollVertical(y - topMargin, true); } } } else { int lineCount = content.getLineCount(); if (lineIndex + 1 < lineCount) { y = getLinePixel(lineIndex + 2) - 1; setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true); int bottom = clientAreaHeight - bottomMargin; if (y > bottom) { scrollVertical(y - bottom, true); } } } } void doBlockLineHorizontal(boolean end) { if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); int x = blockXLocation - horizontalScrollOffset; int y = blockYLocation - getVerticalScrollOffset(); int lineIndex = getLineIndex(y); int lineOffset = content.getOffsetAtLine(lineIndex); String lineText = content.getLine(lineIndex); int lineLength = lineText.length(); int[] trailing = new int[1]; int offset = getOffsetAtPoint(x, y, trailing, true); if (offset != -1) { offset += trailing[0]; int newOffset = offset; if (end) { if (offset < lineOffset + lineLength) { newOffset = lineOffset + lineLength; } } else { if (offset > lineOffset) { newOffset = lineOffset; } } offset = newOffset != offset ? newOffset : -1; } else { if (!end) offset = lineOffset + lineLength; } if (offset != -1) { setBlockSelectionOffset(offset, true); showCaret(); } else { int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth()); x = (end ? maxWidth : 0) - horizontalScrollOffset; setBlockSelectionLocation(x, y, true); Rectangle rect = new Rectangle(x, y, 0, 0); showLocation(rect, true); } } void doBlockSelection(boolean sendEvent) { if (caretOffset > selectionAnchor) { selection.x = selectionAnchor; selection.y = caretOffset; } else { selection.x = caretOffset; selection.y = selectionAnchor; } updateCaretVisibility(); setCaretLocation(); super.redraw(); if (sendEvent) { sendSelectionEvent(); } sendAccessibleTextCaretMoved(); } /** * Replaces the selection with the character or insert the character at the * current caret position if no selection exists. * <p> * If a carriage return was typed replace it with the line break character * used by the widget on this platform. * </p> * * @param key the character typed by the user */ void doContent(char key) { if (blockSelection && blockXLocation != -1) { insertBlockSelectionText(key, SWT.NULL); return; } Event event = new Event(); event.start = selection.x; event.end = selection.y; // replace a CR line break with the widget line break // CR does not make sense on Windows since most (all?) applications // don't recognize CR as a line break. if (key == SWT.CR || key == SWT.LF) { if (!isSingleLine()) { event.text = getLineDelimiter(); } } else if (selection.x == selection.y && overwrite && key != TAB) { // no selection and overwrite mode is on and the typed key is not a // tab character (tabs are always inserted without overwriting)? int lineIndex = content.getLineAtOffset(event.end); int lineOffset = content.getOffsetAtLine(lineIndex); String line = content.getLine(lineIndex); // replace character at caret offset if the caret is not at the // end of the line if (event.end < lineOffset + line.length()) { event.end++; } event.text = new String(new char[] { key }); } else { event.text = new String(new char[] { key }); } if (event.text != null) { if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) { return; } sendKeyEvent(event); } } /** * Moves the caret after the last character of the widget content. */ void doContentEnd() { // place caret at end of first line if receiver is in single // line mode. fixes 4820. if (isSingleLine()) { doLineEnd(); } else { int length = content.getCharCount(); setCaretOffset(length, SWT.DEFAULT); showCaret(); } } /** * Moves the caret in front of the first character of the widget content. */ void doContentStart() { setCaretOffset(0, SWT.DEFAULT); showCaret(); } /** * Moves the caret to the start of the selection if a selection exists. * Otherwise, if no selection exists move the cursor according to the * cursor selection rules. * * @see #doSelectionCursorPrevious */ void doCursorPrevious() { if (selection.y - selection.x > 0) { setCaretOffset(selection.x, OFFSET_LEADING); showCaret(); } else { doSelectionCursorPrevious(); } } /** * Moves the caret to the end of the selection if a selection exists. * Otherwise, if no selection exists move the cursor according to the * cursor selection rules. * * @see #doSelectionCursorNext */ void doCursorNext() { if (selection.y - selection.x > 0) { setCaretOffset(selection.y, PREVIOUS_OFFSET_TRAILING); showCaret(); } else { doSelectionCursorNext(); } } /** * Deletes the next character. Delete the selected text if any. */ void doDelete() { Event event = new Event(); event.text = ""; if (selection.x != selection.y) { event.start = selection.x; event.end = selection.y; sendKeyEvent(event); } else if (caretOffset < content.getCharCount()) { int line = content.getLineAtOffset(caretOffset); int lineOffset = content.getOffsetAtLine(line); int lineLength = content.getLine(line).length(); if (caretOffset == lineOffset + lineLength) { event.start = caretOffset; event.end = content.getOffsetAtLine(line + 1); } else { event.start = caretOffset; event.end = getClusterNext(caretOffset, line); } sendKeyEvent(event); } } /** * Deletes the next word. */ void doDeleteWordNext() { if (selection.x != selection.y) { // if a selection exists, treat the as if // only the delete key was pressed doDelete(); } else { Event event = new Event(); event.text = ""; event.start = caretOffset; event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD); sendKeyEvent(event); } } /** * Deletes the previous word. */ void doDeleteWordPrevious() { if (selection.x != selection.y) { // if a selection exists, treat as if // only the backspace key was pressed doBackspace(); } else { Event event = new Event(); event.text = ""; event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD); event.end = caretOffset; sendKeyEvent(event); } } /** * Moves the caret one line down and to the same character offset relative * to the beginning of the line. Move the caret to the end of the new line * if the new line is shorter than the character offset. Moves the caret to * the end of the text if the caret already is on the last line. */ void doLineDown(boolean select) { int caretLine = getCaretLine(); int lineCount = content.getLineCount(); int y = 0; boolean lastLine = false; if (isWordWrap()) { int lineOffset = content.getOffsetAtLine(caretLine); int offsetInLine = caretOffset - lineOffset; TextLayout layout = renderer.getTextLayout(caretLine); int lineIndex = getVisualLineIndex(layout, offsetInLine); int layoutLineCount = layout.getLineCount(); if (lineIndex == layoutLineCount - 1) { lastLine = caretLine == lineCount - 1; caretLine++; } else { y = layout.getLineBounds(lineIndex + 1).y; y++; // bug 485722: workaround for fractional line heights } renderer.disposeTextLayout(layout); } else { lastLine = caretLine == lineCount - 1; caretLine++; } if (lastLine) { setCaretOffset(content.getCharCount(), SWT.DEFAULT); } else { int[] alignment = new int[1]; int offset = getOffsetAtPoint(columnX, y, caretLine, alignment); setCaretOffset(offset, alignment[0]); } int oldColumnX = columnX; int oldHScrollOffset = horizontalScrollOffset; if (select) { setMouseWordSelectionAnchor(); // select first and then scroll to reduce flash when key // repeat scrolls lots of lines doSelection(ST.COLUMN_NEXT); } showCaret(); int hScrollChange = oldHScrollOffset - horizontalScrollOffset; columnX = oldColumnX + hScrollChange; } /** * Moves the caret to the end of the line. */ void doLineEnd() { int caretLine = getCaretLine(); int lineOffset = content.getOffsetAtLine(caretLine); int lineEndOffset; if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(caretLine); int offsetInLine = caretOffset - lineOffset; int lineIndex = getVisualLineIndex(layout, offsetInLine); int[] offsets = layout.getLineOffsets(); lineEndOffset = lineOffset + offsets[lineIndex + 1]; renderer.disposeTextLayout(layout); } else { int lineLength = content.getLine(caretLine).length(); lineEndOffset = lineOffset + lineLength; } setCaretOffset(lineEndOffset, PREVIOUS_OFFSET_TRAILING); showCaret(); } /** * Moves the caret to the beginning of the line. */ void doLineStart() { int caretLine = getCaretLine(); int lineOffset = content.getOffsetAtLine(caretLine); if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(caretLine); int offsetInLine = caretOffset - lineOffset; int lineIndex = getVisualLineIndex(layout, offsetInLine); int[] offsets = layout.getLineOffsets(); lineOffset += offsets[lineIndex]; renderer.disposeTextLayout(layout); } setCaretOffset(lineOffset, OFFSET_LEADING); showCaret(); } /** * Moves the caret one line up and to the same character offset relative * to the beginning of the line. Move the caret to the end of the new line * if the new line is shorter than the character offset. Moves the caret to * the beginning of the document if it is already on the first line. */ void doLineUp(boolean select) { int caretLine = getCaretLine(), y = 0; boolean firstLine = false; if (isWordWrap()) { int lineOffset = content.getOffsetAtLine(caretLine); int offsetInLine = caretOffset - lineOffset; TextLayout layout = renderer.getTextLayout(caretLine); int lineIndex = getVisualLineIndex(layout, offsetInLine); if (lineIndex == 0) { firstLine = caretLine == 0; if (!firstLine) { caretLine--; y = renderer.getLineHeight(caretLine) - 1; y--; // bug 485722: workaround for fractional line heights } } else { y = layout.getLineBounds(lineIndex - 1).y; y++; // bug 485722: workaround for fractional line heights } renderer.disposeTextLayout(layout); } else { firstLine = caretLine == 0; caretLine--; } if (firstLine) { setCaretOffset(0, SWT.DEFAULT); } else { int[] alignment = new int[1]; int offset = getOffsetAtPoint(columnX, y, caretLine, alignment); setCaretOffset(offset, alignment[0]); } int oldColumnX = columnX; int oldHScrollOffset = horizontalScrollOffset; if (select) setMouseWordSelectionAnchor(); showCaret(); if (select) doSelection(ST.COLUMN_PREVIOUS); int hScrollChange = oldHScrollOffset - horizontalScrollOffset; columnX = oldColumnX + hScrollChange; } void doMouseLinkCursor() { Display display = getDisplay(); Point point = display.getCursorLocation(); point = display.map(null, this, point); doMouseLinkCursor(point.x, point.y); } void doMouseLinkCursor(int x, int y) { int offset = getOffsetAtPoint(x, y, null, true); Display display = getDisplay(); Cursor newCursor = cursor; if (renderer.hasLink(offset)) { newCursor = display.getSystemCursor(SWT.CURSOR_HAND); } else { if (cursor == null) { int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM; newCursor = display.getSystemCursor(type); } } if (newCursor != getCursor()) super.setCursor(newCursor); } /** * Moves the caret to the specified location. * * @param x x location of the new caret position * @param y y location of the new caret position * @param select the location change is a selection operation. * include the line delimiter in the selection */ void doMouseLocationChange(int x, int y, boolean select) { int line = getLineIndex(y); updateCaretDirection = true; if (blockSelection) { x = Math.max(leftMargin, Math.min(x, clientAreaWidth - rightMargin)); y = Math.max(topMargin, Math.min(y, clientAreaHeight - bottomMargin)); if (doubleClickEnabled && clickCount > 1) { boolean wordSelect = (clickCount & 1) == 0; if (wordSelect) { Point left = getPointAtOffset(doubleClickSelection.x); int[] trailing = new int[1]; int offset = getOffsetAtPoint(x, y, trailing, true); if (offset != -1) { if (x > left.x) { offset = getWordNext(offset + trailing[0], SWT.MOVEMENT_WORD_END); setBlockSelectionOffset(doubleClickSelection.x, offset, true); } else { offset = getWordPrevious(offset + trailing[0], SWT.MOVEMENT_WORD_START); setBlockSelectionOffset(doubleClickSelection.y, offset, true); } } else { if (x > left.x) { setBlockSelectionLocation(left.x, left.y, x, y, true); } else { Point right = getPointAtOffset(doubleClickSelection.y); setBlockSelectionLocation(right.x, right.y, x, y, true); } } } else { setBlockSelectionLocation(blockXLocation, y, true); } return; } else { if (select) { if (blockXLocation == -1) { setBlockSelectionOffset(caretOffset, false); } } else { clearBlockSelection(true, false); } int[] trailing = new int[1]; int offset = getOffsetAtPoint(x, y, trailing, true); if (offset != -1) { if (select) { setBlockSelectionOffset(offset + trailing[0], true); return; } } else { if (isFixedLineHeight() && renderer.fixedPitch) { int avg = renderer.averageCharWidth; x = ((x + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset; } setBlockSelectionLocation(x, y, true); return; } } } // allow caret to be placed below first line only if receiver is // not in single line mode. fixes 4820. if (line < 0 || (isSingleLine() && line > 0)) { return; } int[] alignment = new int[1]; int newCaretOffset = getOffsetAtPoint(x, y, alignment); int newCaretAlignemnt = alignment[0]; if (doubleClickEnabled && clickCount > 1) { newCaretOffset = doMouseWordSelect(x, newCaretOffset, line); } int newCaretLine = content.getLineAtOffset(newCaretOffset); // Is the mouse within the left client area border or on // a different line? If not the autoscroll selection // could be incorrectly reset. Fixes 1GKM3XS boolean vchange = 0 <= y && y < clientAreaHeight || newCaretLine == 0 || newCaretLine == content.getLineCount() - 1; boolean hchange = 0 <= x && x < clientAreaWidth || wordWrap || newCaretLine != content.getLineAtOffset(caretOffset); if (vchange && hchange && (newCaretOffset != caretOffset || newCaretAlignemnt != caretAlignment)) { setCaretOffset(newCaretOffset, newCaretAlignemnt); if (select) doMouseSelection(); showCaret(); } if (!select) { setCaretOffset(newCaretOffset, newCaretAlignemnt); clearSelection(true); } } /** * Updates the selection based on the caret position */ void doMouseSelection() { if (caretOffset <= selection.x || (caretOffset > selection.x && caretOffset < selection.y && selectionAnchor == selection.x)) { doSelection(ST.COLUMN_PREVIOUS); } else { doSelection(ST.COLUMN_NEXT); } } /** * Returns the offset of the word at the specified offset. * If the current selection extends from high index to low index * (i.e., right to left, or caret is at left border of selection on * non-bidi platforms) the start offset of the word preceding the * selection is returned. If the current selection extends from * low index to high index the end offset of the word following * the selection is returned. * * @param x mouse x location * @param newCaretOffset caret offset of the mouse cursor location * @param line line index of the mouse cursor location */ int doMouseWordSelect(int x, int newCaretOffset, int line) { // flip selection anchor based on word selection direction from // base double click. Always do this here (and don't rely on doAutoScroll) // because auto scroll only does not cover all possible mouse selections // (e.g., mouse x < 0 && mouse y > caret line y) if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) { selectionAnchor = doubleClickSelection.y; } else if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) { selectionAnchor = doubleClickSelection.x; } if (0 <= x && x < clientAreaWidth) { boolean wordSelect = (clickCount & 1) == 0; if (caretOffset == selection.x) { if (wordSelect) { newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START); } else { newCaretOffset = content.getOffsetAtLine(line); } } else { if (wordSelect) { newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END); } else { int lineEnd = content.getCharCount(); if (line + 1 < content.getLineCount()) { lineEnd = content.getOffsetAtLine(line + 1); } newCaretOffset = lineEnd; } } } return newCaretOffset; } /** * Scrolls one page down so that the last line (truncated or whole) * of the current page becomes the fully visible top line. * <p> * The caret is scrolled the same number of lines so that its location * relative to the top line remains the same. The exception is the end * of the text where a full page scroll is not possible. In this case * the caret is moved after the last character. * </p> * * @param select whether or not to select the page */ void doPageDown(boolean select, int height) { if (isSingleLine()) return; int oldColumnX = columnX; int oldHScrollOffset = horizontalScrollOffset; if (isFixedLineHeight()) { int lineCount = content.getLineCount(); int caretLine = getCaretLine(); if (caretLine < lineCount - 1) { int lineHeight = renderer.getLineHeight(); int lines = (height == -1 ? clientAreaHeight : height) / lineHeight; int scrollLines = Math.min(lineCount - caretLine - 1, lines); // ensure that scrollLines never gets negative and at least one // line is scrolled. fixes bug 5602. scrollLines = Math.max(1, scrollLines); int[] alignment = new int[1]; int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines), alignment); setCaretOffset(offset, alignment[0]); if (select) { doSelection(ST.COLUMN_NEXT); } // scroll one page down or to the bottom int verticalMaximum = lineCount * getVerticalIncrement(); int pageSize = clientAreaHeight; int verticalScrollOffset = getVerticalScrollOffset(); int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement(); if (scrollOffset + pageSize > verticalMaximum) { scrollOffset = verticalMaximum - pageSize; } if (scrollOffset > verticalScrollOffset) { scrollVertical(scrollOffset - verticalScrollOffset, true); } } } else { int lineCount = content.getLineCount(); int caretLine = getCaretLine(); int lineIndex, lineHeight; if (height == -1) { lineIndex = getPartialBottomIndex(); int topY = getLinePixel(lineIndex); lineHeight = renderer.getLineHeight(lineIndex); height = topY; if (topY + lineHeight <= clientAreaHeight) { height += lineHeight; } else { if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(lineIndex); int y = clientAreaHeight - topY; for (int i = 0; i < layout.getLineCount(); i++) { Rectangle bounds = layout.getLineBounds(i); if (bounds.contains(bounds.x, y)) { height += bounds.y; break; } } renderer.disposeTextLayout(layout); } } } else { lineIndex = getLineIndex(height); int topLineY = getLinePixel(lineIndex); if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(lineIndex); int y = height - topLineY; for (int i = 0; i < layout.getLineCount(); i++) { Rectangle bounds = layout.getLineBounds(i); if (bounds.contains(bounds.x, y)) { height = topLineY + bounds.y + bounds.height; break; } } renderer.disposeTextLayout(layout); } else { height = topLineY + renderer.getLineHeight(lineIndex); } } int caretHeight = height; if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(caretLine); int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine); lineIndex = getVisualLineIndex(layout, offsetInLine); caretHeight += layout.getLineBounds(lineIndex).y; renderer.disposeTextLayout(layout); } lineIndex = caretLine; lineHeight = renderer.getLineHeight(lineIndex); while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) { caretHeight -= lineHeight; lineHeight = renderer.getLineHeight(++lineIndex); } int[] alignment = new int[1]; int offset = getOffsetAtPoint(columnX, caretHeight, lineIndex, alignment); setCaretOffset(offset, alignment[0]); if (select) doSelection(ST.COLUMN_NEXT); height = getAvailableHeightBellow(height); scrollVertical(height, true); if (height == 0) setCaretLocation(); } showCaret(); int hScrollChange = oldHScrollOffset - horizontalScrollOffset; columnX = oldColumnX + hScrollChange; } /** * Moves the cursor to the end of the last fully visible line. */ void doPageEnd() { // go to end of line if in single line mode. fixes 5673 if (isSingleLine()) { doLineEnd(); } else { int bottomOffset; if (isWordWrap()) { int lineIndex = getPartialBottomIndex(); TextLayout layout = renderer.getTextLayout(lineIndex); int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex); int index = layout.getLineCount() - 1; while (index >= 0) { Rectangle bounds = layout.getLineBounds(index); if (y >= bounds.y + bounds.height) break; index--; } if (index == -1 && lineIndex > 0) { bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length(); } else { bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1); } renderer.disposeTextLayout(layout); } else { int lineIndex = getBottomIndex(); bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length(); } if (caretOffset < bottomOffset) { setCaretOffset(bottomOffset, OFFSET_LEADING); showCaret(); } } } /** * Moves the cursor to the beginning of the first fully visible line. */ void doPageStart() { int topOffset; if (isWordWrap()) { int y, lineIndex; if (topIndexY > 0) { lineIndex = topIndex - 1; y = renderer.getLineHeight(lineIndex) - topIndexY; } else { lineIndex = topIndex; y = -topIndexY; } TextLayout layout = renderer.getTextLayout(lineIndex); int index = 0; int lineCount = layout.getLineCount(); while (index < lineCount) { Rectangle bounds = layout.getLineBounds(index); if (y <= bounds.y) break; index++; } if (index == lineCount) { topOffset = content.getOffsetAtLine(lineIndex + 1); } else { topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index]; } renderer.disposeTextLayout(layout); } else { topOffset = content.getOffsetAtLine(topIndex); } if (caretOffset > topOffset) { setCaretOffset(topOffset, OFFSET_LEADING); showCaret(); } } /** * Scrolls one page up so that the first line (truncated or whole) * of the current page becomes the fully visible last line. * The caret is scrolled the same number of lines so that its location * relative to the top line remains the same. The exception is the beginning * of the text where a full page scroll is not possible. In this case the * caret is moved in front of the first character. */ void doPageUp(boolean select, int height) { if (isSingleLine()) return; int oldHScrollOffset = horizontalScrollOffset; int oldColumnX = columnX; if (isFixedLineHeight()) { int caretLine = getCaretLine(); if (caretLine > 0) { int lineHeight = renderer.getLineHeight(); int lines = (height == -1 ? clientAreaHeight : height) / lineHeight; int scrollLines = Math.max(1, Math.min(caretLine, lines)); caretLine -= scrollLines; int[] alignment = new int[1]; int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine), alignment); setCaretOffset(offset, alignment[0]); if (select) { doSelection(ST.COLUMN_PREVIOUS); } int verticalScrollOffset = getVerticalScrollOffset(); int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement()); if (scrollOffset < verticalScrollOffset) { scrollVertical(scrollOffset - verticalScrollOffset, true); } } } else { int caretLine = getCaretLine(); int lineHeight, lineIndex; if (height == -1) { if (topIndexY == 0) { height = clientAreaHeight; } else { int y; if (topIndex > 0) { lineIndex = topIndex - 1; lineHeight = renderer.getLineHeight(lineIndex); height = clientAreaHeight - topIndexY; y = lineHeight - topIndexY; } else { lineIndex = topIndex; lineHeight = renderer.getLineHeight(lineIndex); height = clientAreaHeight - (lineHeight + topIndexY); y = -topIndexY; } if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(lineIndex); for (int i = 0; i < layout.getLineCount(); i++) { Rectangle bounds = layout.getLineBounds(i); if (bounds.contains(bounds.x, y)) { height += lineHeight - (bounds.y + bounds.height); break; } } renderer.disposeTextLayout(layout); } } } else { lineIndex = getLineIndex(clientAreaHeight - height); int topLineY = getLinePixel(lineIndex); if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(lineIndex); int y = topLineY; for (int i = 0; i < layout.getLineCount(); i++) { Rectangle bounds = layout.getLineBounds(i); if (bounds.contains(bounds.x, y)) { height = clientAreaHeight - (topLineY + bounds.y); break; } } renderer.disposeTextLayout(layout); } else { height = clientAreaHeight - topLineY; } } int caretHeight = height; if (isWordWrap()) { TextLayout layout = renderer.getTextLayout(caretLine); int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine); lineIndex = getVisualLineIndex(layout, offsetInLine); caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y; renderer.disposeTextLayout(layout); } lineIndex = caretLine; lineHeight = renderer.getLineHeight(lineIndex); while (caretHeight - lineHeight >= 0 && lineIndex > 0) { caretHeight -= lineHeight; lineHeight = renderer.getLineHeight(--lineIndex); } lineHeight = renderer.getLineHeight(lineIndex); int[] alignment = new int[1]; int offset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex, alignment); setCaretOffset(offset, alignment[0]); if (select) doSelection(ST.COLUMN_PREVIOUS); height = getAvailableHeightAbove(height); scrollVertical(-height, true); if (height == 0) setCaretLocation(); } showCaret(); int hScrollChange = oldHScrollOffset - horizontalScrollOffset; columnX = oldColumnX + hScrollChange; } /** * Updates the selection to extend to the current caret position. */ void doSelection(int direction) { int redrawStart = -1; int redrawEnd = -1; if (selectionAnchor == -1) { selectionAnchor = selection.x; } if (direction == ST.COLUMN_PREVIOUS) { if (caretOffset < selection.x) { // grow selection redrawEnd = selection.x; redrawStart = selection.x = caretOffset; // check if selection has reversed direction if (selection.y != selectionAnchor) { redrawEnd = selection.y; selection.y = selectionAnchor; } // test whether selection actually changed. Fixes 1G71EO1 } else if (selectionAnchor == selection.x && caretOffset < selection.y) { // caret moved towards selection anchor (left side of selection). // shrink selection redrawEnd = selection.y; redrawStart = selection.y = caretOffset; } } else { if (caretOffset > selection.y) { // grow selection redrawStart = selection.y; redrawEnd = selection.y = caretOffset; // check if selection has reversed direction if (selection.x != selectionAnchor) { redrawStart = selection.x; selection.x = selectionAnchor; } // test whether selection actually changed. Fixes 1G71EO1 } else if (selectionAnchor == selection.y && caretOffset > selection.x) { // caret moved towards selection anchor (right side of selection). // shrink selection redrawStart = selection.x; redrawEnd = selection.x = caretOffset; } } if (redrawStart != -1 && redrawEnd != -1) { internalRedrawRange(redrawStart, redrawEnd - redrawStart); sendSelectionEvent(); } sendAccessibleTextCaretMoved(); } /** * Moves the caret to the next character or to the beginning of the * next line if the cursor is at the end of a line. */ void doSelectionCursorNext() { int caretLine = getCaretLine(); int lineOffset = content.getOffsetAtLine(caretLine); int offsetInLine = caretOffset - lineOffset; int offset, alignment; if (offsetInLine < content.getLine(caretLine).length()) { TextLayout layout = renderer.getTextLayout(caretLine); offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER); int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)]; renderer.disposeTextLayout(layout); offset = offsetInLine + lineOffset; alignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING; setCaretOffset(offset, alignment); showCaret(); } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) { caretLine++; offset = content.getOffsetAtLine(caretLine); alignment = PREVIOUS_OFFSET_TRAILING; setCaretOffset(offset, alignment); showCaret(); } } /** * Moves the caret to the previous character or to the end of the previous * line if the cursor is at the beginning of a line. */ void doSelectionCursorPrevious() { int caretLine = getCaretLine(); int lineOffset = content.getOffsetAtLine(caretLine); int offsetInLine = caretOffset - lineOffset; if (offsetInLine > 0) { int offset = getClusterPrevious(caretOffset, caretLine); setCaretOffset(offset, OFFSET_LEADING); showCaret(); } else if (caretLine > 0) { caretLine--; lineOffset = content.getOffsetAtLine(caretLine); int offset = lineOffset + content.getLine(caretLine).length(); setCaretOffset(offset, OFFSET_LEADING); showCaret(); } } /** * Moves the caret one line down and to the same character offset relative * to the beginning of the line. Moves the caret to the end of the new line * if the new line is shorter than the character offset. * Moves the caret to the end of the text if the caret already is on the * last line. * Adjusts the selection according to the caret change. This can either add * to or subtract from the old selection, depending on the previous selection * direction. */ void doSelectionLineDown() { int oldColumnX = columnX = getPointAtOffset(caretOffset).x; doLineDown(true); columnX = oldColumnX; } /** * Moves the caret one line up and to the same character offset relative * to the beginning of the line. Moves the caret to the end of the new line * if the new line is shorter than the character offset. * Moves the caret to the beginning of the document if it is already on the * first line. * Adjusts the selection according to the caret change. This can either add * to or subtract from the old selection, depending on the previous selection * direction. */ void doSelectionLineUp() { int oldColumnX = columnX = getPointAtOffset(caretOffset).x; doLineUp(true); columnX = oldColumnX; } /** * Scrolls one page down so that the last line (truncated or whole) * of the current page becomes the fully visible top line. * <p> * The caret is scrolled the same number of lines so that its location * relative to the top line remains the same. The exception is the end * of the text where a full page scroll is not possible. In this case * the caret is moved after the last character. * <p></p> * Adjusts the selection according to the caret change. This can either add * to or subtract from the old selection, depending on the previous selection * direction. * </p> */ void doSelectionPageDown(int pixels) { int oldColumnX = columnX = getPointAtOffset(caretOffset).x; doPageDown(true, pixels); columnX = oldColumnX; } /** * Scrolls one page up so that the first line (truncated or whole) * of the current page becomes the fully visible last line. * <p> * The caret is scrolled the same number of lines so that its location * relative to the top line remains the same. The exception is the beginning * of the text where a full page scroll is not possible. In this case the * caret is moved in front of the first character. * </p><p> * Adjusts the selection according to the caret change. This can either add * to or subtract from the old selection, depending on the previous selection * direction. * </p> */ void doSelectionPageUp(int pixels) { int oldColumnX = columnX = getPointAtOffset(caretOffset).x; doPageUp(true, pixels); columnX = oldColumnX; } /** * Moves the caret to the end of the next word . */ void doSelectionWordNext() { int offset = getWordNext(caretOffset, SWT.MOVEMENT_WORD); // don't change caret position if in single line mode and the cursor // would be on a different line. fixes 5673 if (!isSingleLine() || content.getLineAtOffset(caretOffset) == content.getLineAtOffset(offset)) { // Force symmetrical movement for word next and previous. Fixes 14536 setCaretOffset(offset, OFFSET_LEADING); showCaret(); } } /** * Moves the caret to the start of the previous word. */ void doSelectionWordPrevious() { int offset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD); setCaretOffset(offset, OFFSET_LEADING); showCaret(); } /** * Moves the caret one character to the left. Do not go to the previous line. * When in a bidi locale and at a R2L character the caret is moved to the * beginning of the R2L segment (visually right) and then one character to the * left (visually left because it's now in a L2R segment). */ void doVisualPrevious() { int offset = getClusterPrevious(caretOffset, getCaretLine()); setCaretOffset(offset, SWT.DEFAULT); showCaret(); } /** * Moves the caret one character to the right. Do not go to the next line. * When in a bidi locale and at a R2L character the caret is moved to the * end of the R2L segment (visually left) and then one character to the * right (visually right because it's now in a L2R segment). */ void doVisualNext() { int offset = getClusterNext(caretOffset, getCaretLine()); setCaretOffset(offset, SWT.DEFAULT); showCaret(); } /** * Moves the caret to the end of the next word. * If a selection exists, move the caret to the end of the selection * and remove the selection. */ void doWordNext() { if (selection.y - selection.x > 0) { setCaretOffset(selection.y, SWT.DEFAULT); showCaret(); } else { doSelectionWordNext(); } } /** * Moves the caret to the start of the previous word. * If a selection exists, move the caret to the start of the selection * and remove the selection. */ void doWordPrevious() { if (selection.y - selection.x > 0) { setCaretOffset(selection.x, SWT.DEFAULT); showCaret(); } else { doSelectionWordPrevious(); } } /** * Ends the autoscroll process. */ void endAutoScroll() { autoScrollDirection = SWT.NULL; } @Override public Color getBackground() { checkWidget(); if (background == null) { return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); } return background; } /** * Returns the baseline, in points. * * Note: this API should not be used if a StyleRange attribute causes lines to * have different heights (i.e. different fonts, rise, etc). * * @return baseline the baseline * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 3.0 * * @see #getBaseline(int) */ public int getBaseline() { checkWidget(); return renderer.getBaseline(); } /** * Returns the baseline at the given offset, in points. * * @param offset the offset * * @return baseline the baseline * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> * </ul> * * @since 3.2 */ public int getBaseline(int offset) { checkWidget(); if (!(0 <= offset && offset <= content.getCharCount())) { SWT.error(SWT.ERROR_INVALID_RANGE); } if (isFixedLineHeight()) { return renderer.getBaseline(); } int lineIndex = content.getLineAtOffset(offset); int lineOffset = content.getOffsetAtLine(lineIndex); TextLayout layout = renderer.getTextLayout(lineIndex); int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length())); FontMetrics metrics = layout.getLineMetrics(lineInParagraph); renderer.disposeTextLayout(layout); return metrics.getAscent() + metrics.getLeading(); } /** * Gets the BIDI coloring mode. When true the BIDI text display * algorithm is applied to segments of text that are the same * color. * * @return the current coloring mode * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @deprecated use BidiSegmentListener instead. */ @Deprecated public boolean getBidiColoring() { checkWidget(); return bidiColoring; } /** * Returns whether the widget is in block selection mode. * * @return true if widget is in block selection mode, false otherwise * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public boolean getBlockSelection() { checkWidget(); return blockSelection; } Rectangle getBlockSelectionPosition() { int firstLine = getLineIndex(blockYAnchor - getVerticalScrollOffset()); int lastLine = getLineIndex(blockYLocation - getVerticalScrollOffset()); if (firstLine > lastLine) { int temp = firstLine; firstLine = lastLine; lastLine = temp; } int left = blockXAnchor; int right = blockXLocation; if (left > right) { left = blockXLocation; right = blockXAnchor; } return new Rectangle(left - horizontalScrollOffset, firstLine, right - horizontalScrollOffset, lastLine); } /** * Returns the block selection bounds. The bounds is * relative to the upper left corner of the document. * * @return the block selection bounds * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public Rectangle getBlockSelectionBounds() { Rectangle rect; if (blockSelection && blockXLocation != -1) { rect = getBlockSelectionRectangle(); } else { Point startPoint = getPointAtOffset(selection.x); Point endPoint = getPointAtOffset(selection.y); int height = getLineHeight(selection.y); rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y); if (selection.x == selection.y) { rect.width = getCaretWidth(); } } rect.x += horizontalScrollOffset; rect.y += getVerticalScrollOffset(); return rect; } Rectangle getBlockSelectionRectangle() { Rectangle rect = getBlockSelectionPosition(); rect.y = getLinePixel(rect.y); rect.width = rect.width - rect.x; rect.height = getLinePixel(rect.height + 1) - rect.y; return rect; } String getBlockSelectionText(String delimiter) { Rectangle rect = getBlockSelectionPosition(); int firstLine = rect.y; int lastLine = rect.height; int left = rect.x; int right = rect.width; StringBuilder buffer = new StringBuilder(); for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) { int start = getOffsetAtPoint(left, 0, lineIndex, null); int end = getOffsetAtPoint(right, 0, lineIndex, null); if (start > end) { int temp = start; start = end; end = temp; } String text = content.getTextRange(start, end - start); buffer.append(text); if (lineIndex < lastLine) buffer.append(delimiter); } return buffer.toString(); } /** * Returns the index of the last fully visible line. * * @return index of the last fully visible line. */ int getBottomIndex() { int bottomIndex; if (isFixedLineHeight()) { int lineCount = 1; int lineHeight = renderer.getLineHeight(); if (lineHeight != 0) { // calculate the number of lines that are fully visible int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset(); lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight; } bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1)); } else { int clientAreaHeight = this.clientAreaHeight - bottomMargin; bottomIndex = getLineIndex(clientAreaHeight); if (bottomIndex > 0) { int linePixel = getLinePixel(bottomIndex); int lineHeight = renderer.getLineHeight(bottomIndex); if (linePixel + lineHeight > clientAreaHeight) { if (getLinePixel(bottomIndex - 1) >= topMargin) { bottomIndex--; } } } } return bottomIndex; } /** * Returns the bottom margin. * * @return the bottom margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public int getBottomMargin() { checkWidget(); return bottomMargin; } Rectangle getBoundsAtOffset(int offset) { int lineIndex = content.getLineAtOffset(offset); int lineOffset = content.getOffsetAtLine(lineIndex); String line = content.getLine(lineIndex); Rectangle bounds; if (line.length() != 0) { TextLayout layout = renderer.getTextLayout(lineIndex); int offsetInLine = Math.min(layout.getText().length(), Math.max(0, offset - lineOffset)); bounds = layout.getBounds(offsetInLine, offsetInLine); if (getListeners(ST.LineGetSegments).length > 0 && caretAlignment == PREVIOUS_OFFSET_TRAILING && offsetInLine != 0) { offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER); Point point = layout.getLocation(offsetInLine, true); bounds = new Rectangle(point.x, point.y, 0, bounds.height); } renderer.disposeTextLayout(layout); } else { bounds = new Rectangle(0, 0, 0, renderer.getLineHeight()); } if (offset == caretOffset && !isWordWrap()) { int lineEnd = lineOffset + line.length(); if (offset == lineEnd) { bounds.width += getCaretWidth(); } } bounds.x += leftMargin - horizontalScrollOffset; bounds.y += getLinePixel(lineIndex); return bounds; } /** * Returns the caret position relative to the start of the text. * * @return the caret position relative to the start of the text. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getCaretOffset() { checkWidget(); return caretOffset; } /** * Returns the caret width. * * @return the caret width, 0 if caret is null. */ int getCaretWidth() { Caret caret = getCaret(); if (caret == null) return 0; return caret.getSize().x; } Object getClipboardContent(int clipboardType) { TextTransfer plainTextTransfer = TextTransfer.getInstance(); return clipboard.getContents(plainTextTransfer, clipboardType); } int getClusterNext(int offset, int lineIndex) { int lineOffset = content.getOffsetAtLine(lineIndex); TextLayout layout = renderer.getTextLayout(lineIndex); offset -= lineOffset; offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER); offset += lineOffset; renderer.disposeTextLayout(layout); return offset; } int getClusterPrevious(int offset, int lineIndex) { int lineOffset = content.getOffsetAtLine(lineIndex); TextLayout layout = renderer.getTextLayout(lineIndex); offset -= lineOffset; offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER); offset += lineOffset; renderer.disposeTextLayout(layout); return offset; } /** * Returns the content implementation that is used for text storage. * * @return content the user defined content implementation that is used for * text storage or the default content implementation if no user defined * content implementation has been set. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public StyledTextContent getContent() { checkWidget(); return content; } @Override public boolean getDragDetect() { checkWidget(); return dragDetect; } /** * Returns whether the widget implements double click mouse behavior. * * @return true if double clicking a word selects the word, false if double clicks * have the same effect as regular mouse clicks * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getDoubleClickEnabled() { checkWidget(); return doubleClickEnabled; } /** * Returns whether the widget content can be edited. * * @return true if content can be edited, false otherwise * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getEditable() { checkWidget(); return editable; } @Override public Color getForeground() { checkWidget(); if (foreground == null) { return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND); } return foreground; } /** * Returns the horizontal scroll increment. * * @return horizontal scroll increment. */ int getHorizontalIncrement() { return renderer.averageCharWidth; } /** * Returns the horizontal scroll offset relative to the start of the line. * * @return horizontal scroll offset relative to the start of the line, * measured in character increments starting at 0, if > 0 the content is scrolled * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getHorizontalIndex() { checkWidget(); return horizontalScrollOffset / getHorizontalIncrement(); } /** * Returns the horizontal scroll offset relative to the start of the line. * * @return the horizontal scroll offset relative to the start of the line, * measured in SWT logical point starting at 0, if > 0 the content is scrolled. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getHorizontalPixel() { checkWidget(); return horizontalScrollOffset; } /** * Returns the line indentation of the widget. * * @return the line indentation * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getLineIndent(int) * * @since 3.2 */ public int getIndent() { checkWidget(); return indent; } /** * Returns whether the widget justifies lines. * * @return whether lines are justified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getLineJustify(int) * * @since 3.2 */ public boolean getJustify() { checkWidget(); return justify; } /** * Returns the action assigned to the key. * Returns SWT.NULL if there is no action associated with the key. * * @param key a key code defined in SWT.java or a character. * Optionally ORd with a state mask. Preferred state masks are one or more of * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform * differences. However, there may be cases where using the specific state masks * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense. * @return one of the predefined actions defined in ST.java or SWT.NULL * if there is no action associated with the key. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getKeyBinding(int key) { checkWidget(); Integer action = keyActionMap.get(key); return action == null ? SWT.NULL : action.intValue(); } /** * Gets the number of characters. * * @return number of characters in the widget * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getCharCount() { checkWidget(); return content.getCharCount(); } /** * Returns the line at the given line index without delimiters. * Index 0 is the first line of the content. When there are not * any lines, getLine(0) is a valid call that answers an empty string. * <p> * * @param lineIndex index of the line to return. * @return the line text without delimiters * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li> * </ul> * @since 3.4 */ public String getLine(int lineIndex) { checkWidget(); if (lineIndex < 0 || (lineIndex > 0 && lineIndex >= content.getLineCount())) { SWT.error(SWT.ERROR_INVALID_RANGE); } return content.getLine(lineIndex); } /** * Returns the alignment of the line at the given index. * * @param index the index of the line * * @return the line alignment * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> * * @see #getAlignment() * * @since 3.2 */ public int getLineAlignment(int index) { checkWidget(); if (index < 0 || index > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return renderer.getLineAlignment(index, alignment); } /** * Returns the line at the specified offset in the text * where 0 < offset < getCharCount() so that getLineAtOffset(getCharCount()) * returns the line of the insert location. * * @param offset offset relative to the start of the content. * 0 <= offset <= getCharCount() * @return line at the specified offset in the text * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> * </ul> */ public int getLineAtOffset(int offset) { checkWidget(); if (offset < 0 || offset > getCharCount()) { SWT.error(SWT.ERROR_INVALID_RANGE); } return content.getLineAtOffset(offset); } /** * Returns the background color of the line at the given index. * Returns null if a LineBackgroundListener has been set or if no background * color has been specified for the line. Should not be called if a * LineBackgroundListener has been set since the listener maintains the * line background colors. * * @param index the index of the line * @return the background color of the line at the given index. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> */ public Color getLineBackground(int index) { checkWidget(); if (index < 0 || index > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return isListening(ST.LineGetBackground) ? null : renderer.getLineBackground(index, null); } /** * Returns the bullet of the line at the given index. * * @param index the index of the line * * @return the line bullet * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> * * @since 3.2 */ public Bullet getLineBullet(int index) { checkWidget(); if (index < 0 || index > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return isListening(ST.LineGetStyle) ? null : renderer.getLineBullet(index, null); } /** * Returns the line background data for the given line or null if * there is none. * * @param lineOffset offset of the line start relative to the start * of the content. * @param line line to get line background data for * @return line background data for the given line. */ StyledTextEvent getLineBackgroundData(int lineOffset, String line) { return sendLineEvent(ST.LineGetBackground, lineOffset, line); } /** * Gets the number of text lines. * * @return the number of lines in the widget * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getLineCount() { checkWidget(); return content.getLineCount(); } /** * Returns the number of lines that can be completely displayed in the * widget client area. * * @return number of lines that can be completely displayed in the widget * client area. */ int getLineCountWhole() { if (isFixedLineHeight()) { int lineHeight = renderer.getLineHeight(); return lineHeight != 0 ? clientAreaHeight / lineHeight : 1; } return getBottomIndex() - topIndex + 1; } /** * Returns the line delimiter used for entering new lines by key down * or paste operation. * * @return line delimiter used for entering new lines by key down * or paste operation. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public String getLineDelimiter() { checkWidget(); return content.getLineDelimiter(); } /** * Returns the line height. * <p> * Note: this API should not be used if a StyleRange attribute causes lines to * have different heights (i.e. different fonts, rise, etc). * </p> * * @return line height in points. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @see #getLineHeight(int) */ public int getLineHeight() { checkWidget(); return renderer.getLineHeight(); } /** * Returns the line height at the given offset. * * @param offset the offset * * @return line height in points * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> * </ul> * * @since 3.2 */ public int getLineHeight(int offset) { checkWidget(); if (!(0 <= offset && offset <= content.getCharCount())) { SWT.error(SWT.ERROR_INVALID_RANGE); } if (isFixedLineHeight()) { return renderer.getLineHeight(); } int lineIndex = content.getLineAtOffset(offset); int lineOffset = content.getOffsetAtLine(lineIndex); TextLayout layout = renderer.getTextLayout(lineIndex); int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length())); int height = layout.getLineBounds(lineInParagraph).height; renderer.disposeTextLayout(layout); return height; } /** * Returns the indentation of the line at the given index. * * @param index the index of the line * * @return the line indentation * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> * * @see #getIndent() * * @since 3.2 */ public int getLineIndent(int index) { checkWidget(); if (index < 0 || index > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return isListening(ST.LineGetStyle) ? 0 : renderer.getLineIndent(index, indent); } /** * Returns the vertical indentation of the line at the given index. * * @param index the index of the line * * @return the line vertical indentation * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> * * @since 3.109 */ public int getLineVerticalIndent(int index) { checkWidget(); if (index < 0 || index >= content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return isListening(ST.LineGetStyle) ? 0 : renderer.getLineVerticalIndent(index); } /** * Returns whether the line at the given index is justified. * * @param index the index of the line * * @return whether the line is justified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> * * @see #getJustify() * * @since 3.2 */ public boolean getLineJustify(int index) { checkWidget(); if (index < 0 || index > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return isListening(ST.LineGetStyle) ? false : renderer.getLineJustify(index, justify); } /** * Returns the line spacing of the widget. * * @return the line spacing * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.2 */ public int getLineSpacing() { checkWidget(); return lineSpacing; } /** * Returns the line style data for the given line or null if there is * none. * <p> * If there is a LineStyleListener but it does not set any styles, * the StyledTextEvent.styles field will be initialized to an empty * array. * </p> * * @param lineOffset offset of the line start relative to the start of * the content. * @param line line to get line styles for * @return line style data for the given line. Styles may start before * line start and end after line end */ StyledTextEvent getLineStyleData(int lineOffset, String line) { return sendLineEvent(ST.LineGetStyle, lineOffset, line); } /** * Returns the top SWT logical point, relative to the client area, of a given line. * Clamps out of ranges index. * * @param lineIndex the line index, the max value is lineCount. If * lineIndex == lineCount it returns the bottom SWT logical point of the last line. * It means this function can be used to retrieve the bottom SWT logical point of any line. * * @return the top SWT logical point of a given line index * * @since 3.2 */ public int getLinePixel(int lineIndex) { checkWidget(); int lineCount = content.getLineCount(); lineIndex = Math.max(0, Math.min(lineCount, lineIndex)); if (isFixedLineHeight()) { int lineHeight = renderer.getLineHeight(); return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin; } if (lineIndex == topIndex) return topIndexY + topMargin; int height = topIndexY; if (lineIndex > topIndex) { for (int i = topIndex; i < lineIndex; i++) { height += renderer.getLineHeight(i); } } else { for (int i = topIndex - 1; i >= lineIndex; i--) { height -= renderer.getLineHeight(i); } } return height + topMargin; } /** * Returns the line index for a y, relative to the client area. * The line index returned is always in the range 0..lineCount - 1. * * @param y the y-coordinate point * * @return the line index for a given y-coordinate point * * @since 3.2 */ public int getLineIndex(int y) { checkWidget(); y -= topMargin; if (isFixedLineHeight()) { int lineHeight = renderer.getLineHeight(); int lineIndex = (y + getVerticalScrollOffset()) / lineHeight; int lineCount = content.getLineCount(); lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex)); return lineIndex; } if (y == topIndexY) return topIndex; int line = topIndex; if (y < topIndexY) { while (y < topIndexY && line > 0) { y += renderer.getLineHeight(--line); } } else { int lineCount = content.getLineCount(); int lineHeight = renderer.getLineHeight(line); while (y - lineHeight >= topIndexY && line < lineCount - 1) { y -= lineHeight; lineHeight = renderer.getLineHeight(++line); } } return line; } /** * Returns the tab stops of the line at the given <code>index</code>. * * @param index the index of the line * * @return the tab stops for the line * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> * * @see #getTabStops() * * @since 3.6 */ public int[] getLineTabStops(int index) { checkWidget(); if (index < 0 || index > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (isListening(ST.LineGetStyle)) return null; int[] tabs = renderer.getLineTabStops(index, null); if (tabs == null) tabs = this.tabs; if (tabs == null) return new int[] { renderer.tabWidth }; int[] result = new int[tabs.length]; System.arraycopy(tabs, 0, result, 0, tabs.length); return result; } /** * Returns the wrap indentation of the line at the given <code>index</code>. * * @param index the index of the line * * @return the wrap indentation * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> * </ul> * * @see #getWrapIndent() * * @since 3.6 */ public int getLineWrapIndent(int index) { checkWidget(); if (index < 0 || index > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return isListening(ST.LineGetStyle) ? 0 : renderer.getLineWrapIndent(index, wrapIndent); } /** * Returns the left margin. * * @return the left margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public int getLeftMargin() { checkWidget(); return leftMargin - alignmentMargin; } /** * Returns the x, y location of the upper left corner of the character * bounding box at the specified offset in the text. The point is * relative to the upper left corner of the widget client area. * * @param offset offset relative to the start of the content. * 0 <= offset <= getCharCount() * @return x, y location of the upper left corner of the character * bounding box at the specified offset in the text. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> * </ul> */ public Point getLocationAtOffset(int offset) { checkWidget(); if (offset < 0 || offset > getCharCount()) { SWT.error(SWT.ERROR_INVALID_RANGE); } return getPointAtOffset(offset); } /** * Returns <code>true</code> if the mouse navigator is enabled. * When mouse navigator is enabled, the user can navigate through the widget by pressing the middle button and moving the cursor * * @return the mouse navigator's enabled state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getEnabled * @since 3.110 */ public boolean getMouseNavigatorEnabled() { checkWidget(); return mouseNavigator != null; } /** * Returns the character offset of the first character of the given line. * * @param lineIndex index of the line, 0 based relative to the first * line in the content. 0 <= lineIndex < getLineCount(), except * lineIndex may always be 0 * @return offset offset of the first character of the line, relative to * the beginning of the document. The first character of the document is * at offset 0. * When there are not any lines, getOffsetAtLine(0) is a valid call that * answers 0. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li> * </ul> * @since 2.0 */ public int getOffsetAtLine(int lineIndex) { checkWidget(); if (lineIndex < 0 || (lineIndex > 0 && lineIndex >= content.getLineCount())) { SWT.error(SWT.ERROR_INVALID_RANGE); } return content.getOffsetAtLine(lineIndex); } /** * Returns the offset of the character at the given location relative * to the first character in the document. * <p> * The return value reflects the character offset that the caret will * be placed at if a mouse click occurred at the specified location. * If the x coordinate of the location is beyond the center of a character * the returned offset will be behind the character. * </p> * * @param point the origin of character bounding box relative to * the origin of the widget client area. * @return offset of the character at the given location relative * to the first character in the document. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when point is null</li> * <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li> * </ul> * * @deprecated Use {@link #getOffsetAtPoint(Point)} instead for better performance */ @Deprecated public int getOffsetAtLocation(Point point) { checkWidget(); if (point == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } int[] trailing = new int[1]; int offset = getOffsetAtPoint(point.x, point.y, trailing, true); if (offset == -1) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } return offset + trailing[0]; } /** * Returns the offset of the character at the given point relative * to the first character in the document. * <p> * The return value reflects the character offset that the caret will * be placed at if a mouse click occurred at the specified point. * If the x coordinate of the point is beyond the center of a character * the returned offset will be behind the character. * </p> * Note: This method is functionally similar to {@link #getOffsetAtLocation(Point)} except that * it does not throw an exception when no character is found and thus performs faster. * * @param point the origin of character bounding box relative to * the origin of the widget client area. * @return offset of the character at the given point relative * to the first character in the document. * -1 when there is no character at the specified location. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when point is <code>null</code></li> * </ul> * * @since 3.107 */ public int getOffsetAtPoint(Point point) { checkWidget(); if (point == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } int[] trailing = new int[1]; int offset = getOffsetAtPoint(point.x, point.y, trailing, true); return offset != -1 ? offset + trailing[0] : -1; } int getOffsetAtPoint(int x, int y, int[] alignment) { int lineIndex = getLineIndex(y); y -= getLinePixel(lineIndex); return getOffsetAtPoint(x, y, lineIndex, alignment); } int getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment) { TextLayout layout = renderer.getTextLayout(lineIndex); x += horizontalScrollOffset - leftMargin; int[] trailing = new int[1]; int offsetInLine = layout.getOffset(x, y, trailing); if (alignment != null) alignment[0] = OFFSET_LEADING; if (trailing[0] != 0) { int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]); int lineStart = layout.getLineOffsets()[lineInParagraph]; if (offsetInLine + trailing[0] == lineStart) { offsetInLine += trailing[0]; if (alignment != null) alignment[0] = PREVIOUS_OFFSET_TRAILING; } else { String line = content.getLine(lineIndex); int level = 0; if (alignment != null) { int offset = offsetInLine; while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--; if (offset == 0 && Character.isDigit(line.charAt(offset))) { level = isMirrored() ? 1 : 0; } else { level = layout.getLevel(offset) & 0x1; } } offsetInLine += trailing[0]; if (alignment != null) { int trailingLevel = layout.getLevel(offsetInLine) & 0x1; if ((level ^ trailingLevel) != 0) { alignment[0] = PREVIOUS_OFFSET_TRAILING; } else { alignment[0] = OFFSET_LEADING; } } } } renderer.disposeTextLayout(layout); return offsetInLine + content.getOffsetAtLine(lineIndex); } int getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly) { if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) { return -1; } int bottomIndex = getPartialBottomIndex(); int height = getLinePixel(bottomIndex + 1); if (inTextOnly && y > height) { return -1; } int lineIndex = getLineIndex(y); int lineOffset = content.getOffsetAtLine(lineIndex); TextLayout layout = renderer.getTextLayout(lineIndex); x += horizontalScrollOffset - leftMargin; y -= getLinePixel(lineIndex); int offset = layout.getOffset(x, y, trailing); Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset)); renderer.disposeTextLayout(layout); if (inTextOnly && !(rect.x <= x && x <= rect.x + rect.width)) { return -1; } return offset + lineOffset; } /** * Returns the orientation of the receiver. * * @return the orientation style * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 2.1.2 */ @Override public int getOrientation() { return super.getOrientation(); } /** * Returns the index of the last partially visible line. * * @return index of the last partially visible line. */ int getPartialBottomIndex() { if (isFixedLineHeight()) { int lineHeight = renderer.getLineHeight(); int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight); return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1); } return getLineIndex(clientAreaHeight - bottomMargin); } /** * Returns the index of the first partially visible line. * * @return index of the first partially visible line. */ int getPartialTopIndex() { if (isFixedLineHeight()) { int lineHeight = renderer.getLineHeight(); return getVerticalScrollOffset() / lineHeight; } return topIndexY <= 0 ? topIndex : topIndex - 1; } /** * Returns the content in the specified range using the platform line * delimiter to separate lines. * * @param writer the TextWriter to write line text into * @return the content in the specified range using the platform line * delimiter to separate lines as written by the specified TextWriter. */ String getPlatformDelimitedText(TextWriter writer) { int end = writer.getStart() + writer.getCharCount(); int startLine = content.getLineAtOffset(writer.getStart()); int endLine = content.getLineAtOffset(end); String endLineText = content.getLine(endLine); int endLineOffset = content.getOffsetAtLine(endLine); for (int i = startLine; i <= endLine; i++) { writer.writeLine(content.getLine(i), content.getOffsetAtLine(i)); if (i < endLine) { writer.writeLineDelimiter(PlatformLineDelimiter); } } if (end > endLineOffset + endLineText.length()) { writer.writeLineDelimiter(PlatformLineDelimiter); } writer.close(); return writer.toString(); } /** * Returns all the ranges of text that have an associated StyleRange. * Returns an empty array if a LineStyleListener has been set. * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * <p> * The ranges array contains start and length pairs. Each pair refers to * the corresponding style in the styles array. For example, the pair * that starts at ranges[n] with length ranges[n+1] uses the style * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>. * </p> * * @return the ranges or an empty array if a LineStyleListener has been set. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.2 * * @see #getStyleRanges(boolean) */ public int[] getRanges() { checkWidget(); if (!isListening(ST.LineGetStyle)) { int[] ranges = renderer.getRanges(0, content.getCharCount()); if (ranges != null) return ranges; } return new int[0]; } /** * Returns the ranges of text that have an associated StyleRange. * Returns an empty array if a LineStyleListener has been set. * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * <p> * The ranges array contains start and length pairs. Each pair refers to * the corresponding style in the styles array. For example, the pair * that starts at ranges[n] with length ranges[n+1] uses the style * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>. * </p> * * @param start the start offset of the style ranges to return * @param length the number of style ranges to return * * @return the ranges or an empty array if a LineStyleListener has been set. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li> * </ul> * * @since 3.2 * * @see #getStyleRanges(int, int, boolean) */ public int[] getRanges(int start, int length) { checkWidget(); int contentLength = getCharCount(); int end = start + length; if (start > end || start < 0 || end > contentLength) { SWT.error(SWT.ERROR_INVALID_RANGE); } if (!isListening(ST.LineGetStyle)) { int[] ranges = renderer.getRanges(start, length); if (ranges != null) return ranges; } return new int[0]; } /** * Returns the right margin. * * @return the right margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public int getRightMargin() { checkWidget(); return rightMargin; } /** * Returns the selection. * <p> * Text selections are specified in terms of caret positions. In a text * widget that contains N characters, there are N+1 caret positions, * ranging from 0..N * </p> * * @return start and end of the selection, x is the offset of the first * selected character, y is the offset after the last selected character. * The selection values returned are visual (i.e., x will always always be * <= y). To determine if a selection is right-to-left (RtoL) vs. left-to-right * (LtoR), compare the caretOffset to the start and end of the selection * (e.g., caretOffset == start of selection implies that the selection is RtoL). * @see #getSelectionRange * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public Point getSelection() { checkWidget(); return new Point(selection.x, selection.y); } /** * Returns the selection. * * @return start and length of the selection, x is the offset of the * first selected character, relative to the first character of the * widget content. y is the length of the selection. * The selection values returned are visual (i.e., length will always always be * positive). To determine if a selection is right-to-left (RtoL) vs. left-to-right * (LtoR), compare the caretOffset to the start and end of the selection * (e.g., caretOffset == start of selection implies that the selection is RtoL). * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public Point getSelectionRange() { checkWidget(); return new Point(selection.x, selection.y - selection.x); } /** * Returns the ranges of text that are inside the block selection rectangle. * <p> * The ranges array contains start and length pairs. When the receiver is not * in block selection mode the return arrays contains the start and length of * the regular selection. * * @return the ranges array * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public int[] getSelectionRanges() { checkWidget(); if (blockSelection && blockXLocation != -1) { Rectangle rect = getBlockSelectionPosition(); int firstLine = rect.y; int lastLine = rect.height; int left = rect.x; int right = rect.width; int[] ranges = new int[(lastLine - firstLine + 1) * 2]; int index = 0; for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) { int start = getOffsetAtPoint(left, 0, lineIndex, null); int end = getOffsetAtPoint(right, 0, lineIndex, null); if (start > end) { int temp = start; start = end; end = temp; } ranges[index++] = start; ranges[index++] = end - start; } return ranges; } return new int[] { selection.x, selection.y - selection.x }; } /** * Returns the receiver's selection background color. * * @return the selection background color * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 2.1 */ public Color getSelectionBackground() { checkWidget(); if (selectionBackground == null) { return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION); } return selectionBackground; } /** * Gets the number of selected characters. * * @return the number of selected characters. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getSelectionCount() { checkWidget(); if (blockSelection && blockXLocation != -1) { return getBlockSelectionText(content.getLineDelimiter()).length(); } return getSelectionRange().y; } /** * Returns the receiver's selection foreground color. * * @return the selection foreground color * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 2.1 */ public Color getSelectionForeground() { checkWidget(); if (selectionForeground == null) { return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); } return selectionForeground; } /** * Returns the selected text. * * @return selected text, or an empty String if there is no selection. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public String getSelectionText() { checkWidget(); if (blockSelection && blockXLocation != -1) { return getBlockSelectionText(content.getLineDelimiter()); } return content.getTextRange(selection.x, selection.y - selection.x); } StyledTextEvent getBidiSegments(int lineOffset, String line) { if (!isListening(ST.LineGetSegments)) { if (!bidiColoring) return null; StyledTextEvent event = new StyledTextEvent(content); event.segments = getBidiSegmentsCompatibility(line, lineOffset); return event; } StyledTextEvent event = sendLineEvent(ST.LineGetSegments, lineOffset, line); if (event == null || event.segments == null || event.segments.length == 0) return null; int lineLength = line.length(); int[] segments = event.segments; if (segments[0] > lineLength) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } char[] segmentsChars = event.segmentsChars; boolean hasSegmentsChars = segmentsChars != null; for (int i = 1; i < segments.length; i++) { if ((hasSegmentsChars ? segments[i] < segments[i - 1] : segments[i] <= segments[i - 1]) || segments[i] > lineLength) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } } if (hasSegmentsChars && !visualWrap) { for (int i = 0; i < segmentsChars.length; i++) { if (segmentsChars[i] == '\n' || segmentsChars[i] == '\r') { visualWrap = true; setVariableLineHeight(); break; } } } return event; } /** * @see #getBidiSegments * Supports deprecated setBidiColoring API. Remove when API is removed. */ int[] getBidiSegmentsCompatibility(String line, int lineOffset) { int lineLength = line.length(); StyleRange[] styles = null; StyledTextEvent event = getLineStyleData(lineOffset, line); if (event != null) { styles = event.styles; } else { styles = renderer.getStyleRanges(lineOffset, lineLength, true); } if (styles == null || styles.length == 0) { return new int[] { 0, lineLength }; } int k = 0, count = 1; while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) { k++; } int[] offsets = new int[(styles.length - k) * 2 + 2]; for (int i = k; i < styles.length; i++) { StyleRange style = styles[i]; int styleLineStart = Math.max(style.start - lineOffset, 0); int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart); styleLineEnd = Math.min(styleLineEnd, line.length()); if (i > 0 && count > 1 && ((styleLineStart >= offsets[count - 2] && styleLineStart <= offsets[count - 1]) || (styleLineEnd >= offsets[count - 2] && styleLineEnd <= offsets[count - 1])) && style.similarTo(styles[i - 1])) { offsets[count - 2] = Math.min(offsets[count - 2], styleLineStart); offsets[count - 1] = Math.max(offsets[count - 1], styleLineEnd); } else { if (styleLineStart > offsets[count - 1]) { offsets[count] = styleLineStart; count++; } offsets[count] = styleLineEnd; count++; } } // add offset for last non-colored segment in line, if any if (lineLength > offsets[count - 1]) { offsets[count] = lineLength; count++; } if (count == offsets.length) { return offsets; } int[] result = new int[count]; System.arraycopy(offsets, 0, result, 0, count); return result; } /** * Returns the style range at the given offset. * <p> * Returns null if a LineStyleListener has been set or if a style is not set * for the offset. * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p> * * @param offset the offset to return the style for. * 0 <= offset < getCharCount() must be true. * @return a StyleRange with start == offset and length == 1, indicating * the style at the given offset. null if a LineStyleListener has been set * or if a style is not set for the given offset. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li> * </ul> */ public StyleRange getStyleRangeAtOffset(int offset) { checkWidget(); if (offset < 0 || offset >= getCharCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (!isListening(ST.LineGetStyle)) { StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true); if (ranges != null) return ranges[0]; } return null; } /** * Returns the styles. * <p> * Returns an empty array if a LineStyleListener has been set. * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p><p> * Note: Because a StyleRange includes the start and length, the * same instance cannot occur multiple times in the array of styles. * If the same style attributes, such as font and color, occur in * multiple StyleRanges, <code>getStyleRanges(boolean)</code> * can be used to get the styles without the ranges. * </p> * * @return the styles or an empty array if a LineStyleListener has been set. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getStyleRanges(boolean) */ public StyleRange[] getStyleRanges() { checkWidget(); return getStyleRanges(0, content.getCharCount(), true); } /** * Returns the styles. * <p> * Returns an empty array if a LineStyleListener has been set. * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p><p> * Note: When <code>includeRanges</code> is true, the start and length * fields of each StyleRange will be valid, however the StyleRange * objects may need to be cloned. When <code>includeRanges</code> is * false, <code>getRanges(int, int)</code> can be used to get the * associated ranges. * </p> * * @param includeRanges whether the start and length field of the StyleRanges should be set. * * @return the styles or an empty array if a LineStyleListener has been set. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.2 * * @see #getRanges(int, int) * @see #setStyleRanges(int[], StyleRange[]) */ public StyleRange[] getStyleRanges(boolean includeRanges) { checkWidget(); return getStyleRanges(0, content.getCharCount(), includeRanges); } /** * Returns the styles for the given text range. * <p> * Returns an empty array if a LineStyleListener has been set. * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p><p> * Note: Because the StyleRange includes the start and length, the * same instance cannot occur multiple times in the array of styles. * If the same style attributes, such as font and color, occur in * multiple StyleRanges, <code>getStyleRanges(int, int, boolean)</code> * can be used to get the styles without the ranges. * </p> * @param start the start offset of the style ranges to return * @param length the number of style ranges to return * * @return the styles or an empty array if a LineStyleListener has * been set. The returned styles will reflect the given range. The first * returned <code>StyleRange</code> will have a starting offset >= start * and the last returned <code>StyleRange</code> will have an ending * offset <= start + length - 1 * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> * </ul> * * @see #getStyleRanges(int, int, boolean) * * @since 3.0 */ public StyleRange[] getStyleRanges(int start, int length) { checkWidget(); return getStyleRanges(start, length, true); } /** * Returns the styles for the given text range. * <p> * Returns an empty array if a LineStyleListener has been set. * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p><p> * Note: When <code>includeRanges</code> is true, the start and length * fields of each StyleRange will be valid, however the StyleRange * objects may need to be cloned. When <code>includeRanges</code> is * false, <code>getRanges(int, int)</code> can be used to get the * associated ranges. * </p> * * @param start the start offset of the style ranges to return * @param length the number of style ranges to return * @param includeRanges whether the start and length field of the StyleRanges should be set. * * @return the styles or an empty array if a LineStyleListener has * been set. The returned styles will reflect the given range. The first * returned <code>StyleRange</code> will have a starting offset >= start * and the last returned <code>StyleRange</code> will have an ending * offset >= start + length - 1 * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> * </ul> * * @since 3.2 * * @see #getRanges(int, int) * @see #setStyleRanges(int[], StyleRange[]) */ public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) { checkWidget(); int contentLength = getCharCount(); int end = start + length; if (start > end || start < 0 || end > contentLength) { SWT.error(SWT.ERROR_INVALID_RANGE); } if (!isListening(ST.LineGetStyle)) { StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges); if (ranges != null) return ranges; } return new StyleRange[0]; } /** * Returns the tab width measured in characters. * * @return tab width measured in characters * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getTabStops() */ public int getTabs() { checkWidget(); return tabLength; } /** * Returns the tab list of the receiver. * * @return the tab list * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.6 */ public int[] getTabStops() { checkWidget(); if (tabs == null) return new int[] { renderer.tabWidth }; int[] result = new int[tabs.length]; System.arraycopy(tabs, 0, result, 0, tabs.length); return result; } /** * Returns a copy of the widget content. * * @return copy of the widget content * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public String getText() { checkWidget(); return content.getTextRange(0, getCharCount()); } /** * Returns the widget content between the two offsets. * * @param start offset of the first character in the returned String * @param end offset of the last character in the returned String * @return widget content starting at start and ending at end * @see #getTextRange(int,int) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> * </ul> */ public String getText(int start, int end) { checkWidget(); int contentLength = getCharCount(); if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) { SWT.error(SWT.ERROR_INVALID_RANGE); } return content.getTextRange(start, end - start + 1); } /** * Returns the smallest bounding rectangle that includes the characters between two offsets. * * @param start offset of the first character included in the bounding box * @param end offset of the last character included in the bounding box * @return bounding box of the text between start and end * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> * </ul> * @since 3.1 */ public Rectangle getTextBounds(int start, int end) { checkWidget(); int contentLength = getCharCount(); if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) { SWT.error(SWT.ERROR_INVALID_RANGE); } int lineStart = content.getLineAtOffset(start); int lineEnd = content.getLineAtOffset(end); Rectangle rect; int y = getLinePixel(lineStart); int height = 0; int left = 0x7fffffff, right = 0; for (int i = lineStart; i <= lineEnd; i++) { int lineOffset = content.getOffsetAtLine(i); TextLayout layout = renderer.getTextLayout(i); int length = layout.getText().length(); if (length > 0) { if (i == lineStart) { if (i == lineEnd) { rect = layout.getBounds(start - lineOffset, end - lineOffset); } else { rect = layout.getBounds(start - lineOffset, length); } y += rect.y; } else if (i == lineEnd) { rect = layout.getBounds(0, end - lineOffset); } else { rect = layout.getBounds(); } left = Math.min(left, rect.x); right = Math.max(right, rect.x + rect.width); height += rect.height; } else { height += renderer.getLineHeight(); } renderer.disposeTextLayout(layout); } rect = new Rectangle(left, y, right - left, height); rect.x += leftMargin - horizontalScrollOffset; return rect; } /** * Returns the widget content starting at start for length characters. * * @param start offset of the first character in the returned String * @param length number of characters to return * @return widget content starting at start and extending length characters. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li> * </ul> */ public String getTextRange(int start, int length) { checkWidget(); int contentLength = getCharCount(); int end = start + length; if (start > end || start < 0 || end > contentLength) { SWT.error(SWT.ERROR_INVALID_RANGE); } return content.getTextRange(start, length); } /** * Returns the maximum number of characters that the receiver is capable of holding. * * @return the text limit * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getTextLimit() { checkWidget(); return textLimit; } /** * Gets the top index. * <p> * The top index is the index of the fully visible line that is currently * at the top of the widget or the topmost partially visible line if no line is fully visible. * The top index changes when the widget is scrolled. Indexing is zero based. * </p> * * @return the index of the top line * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getTopIndex() { checkWidget(); return topIndex; } /** * Returns the top margin. * * @return the top margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public int getTopMargin() { checkWidget(); return topMargin; } /** * Gets the top SWT logical point. * <p> * The top point is the SWT logical point position of the line that is * currently at the top of the widget. The text widget can be scrolled by points * by dragging the scroll thumb so that a partial line may be displayed at the top * the widget. The top point changes when the widget is scrolled. The top point * does not include the widget trimming. * </p> * * @return SWT logical point position of the top line * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public int getTopPixel() { checkWidget(); return getVerticalScrollOffset(); } /** * Returns the vertical scroll increment. * * @return vertical scroll increment. */ int getVerticalIncrement() { return renderer.getLineHeight(); } int getVerticalScrollOffset() { if (verticalScrollOffset == -1) { renderer.calculate(0, topIndex); int height = 0; for (int i = 0; i < topIndex; i++) { height += renderer.getLineHeight(i); } height -= topIndexY; verticalScrollOffset = height; } return verticalScrollOffset; } int getVisualLineIndex(TextLayout layout, int offsetInLine) { int lineIndex = layout.getLineIndex(offsetInLine); int[] offsets = layout.getLineOffsets(); Caret caret = getCaret(); if (caret != null && lineIndex != 0 && offsetInLine == offsets[lineIndex]) { int lineY = layout.getLineBounds(lineIndex).y; int caretY = caret.getLocation().y - getLinePixel(getCaretLine()); if (lineY > caretY) lineIndex--; caretAlignment = OFFSET_LEADING; } return lineIndex; } int getCaretDirection() { if (!isBidiCaret()) return SWT.DEFAULT; if (ime.getCompositionOffset() != -1) return SWT.DEFAULT; if (!updateCaretDirection && caretDirection != SWT.NULL) return caretDirection; updateCaretDirection = false; int caretLine = getCaretLine(); int lineOffset = content.getOffsetAtLine(caretLine); String line = content.getLine(caretLine); int offset = caretOffset - lineOffset; int lineLength = line.length(); if (lineLength == 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT; if (caretAlignment == PREVIOUS_OFFSET_TRAILING && offset > 0) offset--; if (offset == lineLength && offset > 0) offset--; while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--; if (offset == 0 && Character.isDigit(line.charAt(offset))) { return isMirrored() ? SWT.RIGHT : SWT.LEFT; } TextLayout layout = renderer.getTextLayout(caretLine); int level = layout.getLevel(offset); renderer.disposeTextLayout(layout); return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT; } /* * Returns the index of the line the caret is on. */ int getCaretLine() { return content.getLineAtOffset(caretOffset); } int getWrapWidth() { if (wordWrap && !isSingleLine()) { int width = clientAreaWidth - leftMargin - rightMargin; return width > 0 ? width : 1; } return -1; } int getWordNext(int offset, int movement) { return getWordNext(offset, movement, false); } int getWordNext(int offset, int movement, boolean ignoreListener) { int newOffset, lineOffset; String lineText; if (offset >= getCharCount()) { newOffset = offset; int lineIndex = content.getLineCount() - 1; lineOffset = content.getOffsetAtLine(lineIndex); lineText = content.getLine(lineIndex); } else { int lineIndex = content.getLineAtOffset(offset); lineOffset = content.getOffsetAtLine(lineIndex); lineText = content.getLine(lineIndex); int lineLength = lineText.length(); if (offset >= lineOffset + lineLength) { newOffset = content.getOffsetAtLine(lineIndex + 1); } else { TextLayout layout = renderer.getTextLayout(lineIndex); newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement); renderer.disposeTextLayout(layout); } } if (ignoreListener) return newOffset; return sendWordBoundaryEvent(ST.WordNext, movement, offset, newOffset, lineText, lineOffset); } int getWordPrevious(int offset, int movement) { return getWordPrevious(offset, movement, false); } int getWordPrevious(int offset, int movement, boolean ignoreListener) { int newOffset, lineOffset; String lineText; if (offset <= 0) { newOffset = 0; int lineIndex = content.getLineAtOffset(newOffset); lineOffset = content.getOffsetAtLine(lineIndex); lineText = content.getLine(lineIndex); } else { int lineIndex = content.getLineAtOffset(offset); lineOffset = content.getOffsetAtLine(lineIndex); lineText = content.getLine(lineIndex); if (offset == lineOffset) { String nextLineText = content.getLine(lineIndex - 1); int nextLineOffset = content.getOffsetAtLine(lineIndex - 1); newOffset = nextLineOffset + nextLineText.length(); } else { int layoutOffset = Math.min(offset - lineOffset, lineText.length()); TextLayout layout = renderer.getTextLayout(lineIndex); newOffset = lineOffset + layout.getPreviousOffset(layoutOffset, movement); renderer.disposeTextLayout(layout); } } if (ignoreListener) return newOffset; return sendWordBoundaryEvent(ST.WordPrevious, movement, offset, newOffset, lineText, lineOffset); } /** * Returns whether the widget wraps lines. * * @return true if widget wraps lines, false otherwise * @since 2.0 */ public boolean getWordWrap() { checkWidget(); return wordWrap; } /** * Returns the wrap indentation of the widget. * * @return the wrap indentation * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getLineWrapIndent(int) * * @since 3.6 */ public int getWrapIndent() { checkWidget(); return wrapIndent; } /** * Returns the location of the given offset. * <p> * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts). * </p> * * @return location of the character at the given offset in the line. */ Point getPointAtOffset(int offset) { int lineIndex = content.getLineAtOffset(offset); String line = content.getLine(lineIndex); int lineOffset = content.getOffsetAtLine(lineIndex); int offsetInLine = Math.max(0, offset - lineOffset); int lineLength = line.length(); if (lineIndex < content.getLineCount() - 1) { int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1; if (lineLength < offsetInLine && offsetInLine <= endLineOffset) { offsetInLine = lineLength; } } Point point; TextLayout layout = renderer.getTextLayout(lineIndex); if (lineLength != 0 && offsetInLine <= lineLength) { if (offsetInLine == lineLength) { offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER); point = layout.getLocation(offsetInLine, true); } else { switch (caretAlignment) { case OFFSET_LEADING: point = layout.getLocation(offsetInLine, false); break; case PREVIOUS_OFFSET_TRAILING: default: boolean lineBegin = offsetInLine == 0; // If word wrap is enabled, we should also consider offsets // of wrapped line parts as line begin and do NOT go back. // This prevents clients to jump one line higher than // expected, see bug 488172. // Respect caretAlignment at the caretOffset, unless there's // a non-empty selection, see bug 488172 comment 6. if (wordWrap && !lineBegin && (offset != caretOffset || selection.x != selection.y)) { int[] offsets = layout.getLineOffsets(); for (int i : offsets) { if (i == offsetInLine) { lineBegin = true; break; } } } if (lineBegin) { point = layout.getLocation(offsetInLine, false); } else { offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER); point = layout.getLocation(offsetInLine, true); } break; } } } else { point = new Point(layout.getIndent(), layout.getVerticalIndent()); } renderer.disposeTextLayout(layout); point.x += leftMargin - horizontalScrollOffset; point.y += getLinePixel(lineIndex); return point; } /** * Inserts a string. The old selection is replaced with the new text. * * @param string the string * @see #replaceTextRange(int,int,String) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when string is null</li> * </ul> */ public void insert(String string) { checkWidget(); if (string == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } if (blockSelection) { insertBlockSelectionText(string, false); } else { Point sel = getSelectionRange(); replaceTextRange(sel.x, sel.y, string); } } int insertBlockSelectionText(String text, boolean fillWithSpaces) { int lineCount = 1; for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); if (ch == '\n' || ch == '\r') { lineCount++; if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') { i++; } } } String[] lines = new String[lineCount]; int start = 0; lineCount = 0; for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); if (ch == '\n' || ch == '\r') { lines[lineCount++] = text.substring(start, i); if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') { i++; } start = i + 1; } } lines[lineCount++] = text.substring(start); if (fillWithSpaces) { int maxLength = 0; for (int i = 0; i < lines.length; i++) { int length = lines[i].length(); maxLength = Math.max(maxLength, length); } for (int i = 0; i < lines.length; i++) { String line = lines[i]; int length = line.length(); if (length < maxLength) { int numSpaces = maxLength - length; StringBuilder buffer = new StringBuilder(length + numSpaces); buffer.append(line); for (int j = 0; j < numSpaces; j++) buffer.append(' '); lines[i] = buffer.toString(); } } } int firstLine, lastLine, left, right; if (blockXLocation != -1) { Rectangle rect = getBlockSelectionPosition(); firstLine = rect.y; lastLine = rect.height; left = rect.x; right = rect.width; } else { firstLine = lastLine = getCaretLine(); left = right = getPointAtOffset(caretOffset).x; } start = caretOffset; int caretLine = getCaretLine(); int index = 0, lineIndex = firstLine; while (lineIndex <= lastLine) { String string = index < lineCount ? lines[index++] : ""; int lineStart = sendTextEvent(left, right, lineIndex, string, fillWithSpaces); if (lineIndex == caretLine) start = lineStart; lineIndex++; } while (index < lineCount) { int lineStart = sendTextEvent(left, left, lineIndex, lines[index++], fillWithSpaces); if (lineIndex == caretLine) start = lineStart; lineIndex++; } return start; } void insertBlockSelectionText(char key, int action) { if (key == SWT.CR || key == SWT.LF) return; Rectangle rect = getBlockSelectionPosition(); int firstLine = rect.y; int lastLine = rect.height; int left = rect.x; int right = rect.width; int[] trailing = new int[1]; int offset = 0, delta = 0; String text = key != 0 ? new String(new char[] { key }) : ""; int length = text.length(); for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) { String line = content.getLine(lineIndex); int lineOffset = content.getOffsetAtLine(lineIndex); int lineEndOffset = lineOffset + line.length(); int linePixel = getLinePixel(lineIndex); int start = getOffsetAtPoint(left, linePixel, trailing, true); boolean outOfLine = start == -1; if (outOfLine) { start = left < leftMargin ? lineOffset : lineEndOffset; } else { start += trailing[0]; } int end = getOffsetAtPoint(right, linePixel, trailing, true); if (end == -1) { end = right < leftMargin ? lineOffset : lineEndOffset; } else { end += trailing[0]; } if (start > end) { int temp = start; start = end; end = temp; } if (start == end && !outOfLine) { switch (action) { case ST.DELETE_PREVIOUS: if (start > lineOffset) start = getClusterPrevious(start, lineIndex); break; case ST.DELETE_NEXT: if (end < lineEndOffset) end = getClusterNext(end, lineIndex); break; } } if (outOfLine) { if (line.length() >= delta) { delta = line.length(); offset = lineEndOffset + length; } } else { offset = start + length; delta = content.getCharCount(); } Event event = new Event(); event.text = text; event.start = start; event.end = end; sendKeyEvent(event); } int x = getPointAtOffset(offset).x; int verticalScrollOffset = getVerticalScrollOffset(); setBlockSelectionLocation(x, blockYAnchor - verticalScrollOffset, x, blockYLocation - verticalScrollOffset, false); } /** * Creates content change listeners and set the default content model. */ void installDefaultContent() { textChangeListener = new TextChangeListener() { @Override public void textChanging(TextChangingEvent event) { handleTextChanging(event); } @Override public void textChanged(TextChangedEvent event) { handleTextChanged(event); } @Override public void textSet(TextChangedEvent event) { handleTextSet(event); } }; content = new DefaultContent(); content.addTextChangeListener(textChangeListener); } /** * Adds event listeners */ void installListeners() { ScrollBar verticalBar = getVerticalBar(); ScrollBar horizontalBar = getHorizontalBar(); listener = event -> { switch (event.type) { case SWT.Dispose: handleDispose(event); break; case SWT.KeyDown: handleKeyDown(event); break; case SWT.KeyUp: handleKeyUp(event); break; case SWT.MenuDetect: handleMenuDetect(event); break; case SWT.MouseDown: handleMouseDown(event); break; case SWT.MouseUp: handleMouseUp(event); break; case SWT.MouseMove: handleMouseMove(event); break; case SWT.Paint: handlePaint(event); break; case SWT.Resize: handleResize(event); break; case SWT.Traverse: handleTraverse(event); break; } }; addListener(SWT.Dispose, listener); addListener(SWT.KeyDown, listener); addListener(SWT.KeyUp, listener); addListener(SWT.MenuDetect, listener); addListener(SWT.MouseDown, listener); addListener(SWT.MouseUp, listener); addListener(SWT.MouseMove, listener); addListener(SWT.Paint, listener); addListener(SWT.Resize, listener); addListener(SWT.Traverse, listener); ime.addListener(SWT.ImeComposition, event -> { if (!editable) { event.doit = false; event.start = 0; event.end = 0; event.text = ""; return; } switch (event.detail) { case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break; case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break; case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break; } }); if (verticalBar != null) { verticalBar.addListener(SWT.Selection, event -> handleVerticalScroll(event)); } if (horizontalBar != null) { horizontalBar.addListener(SWT.Selection, event -> handleHorizontalScroll(event)); } } void internalRedrawRange(int start, int length) { if (length <= 0) return; int end = start + length; int startLine = content.getLineAtOffset(start); int endLine = content.getLineAtOffset(end); int partialBottomIndex = getPartialBottomIndex(); int partialTopIndex = getPartialTopIndex(); if (startLine > partialBottomIndex || endLine < partialTopIndex) { return; } if (partialTopIndex > startLine) { startLine = partialTopIndex; start = 0; } else { start -= content.getOffsetAtLine(startLine); } if (partialBottomIndex < endLine) { endLine = partialBottomIndex + 1; end = 0; } else { end -= content.getOffsetAtLine(endLine); } TextLayout layout = renderer.getTextLayout(startLine); int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine); int[] offsets = layout.getLineOffsets(); int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length())); /* Redraw end of line before start line if wrapped and start offset is first char */ if (isWordWrap() && startIndex > 0 && offsets[startIndex] == start) { Rectangle rect = layout.getLineBounds(startIndex - 1); rect.x = rect.width; rect.width = clientAreaWidth - rightMargin - rect.x; rect.x += lineX; rect.y += startLineY; super.redraw(rect.x, rect.y, rect.width, rect.height, false); } if (startLine == endLine) { int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length())); if (startIndex == endIndex) { /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */ Rectangle rect = layout.getBounds(start, end - 1); rect.x += lineX; rect.y += startLineY; super.redraw(rect.x, rect.y, rect.width, rect.height, false); renderer.disposeTextLayout(layout); return; } } /* Redraw start line from the start offset to the end of client area */ Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1); if (startRect.height == 0) { Rectangle bounds = layout.getLineBounds(startIndex); startRect.x = bounds.width; startRect.y = bounds.y; startRect.height = bounds.height; } startRect.x += lineX; startRect.y += startLineY; startRect.width = clientAreaWidth - rightMargin - startRect.x; super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false); /* Redraw end line from the beginning of the line to the end offset */ if (startLine != endLine) { renderer.disposeTextLayout(layout); layout = renderer.getTextLayout(endLine); offsets = layout.getLineOffsets(); } int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length())); Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1); if (endRect.height == 0) { Rectangle bounds = layout.getLineBounds(endIndex); endRect.y = bounds.y; endRect.height = bounds.height; } endRect.x += lineX; endRect.y += getLinePixel(endLine); super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false); renderer.disposeTextLayout(layout); /* Redraw all lines in between start and end line */ int y = startRect.y + startRect.height; if (endRect.y > y) { super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false); } } void handleCompositionOffset(Event event) { int[] trailing = new int[1]; event.index = getOffsetAtPoint(event.x, event.y, trailing, true); event.count = trailing[0]; } void handleCompositionSelection(Event event) { if (event.start != event.end) { int charCount = getCharCount(); event.start = Math.max(0, Math.min(event.start, charCount)); event.end = Math.max(0, Math.min(event.end, charCount)); if (event.text != null) { setSelection(event.start, event.end); } else { event.text = getTextRange(event.start, event.end - event.start); } } else { event.start = selection.x; event.end = selection.y; event.text = getSelectionText(); } } void handleCompositionChanged(Event event) { String text = event.text; int start = event.start; int end = event.end; int charCount = content.getCharCount(); start = Math.min(start, charCount); end = Math.min(end, charCount); int length = text.length(); if (length == ime.getCommitCount()) { content.replaceTextRange(start, end - start, ""); setCaretOffset(ime.getCompositionOffset(), SWT.DEFAULT); caretWidth = 0; caretDirection = SWT.NULL; } else { content.replaceTextRange(start, end - start, text); int alignment = SWT.DEFAULT; if (ime.getWideCaret()) { start = ime.getCompositionOffset(); int lineIndex = getCaretLine(); int lineOffset = content.getOffsetAtLine(lineIndex); TextLayout layout = renderer.getTextLayout(lineIndex); caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width; renderer.disposeTextLayout(layout); alignment = OFFSET_LEADING; } setCaretOffset(ime.getCaretOffset(), alignment); } resetSelection(); showCaret(); } /** * Frees resources. */ void handleDispose(Event event) { removeListener(SWT.Dispose, listener); notifyListeners(SWT.Dispose, event); event.type = SWT.None; clipboard.dispose(); if (renderer != null) { renderer.dispose(); renderer = null; } if (content != null) { content.removeTextChangeListener(textChangeListener); content = null; } if (defaultCaret != null) { defaultCaret.dispose(); defaultCaret = null; } if (leftCaretBitmap != null) { leftCaretBitmap.dispose(); leftCaretBitmap = null; } if (rightCaretBitmap != null) { rightCaretBitmap.dispose(); rightCaretBitmap = null; } if (isBidiCaret()) { BidiUtil.removeLanguageListener(this); } selectionBackground = null; selectionForeground = null; marginColor = null; textChangeListener = null; selection = null; doubleClickSelection = null; keyActionMap = null; background = null; foreground = null; clipboard = null; tabs = null; } /** * Scrolls the widget horizontally. */ void handleHorizontalScroll(Event event) { int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset; scrollHorizontal(scrollPixel, false); } /** * If an action has been registered for the key stroke execute the action. * Otherwise, if a character has been entered treat it as new content. * * @param event keyboard event */ void handleKey(Event event) { int action; caretAlignment = PREVIOUS_OFFSET_TRAILING; if (event.keyCode != 0) { // special key pressed (e.g., F1) action = getKeyBinding(event.keyCode | event.stateMask); } else { // character key pressed action = getKeyBinding(event.character | event.stateMask); if (action == SWT.NULL) { // see if we have a control character if ((event.stateMask & SWT.CTRL) != 0 && event.character <= 31) { // get the character from the CTRL+char sequence, the control // key subtracts 64 from the value of the key that it modifies int c = event.character + 64; action = getKeyBinding(c | event.stateMask); } } } if (action == SWT.NULL) { boolean ignore = false; if (IS_MAC) { // Ignore accelerator key combinations (we do not want to // insert a character in the text in this instance). ignore = (event.stateMask & (SWT.COMMAND | SWT.CTRL)) != 0; } else { // Ignore accelerator key combinations (we do not want to // insert a character in the text in this instance). Don't // ignore CTRL+ALT combinations since that is the Alt Gr // key on some keyboards. See bug 20953. ignore = (event.stateMask ^ SWT.ALT) == 0 || (event.stateMask ^ SWT.CTRL) == 0 || (event.stateMask ^ (SWT.ALT | SWT.SHIFT)) == 0 || (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) == 0; } // -ignore anything below SPACE except for line delimiter keys and tab. // -ignore DEL if (!ignore && event.character > 31 && event.character != SWT.DEL || event.character == SWT.CR || event.character == SWT.LF || event.character == TAB) { doContent(event.character); update(); } } else { invokeAction(action); } } /** * If a VerifyKey listener exists, verify that the key that was entered * should be processed. * * @param event keyboard event */ void handleKeyDown(Event event) { if (clipboardSelection == null) { clipboardSelection = new Point(selection.x, selection.y); } newOrientation = SWT.NONE; event.stateMask &= SWT.MODIFIER_MASK; Event verifyEvent = new Event(); verifyEvent.character = event.character; verifyEvent.keyCode = event.keyCode; verifyEvent.keyLocation = event.keyLocation; verifyEvent.stateMask = event.stateMask; verifyEvent.doit = event.doit; notifyListeners(ST.VerifyKey, verifyEvent); if (verifyEvent.doit) { if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL && event.keyCode == SWT.SHIFT && isBidiCaret()) { newOrientation = event.keyLocation == SWT.LEFT ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT; } handleKey(event); } } /** * Update the Selection Clipboard. * * @param event keyboard event */ void handleKeyUp(Event event) { if (clipboardSelection != null) { if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) { copySelection(DND.SELECTION_CLIPBOARD); } } clipboardSelection = null; if (newOrientation != SWT.NONE) { if (newOrientation != getOrientation()) { Event e = new Event(); e.doit = true; notifyListeners(SWT.OrientationChange, e); if (e.doit) { setOrientation(newOrientation); } } newOrientation = SWT.NONE; } } /** * Update the event location for focus-based context menu triggers. * * @param event menu detect event */ void handleMenuDetect(Event event) { if (event.detail == SWT.MENU_KEYBOARD) { Point point = getDisplay().map(this, null, getPointAtOffset(caretOffset)); event.x = point.x; event.y = point.y + getLineHeight(caretOffset); } } /** * Updates the caret location and selection if mouse button 1 has been * pressed. */ void handleMouseDown(Event event) { //force focus (object support) forceFocus(); //drag detect if (dragDetect && checkDragDetect(event)) return; //paste clipboard selection if (event.button == 2) { // On GTK, if mouseNavigator is enabled we have to distinguish a short middle-click (to paste content) from // a long middle-click (mouse navigation started) if (IS_GTK && mouseNavigator != null) { middleClickPressed = true; getDisplay().timerExec(200, () -> { boolean click = middleClickPressed; middleClickPressed = false; if (click && mouseNavigator != null) { mouseNavigator.onMouseDown(event); } else { pasteOnMiddleClick(event); } }); return; } else { pasteOnMiddleClick(event); } } //set selection if ((event.button != 1) || (IS_MAC && (event.stateMask & SWT.MOD4) != 0)) { return; } clickCount = event.count; if (clickCount == 1) { boolean select = (event.stateMask & SWT.MOD2) != 0; doMouseLocationChange(event.x, event.y, select); } else { if (doubleClickEnabled) { boolean wordSelect = (clickCount & 1) == 0; int offset = getOffsetAtPoint(event.x, event.y, null); int lineIndex = content.getLineAtOffset(offset); int lineOffset = content.getOffsetAtLine(lineIndex); if (wordSelect) { int min = blockSelection ? lineOffset : 0; int max = blockSelection ? lineOffset + content.getLine(lineIndex).length() : content.getCharCount(); int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START)); int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END)); setSelection(start, end - start, false, true); sendSelectionEvent(); } else { if (blockSelection) { setBlockSelectionLocation(leftMargin, event.y, clientAreaWidth - rightMargin, event.y, true); } else { int lineEnd = content.getCharCount(); if (lineIndex + 1 < content.getLineCount()) { lineEnd = content.getOffsetAtLine(lineIndex + 1); } setSelection(lineOffset, lineEnd - lineOffset, false, false); sendSelectionEvent(); } } doubleClickSelection = new Point(selection.x, selection.y); showCaret(); } } } /** * Updates the caret location and selection if mouse button 1 is pressed * during the mouse move. */ void handleMouseMove(Event event) { if (clickCount > 0) { update(); doAutoScroll(event); doMouseLocationChange(event.x, event.y, true); } if (renderer.hasLinks) { doMouseLinkCursor(event.x, event.y); } } /** * Autoscrolling ends when the mouse button is released. */ void handleMouseUp(Event event) { middleClickPressed = false; clickCount = 0; endAutoScroll(); if (event.button == 1) { copySelection(DND.SELECTION_CLIPBOARD); } } /** * Renders the invalidated area specified in the paint event. * * @param event paint event */ void handlePaint(Event event) { if (event.width == 0 || event.height == 0) return; if (clientAreaWidth == 0 || clientAreaHeight == 0) return; int startLine = getLineIndex(event.y); int y = getLinePixel(startLine); int endY = event.y + event.height; GC gc = event.gc; Color background = getBackground(); Color foreground = getForeground(); if (endY > 0) { int lineCount = isSingleLine() ? 1 : content.getLineCount(); int x = leftMargin - horizontalScrollOffset; for (int i = startLine; y < endY && i < lineCount; i++) { y += renderer.drawLine(i, x, y, gc, background, foreground); } if (y < endY) { gc.setBackground(background); drawBackground(gc, 0, y, clientAreaWidth, endY - y); } } if (blockSelection && blockXLocation != -1) { gc.setBackground(getSelectionBackground()); Rectangle rect = getBlockSelectionRectangle(); gc.drawRectangle(rect.x, rect.y, Math.max(1, rect.width - 1), Math.max(1, rect.height - 1)); gc.setAdvanced(true); if (gc.getAdvanced()) { gc.setAlpha(100); gc.fillRectangle(rect); gc.setAdvanced(false); } } // fill the margin background gc.setBackground(marginColor != null ? marginColor : background); if (topMargin > 0) { drawBackground(gc, 0, 0, clientAreaWidth, topMargin); } if (bottomMargin > 0) { drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin); } if (leftMargin - alignmentMargin > 0) { drawBackground(gc, 0, 0, leftMargin - alignmentMargin, clientAreaHeight); } if (rightMargin > 0) { drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight); } } /** * Recalculates the scroll bars. Rewraps all lines when in word * wrap mode. * * @param event resize event */ void handleResize(Event event) { int oldHeight = clientAreaHeight; int oldWidth = clientAreaWidth; Rectangle clientArea = getClientArea(); clientAreaHeight = clientArea.height; clientAreaWidth = clientArea.width; if (!alwaysShowScroll && ignoreResize != 0) return; redrawMargins(oldHeight, oldWidth); if (wordWrap) { if (oldWidth != clientAreaWidth) { renderer.reset(0, content.getLineCount()); verticalScrollOffset = -1; renderer.calculateIdle(); super.redraw(); } if (oldHeight != clientAreaHeight) { if (oldHeight == 0) topIndexY = 0; setScrollBars(true); } setCaretLocation(); } else { renderer.calculateClientArea(); setScrollBars(true); claimRightFreeSpace(); // StyledText allows any value for horizontalScrollOffset when clientArea is zero // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429. if (clientAreaWidth != 0) { ScrollBar horizontalBar = getHorizontalBar(); if (horizontalBar != null && horizontalBar.getVisible()) { if (horizontalScrollOffset != horizontalBar.getSelection()) { horizontalBar.setSelection(horizontalScrollOffset); horizontalScrollOffset = horizontalBar.getSelection(); } } } } updateCaretVisibility(); claimBottomFreeSpace(); setAlignment(); //TODO FIX TOP INDEX DURING RESIZE // if (oldHeight != clientAreaHeight || wordWrap) { // calculateTopIndex(0); // } } /** * Updates the caret position and selection and the scroll bars to reflect * the content change. */ void handleTextChanged(TextChangedEvent event) { int offset = ime.getCompositionOffset(); if (offset != -1 && lastTextChangeStart < offset) { ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount); } int firstLine = content.getLineAtOffset(lastTextChangeStart); resetCache(firstLine, 0); if (!isFixedLineHeight() && topIndex > firstLine) { topIndex = firstLine; if (topIndex < 0) { // TODO: This logging is in place to determine why topIndex is getting set to negative values. // It should be deleted once we fix the root cause of this issue. See bug 487254 for details. System.err.println("StyledText: topIndex was " + topIndex + ", lastTextChangeStart = " + lastTextChangeStart + ", content.getClass() = " + content.getClass()); topIndex = 0; } topIndexY = 0; super.redraw(); } else { int lastLine = firstLine + lastTextChangeNewLineCount; int firstLineTop = getLinePixel(firstLine); int newLastLineBottom = getLinePixel(lastLine + 1); if (lastLineBottom != newLastLineBottom) { super.redraw(); } else { super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false); redrawLinesBullet(renderer.redrawLines); } } renderer.redrawLines = null; // update selection/caret location after styles have been changed. // otherwise any text measuring could be incorrect // // also, this needs to be done after all scrolling. Otherwise, // selection redraw would be flushed during scroll which is wrong. // in some cases new text would be drawn in scroll source area even // though the intent is to scroll it. if (!(blockSelection && blockXLocation != -1)) { updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount); } if (lastTextChangeReplaceLineCount > 0 || wordWrap || visualWrap) { claimBottomFreeSpace(); } if (lastTextChangeReplaceCharCount > 0) { claimRightFreeSpace(); } sendAccessibleTextChanged(lastTextChangeStart, lastTextChangeNewCharCount, 0); lastCharCount += lastTextChangeNewCharCount; lastCharCount -= lastTextChangeReplaceCharCount; setAlignment(); } /** * Updates the screen to reflect a pending content change. * * @param event .start the start offset of the change * @param event .newText text that is going to be inserted or empty String * if no text will be inserted * @param event .replaceCharCount length of text that is going to be replaced * @param event .newCharCount length of text that is going to be inserted * @param event .replaceLineCount number of lines that are going to be replaced * @param event .newLineCount number of new lines that are going to be inserted */ void handleTextChanging(TextChangingEvent event) { if (event.replaceCharCount < 0) { event.start += event.replaceCharCount; event.replaceCharCount *= -1; } lastTextChangeStart = event.start; lastTextChangeNewLineCount = event.newLineCount; lastTextChangeNewCharCount = event.newCharCount; lastTextChangeReplaceLineCount = event.replaceLineCount; lastTextChangeReplaceCharCount = event.replaceCharCount; int lineIndex = content.getLineAtOffset(event.start); int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1); int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight(); lastLineBottom = destY; if (srcY < 0 && destY < 0) { lastLineBottom += srcY - destY; verticalScrollOffset += destY - srcY; calculateTopIndex(destY - srcY); setScrollBars(true); } else { scrollText(srcY, destY); } sendAccessibleTextChanged(lastTextChangeStart, 0, lastTextChangeReplaceCharCount); renderer.textChanging(event); // Update the caret offset if it is greater than the length of the content. // This is necessary since style range API may be called between the // handleTextChanging and handleTextChanged events and this API sets the // caretOffset. int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount; if (caretOffset > newEndOfText) setCaretOffset(newEndOfText, SWT.DEFAULT); } /** * Called when the widget content is set programmatically, overwriting * the old content. Resets the caret position, selection and scroll offsets. * Recalculates the content width and scroll bars. Redraws the widget. * * @param event text change event. */ void handleTextSet(TextChangedEvent event) { reset(); int newCharCount = getCharCount(); sendAccessibleTextChanged(0, newCharCount, lastCharCount); lastCharCount = newCharCount; setAlignment(); } /** * Called when a traversal key is pressed. * Allow tab next traversal to occur when the widget is in single * line mode or in multi line and non-editable mode . * When in editable multi line mode we want to prevent the tab * traversal and receive the tab key event instead. * * @param event the event */ void handleTraverse(Event event) { switch (event.detail) { case SWT.TRAVERSE_ESCAPE: case SWT.TRAVERSE_PAGE_NEXT: case SWT.TRAVERSE_PAGE_PREVIOUS: event.doit = true; break; case SWT.TRAVERSE_RETURN: case SWT.TRAVERSE_TAB_NEXT: case SWT.TRAVERSE_TAB_PREVIOUS: if ((getStyle() & SWT.SINGLE) != 0) { event.doit = true; } else { if (!editable || (event.stateMask & SWT.MODIFIER_MASK) != 0) { event.doit = true; } } break; } } /** * Scrolls the widget vertically. */ void handleVerticalScroll(Event event) { int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset(); scrollVertical(scrollPixel, false); } /** * Add accessibility support for the widget. */ void initializeAccessible() { acc = getAccessible(); accAdapter = new AccessibleAdapter() { @Override public void getName(AccessibleEvent e) { String name = null; String text = getAssociatedLabel(); if (text != null) { name = stripMnemonic(text); } e.result = name; } @Override public void getHelp(AccessibleEvent e) { e.result = getToolTipText(); } @Override public void getKeyboardShortcut(AccessibleEvent e) { String shortcut = null; String text = getAssociatedLabel(); if (text != null) { char mnemonic = _findMnemonic(text); if (mnemonic != '\0') { shortcut = "Alt+" + mnemonic; //$NON-NLS-1$ } } e.result = shortcut; } }; acc.addAccessibleListener(accAdapter); accTextExtendedAdapter = new AccessibleTextExtendedAdapter() { @Override public void getCaretOffset(AccessibleTextEvent e) { e.offset = StyledText.this.getCaretOffset(); } @Override public void setCaretOffset(AccessibleTextEvent e) { StyledText.this.setCaretOffset(e.offset); e.result = ACC.OK; } @Override public void getSelectionRange(AccessibleTextEvent e) { Point selection = StyledText.this.getSelectionRange(); e.offset = selection.x; e.length = selection.y; } @Override public void addSelection(AccessibleTextEvent e) { StyledText st = StyledText.this; Point point = st.getSelection(); if (point.x == point.y) { int end = e.end; if (end == -1) end = st.getCharCount(); st.setSelection(e.start, end); e.result = ACC.OK; } } @Override public void getSelection(AccessibleTextEvent e) { StyledText st = StyledText.this; if (st.blockSelection && st.blockXLocation != -1) { Rectangle rect = st.getBlockSelectionPosition(); int lineIndex = rect.y + e.index; int linePixel = st.getLinePixel(lineIndex); e.ranges = getRanges(rect.x, linePixel, rect.width, linePixel); if (e.ranges.length > 0) { e.start = e.ranges[0]; e.end = e.ranges[e.ranges.length - 1]; } } else { if (e.index == 0) { Point point = st.getSelection(); e.start = point.x; e.end = point.y; if (e.start > e.end) { int temp = e.start; e.start = e.end; e.end = temp; } } } } @Override public void getSelectionCount(AccessibleTextEvent e) { StyledText st = StyledText.this; if (st.blockSelection && st.blockXLocation != -1) { Rectangle rect = st.getBlockSelectionPosition(); e.count = rect.height - rect.y + 1; } else { Point point = st.getSelection(); e.count = point.x == point.y ? 0 : 1; } } @Override public void removeSelection(AccessibleTextEvent e) { StyledText st = StyledText.this; if (e.index == 0) { if (st.blockSelection) { st.clearBlockSelection(true, false); } else { st.clearSelection(false); } e.result = ACC.OK; } } @Override public void setSelection(AccessibleTextEvent e) { if (e.index != 0) return; StyledText st = StyledText.this; Point point = st.getSelection(); if (point.x == point.y) return; int end = e.end; if (end == -1) end = st.getCharCount(); st.setSelection(e.start, end); e.result = ACC.OK; } @Override public void getCharacterCount(AccessibleTextEvent e) { e.count = StyledText.this.getCharCount(); } @Override public void getOffsetAtPoint(AccessibleTextEvent e) { StyledText st = StyledText.this; Point point = new Point(e.x, e.y); Display display = st.getDisplay(); point = display.map(null, st, point); e.offset = st.getOffsetAtPoint(point.x, point.y, null, true); } @Override public void getTextBounds(AccessibleTextEvent e) { StyledText st = StyledText.this; int start = e.start; int end = e.end; int contentLength = st.getCharCount(); start = Math.max(0, Math.min(start, contentLength)); end = Math.max(0, Math.min(end, contentLength)); if (start > end) { int temp = start; start = end; end = temp; } int startLine = st.getLineAtOffset(start); int endLine = st.getLineAtOffset(end); Rectangle[] rects = new Rectangle[endLine - startLine + 1]; Rectangle bounds = null; int index = 0; Display display = st.getDisplay(); for (int lineIndex = startLine; lineIndex <= endLine; lineIndex++) { Rectangle rect = new Rectangle(0, 0, 0, 0); rect.y = st.getLinePixel(lineIndex); rect.height = st.renderer.getLineHeight(lineIndex); if (lineIndex == startLine) { rect.x = st.getPointAtOffset(start).x; } else { rect.x = st.leftMargin - st.horizontalScrollOffset; } if (lineIndex == endLine) { rect.width = st.getPointAtOffset(end).x - rect.x; } else { TextLayout layout = st.renderer.getTextLayout(lineIndex); rect.width = layout.getBounds().width - rect.x; st.renderer.disposeTextLayout(layout); } rects[index++] = rect = display.map(st, null, rect); if (bounds == null) { bounds = new Rectangle(rect.x, rect.y, rect.width, rect.height); } else { bounds.add(rect); } } e.rectangles = rects; if (bounds != null) { e.x = bounds.x; e.y = bounds.y; e.width = bounds.width; e.height = bounds.height; } } int[] getRanges(int left, int top, int right, int bottom) { StyledText st = StyledText.this; int lineStart = st.getLineIndex(top); int lineEnd = st.getLineIndex(bottom); int count = lineEnd - lineStart + 1; int[] ranges = new int[count * 2]; int index = 0; for (int lineIndex = lineStart; lineIndex <= lineEnd; lineIndex++) { String line = st.content.getLine(lineIndex); int lineOffset = st.content.getOffsetAtLine(lineIndex); int lineEndOffset = lineOffset + line.length(); int linePixel = st.getLinePixel(lineIndex); int start = st.getOffsetAtPoint(left, linePixel, null, true); if (start == -1) { start = left < st.leftMargin ? lineOffset : lineEndOffset; } int[] trailing = new int[1]; int end = st.getOffsetAtPoint(right, linePixel, trailing, true); if (end == -1) { end = right < st.leftMargin ? lineOffset : lineEndOffset; } else { end += trailing[0]; } if (start > end) { int temp = start; start = end; end = temp; } ranges[index++] = start; ranges[index++] = end; } return ranges; } @Override public void getRanges(AccessibleTextEvent e) { StyledText st = StyledText.this; Point point = new Point(e.x, e.y); Display display = st.getDisplay(); point = display.map(null, st, point); e.ranges = getRanges(point.x, point.y, point.x + e.width, point.y + e.height); if (e.ranges.length > 0) { e.start = e.ranges[0]; e.end = e.ranges[e.ranges.length - 1]; } } @Override public void getText(AccessibleTextEvent e) { StyledText st = StyledText.this; int start = e.start; int end = e.end; int contentLength = st.getCharCount(); if (end == -1) end = contentLength; start = Math.max(0, Math.min(start, contentLength)); end = Math.max(0, Math.min(end, contentLength)); if (start > end) { int temp = start; start = end; end = temp; } int count = e.count; switch (e.type) { case ACC.TEXT_BOUNDARY_ALL: //nothing to do break; case ACC.TEXT_BOUNDARY_CHAR: { int newCount = 0; if (count > 0) { while (count-- > 0) { int newEnd = st.getWordNext(end, SWT.MOVEMENT_CLUSTER); if (newEnd == contentLength) break; if (newEnd == end) break; end = newEnd; newCount++; } start = end; end = st.getWordNext(end, SWT.MOVEMENT_CLUSTER); } else { while (count++ < 0) { int newStart = st.getWordPrevious(start, SWT.MOVEMENT_CLUSTER); if (newStart == start) break; start = newStart; newCount--; } end = st.getWordNext(start, SWT.MOVEMENT_CLUSTER); } count = newCount; break; } case ACC.TEXT_BOUNDARY_WORD: { int newCount = 0; if (count > 0) { while (count-- > 0) { int newEnd = st.getWordNext(end, SWT.MOVEMENT_WORD_START, true); if (newEnd == end) break; newCount++; end = newEnd; } start = end; end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true); } else { if (st.getWordPrevious(Math.min(start + 1, contentLength), SWT.MOVEMENT_WORD_START, true) == start) { //start is a word start already count++; } while (count <= 0) { int newStart = st.getWordPrevious(start, SWT.MOVEMENT_WORD_START, true); if (newStart == start) break; count++; start = newStart; if (count != 0) newCount--; } if (count <= 0 && start == 0) { end = start; } else { end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true); } } count = newCount; break; } case ACC.TEXT_BOUNDARY_LINE: //TODO implement line case ACC.TEXT_BOUNDARY_PARAGRAPH: case ACC.TEXT_BOUNDARY_SENTENCE: { int offset = count > 0 ? end : start; int lineIndex = st.getLineAtOffset(offset) + count; lineIndex = Math.max(0, Math.min(lineIndex, st.getLineCount() - 1)); start = st.getOffsetAtLine(lineIndex); String line = st.getLine(lineIndex); end = start + line.length(); count = lineIndex - st.getLineAtOffset(offset); break; } } e.start = start; e.end = end; e.count = count; e.result = st.content.getTextRange(start, end - start); } @Override public void getVisibleRanges(AccessibleTextEvent e) { e.ranges = getRanges(leftMargin, topMargin, clientAreaWidth - rightMargin, clientAreaHeight - bottomMargin); if (e.ranges.length > 0) { e.start = e.ranges[0]; e.end = e.ranges[e.ranges.length - 1]; } } @Override public void scrollText(AccessibleTextEvent e) { StyledText st = StyledText.this; int topPixel = getTopPixel(), horizontalPixel = st.getHorizontalPixel(); switch (e.type) { case ACC.SCROLL_TYPE_ANYWHERE: case ACC.SCROLL_TYPE_TOP_LEFT: case ACC.SCROLL_TYPE_LEFT_EDGE: case ACC.SCROLL_TYPE_TOP_EDGE: { Rectangle rect = st.getBoundsAtOffset(e.start); if (e.type != ACC.SCROLL_TYPE_TOP_EDGE) { horizontalPixel = horizontalPixel + rect.x - st.leftMargin; } if (e.type != ACC.SCROLL_TYPE_LEFT_EDGE) { topPixel = topPixel + rect.y - st.topMargin; } break; } case ACC.SCROLL_TYPE_BOTTOM_RIGHT: case ACC.SCROLL_TYPE_BOTTOM_EDGE: case ACC.SCROLL_TYPE_RIGHT_EDGE: { Rectangle rect = st.getBoundsAtOffset(e.end - 1); if (e.type != ACC.SCROLL_TYPE_BOTTOM_EDGE) { horizontalPixel = horizontalPixel - st.clientAreaWidth + rect.x + rect.width + st.rightMargin; } if (e.type != ACC.SCROLL_TYPE_RIGHT_EDGE) { topPixel = topPixel - st.clientAreaHeight + rect.y + rect.height + st.bottomMargin; } break; } case ACC.SCROLL_TYPE_POINT: { Point point = new Point(e.x, e.y); Display display = st.getDisplay(); point = display.map(null, st, point); Rectangle rect = st.getBoundsAtOffset(e.start); topPixel = topPixel - point.y + rect.y; horizontalPixel = horizontalPixel - point.x + rect.x; break; } } st.setTopPixel(topPixel); st.setHorizontalPixel(horizontalPixel); e.result = ACC.OK; } }; acc.addAccessibleTextListener(accTextExtendedAdapter); accEditableTextListener = new AccessibleEditableTextListener() { @Override public void setTextAttributes(AccessibleTextAttributeEvent e) { // This method must be implemented by the application e.result = ACC.OK; } @Override public void replaceText(AccessibleEditableTextEvent e) { StyledText st = StyledText.this; st.replaceTextRange(e.start, e.end - e.start, e.string); e.result = ACC.OK; } @Override public void pasteText(AccessibleEditableTextEvent e) { StyledText st = StyledText.this; st.setSelection(e.start); st.paste(); e.result = ACC.OK; } @Override public void cutText(AccessibleEditableTextEvent e) { StyledText st = StyledText.this; st.setSelection(e.start, e.end); st.cut(); e.result = ACC.OK; } @Override public void copyText(AccessibleEditableTextEvent e) { StyledText st = StyledText.this; st.setSelection(e.start, e.end); st.copy(); e.result = ACC.OK; } }; acc.addAccessibleEditableTextListener(accEditableTextListener); accAttributeAdapter = new AccessibleAttributeAdapter() { @Override public void getAttributes(AccessibleAttributeEvent e) { StyledText st = StyledText.this; e.leftMargin = st.getLeftMargin(); e.topMargin = st.getTopMargin(); e.rightMargin = st.getRightMargin(); e.bottomMargin = st.getBottomMargin(); e.tabStops = st.getTabStops(); e.justify = st.getJustify(); e.alignment = st.getAlignment(); e.indent = st.getIndent(); } @Override public void getTextAttributes(AccessibleTextAttributeEvent e) { StyledText st = StyledText.this; int contentLength = st.getCharCount(); if (!isListening(ST.LineGetStyle) && st.renderer.styleCount == 0) { e.start = 0; e.end = contentLength; e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background); return; } int offset = Math.max(0, Math.min(e.offset, contentLength - 1)); int lineIndex = st.getLineAtOffset(offset); int lineOffset = st.getOffsetAtLine(lineIndex); int lineCount = st.getLineCount(); offset = offset - lineOffset; TextLayout layout = st.renderer.getTextLayout(lineIndex); int lineLength = layout.getText().length(); if (lineLength > 0) { e.textStyle = layout.getStyle(Math.max(0, Math.min(offset, lineLength - 1))); } // If no override info available, use defaults. Don't supply default colors, though. if (e.textStyle == null) { e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background); } else { if (e.textStyle.foreground == null || e.textStyle.background == null || e.textStyle.font == null) { TextStyle textStyle = new TextStyle(e.textStyle); if (textStyle.foreground == null) textStyle.foreground = st.foreground; if (textStyle.background == null) textStyle.background = st.background; if (textStyle.font == null) textStyle.font = st.getFont(); e.textStyle = textStyle; } } //offset at line delimiter case if (offset >= lineLength) { e.start = lineOffset + lineLength; if (lineIndex + 1 < lineCount) { e.end = st.getOffsetAtLine(lineIndex + 1); } else { e.end = contentLength; } return; } int[] ranges = layout.getRanges(); st.renderer.disposeTextLayout(layout); int index = 0; int end = 0; while (index < ranges.length) { int styleStart = ranges[index++]; int styleEnd = ranges[index++]; if (styleStart <= offset && offset <= styleEnd) { e.start = lineOffset + styleStart; e.end = lineOffset + styleEnd + 1; return; } if (styleStart > offset) { e.start = lineOffset + end; e.end = lineOffset + styleStart; return; } end = styleEnd + 1; } if (index == ranges.length) { e.start = lineOffset + end; if (lineIndex + 1 < lineCount) { e.end = st.getOffsetAtLine(lineIndex + 1); } else { e.end = contentLength; } } } }; acc.addAccessibleAttributeListener(accAttributeAdapter); accControlAdapter = new AccessibleControlAdapter() { @Override public void getRole(AccessibleControlEvent e) { e.detail = ACC.ROLE_TEXT; } @Override public void getState(AccessibleControlEvent e) { int state = 0; if (isEnabled()) state |= ACC.STATE_FOCUSABLE; if (isFocusControl()) state |= ACC.STATE_FOCUSED; if (!isVisible()) state |= ACC.STATE_INVISIBLE; if (!getEditable()) state |= ACC.STATE_READONLY; if (isSingleLine()) state |= ACC.STATE_SINGLELINE; else state |= ACC.STATE_MULTILINE; e.detail = state; } @Override public void getValue(AccessibleControlEvent e) { e.result = StyledText.this.getText(); } }; acc.addAccessibleControlListener(accControlAdapter); addListener(SWT.FocusIn, event -> acc.setFocus(ACC.CHILDID_SELF)); } @Override public void dispose() { /* * Note: It is valid to attempt to dispose a widget more than once. * Added check for this. */ if (!isDisposed()) { acc.removeAccessibleControlListener(accControlAdapter); acc.removeAccessibleAttributeListener(accAttributeAdapter); acc.removeAccessibleEditableTextListener(accEditableTextListener); acc.removeAccessibleTextListener(accTextExtendedAdapter); acc.removeAccessibleListener(accAdapter); } super.dispose(); } /* * Return the Label immediately preceding the receiver in the z-order, * or null if none. */ String getAssociatedLabel() { Control[] siblings = getParent().getChildren(); for (int i = 0; i < siblings.length; i++) { if (siblings[i] == StyledText.this) { if (i > 0) { Control sibling = siblings[i - 1]; if (sibling instanceof Label) return ((Label) sibling).getText(); if (sibling instanceof CLabel) return ((CLabel) sibling).getText(); } break; } } return null; } String stripMnemonic(String string) { int index = 0; int length = string.length(); do { while ((index < length) && (string.charAt(index) != '&')) index++; if (++index >= length) return string; if (string.charAt(index) != '&') { return string.substring(0, index - 1) + string.substring(index, length); } index++; } while (index < length); return string; } /* * Return the lowercase of the first non-'&' character following * an '&' character in the given string. If there are no '&' * characters in the given string, return '\0'. */ char _findMnemonic(String string) { if (string == null) return '\0'; int index = 0; int length = string.length(); do { while (index < length && string.charAt(index) != '&') index++; if (++index >= length) return '\0'; if (string.charAt(index) != '&') return Character.toLowerCase(string.charAt(index)); index++; } while (index < length); return '\0'; } /** * Executes the action. * * @param action one of the actions defined in ST.java */ public void invokeAction(int action) { checkWidget(); if (blockSelection && invokeBlockAction(action)) return; updateCaretDirection = true; switch (action) { // Navigation case ST.LINE_UP: doLineUp(false); clearSelection(true); break; case ST.LINE_DOWN: doLineDown(false); clearSelection(true); break; case ST.LINE_START: doLineStart(); clearSelection(true); break; case ST.LINE_END: doLineEnd(); clearSelection(true); break; case ST.COLUMN_PREVIOUS: doCursorPrevious(); clearSelection(true); break; case ST.COLUMN_NEXT: doCursorNext(); clearSelection(true); break; case ST.PAGE_UP: doPageUp(false, -1); clearSelection(true); break; case ST.PAGE_DOWN: doPageDown(false, -1); clearSelection(true); break; case ST.WORD_PREVIOUS: doWordPrevious(); clearSelection(true); break; case ST.WORD_NEXT: doWordNext(); clearSelection(true); break; case ST.TEXT_START: doContentStart(); clearSelection(true); break; case ST.TEXT_END: doContentEnd(); clearSelection(true); break; case ST.WINDOW_START: doPageStart(); clearSelection(true); break; case ST.WINDOW_END: doPageEnd(); clearSelection(true); break; // Selection case ST.SELECT_LINE_UP: doSelectionLineUp(); break; case ST.SELECT_ALL: selectAll(); break; case ST.SELECT_LINE_DOWN: doSelectionLineDown(); break; case ST.SELECT_LINE_START: doLineStart(); doSelection(ST.COLUMN_PREVIOUS); break; case ST.SELECT_LINE_END: doLineEnd(); doSelection(ST.COLUMN_NEXT); break; case ST.SELECT_COLUMN_PREVIOUS: doSelectionCursorPrevious(); doSelection(ST.COLUMN_PREVIOUS); break; case ST.SELECT_COLUMN_NEXT: doSelectionCursorNext(); doSelection(ST.COLUMN_NEXT); break; case ST.SELECT_PAGE_UP: doSelectionPageUp(-1); break; case ST.SELECT_PAGE_DOWN: doSelectionPageDown(-1); break; case ST.SELECT_WORD_PREVIOUS: doSelectionWordPrevious(); doSelection(ST.COLUMN_PREVIOUS); break; case ST.SELECT_WORD_NEXT: doSelectionWordNext(); doSelection(ST.COLUMN_NEXT); break; case ST.SELECT_TEXT_START: doContentStart(); doSelection(ST.COLUMN_PREVIOUS); break; case ST.SELECT_TEXT_END: doContentEnd(); doSelection(ST.COLUMN_NEXT); break; case ST.SELECT_WINDOW_START: doPageStart(); doSelection(ST.COLUMN_PREVIOUS); break; case ST.SELECT_WINDOW_END: doPageEnd(); doSelection(ST.COLUMN_NEXT); break; // Modification case ST.CUT: cut(); break; case ST.COPY: copy(); break; case ST.PASTE: paste(); break; case ST.DELETE_PREVIOUS: doBackspace(); break; case ST.DELETE_NEXT: doDelete(); break; case ST.DELETE_WORD_PREVIOUS: doDeleteWordPrevious(); break; case ST.DELETE_WORD_NEXT: doDeleteWordNext(); break; // Miscellaneous case ST.TOGGLE_OVERWRITE: overwrite = !overwrite; // toggle insert/overwrite mode break; case ST.TOGGLE_BLOCKSELECTION: setBlockSelection(!blockSelection); break; } } /** * Returns true if an action should not be performed when block * selection in active */ boolean invokeBlockAction(int action) { switch (action) { // Navigation case ST.LINE_UP: case ST.LINE_DOWN: case ST.LINE_START: case ST.LINE_END: case ST.COLUMN_PREVIOUS: case ST.COLUMN_NEXT: case ST.PAGE_UP: case ST.PAGE_DOWN: case ST.WORD_PREVIOUS: case ST.WORD_NEXT: case ST.TEXT_START: case ST.TEXT_END: case ST.WINDOW_START: case ST.WINDOW_END: clearBlockSelection(false, false); return false; // Selection case ST.SELECT_LINE_UP: doBlockLineVertical(true); return true; case ST.SELECT_LINE_DOWN: doBlockLineVertical(false); return true; case ST.SELECT_LINE_START: doBlockLineHorizontal(false); return true; case ST.SELECT_LINE_END: doBlockLineHorizontal(true); return false; case ST.SELECT_COLUMN_PREVIOUS: doBlockColumn(false); return true; case ST.SELECT_COLUMN_NEXT: doBlockColumn(true); return true; case ST.SELECT_WORD_PREVIOUS: doBlockWord(false); return true; case ST.SELECT_WORD_NEXT: doBlockWord(true); return true; case ST.SELECT_ALL: return false; case ST.SELECT_TEXT_START: doBlockContentStartEnd(false); break; case ST.SELECT_TEXT_END: doBlockContentStartEnd(true); break; case ST.SELECT_PAGE_UP: case ST.SELECT_PAGE_DOWN: case ST.SELECT_WINDOW_START: case ST.SELECT_WINDOW_END: //blocked actions return true; // Modification case ST.CUT: case ST.COPY: case ST.PASTE: return false; case ST.DELETE_PREVIOUS: case ST.DELETE_NEXT: if (blockXLocation != -1) { insertBlockSelectionText((char) 0, action); return true; } return false; case ST.DELETE_WORD_PREVIOUS: case ST.DELETE_WORD_NEXT: //blocked actions return blockXLocation != -1; } return false; } boolean isBidiCaret() { return BidiUtil.isBidiPlatform(); } boolean isFixedLineHeight() { return fixedLineHeight; } /** * Returns whether the given offset is inside a multi byte line delimiter. * Example: * "Line1\r\n" isLineDelimiter(5) == false but isLineDelimiter(6) == true * * @return true if the given offset is inside a multi byte line delimiter. * false if the given offset is before or after a line delimiter. */ boolean isLineDelimiter(int offset) { int line = content.getLineAtOffset(offset); int lineOffset = content.getOffsetAtLine(line); int offsetInLine = offset - lineOffset; // offsetInLine will be greater than line length if the line // delimiter is longer than one character and the offset is set // in between parts of the line delimiter. return offsetInLine > content.getLine(line).length(); } /** * Returns whether the widget is mirrored (right oriented/right to left * writing order). * * @return isMirrored true=the widget is right oriented, false=the widget * is left oriented */ boolean isMirrored() { return (getStyle() & SWT.MIRRORED) != 0; } /** * Returns <code>true</code> if any text in the widget is selected, * and <code>false</code> otherwise. * * @return the text selection state * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.103 */ public boolean isTextSelected() { checkWidget(); if (blockSelection && blockXLocation != -1) { Rectangle rect = getBlockSelectionPosition(); return !rect.isEmpty(); } return selection.y != selection.x; } /** * Returns whether the widget can have only one line. * * @return true if widget can have only one line, false if widget can have * multiple lines */ boolean isSingleLine() { return (getStyle() & SWT.SINGLE) != 0; } /** * Sends the specified verify event, replace/insert text as defined by * the event and send a modify event. * * @param event the text change event. * <ul> * <li>event.start - the replace start offset</li> * <li>event.end - the replace end offset</li> * <li>event.text - the new text</li> * </ul> * @param updateCaret whether or not he caret should be set behind * the new text */ void modifyContent(Event event, boolean updateCaret) { event.doit = true; notifyListeners(SWT.Verify, event); if (event.doit) { StyledTextEvent styledTextEvent = null; int replacedLength = event.end - event.start; if (isListening(ST.ExtendedModify)) { styledTextEvent = new StyledTextEvent(content); styledTextEvent.start = event.start; styledTextEvent.end = event.start + event.text.length(); styledTextEvent.text = content.getTextRange(event.start, replacedLength); } if (updateCaret) { //Fix advancing flag for delete/backspace key on direction boundary if (event.text.length() == 0) { int lineIndex = content.getLineAtOffset(event.start); int lineOffset = content.getOffsetAtLine(lineIndex); TextLayout layout = renderer.getTextLayout(lineIndex); int levelStart = layout.getLevel(event.start - lineOffset); int lineIndexEnd = content.getLineAtOffset(event.end); if (lineIndex != lineIndexEnd) { renderer.disposeTextLayout(layout); lineOffset = content.getOffsetAtLine(lineIndexEnd); layout = renderer.getTextLayout(lineIndexEnd); } int levelEnd = layout.getLevel(event.end - lineOffset); renderer.disposeTextLayout(layout); if (levelStart != levelEnd) { caretAlignment = PREVIOUS_OFFSET_TRAILING; } else { caretAlignment = OFFSET_LEADING; } } } content.replaceTextRange(event.start, replacedLength, event.text); // set the caret position prior to sending the modify event. // fixes 1GBB8NJ if (updateCaret && !(blockSelection && blockXLocation != -1)) { // always update the caret location. fixes 1G8FODP setSelection(event.start + event.text.length(), 0, true, false); showCaret(); } notifyListeners(SWT.Modify, event); if (isListening(ST.ExtendedModify)) { notifyListeners(ST.ExtendedModify, styledTextEvent); } } } void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) { if (isListening(ST.PaintObject)) { StyledTextEvent event = new StyledTextEvent(content); event.gc = gc; event.x = x; event.y = y; event.ascent = ascent; event.descent = descent; event.style = style; event.bullet = bullet; event.bulletIndex = bulletIndex; notifyListeners(ST.PaintObject, event); } } /** * Replaces the selection with the text on the <code>DND.CLIPBOARD</code> * clipboard or, if there is no selection, inserts the text at the current * caret offset. If the widget has the SWT.SINGLE style and the * clipboard text contains more than one line, only the first line without * line delimiters is inserted in the widget. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void paste() { checkWidget(); String text = (String) getClipboardContent(DND.CLIPBOARD); if (text != null && text.length() > 0) { if (blockSelection) { boolean fillWithSpaces = isFixedLineHeight() && renderer.fixedPitch; int offset = insertBlockSelectionText(text, fillWithSpaces); setCaretOffset(offset, SWT.DEFAULT); clearBlockSelection(true, true); setCaretLocation(); return; } Event event = new Event(); event.start = selection.x; event.end = selection.y; String delimitedText = getModelDelimitedText(text); if (textLimit > 0) { int uneditedTextLength = getCharCount() - (selection.y - selection.x); if ((uneditedTextLength + delimitedText.length()) > textLimit) { int endIndex = textLimit - uneditedTextLength; delimitedText = delimitedText.substring(0, Math.max(endIndex, 0)); } } event.text = delimitedText; sendKeyEvent(event); } } private void pasteOnMiddleClick(Event event) { String text = (String) getClipboardContent(DND.SELECTION_CLIPBOARD); if (text != null && text.length() > 0) { // position cursor doMouseLocationChange(event.x, event.y, false); // insert text Event e = new Event(); e.start = selection.x; e.end = selection.y; e.text = getModelDelimitedText(text); sendKeyEvent(e); } } /** * Prints the widget's text to the default printer. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void print() { checkWidget(); Printer printer = new Printer(); StyledTextPrintOptions options = new StyledTextPrintOptions(); options.printTextForeground = true; options.printTextBackground = true; options.printTextFontStyle = true; options.printLineBackground = true; new Printing(this, printer, options).run(); printer.dispose(); } /** * Returns a runnable that will print the widget's text * to the specified printer. * <p> * The runnable may be run in a non-UI thread. * </p> * * @param printer the printer to print to * * @return a <code>Runnable</code> for printing the receiver's text * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when printer is null</li> * </ul> */ public Runnable print(Printer printer) { checkWidget(); if (printer == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } StyledTextPrintOptions options = new StyledTextPrintOptions(); options.printTextForeground = true; options.printTextBackground = true; options.printTextFontStyle = true; options.printLineBackground = true; return print(printer, options); } /** * Returns a runnable that will print the widget's text * to the specified printer. * <p> * The runnable may be run in a non-UI thread. * </p> * * @param printer the printer to print to * @param options print options to use during printing * * @return a <code>Runnable</code> for printing the receiver's text * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when printer or options is null</li> * </ul> * @since 2.1 */ public Runnable print(Printer printer, StyledTextPrintOptions options) { checkWidget(); if (printer == null || options == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } return new Printing(this, printer, options); } /** * Causes the entire bounds of the receiver to be marked * as needing to be redrawn. The next time a paint request * is processed, the control will be completely painted. * <p> * Recalculates the content width for all lines in the bounds. * When a <code>LineStyleListener</code> is used a redraw call * is the only notification to the widget that styles have changed * and that the content width may have changed. * </p> * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see Control#update() */ @Override public void redraw() { super.redraw(); int itemCount = getPartialBottomIndex() - topIndex + 1; renderer.reset(topIndex, itemCount); renderer.calculate(topIndex, itemCount); setScrollBars(false); doMouseLinkCursor(); } /** * Causes the rectangular area of the receiver specified by * the arguments to be marked as needing to be redrawn. * The next time a paint request is processed, that area of * the receiver will be painted. If the <code>all</code> flag * is <code>true</code>, any children of the receiver which * intersect with the specified area will also paint their * intersecting areas. If the <code>all</code> flag is * <code>false</code>, the children will not be painted. * <p> * Marks the content width of all lines in the specified rectangle * as unknown. Recalculates the content width of all visible lines. * When a <code>LineStyleListener</code> is used a redraw call * is the only notification to the widget that styles have changed * and that the content width may have changed. * </p> * * @param x the x coordinate of the area to draw * @param y the y coordinate of the area to draw * @param width the width of the area to draw * @param height the height of the area to draw * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see Control#update() */ @Override public void redraw(int x, int y, int width, int height, boolean all) { super.redraw(x, y, width, height, all); if (height > 0) { int firstLine = getLineIndex(y); int lastLine = getLineIndex(y + height); resetCache(firstLine, lastLine - firstLine + 1); doMouseLinkCursor(); } } void redrawLines(int startLine, int lineCount, boolean bottomChanged) { // do nothing if redraw range is completely invisible int endLine = startLine + lineCount - 1; int partialBottomIndex = getPartialBottomIndex(); int partialTopIndex = getPartialTopIndex(); if (startLine > partialBottomIndex || endLine < partialTopIndex) { return; } // only redraw visible lines if (startLine < partialTopIndex) { startLine = partialTopIndex; } if (endLine > partialBottomIndex) { endLine = partialBottomIndex; } int redrawTop = getLinePixel(startLine); int redrawBottom = getLinePixel(endLine + 1); if (bottomChanged) redrawBottom = clientAreaHeight - bottomMargin; int redrawWidth = clientAreaWidth - leftMargin - rightMargin; super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true); } void redrawLinesBullet(int[] redrawLines) { if (redrawLines == null) return; int topIndex = getPartialTopIndex(); int bottomIndex = getPartialBottomIndex(); for (int i = 0; i < redrawLines.length; i++) { int lineIndex = redrawLines[i]; if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue; int width = -1; Bullet bullet = renderer.getLineBullet(lineIndex, null); if (bullet != null) { StyleRange style = bullet.style; GlyphMetrics metrics = style.metrics; width = metrics.width; } if (width == -1) width = getClientArea().width; int height = renderer.getLineHeight(lineIndex); int y = getLinePixel(lineIndex); super.redraw(0, y, width, height, false); } } void redrawMargins(int oldHeight, int oldWidth) { /* Redraw the old or new right/bottom margin if needed */ if (oldWidth != clientAreaWidth) { if (rightMargin > 0) { int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin; super.redraw(x, 0, rightMargin, oldHeight, false); } } if (oldHeight != clientAreaHeight) { if (bottomMargin > 0) { int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin; super.redraw(0, y, oldWidth, bottomMargin, false); } } } /** * Redraws the specified text range. * * @param start offset of the first character to redraw * @param length number of characters to redraw * @param clearBackground true if the background should be cleared as * part of the redraw operation. If true, the entire redraw range will * be cleared before anything is redrawn. If the redraw range includes * the last character of a line (i.e., the entire line is redrawn) the * line is cleared all the way to the right border of the widget. * The redraw operation will be faster and smoother if clearBackground * is set to false. Whether or not the flag can be set to false depends * on the type of change that has taken place. If font styles or * background colors for the redraw range have changed, clearBackground * should be set to true. If only foreground colors have changed for * the redraw range, clearBackground can be set to false. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> * </ul> */ public void redrawRange(int start, int length, boolean clearBackground) { checkWidget(); int end = start + length; int contentLength = content.getCharCount(); if (start > end || start < 0 || end > contentLength) { SWT.error(SWT.ERROR_INVALID_RANGE); } int firstLine = content.getLineAtOffset(start); int lastLine = content.getLineAtOffset(end); resetCache(firstLine, lastLine - firstLine + 1); internalRedrawRange(start, length); doMouseLinkCursor(); } /** * Removes the specified bidirectional segment listener. * * @param listener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * * @since 2.0 */ public void removeBidiSegmentListener(BidiSegmentListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.LineGetSegments, listener); resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Removes the specified caret listener. * * @param listener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * * @since 3.5 */ public void removeCaretListener(CaretListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.CaretMoved, listener); } /** * Removes the specified extended modify listener. * * @param extendedModifyListener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) { checkWidget(); if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.ExtendedModify, extendedModifyListener); } /** * Removes the specified line background listener. * * @param listener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void removeLineBackgroundListener(LineBackgroundListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.LineGetBackground, listener); } /** * Removes the specified line style listener. * * @param listener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void removeLineStyleListener(LineStyleListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.LineGetStyle, listener); setCaretLocation(); } /** * Removes the specified modify listener. * * @param modifyListener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void removeModifyListener(ModifyListener modifyListener) { checkWidget(); if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(SWT.Modify, modifyListener); } /** * Removes the specified listener. * * @param listener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * @since 3.2 */ public void removePaintObjectListener(PaintObjectListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.PaintObject, listener); } /** * Removes the listener from the collection of listeners who will * be notified when the user changes the receiver's selection. * * @param listener the listener which should no longer be notified * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see SelectionListener * @see #addSelectionListener */ public void removeSelectionListener(SelectionListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(SWT.Selection, listener); } /** * Removes the specified verify listener. * * @param verifyListener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void removeVerifyListener(VerifyListener verifyListener) { checkWidget(); if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(SWT.Verify, verifyListener); } /** * Removes the specified key verify listener. * * @param listener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void removeVerifyKeyListener(VerifyKeyListener listener) { if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.VerifyKey, listener); } /** * Removes the specified word movement listener. * * @param listener the listener which should no longer be notified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> * * @see MovementEvent * @see MovementListener * @see #addWordMovementListener * * @since 3.3 */ public void removeWordMovementListener(MovementListener listener) { checkWidget(); if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); removeListener(ST.WordNext, listener); removeListener(ST.WordPrevious, listener); } /** * Replaces the styles in the given range with new styles. This method * effectively deletes the styles in the given range and then adds the * the new styles. * <p> * Note: Because a StyleRange includes the start and length, the * same instance cannot occur multiple times in the array of styles. * If the same style attributes, such as font and color, occur in * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code> * can be used to share styles and reduce memory usage. * </p><p> * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p> * * @param start offset of first character where styles will be deleted * @param length length of the range to delete styles in * @param ranges StyleRange objects containing the new style information. * The ranges should not overlap and should be within the specified start * and length. The style rendering is undefined if the ranges do overlap * or are ill-defined. Must not be null. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li> * <li>ERROR_NULL_ARGUMENT when ranges is null</li> * </ul> * * @since 2.0 * * @see #setStyleRanges(int, int, int[], StyleRange[]) */ public void replaceStyleRanges(int start, int length, StyleRange[] ranges) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); setStyleRanges(start, length, null, ranges, false); } /** * Replaces the given text range with new text. * If the widget has the SWT.SINGLE style and "text" contains more than * one line, only the first line is rendered but the text is stored * unchanged. A subsequent call to getText will return the same text * that was set. Note that only a single line of text should be set when * the SWT.SINGLE style is used. * <p> * <b>NOTE:</b> During the replace operation the current selection is * changed as follows: * </p> * <ul> * <li>selection before replaced text: selection unchanged * <li>selection after replaced text: adjust the selection so that same text * remains selected * <li>selection intersects replaced text: selection is cleared and caret * is placed after inserted text * </ul> * * @param start offset of first character to replace * @param length number of characters to replace. Use 0 to insert text * @param text new text. May be empty to delete text. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li> * <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter. * Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li> * <li>ERROR_NULL_ARGUMENT when string is null</li> * </ul> */ public void replaceTextRange(int start, int length, String text) { checkWidget(); if (text == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } int contentLength = getCharCount(); int end = start + length; if (start > end || start < 0 || end > contentLength) { SWT.error(SWT.ERROR_INVALID_RANGE); } Event event = new Event(); event.start = start; event.end = end; event.text = text; modifyContent(event, false); } /** * Resets the caret position, selection and scroll offsets. Recalculate * the content width and scroll bars. Redraw the widget. */ void reset() { ScrollBar verticalBar = getVerticalBar(); ScrollBar horizontalBar = getHorizontalBar(); setCaretOffset(0, SWT.DEFAULT); topIndex = 0; topIndexY = 0; verticalScrollOffset = 0; horizontalScrollOffset = 0; resetSelection(); renderer.setContent(content); if (verticalBar != null) { verticalBar.setSelection(0); } if (horizontalBar != null) { horizontalBar.setSelection(0); } resetCache(0, 0); setCaretLocation(); super.redraw(); } void resetBidiData() { caretDirection = SWT.NULL; resetCache(0, content.getLineCount()); setCaretLocation(); keyActionMap.clear(); createKeyBindings(); super.redraw(); } void resetCache(SortedSet<Integer> lines) { if (lines == null || lines.isEmpty()) return; int maxLineIndex = renderer.maxWidthLineIndex; renderer.reset(lines); renderer.calculateClientArea(); if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) { renderer.calculate(maxLineIndex, 1); } setScrollBars(true); if (!isFixedLineHeight()) { if (topIndex > lines.iterator().next()) { verticalScrollOffset = -1; } renderer.calculateIdle(); } } void resetCache(int firstLine, int count) { int maxLineIndex = renderer.maxWidthLineIndex; renderer.reset(firstLine, count); renderer.calculateClientArea(); if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) { renderer.calculate(maxLineIndex, 1); } setScrollBars(true); if (!isFixedLineHeight()) { if (topIndex > firstLine) { verticalScrollOffset = -1; } renderer.calculateIdle(); } } /** * Resets the selection. */ void resetSelection() { selection.x = selection.y = caretOffset; selectionAnchor = -1; sendAccessibleTextCaretMoved(); } @Override public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) { super.scroll(destX, destY, x, y, width, height, false); if (all) { int deltaX = destX - x, deltaY = destY - y; Control[] children = getChildren(); for (int i = 0; i < children.length; i++) { Control child = children[i]; Rectangle rect = child.getBounds(); child.setLocation(rect.x + deltaX, rect.y + deltaY); } } } /** * Scrolls the widget horizontally. * * @param pixels number of SWT logical points to scroll, > 0 = scroll left, * < 0 scroll right * @param adjustScrollBar * true= the scroll thumb will be moved to reflect the new scroll offset. * false = the scroll thumb will not be moved * @return * true=the widget was scrolled * false=the widget was not scrolled, the given offset is not valid. */ boolean scrollHorizontal(int pixels, boolean adjustScrollBar) { if (pixels == 0) return false; if (wordWrap) return false; ScrollBar horizontalBar = getHorizontalBar(); if (horizontalBar != null && adjustScrollBar) { horizontalBar.setSelection(horizontalScrollOffset + pixels); } int scrollHeight = clientAreaHeight - topMargin - bottomMargin; if (pixels > 0) { int sourceX = leftMargin + pixels; int scrollWidth = clientAreaWidth - sourceX - rightMargin; if (scrollWidth > 0) { scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true); } if (sourceX > scrollWidth) { super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true); } } else { int destinationX = leftMargin - pixels; int scrollWidth = clientAreaWidth - destinationX - rightMargin; if (scrollWidth > 0) { scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true); } if (destinationX > scrollWidth) { super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true); } } horizontalScrollOffset += pixels; setCaretLocation(); return true; } /** * Scrolls the widget vertically. * * @param pixel the new vertical scroll offset * @param adjustScrollBar * true= the scroll thumb will be moved to reflect the new scroll offset. * false = the scroll thumb will not be moved * @return * true=the widget was scrolled * false=the widget was not scrolled */ boolean scrollVertical(int pixels, boolean adjustScrollBar) { if (pixels == 0) { return false; } if (verticalScrollOffset != -1) { ScrollBar verticalBar = getVerticalBar(); if (verticalBar != null && adjustScrollBar) { verticalBar.setSelection(verticalScrollOffset + pixels); } int deltaY = 0; if (pixels > 0) { int sourceY = topMargin + pixels; int scrollHeight = clientAreaHeight - sourceY - bottomMargin; if (scrollHeight > 0) { deltaY = -pixels; } } else { int destinationY = topMargin - pixels; int scrollHeight = clientAreaHeight - destinationY - bottomMargin; if (scrollHeight > 0) { deltaY = -pixels; } } Control[] children = getChildren(); for (int i = 0; i < children.length; i++) { Control child = children[i]; Rectangle rect = child.getBounds(); child.setLocation(rect.x, rect.y + deltaY); } verticalScrollOffset += pixels; calculateTopIndex(pixels); super.redraw(); } else { calculateTopIndex(pixels); super.redraw(); } setCaretLocation(); return true; } void scrollText(int srcY, int destY) { if (srcY == destY) return; int deltaY = destY - srcY; int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight; if (deltaY > 0) { scrollHeight = clientAreaHeight - srcY - bottomMargin; } else { scrollHeight = clientAreaHeight - destY - bottomMargin; } scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true); if ((0 < srcY + scrollHeight) && (topMargin > srcY)) { super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false); } if ((0 < destY + scrollHeight) && (topMargin > destY)) { super.redraw(leftMargin, 0, scrollWidth, topMargin, false); } if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) { super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false); } if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) { super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false); } } void sendAccessibleTextCaretMoved() { if (caretOffset != accCaretOffset) { accCaretOffset = caretOffset; getAccessible().textCaretMoved(caretOffset); } } void sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount) { Accessible accessible = getAccessible(); if (replaceCharCount != 0) { accessible.textChanged(ACC.TEXT_DELETE, start, replaceCharCount); } if (newCharCount != 0) { accessible.textChanged(ACC.TEXT_INSERT, start, newCharCount); } } /** * Selects all the text. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void selectAll() { checkWidget(); if (blockSelection) { renderer.calculate(0, content.getLineCount()); setScrollBars(false); int verticalScrollOffset = getVerticalScrollOffset(); int left = leftMargin - horizontalScrollOffset; int top = topMargin - verticalScrollOffset; int right = renderer.getWidth() - rightMargin - horizontalScrollOffset; int bottom = renderer.getHeight() - bottomMargin - verticalScrollOffset; setBlockSelectionLocation(left, top, right, bottom, false); return; } setSelection(0, Math.max(getCharCount(), 0)); } /** * Replaces/inserts text as defined by the event. * * @param event the text change event. * <ul> * <li>event.start - the replace start offset</li> * <li>event.end - the replace end offset</li> * <li>event.text - the new text</li> * </ul> */ void sendKeyEvent(Event event) { if (editable) { modifyContent(event, true); } } /** * Returns a StyledTextEvent that can be used to request data such * as styles and background color for a line. * <p> * The specified line may be a visual (wrapped) line if in word * wrap mode. The returned object will always be for a logical * (unwrapped) line. * </p> * * @param lineOffset offset of the line. This may be the offset of * a visual line if the widget is in word wrap mode. * @param line line text. This may be the text of a visual line if * the widget is in word wrap mode. * @return StyledTextEvent that can be used to request line data * for the given line. */ StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) { StyledTextEvent event = null; if (isListening(eventType)) { event = new StyledTextEvent(content); event.detail = lineOffset; event.text = line; event.alignment = alignment; event.indent = indent; event.wrapIndent = wrapIndent; event.justify = justify; notifyListeners(eventType, event); } return event; } /** * Sends the specified selection event. */ void sendSelectionEvent() { getAccessible().textSelectionChanged(); Event event = new Event(); event.x = selection.x; event.y = selection.y; notifyListeners(SWT.Selection, event); } int sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces) { int lineWidth = 0, start, end; StringBuilder buffer = new StringBuilder(); if (lineIndex < content.getLineCount()) { int[] trailing = new int[1]; start = getOffsetAtPoint(left, getLinePixel(lineIndex), trailing, true); if (start == -1) { int lineOffset = content.getOffsetAtLine(lineIndex); int lineLegth = content.getLine(lineIndex).length(); start = end = lineOffset + lineLegth; if (fillWithSpaces) { TextLayout layout = renderer.getTextLayout(lineIndex); lineWidth = layout.getBounds().width; renderer.disposeTextLayout(layout); } } else { start += trailing[0]; end = left == right ? start : getOffsetAtPoint(right, 0, lineIndex, null); fillWithSpaces = false; } } else { start = end = content.getCharCount(); buffer.append(content.getLineDelimiter()); } if (start > end) { int temp = start; start = end; end = temp; } if (fillWithSpaces) { int spacesWidth = left - lineWidth + horizontalScrollOffset - leftMargin; int spacesCount = spacesWidth / renderer.averageCharWidth; for (int i = 0; i < spacesCount; i++) { buffer.append(' '); } } buffer.append(text); Event event = new Event(); event.start = start; event.end = end; event.text = buffer.toString(); sendKeyEvent(event); return event.start + event.text.length(); } int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) { if (isListening(eventType)) { StyledTextEvent event = new StyledTextEvent(content); event.detail = lineOffset; event.text = lineText; event.count = movement; event.start = offset; event.end = newOffset; notifyListeners(eventType, event); offset = event.end; if (offset != newOffset) { int length = getCharCount(); if (offset < 0) { offset = 0; } else if (offset > length) { offset = length; } else { if (isLineDelimiter(offset)) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } } } return offset; } return newOffset; } void setAlignment() { if ((getStyle() & SWT.SINGLE) == 0) return; int alignment = renderer.getLineAlignment(0, this.alignment); int newAlignmentMargin = 0; if (alignment != SWT.LEFT) { renderer.calculate(0, 1); int width = renderer.getWidth() - alignmentMargin; newAlignmentMargin = clientAreaWidth - width; if (newAlignmentMargin < 0) newAlignmentMargin = 0; if (alignment == SWT.CENTER) newAlignmentMargin /= 2; } if (alignmentMargin != newAlignmentMargin) { leftMargin -= alignmentMargin; leftMargin += newAlignmentMargin; alignmentMargin = newAlignmentMargin; resetCache(0, 1); setCaretLocation(); super.redraw(); } } /** * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>, * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines. * <p> * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set * in order to stabilize the right edge before setting alignment. * </p> * * @param alignment the new alignment * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #setLineAlignment(int, int, int) * * @since 3.2 */ public void setAlignment(int alignment) { checkWidget(); alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER); if (alignment == 0 || this.alignment == alignment) return; this.alignment = alignment; resetCache(0, content.getLineCount()); setCaretLocation(); setAlignment(); super.redraw(); } /** * Set the Always Show Scrollbars flag. True if the scrollbars are * always shown even if they are not required (default value). False if the scrollbars are only * visible when some part of the content needs to be scrolled to be seen. * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the * horizontal and vertical directions. * * @param show true to show the scrollbars even when not required, false to show scrollbars only when required * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.8 */ public void setAlwaysShowScrollBars(boolean show) { checkWidget(); if (show == alwaysShowScroll) return; alwaysShowScroll = show; setScrollBars(true); } /** * @see Control#setBackground(Color) */ @Override public void setBackground(Color color) { checkWidget(); background = color; super.setBackground(color); resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets the block selection mode. * * @param blockSelection true=enable block selection, false=disable block selection * * @since 3.5 */ public void setBlockSelection(boolean blockSelection) { checkWidget(); if ((getStyle() & SWT.SINGLE) != 0) return; if (blockSelection == this.blockSelection) return; if (wordWrap) return; this.blockSelection = blockSelection; if (cursor == null) { Display display = getDisplay(); int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM; super.setCursor(display.getSystemCursor(type)); } if (blockSelection) { int start = selection.x; int end = selection.y; if (start != end) { setBlockSelectionOffset(start, end, false); } } else { clearBlockSelection(false, false); } } /** * Sets the block selection bounds. The bounds is * relative to the upper left corner of the document. * * @param rect the new bounds for the block selection * * @see #setBlockSelectionBounds(int, int, int, int) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when point is null</li> * </ul> * * @since 3.5 */ public void setBlockSelectionBounds(Rectangle rect) { checkWidget(); if (rect == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); setBlockSelectionBounds(rect.x, rect.y, rect.width, rect.height); } /** * Sets the block selection bounds. The bounds is * relative to the upper left corner of the document. * * @param x the new x coordinate for the block selection * @param y the new y coordinate for the block selection * @param width the new width for the block selection * @param height the new height for the block selection * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public void setBlockSelectionBounds(int x, int y, int width, int height) { checkWidget(); int verticalScrollOffset = getVerticalScrollOffset(); if (!blockSelection) { x -= horizontalScrollOffset; y -= verticalScrollOffset; int start = getOffsetAtPoint(x, y, null); int end = getOffsetAtPoint(x + width - 1, y + height - 1, null); setSelection(start, end - start, false, false); setCaretLocation(); return; } int minY = topMargin; int minX = leftMargin; int maxY = renderer.getHeight() - bottomMargin; int maxX = Math.max(clientAreaWidth, renderer.getWidth()) - rightMargin; int anchorX = Math.max(minX, Math.min(maxX, x)) - horizontalScrollOffset; int anchorY = Math.max(minY, Math.min(maxY, y)) - verticalScrollOffset; int locationX = Math.max(minX, Math.min(maxX, x + width)) - horizontalScrollOffset; int locationY = Math.max(minY, Math.min(maxY, y + height - 1)) - verticalScrollOffset; if (isFixedLineHeight() && renderer.fixedPitch) { int avg = renderer.averageCharWidth; anchorX = ((anchorX - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset; locationX = ((locationX + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset; } setBlockSelectionLocation(anchorX, anchorY, locationX, locationY, false); } void setBlockSelectionLocation(int x, int y, boolean sendEvent) { int verticalScrollOffset = getVerticalScrollOffset(); blockXLocation = x + horizontalScrollOffset; blockYLocation = y + verticalScrollOffset; int[] alignment = new int[1]; int offset = getOffsetAtPoint(x, y, alignment); setCaretOffset(offset, alignment[0]); if (blockXAnchor == -1) { blockXAnchor = blockXLocation; blockYAnchor = blockYLocation; selectionAnchor = caretOffset; } doBlockSelection(sendEvent); } void setBlockSelectionLocation(int anchorX, int anchorY, int x, int y, boolean sendEvent) { int verticalScrollOffset = getVerticalScrollOffset(); blockXAnchor = anchorX + horizontalScrollOffset; blockYAnchor = anchorY + verticalScrollOffset; selectionAnchor = getOffsetAtPoint(anchorX, anchorY, null); setBlockSelectionLocation(x, y, sendEvent); } void setBlockSelectionOffset(int offset, boolean sendEvent) { Point point = getPointAtOffset(offset); int verticalScrollOffset = getVerticalScrollOffset(); blockXLocation = point.x + horizontalScrollOffset; blockYLocation = point.y + verticalScrollOffset; setCaretOffset(offset, SWT.DEFAULT); if (blockXAnchor == -1) { blockXAnchor = blockXLocation; blockYAnchor = blockYLocation; selectionAnchor = caretOffset; } doBlockSelection(sendEvent); } void setBlockSelectionOffset(int anchorOffset, int offset, boolean sendEvent) { int verticalScrollOffset = getVerticalScrollOffset(); Point anchorPoint = getPointAtOffset(anchorOffset); blockXAnchor = anchorPoint.x + horizontalScrollOffset; blockYAnchor = anchorPoint.y + verticalScrollOffset; selectionAnchor = anchorOffset; setBlockSelectionOffset(offset, sendEvent); } /** * Sets the receiver's caret. Set the caret's height and location. * * @param caret the new caret for the receiver * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ @Override public void setCaret(Caret caret) { checkWidget(); super.setCaret(caret); caretDirection = SWT.NULL; if (caret != null) { setCaretLocation(); } } /** * Sets the BIDI coloring mode. When true the BIDI text display * algorithm is applied to segments of text that are the same * color. * * @param mode the new coloring mode * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @deprecated use BidiSegmentListener instead. */ @Deprecated public void setBidiColoring(boolean mode) { checkWidget(); bidiColoring = mode; } /** * Sets the bottom margin. * * @param bottomMargin the bottom margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public void setBottomMargin(int bottomMargin) { checkWidget(); setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin); } /** * Moves the Caret to the current caret offset. */ void setCaretLocation() { Point newCaretPos = getPointAtOffset(caretOffset); setCaretLocation(newCaretPos, getCaretDirection()); } void setCaretLocation(final Point location, int direction) { Caret caret = getCaret(); if (caret != null) { final boolean isDefaultCaret = caret == defaultCaret; final StyleRange styleAtOffset = content.getCharCount() > 0 ? (caretOffset < content.getCharCount() ? getStyleRangeAtOffset(caretOffset) : getStyleRangeAtOffset(content.getCharCount() - 1)) : // caret after last char: use last char style null; final int caretLine = getCaretLine(); int graphicalLineHeight = getLineHeight(); final int lineStartOffset = getOffsetAtLine(caretLine); int graphicalLineFirstOffset = lineStartOffset; final int lineEndOffset = lineStartOffset + getLine(caretLine).length(); int graphicalLineLastOffset = lineEndOffset; if (caretLine < getLineCount() && renderer.getLineHeight(caretLine) != getLineHeight()) { // word wrap, metrics, styles... graphicalLineHeight = getLineHeight(caretOffset); final Rectangle characterBounds = getBoundsAtOffset(caretOffset); graphicalLineFirstOffset = getOffsetAtPoint(new Point(leftMargin, characterBounds.y)); graphicalLineLastOffset = getOffsetAtPoint( new Point(leftMargin, characterBounds.y + graphicalLineHeight)) - 1; if (graphicalLineLastOffset < graphicalLineFirstOffset) { graphicalLineLastOffset = getCharCount(); } } int caretHeight = getLineHeight(); boolean isTextAlignedAtBottom = true; if (graphicalLineFirstOffset >= 0) { for (StyleRange style : getStyleRanges(graphicalLineFirstOffset, graphicalLineLastOffset - graphicalLineFirstOffset)) { isTextAlignedAtBottom &= ((style.font == null || Objects.equals(style.font, getFont())) && style.rise >= 0 && (style.metrics == null || style.metrics.descent <= 0)); } } if (!isTextAlignedAtBottom || (styleAtOffset != null && styleAtOffset.isVariableHeight())) { if (isDefaultCaret) { direction = SWT.DEFAULT; caretHeight = graphicalLineHeight; } else { caretHeight = caret.getSize().y; } } if (isTextAlignedAtBottom && caretHeight < graphicalLineHeight) { location.y += (graphicalLineHeight - caretHeight); } int imageDirection = direction; if (isMirrored()) { if (imageDirection == SWT.LEFT) { imageDirection = SWT.RIGHT; } else if (imageDirection == SWT.RIGHT) { imageDirection = SWT.LEFT; } } if (isDefaultCaret && imageDirection == SWT.RIGHT) { location.x -= (caret.getSize().x - 1); } if (isDefaultCaret) { caret.setBounds(location.x, location.y, caretWidth, caretHeight); } else { caret.setLocation(location); } if (direction != caretDirection) { caretDirection = direction; if (isDefaultCaret) { if (imageDirection == SWT.DEFAULT) { defaultCaret.setImage(null); } else if (imageDirection == SWT.LEFT) { defaultCaret.setImage(leftCaretBitmap); } else if (imageDirection == SWT.RIGHT) { defaultCaret.setImage(rightCaretBitmap); } } if (caretDirection == SWT.LEFT) { BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI); } else if (caretDirection == SWT.RIGHT) { BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI); } } updateCaretVisibility(); } columnX = location.x; } /** * Sets the caret offset. * * @param offset caret offset, relative to the first character in the text. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the offset is inside a multi byte line * delimiter (and thus neither clearly in front of or after the line delimiter) * </ul> */ public void setCaretOffset(int offset) { checkWidget(); int length = getCharCount(); if (length > 0 && offset != caretOffset) { if (offset < 0) { offset = 0; } else if (offset > length) { offset = length; } else { if (isLineDelimiter(offset)) { // offset is inside a multi byte line delimiter. This is an // illegal operation and an exception is thrown. Fixes 1GDKK3R SWT.error(SWT.ERROR_INVALID_ARGUMENT); } } setCaretOffset(offset, PREVIOUS_OFFSET_TRAILING); // clear the selection if the caret is moved. // don't notify listeners about the selection change. if (blockSelection) { clearBlockSelection(true, false); } else { clearSelection(false); } } setCaretLocation(); } void setCaretOffset(int offset, int alignment) { if (caretOffset != offset) { caretOffset = offset; if (isListening(ST.CaretMoved)) { StyledTextEvent event = new StyledTextEvent(content); event.end = caretOffset; notifyListeners(ST.CaretMoved, event); } } if (alignment != SWT.DEFAULT) { caretAlignment = alignment; } } /** * Copies the specified text range to the clipboard. The text will be placed * in the clipboard in plain text format and RTF format. * * @param start start index of the text * @param length length of text to place in clipboard * * @exception SWTError, see Clipboard.setContents * @see org.eclipse.swt.dnd.Clipboard#setContents */ void setClipboardContent(int start, int length, int clipboardType) throws SWTError { if (clipboardType == DND.SELECTION_CLIPBOARD && !IS_GTK) return; TextTransfer plainTextTransfer = TextTransfer.getInstance(); TextWriter plainTextWriter = new TextWriter(start, length); String plainText = getPlatformDelimitedText(plainTextWriter); Object[] data; Transfer[] types; if (clipboardType == DND.SELECTION_CLIPBOARD) { data = new Object[] { plainText }; types = new Transfer[] { plainTextTransfer }; } else { RTFTransfer rtfTransfer = RTFTransfer.getInstance(); RTFWriter rtfWriter = new RTFWriter(start, length); String rtfText = getPlatformDelimitedText(rtfWriter); data = new Object[] { rtfText, plainText }; types = new Transfer[] { rtfTransfer, plainTextTransfer }; } clipboard.setContents(data, types, clipboardType); } /** * Sets the content implementation to use for text storage. * * @param newContent StyledTextContent implementation to use for text storage. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when listener is null</li> * </ul> */ public void setContent(StyledTextContent newContent) { checkWidget(); if (newContent == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } if (content != null) { content.removeTextChangeListener(textChangeListener); } content = newContent; content.addTextChangeListener(textChangeListener); reset(); } /** * Sets the receiver's cursor to the cursor specified by the * argument. Overridden to handle the null case since the * StyledText widget uses an ibeam as its default cursor. * * @see Control#setCursor(Cursor) */ @Override public void setCursor(Cursor cursor) { checkWidget(); if (cursor != null && cursor.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); this.cursor = cursor; if (cursor == null) { Display display = getDisplay(); int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM; super.setCursor(display.getSystemCursor(type)); } else { super.setCursor(cursor); } } /** * Sets whether the widget implements double click mouse behavior. * * @param enable if true double clicking a word selects the word, if false * double clicks have the same effect as regular mouse clicks. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setDoubleClickEnabled(boolean enable) { checkWidget(); doubleClickEnabled = enable; } @Override public void setDragDetect(boolean dragDetect) { checkWidget(); this.dragDetect = dragDetect; } /** * Sets whether the widget content can be edited. * * @param editable if true content can be edited, if false content can not be * edited * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setEditable(boolean editable) { checkWidget(); this.editable = editable; } /** * Sets a new font to render text with. * <p> * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang * and the same baseline as regular fonts. * </p> * * @param font new font * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ @Override public void setFont(Font font) { checkWidget(); int oldLineHeight = renderer.getLineHeight(); super.setFont(font); renderer.setFont(getFont(), tabLength); // keep the same top line visible. fixes 5815 if (isFixedLineHeight()) { int lineHeight = renderer.getLineHeight(); if (lineHeight != oldLineHeight) { int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset(); scrollVertical(vscroll, true); } } resetCache(0, content.getLineCount()); claimBottomFreeSpace(); calculateScrollBars(); if (isBidiCaret()) createCaretBitmaps(); caretDirection = SWT.NULL; setCaretLocation(); super.redraw(); } @Override public void setForeground(Color color) { checkWidget(); foreground = color; super.setForeground(getForeground()); resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets the horizontal scroll offset relative to the start of the line. * Do nothing if there is no text set. * <p> * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the * widget. * </p> * * @param offset horizontal scroll offset relative to the start * of the line, measured in character increments starting at 0, if * equal to 0 the content is not scrolled, if > 0 = the content is scrolled. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setHorizontalIndex(int offset) { checkWidget(); if (getCharCount() == 0) { return; } if (offset < 0) { offset = 0; } offset *= getHorizontalIncrement(); // allow any value if client area width is unknown or 0. // offset will be checked in resize handler. // don't use isVisible since width is known even if widget // is temporarily invisible if (clientAreaWidth > 0) { int width = renderer.getWidth(); // prevent scrolling if the content fits in the client area. // align end of longest line with right border of client area // if offset is out of range. if (offset > width - clientAreaWidth) { offset = Math.max(0, width - clientAreaWidth); } } scrollHorizontal(offset - horizontalScrollOffset, true); } /** * Sets the horizontal SWT logical point offset relative to the start of the line. * Do nothing if there is no text set. * <p> * <b>NOTE:</b> The horizontal SWT logical point offset is reset to 0 when new text * is set in the widget. * </p> * * @param pixel horizontal SWT logical point offset relative to the start * of the line. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 2.0 */ public void setHorizontalPixel(int pixel) { checkWidget(); if (getCharCount() == 0) { return; } if (pixel < 0) { pixel = 0; } // allow any value if client area width is unknown or 0. // offset will be checked in resize handler. // don't use isVisible since width is known even if widget // is temporarily invisible if (clientAreaWidth > 0) { int width = renderer.getWidth(); // prevent scrolling if the content fits in the client area. // align end of longest line with right border of client area // if offset is out of range. if (pixel > width - clientAreaWidth) { pixel = Math.max(0, width - clientAreaWidth); } } scrollHorizontal(pixel - horizontalScrollOffset, true); } /** * Sets the line indentation of the widget. * <p> * It is the amount of blank space, in points, at the beginning of each line. * When a line wraps in several lines only the first one is indented. * </p> * * @param indent the new indent * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #setLineIndent(int, int, int) * * @since 3.2 */ public void setIndent(int indent) { checkWidget(); if (this.indent == indent || indent < 0) return; this.indent = indent; resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets whether the widget should justify lines. * * @param justify whether lines should be justified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #setLineJustify(int, int, boolean) * * @since 3.2 */ public void setJustify(boolean justify) { checkWidget(); if (this.justify == justify) return; this.justify = justify; resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Maps a key to an action. * <p> * One action can be associated with N keys. However, each key can only * have one action (key:action is N:1 relation). * </p> * * @param key a key code defined in SWT.java or a character. * Optionally ORd with a state mask. Preferred state masks are one or more of * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform * differences. However, there may be cases where using the specific state masks * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense. * @param action one of the predefined actions defined in ST.java. * Use SWT.NULL to remove a key binding. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setKeyBinding(int key, int action) { checkWidget(); int modifierValue = key & SWT.MODIFIER_MASK; int keyInt = key & SWT.KEY_MASK; char keyChar = (char) keyInt; /** * Bug 440535: Make sure the key getting mapped to letter is in defiened * character range and filter out incorrect int to char typecasting. For * Example: SWT.KEYPAD_CR int gets wrongly type-cast to char letter 'p' */ if (Character.isDefined(keyInt) && Character.isLetter(keyChar)) { // make the keybinding case insensitive by adding it // in its upper and lower case form char ch = Character.toUpperCase(keyChar); int newKey = ch | modifierValue; if (action == SWT.NULL) { keyActionMap.remove(newKey); } else { keyActionMap.put(newKey, action); } ch = Character.toLowerCase(keyChar); newKey = ch | modifierValue; if (action == SWT.NULL) { keyActionMap.remove(newKey); } else { keyActionMap.put(newKey, action); } } else { if (action == SWT.NULL) { keyActionMap.remove(key); } else { keyActionMap.put(key, action); } } } /** * Sets the left margin. * * @param leftMargin the left margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public void setLeftMargin(int leftMargin) { checkWidget(); setMargins(leftMargin, topMargin, rightMargin, bottomMargin); } /** * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>, * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. * <p> * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set * in order to stabilize the right edge before setting alignment. * </p><p> * Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * * @param startLine first line the alignment is applied to, 0 based * @param lineCount number of lines the alignment applies to. * @param alignment line alignment * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> * </ul> * @see #setAlignment(int) * @since 3.2 */ public void setLineAlignment(int startLine, int lineCount, int alignment) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (startLine < 0 || startLine + lineCount > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } renderer.setLineAlignment(startLine, lineCount, alignment); resetCache(startLine, lineCount); redrawLines(startLine, lineCount, false); int caretLine = getCaretLine(); if (startLine <= caretLine && caretLine < startLine + lineCount) { setCaretLocation(); } setAlignment(); } /** * Sets the background color of the specified lines. * <p> * The background color is drawn for the width of the widget. All * line background colors are discarded when setText is called. * The text background color if defined in a StyleRange overlays the * line background color. * </p><p> * Should not be called if a LineBackgroundListener has been set since the * listener maintains the line backgrounds. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p><p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * </p> * * @param startLine first line the color is applied to, 0 based * @param lineCount number of lines the color applies to. * @param background line background color * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> * </ul> */ public void setLineBackground(int startLine, int lineCount, Color background) { checkWidget(); if (isListening(ST.LineGetBackground)) return; if (startLine < 0 || startLine + lineCount > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (background != null) { renderer.setLineBackground(startLine, lineCount, background); } else { renderer.clearLineBackground(startLine, lineCount); } redrawLines(startLine, lineCount, false); } /** * Sets the bullet of the specified lines. * <p> * Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p><p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * </p> * * @param startLine first line the bullet is applied to, 0 based * @param lineCount number of lines the bullet applies to. * @param bullet line bullet * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> * </ul> * @since 3.2 */ public void setLineBullet(int startLine, int lineCount, Bullet bullet) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (startLine < 0 || startLine + lineCount > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } int oldBottom = getLinePixel(startLine + lineCount); renderer.setLineBullet(startLine, lineCount, bullet); resetCache(startLine, lineCount); int newBottom = getLinePixel(startLine + lineCount); redrawLines(startLine, lineCount, oldBottom != newBottom); int caretLine = getCaretLine(); if (startLine <= caretLine && caretLine < startLine + lineCount) { setCaretLocation(); } } void setVariableLineHeight() { if (!fixedLineHeight) return; fixedLineHeight = false; renderer.calculateIdle(); } /** * Returns true if StyledText is in word wrap mode and false otherwise. * * @return true if StyledText is in word wrap mode and false otherwise. */ boolean isWordWrap() { return wordWrap || visualWrap; } /** * Sets the indent of the specified lines. * <p> * Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p><p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * </p> * * @param startLine first line the indent is applied to, 0 based * @param lineCount number of lines the indent applies to. * @param indent line indent * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> * </ul> * @see #setIndent(int) * @since 3.2 */ public void setLineIndent(int startLine, int lineCount, int indent) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (startLine < 0 || startLine + lineCount > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } int oldBottom = getLinePixel(startLine + lineCount); renderer.setLineIndent(startLine, lineCount, indent); resetCache(startLine, lineCount); int newBottom = getLinePixel(startLine + lineCount); redrawLines(startLine, lineCount, oldBottom != newBottom); int caretLine = getCaretLine(); if (startLine <= caretLine && caretLine < startLine + lineCount) { setCaretLocation(); } } /** * Sets the vertical indent of the specified lines. * <p> * Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p><p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * </p><p> * Setting both line spacing and vertical indent on a line would result in the * spacing and indent add up for the line. * </p> * * @param lineIndex line index the vertical indent is applied to, 0 based * @param verticalLineIndent vertical line indent * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line index is invalid</li> * </ul> * @since 3.109 */ public void setLineVerticalIndent(int lineIndex, int verticalLineIndent) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (lineIndex < 0 || lineIndex >= content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (verticalLineIndent == renderer.getLineVerticalIndent(lineIndex)) { return; } setVariableLineHeight(); int oldBottom = getLinePixel(lineIndex + 1); if (oldBottom <= getClientArea().height) { verticalScrollOffset = -1; } renderer.setLineVerticalIndent(lineIndex, verticalLineIndent); resetCache(lineIndex, 1); int newBottom = getLinePixel(lineIndex + 1); redrawLines(lineIndex, 1, oldBottom != newBottom); int caretLine = getCaretLine(); if (lineIndex <= caretLine && caretLine < lineIndex + 1) { setCaretLocation(); } } /** * Sets the justify of the specified lines. * <p> * Should not be called if a LineStyleListener has been set since the listener * maintains the line attributes. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p><p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * </p> * * @param startLine first line the justify is applied to, 0 based * @param lineCount number of lines the justify applies to. * @param justify true if lines should be justified * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> * </ul> * @see #setJustify(boolean) * @since 3.2 */ public void setLineJustify(int startLine, int lineCount, boolean justify) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (startLine < 0 || startLine + lineCount > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } renderer.setLineJustify(startLine, lineCount, justify); resetCache(startLine, lineCount); redrawLines(startLine, lineCount, false); int caretLine = getCaretLine(); if (startLine <= caretLine && caretLine < startLine + lineCount) { setCaretLocation(); } } /** * Sets the line spacing of the widget. The line spacing applies for all lines. * In the case of #setLineSpacingProvider(StyledTextLineSpacingProvider) is customized, * the line spacing are applied only for the lines which are not managed by {@link StyledTextLineSpacingProvider}. * * @param lineSpacing the line spacing * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @see #setLineSpacingProvider(StyledTextLineSpacingProvider) * @since 3.2 */ public void setLineSpacing(int lineSpacing) { checkWidget(); if (this.lineSpacing == lineSpacing || lineSpacing < 0) return; this.lineSpacing = lineSpacing; setVariableLineHeight(); resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets the line spacing provider of the widget. The line spacing applies for some lines with customized spacing * or reset the customized spacing if the argument is null. * * @param lineSpacingProvider the line spacing provider (or null) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @see #setLineSpacingProvider(StyledTextLineSpacingProvider) * @since 3.107 */ public void setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider) { checkWidget(); 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 (!isFixedLineHeight()) { resetCache(0, content.getLineCount()); } } else { if (isFixedLineHeight()) { 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 setVariableLineHeight(); // reset only the line size renderer.reset(i, 1); if (firstLine == -1) { firstLine = i; } } } if (firstLine != -1) { // call reset cache for the first line which have changed to recompute scrollbars resetCache(firstLine, 0); } } } setCaretLocation(); super.redraw(); } /** * Sets the tab stops of the specified lines. * <p> * Should not be called if a <code>LineStyleListener</code> has been set since the listener * maintains the line attributes. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p><p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * </p> * * @param startLine first line the justify is applied to, 0 based * @param lineCount number of lines the justify applies to. * @param tabStops tab stops * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> * </ul> * @see #setTabStops(int[]) * @since 3.6 */ public void setLineTabStops(int startLine, int lineCount, int[] tabStops) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (startLine < 0 || startLine + lineCount > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (tabStops != null) { int pos = 0; int[] newTabs = new int[tabStops.length]; for (int i = 0; i < tabStops.length; i++) { if (tabStops[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT); newTabs[i] = pos = tabStops[i]; } renderer.setLineTabStops(startLine, lineCount, newTabs); } else { renderer.setLineTabStops(startLine, lineCount, null); } resetCache(startLine, lineCount); redrawLines(startLine, lineCount, false); int caretLine = getCaretLine(); if (startLine <= caretLine && caretLine < startLine + lineCount) { setCaretLocation(); } } /** * Sets the wrap indent of the specified lines. * <p> * Should not be called if a <code>LineStyleListener</code> has been set since the listener * maintains the line attributes. * </p><p> * All line attributes are maintained relative to the line text, not the * line index that is specified in this method call. * During text changes, when entire lines are inserted or removed, the line * attributes that are associated with the lines after the change * will "move" with their respective text. An entire line is defined as * extending from the first character on a line to the last and including the * line delimiter. * </p><p> * When two lines are joined by deleting a line delimiter, the top line * attributes take precedence and the attributes of the bottom line are deleted. * For all other text changes line attributes will remain unchanged. * </p> * * @param startLine first line the wrap indent is applied to, 0 based * @param lineCount number of lines the wrap indent applies to. * @param wrapIndent line wrap indent * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> * </ul> * @see #setWrapIndent(int) * @since 3.6 */ public void setLineWrapIndent(int startLine, int lineCount, int wrapIndent) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (startLine < 0 || startLine + lineCount > content.getLineCount()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } int oldBottom = getLinePixel(startLine + lineCount); renderer.setLineWrapIndent(startLine, lineCount, wrapIndent); resetCache(startLine, lineCount); int newBottom = getLinePixel(startLine + lineCount); redrawLines(startLine, lineCount, oldBottom != newBottom); int caretLine = getCaretLine(); if (startLine <= caretLine && caretLine < startLine + lineCount) { setCaretLocation(); } } /** * Sets the color of the margins. * * @param color the new color (or null) * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public void setMarginColor(Color color) { checkWidget(); if (color != null && color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); marginColor = color; super.redraw(); } /** * Sets the margins. * * @param leftMargin the left margin. * @param topMargin the top margin. * @param rightMargin the right margin. * @param bottomMargin the bottom margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public void setMargins(int leftMargin, int topMargin, int rightMargin, int bottomMargin) { checkWidget(); this.leftMargin = Math.max(0, leftMargin) + alignmentMargin; this.topMargin = Math.max(0, topMargin); this.rightMargin = Math.max(0, rightMargin); this.bottomMargin = Math.max(0, bottomMargin); resetCache(0, content.getLineCount()); setScrollBars(true); setCaretLocation(); setAlignment(); super.redraw(); } /** * Sets the enabled state of the mouse navigator. When the mouse navigator is enabled, the user can navigate through the widget * by pressing the middle button and moving the cursor. * * @param enabled if true, the mouse navigator is enabled, if false the mouse navigator is deactivated * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 3.110 */ public void setMouseNavigatorEnabled(boolean enabled) { checkWidget(); if ((enabled && mouseNavigator != null) || (!enabled && mouseNavigator == null)) { return; } if (enabled) { mouseNavigator = new MouseNavigator(this); } else { mouseNavigator.dispose(); mouseNavigator = null; } } /** * Flips selection anchor based on word selection direction. */ void setMouseWordSelectionAnchor() { if (doubleClickEnabled && clickCount > 1) { if (caretOffset < doubleClickSelection.x) { selectionAnchor = doubleClickSelection.y; } else if (caretOffset > doubleClickSelection.y) { selectionAnchor = doubleClickSelection.x; } } } /** * Sets the orientation of the receiver, which must be one * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>. * * @param orientation new orientation style * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 2.1.2 */ @Override public void setOrientation(int orientation) { int oldOrientation = getOrientation(); super.setOrientation(orientation); int newOrientation = getOrientation(); if (oldOrientation != newOrientation) { resetBidiData(); } } /** * Sets the right margin. * * @param rightMargin the right margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public void setRightMargin(int rightMargin) { checkWidget(); setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin); } void setScrollBar(ScrollBar bar, int clientArea, int maximum, int margin) { int inactive = 1; if (clientArea < maximum) { bar.setMaximum(maximum - margin); bar.setThumb(clientArea - margin); bar.setPageIncrement(clientArea - margin); if (!alwaysShowScroll) bar.setVisible(true); } else if (bar.getThumb() != inactive || bar.getMaximum() != inactive) { bar.setValues(bar.getSelection(), bar.getMinimum(), inactive, inactive, bar.getIncrement(), inactive); } } /** * Adjusts the maximum and the page size of the scroll bars to * reflect content width/length changes. * * @param vertical indicates if the vertical scrollbar also needs to be set */ void setScrollBars(boolean vertical) { ignoreResize++; if (!isFixedLineHeight() || !alwaysShowScroll) vertical = true; ScrollBar verticalBar = vertical ? getVerticalBar() : null; ScrollBar horizontalBar = getHorizontalBar(); int oldHeight = clientAreaHeight; int oldWidth = clientAreaWidth; if (!alwaysShowScroll) { if (verticalBar != null) verticalBar.setVisible(false); if (horizontalBar != null) horizontalBar.setVisible(false); } if (verticalBar != null) { setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin); } if (horizontalBar != null && !wordWrap) { setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin); if (!alwaysShowScroll && horizontalBar.getVisible() && verticalBar != null) { setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin); if (verticalBar.getVisible()) { setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin); } } } if (!alwaysShowScroll) { redrawMargins(oldHeight, oldWidth); } ignoreResize--; } /** * Sets the selection to the given position and scrolls it into view. Equivalent to setSelection(start,start). * * @param start new caret position * @see #setSelection(int,int) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) * </ul> */ public void setSelection(int start) { // checkWidget test done in setSelectionRange setSelection(start, start); } /** * Sets the selection and scrolls it into view. * <p> * Indexing is zero based. Text selections are specified in terms of * caret positions. In a text widget that contains N characters, there are * N+1 caret positions, ranging from 0..N * </p> * * @param point x=selection start offset, y=selection end offset * The caret will be placed at the selection start when x > y. * @see #setSelection(int,int) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when point is null</li> * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) * </ul> */ public void setSelection(Point point) { checkWidget(); if (point == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); setSelection(point.x, point.y); } /** * Sets the receiver's selection background color to the color specified * by the argument, or to the default system color for the control * if the argument is null. * * @param color the new color (or null) * * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 2.1 */ public void setSelectionBackground(Color color) { checkWidget(); if (color != null) { if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); } selectionBackground = color; resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets the receiver's selection foreground color to the color specified * by the argument, or to the default system color for the control * if the argument is null. * <p> * Note that this is a <em>HINT</em>. Some platforms do not allow the application * to change the selection foreground color. * </p> * @param color the new color (or null) * * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 2.1 */ public void setSelectionForeground(Color color) { checkWidget(); if (color != null) { if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); } selectionForeground = color; resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets the selection and scrolls it into view. * <p> * Indexing is zero based. Text selections are specified in terms of * caret positions. In a text widget that contains N characters, there are * N+1 caret positions, ranging from 0..N * </p> * * @param start selection start offset. The caret will be placed at the * selection start when start > end. * @param end selection end offset * @see #setSelectionRange(int,int) * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) * </ul> */ public void setSelection(int start, int end) { setSelectionRange(start, end - start); showSelection(); } /** * Sets the selection. * <p> * The new selection may not be visible. Call showSelection to scroll * the selection into view. * </p> * * @param start offset of the first selected character, start >= 0 must be true. * @param length number of characters to select, 0 <= start + length * <= getCharCount() must be true. * A negative length places the caret at the selection start. * @param sendEvent a Selection event is sent when set to true and when * the selection is reset. */ void setSelection(int start, int length, boolean sendEvent, boolean doBlock) { int end = start + length; if (start > end) { int temp = end; end = start; start = temp; } // is the selection range different or is the selection direction // different? if (selection.x != start || selection.y != end || (length > 0 && selectionAnchor != selection.x) || (length < 0 && selectionAnchor != selection.y)) { if (blockSelection && doBlock) { if (length < 0) { setBlockSelectionOffset(end, start, sendEvent); } else { setBlockSelectionOffset(start, end, sendEvent); } } else { int oldStart = selection.x; int oldLength = selection.y - selection.x; int charCount = content.getCharCount(); // called internally to remove selection after text is removed // therefore make sure redraw range is valid. int redrawX = Math.min(selection.x, charCount); int redrawY = Math.min(selection.y, charCount); if (length < 0) { selectionAnchor = selection.y = end; selection.x = start; setCaretOffset(start, PREVIOUS_OFFSET_TRAILING); } else { selectionAnchor = selection.x = start; selection.y = end; setCaretOffset(end, PREVIOUS_OFFSET_TRAILING); } redrawX = Math.min(redrawX, selection.x); redrawY = Math.max(redrawY, selection.y); if (redrawY - redrawX > 0) { internalRedrawRange(redrawX, redrawY - redrawX); } if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) { sendSelectionEvent(); } sendAccessibleTextCaretMoved(); } } } /** * Sets the selection. * <p> * The new selection may not be visible. Call showSelection to scroll the selection * into view. A negative length places the caret at the visual start of the selection. * </p> * * @param start offset of the first selected character * @param length number of characters to select * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) * </ul> */ public void setSelectionRange(int start, int length) { checkWidget(); int contentLength = getCharCount(); start = Math.max(0, Math.min(start, contentLength)); int end = start + length; if (end < 0) { length = -start; } else { if (end > contentLength) length = contentLength - start; } if (isLineDelimiter(start) || isLineDelimiter(start + length)) { // the start offset or end offset of the selection range is inside a // multi byte line delimiter. This is an illegal operation and an exception // is thrown. Fixes 1GDKK3R SWT.error(SWT.ERROR_INVALID_ARGUMENT); } setSelection(start, length, false, true); setCaretLocation(); } /** * Adds the specified style. * <p> * The new style overwrites existing styles for the specified range. * Existing style ranges are adjusted if they partially overlap with * the new style. To clear an individual style, call setStyleRange * with a StyleRange that has null attributes. * </p><p> * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p> * * @param range StyleRange object containing the style information. * Overwrites the old style in the given range. May be null to delete * all styles. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li> * </ul> */ public void setStyleRange(StyleRange range) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (range != null) { if (range.isUnstyled()) { setStyleRanges(range.start, range.length, null, null, false); } else { setStyleRanges(range.start, 0, null, new StyleRange[] { range }, false); } } else { setStyleRanges(0, 0, null, null, true); } } /** * Clears the styles in the range specified by <code>start</code> and * <code>length</code> and adds the new styles. * <p> * The ranges array contains start and length pairs. Each pair refers to * the corresponding style in the styles array. For example, the pair * that starts at ranges[n] with length ranges[n+1] uses the style * at styles[n/2]. The range fields within each StyleRange are ignored. * If ranges or styles is null, the specified range is cleared. * </p><p> * Note: It is expected that the same instance of a StyleRange will occur * multiple times within the styles array, reducing memory usage. * </p><p> * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p> * * @param start offset of first character where styles will be deleted * @param length length of the range to delete styles in * @param ranges the array of ranges. The ranges must not overlap and must be in order. * @param styles the array of StyleRanges. The range fields within the StyleRange are unused. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li> * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li> * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li> * <li>ERROR_INVALID_RANGE when a range overlaps</li> * </ul> * * @since 3.2 */ public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (ranges == null || styles == null) { setStyleRanges(start, length, null, null, false); } else { setStyleRanges(start, length, ranges, styles, false); } } /** * Sets styles to be used for rendering the widget content. * <p> * All styles in the widget will be replaced with the given set of ranges and styles. * The ranges array contains start and length pairs. Each pair refers to * the corresponding style in the styles array. For example, the pair * that starts at ranges[n] with length ranges[n+1] uses the style * at styles[n/2]. The range fields within each StyleRange are ignored. * If either argument is null, the styles are cleared. * </p><p> * Note: It is expected that the same instance of a StyleRange will occur * multiple times within the styles array, reducing memory usage. * </p><p> * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p> * * @param ranges the array of ranges. The ranges must not overlap and must be in order. * @param styles the array of StyleRanges. The range fields within the StyleRange are unused. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li> * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li> * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li> * <li>ERROR_INVALID_RANGE when a range overlaps</li> * </ul> * * @since 3.2 */ public void setStyleRanges(int[] ranges, StyleRange[] styles) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (ranges == null || styles == null) { setStyleRanges(0, 0, null, null, true); } else { setStyleRanges(0, 0, ranges, styles, true); } } void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) { int[] formerRanges = getRanges(start, length); StyleRange[] formerStyles = getStyleRanges(start, length); int charCount = content.getCharCount(); int end = start + length; 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; boolean variableHeight = false; 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); variableHeight |= styles[i].isVariableHeight(); lastOffset = rangeStart + rangeLength; } if (variableHeight) setVariableLineHeight(); } 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; } } 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); } SortedSet<Integer> modifiedLines = computeModifiedLines(formerRanges, formerStyles, ranges, styles); resetCache(modifiedLines); if (reset) { super.redraw(); } else { int lineStart = content.getLineAtOffset(Math.min(start, rangeStart)); int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd)); int partialTopIndex = getPartialTopIndex(); int partialBottomIndex = getPartialBottomIndex(); if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) { int top = 0; int bottom = clientAreaHeight; if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) { top = Math.max(0, getLinePixel(lineStart)); } if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) { bottom = getLinePixel(lineEnd + 1); } if (!isFixedLineHeight() && bottom != expectedBottom) { bottom = clientAreaHeight; } super.redraw(0, top, clientAreaWidth, bottom - top, false); } } int oldColumnX = columnX; setCaretLocation(); columnX = oldColumnX; doMouseLinkCursor(); } /** * * @param referenceRanges former ranges, sorted by order and without overlapping, typically returned {@link #getRanges(int, int)} * @param referenceStyles * @param newRanges former ranges, sorted by order and without overlappingz * @param newStyles * @return */ private SortedSet<Integer> computeModifiedLines(int[] referenceRanges, StyleRange[] referenceStyles, int[] newRanges, StyleRange[] newStyles) { if (referenceStyles == null) { referenceStyles = new StyleRange[0]; } if (referenceRanges == null) { referenceRanges = createRanges(referenceStyles); } if (newStyles == null) { newStyles = new StyleRange[0]; } if (newRanges == null) { newRanges = createRanges(newStyles); } if (referenceRanges.length != 2 * referenceStyles.length) { throw new IllegalArgumentException(); } if (newRanges.length != 2 * newStyles.length) { throw new IllegalArgumentException(); } SortedSet<Integer> res = new TreeSet<>(); int referenceRangeIndex = 0; int newRangeIndex = 0; StyleRange defaultStyle = new StyleRange(); defaultStyle.foreground = this.foreground; defaultStyle.background = this.background; defaultStyle.font = getFont(); int currentOffset = referenceRanges.length > 0 ? referenceRanges[0] : Integer.MAX_VALUE; if (newRanges.length > 0) { currentOffset = Math.min(currentOffset, newRanges[0]); } while (currentOffset < content.getCharCount() && (referenceRangeIndex < referenceStyles.length || newRangeIndex < newRanges.length)) { int nextMilestoneOffset = Integer.MAX_VALUE; // next new range start/end after current offset while (referenceRangeIndex < referenceStyles.length && endRangeOffset(referenceRanges, referenceRangeIndex) <= currentOffset) { referenceRangeIndex++; } StyleRange referenceStyleAtCurrentOffset = defaultStyle; if (isInRange(referenceRanges, referenceRangeIndex, currentOffset)) { // has styling referenceStyleAtCurrentOffset = referenceStyles[referenceRangeIndex]; nextMilestoneOffset = Math.min(nextMilestoneOffset, endRangeOffset(referenceRanges, referenceRangeIndex)); } else if (referenceRangeIndex + 1 < referenceStyles.length) { // no range, default styling nextMilestoneOffset = referenceRanges[2 * (referenceRangeIndex + 1)]; // 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 + 1 < newStyles.length) { nextMilestoneOffset = newRanges[2 * (newRangeIndex + 1)]; } if (!referenceStyleAtCurrentOffset.similarTo(newStyleAtCurrentOffset)) { int fromLine = getLineAtOffset(currentOffset); int toLine = getLineAtOffset(nextMilestoneOffset - 1); for (int line = fromLine; line <= toLine; line++) { res.add(line); } currentOffset = toLine + 1 < getLineCount() ? getOffsetAtLine(toLine + 1) : content.getCharCount(); } else { currentOffset = nextMilestoneOffset; } } return res; } private int[] createRanges(StyleRange[] referenceStyles) { int[] referenceRanges; referenceRanges = new int[2 * referenceStyles.length]; for (int i = 0; i < referenceStyles.length; i++) { referenceRanges[2 * i] = referenceStyles[i].start; referenceRanges[2 * i + 1] = referenceStyles[i].length; } return referenceRanges; } private boolean isInRange(int[] ranges, int styleIndex, int offset) { if (ranges == null || ranges.length == 0 || styleIndex < 0 || 2 * styleIndex + 1 > ranges.length) { return false; } int start = ranges[2 * styleIndex]; int length = ranges[2 * styleIndex + 1]; return offset >= start && offset < start + length; } /** * The offset on which the range ends (excluded) * @param ranges * @param styleIndex * @return */ private int endRangeOffset(int[] ranges, int styleIndex) { if (styleIndex < 0 || 2 * styleIndex > ranges.length) { throw new IllegalArgumentException(); } int start = ranges[2 * styleIndex]; int length = ranges[2 * styleIndex + 1]; return start + length; } /** * Sets styles to be used for rendering the widget content. All styles * in the widget will be replaced with the given set of styles. * <p> * Note: Because a StyleRange includes the start and length, the * same instance cannot occur multiple times in the array of styles. * If the same style attributes, such as font and color, occur in * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code> * can be used to share styles and reduce memory usage. * </p><p> * Should not be called if a LineStyleListener has been set since the * listener maintains the styles. * </p> * * @param ranges StyleRange objects containing the style information. * The ranges should not overlap. The style rendering is undefined if * the ranges do overlap. Must not be null. The styles need to be in order. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li> * <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li> * </ul> * * @see #setStyleRanges(int[], StyleRange[]) */ public void setStyleRanges(StyleRange[] ranges) { checkWidget(); if (isListening(ST.LineGetStyle)) return; if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); setStyleRanges(0, 0, null, ranges, true); } /** * Sets the tab width. * * @param tabs tab width measured in characters. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #setTabStops(int[]) */ public void setTabs(int tabs) { checkWidget(); tabLength = tabs; renderer.setFont(null, tabs); resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets the receiver's tab list. Each value in the tab list specifies * the space in points from the origin of the document to the respective * tab stop. The last tab stop width is repeated continuously. * * @param tabs the new tab list (or null) * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if a tab stop is negavite or less than the previous stop in the list</li> * </ul> * * @see StyledText#getTabStops() * * @since 3.6 */ public void setTabStops(int[] tabs) { checkWidget(); if (tabs != null) { int pos = 0; int[] newTabs = new int[tabs.length]; for (int i = 0; i < tabs.length; i++) { if (tabs[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT); newTabs[i] = pos = tabs[i]; } this.tabs = newTabs; } else { this.tabs = null; } resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } /** * Sets the widget content. * If the widget has the SWT.SINGLE style and "text" contains more than * one line, only the first line is rendered but the text is stored * unchanged. A subsequent call to getText will return the same text * that was set. * <p> * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE * style is used. * </p> * * @param text new widget content. Replaces existing content. Line styles * that were set using StyledText API are discarded. The * current selection is also discarded. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT when string is null</li> * </ul> */ public void setText(String text) { checkWidget(); if (text == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } Event event = new Event(); event.start = 0; event.end = getCharCount(); event.text = text; event.doit = true; notifyListeners(SWT.Verify, event); if (event.doit) { StyledTextEvent styledTextEvent = null; if (isListening(ST.ExtendedModify)) { styledTextEvent = new StyledTextEvent(content); styledTextEvent.start = event.start; styledTextEvent.end = event.start + event.text.length(); styledTextEvent.text = content.getTextRange(event.start, event.end - event.start); } content.setText(event.text); notifyListeners(SWT.Modify, event); if (styledTextEvent != null) { notifyListeners(ST.ExtendedModify, styledTextEvent); } } } /** * Sets the base text direction (a.k.a. "paragraph direction") of the receiver, * which must be one of the constants <code>SWT.LEFT_TO_RIGHT</code> or * <code>SWT.RIGHT_TO_LEFT</code>. * <p> * <code>setOrientation</code> would override this value with the text direction * that is consistent with the new orientation. * </p> * <p> * <b>Warning</b>: This API is currently only implemented on Windows. * It doesn't set the base text direction on GTK and Cocoa. * </p> * * @param textDirection the base text direction style * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see SWT#FLIP_TEXT_DIRECTION */ @Override public void setTextDirection(int textDirection) { checkWidget(); int oldStyle = getStyle(); super.setTextDirection(textDirection); if (isAutoDirection() || oldStyle != getStyle()) { resetBidiData(); } } /** * Sets the text limit to the specified number of characters. * <p> * The text limit specifies the amount of text that * the user can type into the widget. * </p> * * @param limit the new text limit. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @exception IllegalArgumentException <ul> * <li>ERROR_CANNOT_BE_ZERO when limit is 0</li> * </ul> */ public void setTextLimit(int limit) { checkWidget(); if (limit == 0) { SWT.error(SWT.ERROR_CANNOT_BE_ZERO); } textLimit = limit; } /** * Sets the top index. Do nothing if there is no text set. * <p> * The top index is the index of the line that is currently at the top * of the widget. The top index changes when the widget is scrolled. * Indexing starts from zero. * Note: The top index is reset to 0 when new text is set in the widget. * </p> * * @param topIndex new top index. Must be between 0 and * getLineCount() - fully visible lines per page. If no lines are fully * visible the maximum value is getLineCount() - 1. An out of range * index will be adjusted accordingly. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setTopIndex(int topIndex) { checkWidget(); if (getCharCount() == 0) { return; } int lineCount = content.getLineCount(), pixel; if (isFixedLineHeight()) { int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole())); if (topIndex < 0) { topIndex = 0; } else if (topIndex > lineCount - pageSize) { topIndex = lineCount - pageSize; } pixel = getLinePixel(topIndex); } else { topIndex = Math.max(0, Math.min(lineCount - 1, topIndex)); pixel = getLinePixel(topIndex); if (pixel > 0) { pixel = getAvailableHeightBellow(pixel); } else { pixel = getAvailableHeightAbove(pixel); } } scrollVertical(pixel, true); } /** * Sets the top margin. * * @param topMargin the top margin. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 3.5 */ public void setTopMargin(int topMargin) { checkWidget(); setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin); } /** * Sets the top SWT logical point offset. Do nothing if there is no text set. * <p> * The top point offset is the vertical SWT logical point offset of the widget. The * widget is scrolled so that the given SWT logical point position is at the top. * The top index is adjusted to the corresponding top line. * Note: The top point is reset to 0 when new text is set in the widget. * </p> * * @param pixel new top point offset. Must be between 0 and * (getLineCount() - visible lines per page) / getLineHeight()). An out * of range offset will be adjusted accordingly. * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 2.0 */ public void setTopPixel(int pixel) { checkWidget(); if (getCharCount() == 0) { return; } if (pixel < 0) pixel = 0; int lineCount = content.getLineCount(); int height = clientAreaHeight - topMargin - bottomMargin; int verticalOffset = getVerticalScrollOffset(); if (isFixedLineHeight()) { int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height); if (pixel > maxTopPixel) pixel = maxTopPixel; pixel -= verticalOffset; } else { pixel -= verticalOffset; if (pixel > 0) { pixel = getAvailableHeightBellow(pixel); } } scrollVertical(pixel, true); } /** * Sets whether the widget wraps lines. * <p> * This overrides the creation style bit SWT.WRAP. * </p> * * @param wrap true=widget wraps lines, false=widget does not wrap lines * @since 2.0 */ public void setWordWrap(boolean wrap) { checkWidget(); if ((getStyle() & SWT.SINGLE) != 0) return; if (wordWrap == wrap) return; if (wordWrap && blockSelection) setBlockSelection(false); wordWrap = wrap; setVariableLineHeight(); resetCache(0, content.getLineCount()); horizontalScrollOffset = 0; ScrollBar horizontalBar = getHorizontalBar(); if (horizontalBar != null) { horizontalBar.setVisible(!wordWrap); } setScrollBars(true); setCaretLocation(); super.redraw(); } /** * Sets the wrap line indentation of the widget. * <p> * It is the amount of blank space, in points, at the beginning of each wrapped line. * When a line wraps in several lines all the lines but the first one is indented * by this amount. * </p> * * @param wrapIndent the new wrap indent * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #setLineWrapIndent(int, int, int) * * @since 3.6 */ public void setWrapIndent(int wrapIndent) { checkWidget(); if (this.wrapIndent == wrapIndent || wrapIndent < 0) return; this.wrapIndent = wrapIndent; resetCache(0, content.getLineCount()); setCaretLocation(); super.redraw(); } boolean showLocation(Rectangle rect, boolean scrollPage) { boolean scrolled = false; if (rect.y < topMargin) { scrolled = scrollVertical(rect.y - topMargin, true); } else if (rect.y + rect.height > clientAreaHeight - bottomMargin) { if (clientAreaHeight - topMargin - bottomMargin <= 0) { scrolled = scrollVertical(rect.y - topMargin, true); } else { scrolled = scrollVertical(rect.y + rect.height - (clientAreaHeight - bottomMargin), true); } } int width = clientAreaWidth - rightMargin - leftMargin; if (width > 0) { int minScroll = scrollPage ? width / 4 : 0; if (rect.x < leftMargin) { int scrollWidth = Math.max(leftMargin - rect.x, minScroll); int maxScroll = horizontalScrollOffset; scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true); } else if (rect.x + rect.width > (clientAreaWidth - rightMargin)) { int scrollWidth = Math.max(rect.x + rect.width - (clientAreaWidth - rightMargin), minScroll); int maxScroll = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth; scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true); } } return scrolled; } /** * Sets the caret location and scrolls the caret offset into view. */ void showCaret() { Rectangle bounds = getBoundsAtOffset(caretOffset); if (!showLocation(bounds, true)) { setCaretLocation(); } } /** * Scrolls the selection into view. * <p> * The end of the selection will be scrolled into view. * Note that if a right-to-left selection exists, the end of the selection is * the visual beginning of the selection (i.e., where the caret is located). * </p> * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void showSelection() { checkWidget(); // is selection from right-to-left? boolean rightToLeft = caretOffset == selection.x; int startOffset, endOffset; if (rightToLeft) { startOffset = selection.y; endOffset = selection.x; } else { startOffset = selection.x; endOffset = selection.y; } Rectangle startBounds = getBoundsAtOffset(startOffset); Rectangle endBounds = getBoundsAtOffset(endOffset); // can the selection be fully displayed within the widget's visible width? int w = clientAreaWidth - leftMargin - rightMargin; boolean selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w; if (selectionFits) { // show as much of the selection as possible by first showing // the start of the selection if (showLocation(startBounds, false)) { // endX value could change if showing startX caused a scroll to occur endBounds = getBoundsAtOffset(endOffset); } // the character at endOffset is not part of the selection endBounds.width = endOffset == caretOffset ? getCaretWidth() : 0; showLocation(endBounds, false); } else { // just show the end of the selection since the selection start // will not be visible showLocation(endBounds, true); } } void updateCaretVisibility() { Caret caret = getCaret(); if (caret != null) { if (blockSelection && blockXLocation != -1) { caret.setVisible(false); } else { Point location = caret.getLocation(); Point size = caret.getSize(); boolean visible = topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin && leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin; caret.setVisible(visible); } } } /** * Updates the selection and caret position depending on the text change. * <p> * If the selection intersects with the replaced text, the selection is * reset and the caret moved to the end of the new text. * If the selection is behind the replaced text it is moved so that the * same text remains selected. If the selection is before the replaced text * it is left unchanged. * </p> * * @param startOffset offset of the text change * @param replacedLength length of text being replaced * @param newLength length of new text */ void updateSelection(int startOffset, int replacedLength, int newLength) { if (selection.y <= startOffset) { // selection ends before text change if (isWordWrap()) setCaretLocation(); return; } if (selection.x < startOffset) { // clear selection fragment before text change internalRedrawRange(selection.x, startOffset - selection.x); } if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) { // clear selection fragment after text change. // do this only when the selection is actually affected by the // change. Selection is only affected if it intersects the change (1GDY217). int netNewLength = newLength - replacedLength; int redrawStart = startOffset + newLength; internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart); } if (selection.y > startOffset && selection.x < startOffset + replacedLength) { // selection intersects replaced text. set caret behind text change setSelection(startOffset + newLength, 0, true, false); } else { // move selection to keep same text selected setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true, false); } setCaretLocation(); } }