]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/custom/StyledText.java
61453a505e469f397093de7fda28f4d1992f81aa
[simantics/platform.git] / bundles / org.eclipse.swt.win32.win32.x86_64 / src / org / eclipse / swt / custom / StyledText.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Andrey Loskutov <loskutov@gmx.de> - bug 488172
14  *     Stefan Xenos (Google) - bug 487254 - StyledText.getTopIndex() can return negative values
15  *     Angelo Zerr <angelo.zerr@gmail.com> - Customize different line spacing of StyledText - Bug 522020
16  *     Karsten Thoms <thoms@itemis.de> - bug 528746 add getOffsetAtPoint(Point)
17  *******************************************************************************/
18 package org.eclipse.swt.custom;
19
20
21 import java.util.*;
22 import java.util.List;
23
24 import org.eclipse.swt.*;
25 import org.eclipse.swt.accessibility.*;
26 import org.eclipse.swt.dnd.*;
27 import org.eclipse.swt.events.*;
28 import org.eclipse.swt.graphics.*;
29 import org.eclipse.swt.internal.*;
30 import org.eclipse.swt.printing.*;
31 import org.eclipse.swt.widgets.*;
32
33 /**
34  * A StyledText is an editable user interface object that displays lines
35  * of text.  The following style attributes can be defined for the text:
36  * <ul>
37  * <li>foreground color
38  * <li>background color
39  * <li>font style (bold, italic, bold-italic, regular)
40  * <li>underline
41  * <li>strikeout
42  * </ul>
43  * <p>
44  * In addition to text style attributes, the background color of a line may
45  * be specified.
46  * </p><p>
47  * There are two ways to use this widget when specifying text style information.
48  * You may use the API that is defined for StyledText or you may define your own
49  * LineStyleListener.  If you define your own listener, you will be responsible
50  * for maintaining the text style information for the widget.  IMPORTANT: You may
51  * not define your own listener and use the StyledText API.  The following
52  * StyledText API is not supported if you have defined a LineStyleListener:</p>
53  * <ul>
54  * <li>getStyleRangeAtOffset(int)
55  * <li>getStyleRanges()
56  * <li>replaceStyleRanges(int,int,StyleRange[])
57  * <li>setStyleRange(StyleRange)
58  * <li>setStyleRanges(StyleRange[])
59  * </ul>
60  * <p>
61  * There are two ways to use this widget when specifying line background colors.
62  * You may use the API that is defined for StyledText or you may define your own
63  * LineBackgroundListener.  If you define your own listener, you will be responsible
64  * for maintaining the line background color information for the widget.
65  * IMPORTANT: You may not define your own listener and use the StyledText API.
66  * The following StyledText API is not supported if you have defined a
67  * LineBackgroundListener:</p>
68  * <ul>
69  * <li>getLineBackground(int)
70  * <li>setLineBackground(int,int,Color)
71  * </ul>
72  * <p>
73  * The content implementation for this widget may also be user-defined.  To do so,
74  * you must implement the StyledTextContent interface and use the StyledText API
75  * setContent(StyledTextContent) to initialize the widget.
76  * </p>
77  * <dl>
78  * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
79  * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey, OrientationChange
80  * </dl>
81  * <p>
82  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
83  * </p>
84  *
85  * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a>
86  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a>
87  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
88  * @noextend This class is not intended to be subclassed by clients.
89  */
90 public class StyledText extends Canvas {
91         static final char TAB = '\t';
92         static final String PlatformLineDelimiter = System.getProperty("line.separator");
93         static final int BIDI_CARET_WIDTH = 3;
94         static final int DEFAULT_WIDTH  = 64;
95         static final int DEFAULT_HEIGHT = 64;
96         static final int V_SCROLL_RATE = 50;
97         static final int H_SCROLL_RATE = 10;
98         static final int PREVIOUS_OFFSET_TRAILING = 0;
99         static final int OFFSET_LEADING = 1;
100
101         static final String STYLEDTEXT_KEY = "org.eclipse.swt.internal.cocoa.styledtext"; //$NON-NLS-1$
102
103         Color selectionBackground;      // selection background color
104         Color selectionForeground;      // selection foreground color
105         StyledTextContent content;                      // native content (default or user specified)
106         StyledTextRenderer renderer;
107         Listener listener;
108         TextChangeListener textChangeListener;  // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
109         int verticalScrollOffset = 0;           // pixel based
110         int horizontalScrollOffset = 0;         // pixel based
111         boolean alwaysShowScroll = true;
112         int ignoreResize = 0;
113         int topIndex = 0;                                       // top visible line
114         int topIndexY;
115         int clientAreaHeight = 0;                       // the client area height. Needed to calculate content width for new visible lines during Resize callback
116         int clientAreaWidth = 0;                        // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated
117         int tabLength = 4;                                      // number of characters in a tab
118         int [] tabs;
119         int leftMargin;
120         int topMargin;
121         int rightMargin;
122         int bottomMargin;
123         Color marginColor;
124         int columnX;                                            // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
125         int caretOffset;
126         int caretAlignment;
127         Point selection = new Point(0, 0);      // x and y are start and end caret offsets of selection (x <= y)
128         Point clipboardSelection;           // x and y are start and end caret offsets of previous selection
129         int selectionAnchor;                            // position of selection anchor. 0 based offset from beginning of text
130         Point doubleClickSelection;                     // selection after last mouse double click
131         boolean editable = true;
132         boolean wordWrap = false;                       // text is wrapped automatically
133         boolean visualWrap = false;             // process line breaks inside logical lines (inserted by BidiSegmentEvent)
134         boolean hasStyleWithVariableHeight = false;
135         boolean hasVerticalIndent = false;
136         boolean doubleClickEnabled = true;      // see getDoubleClickEnabled
137         boolean overwrite = false;                      // insert/overwrite edit mode
138         int textLimit = -1;                                     // limits the number of characters the user can type in the widget. Unlimited by default.
139         Map<Integer, Integer> keyActionMap = new HashMap<>();
140         Color background = null;                        // workaround for bug 4791
141         Color foreground = null;                        //
142         /** True if a non-default background color is set */
143         boolean customBackground;
144         /** True if a non-default foreground color is set */
145         boolean customForeground;
146         /** False iff the widget is disabled */
147         boolean enabled = true;
148         /** True iff the widget is in the midst of being enabled or disabled */
149         boolean insideSetEnableCall;
150         Clipboard clipboard;
151         int clickCount;
152         int autoScrollDirection = SWT.NULL;     // the direction of autoscrolling (up, down, right, left)
153         int autoScrollDistance = 0;
154         int lastTextChangeStart;                        // cache data of the
155         int lastTextChangeNewLineCount;         // last text changing
156         int lastTextChangeNewCharCount;         // event for use in the
157         int lastTextChangeReplaceLineCount;     // text changed handler
158         int lastTextChangeReplaceCharCount;
159         int lastCharCount = 0;
160         int lastLineBottom;                                     // the bottom pixel of the last line been replaced
161         boolean bidiColoring = false;           // apply the BIDI algorithm on text segments of the same color
162         Image leftCaretBitmap = null;
163         Image rightCaretBitmap = null;
164         int caretDirection = SWT.NULL;
165         int caretWidth = 0;
166         Caret defaultCaret = null;
167         boolean updateCaretDirection = true;
168         boolean dragDetect = true;
169         IME ime;
170         Cursor cursor;
171         int alignment;
172         boolean justify;
173         int indent, wrapIndent;
174         int lineSpacing;
175         int alignmentMargin;
176         int newOrientation = SWT.NONE;
177         int accCaretOffset;
178         Accessible acc;
179         AccessibleControlAdapter accControlAdapter;
180         AccessibleAttributeAdapter accAttributeAdapter;
181         AccessibleEditableTextListener accEditableTextListener;
182         AccessibleTextExtendedAdapter accTextExtendedAdapter;
183         AccessibleAdapter accAdapter;
184         MouseNavigator mouseNavigator;
185         boolean middleClickPressed;
186
187         //block selection
188         boolean blockSelection;
189         int blockXAnchor = -1, blockYAnchor = -1;
190         int blockXLocation = -1, blockYLocation = -1;
191
192         final static boolean IS_MAC, IS_GTK;
193         static {
194                 String platform = SWT.getPlatform();
195                 IS_MAC = "cocoa".equals(platform);
196                 IS_GTK = "gtk".equals(platform);
197         }
198
199         /**
200          * The Printing class implements printing of a range of text.
201          * An instance of <code>Printing</code> is returned in the
202          * StyledText#print(Printer) API. The run() method may be
203          * invoked from any thread.
204          */
205         static class Printing implements Runnable {
206                 final static int LEFT = 0;                                              // left aligned header/footer segment
207                 final static int CENTER = 1;                                    // centered header/footer segment
208                 final static int RIGHT = 2;                                             // right aligned header/footer segment
209
210                 Printer printer;
211                 StyledTextRenderer printerRenderer;
212                 StyledTextPrintOptions printOptions;
213                 Rectangle clientArea;
214                 FontData fontData;
215                 Font printerFont;
216                 Map<Resource, Resource> resources;
217                 int tabLength;
218                 GC gc;                                                                                  // printer GC
219                 int pageWidth;                                                                  // width of a printer page in pixels
220                 int startPage;                                                                  // first page to print
221                 int endPage;                                                                    // last page to print
222                 int scope;                                                                              // scope of print job
223                 int startLine;                                                                  // first (wrapped) line to print
224                 int endLine;                                                                    // last (wrapped) line to print
225                 boolean singleLine;                                                             // widget single line mode
226                 Point selection = null;                                 // selected text
227                 boolean mirrored;                                               // indicates the printing gc should be mirrored
228                 int lineSpacing;
229                 int printMargin;
230
231         /**
232          * Creates an instance of <code>Printing</code>.
233          * Copies the widget content and rendering data that needs
234          * to be requested from listeners.
235          *
236          * @param parent StyledText widget to print.
237          * @param printer printer device to print on.
238          * @param printOptions print options
239          */
240         Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) {
241                 this.printer = printer;
242                 this.printOptions = printOptions;
243                 this.mirrored = (styledText.getStyle() & SWT.MIRRORED) != 0;
244                 singleLine = styledText.isSingleLine();
245                 startPage = 1;
246                 endPage = Integer.MAX_VALUE;
247                 PrinterData data = printer.getPrinterData();
248                 scope = data.scope;
249                 if (scope == PrinterData.PAGE_RANGE) {
250                         startPage = data.startPage;
251                         endPage = data.endPage;
252                         if (endPage < startPage) {
253                                 int temp = endPage;
254                                 endPage = startPage;
255                                 startPage = temp;
256                         }
257                 } else if (scope == PrinterData.SELECTION) {
258                         selection = styledText.getSelectionRange();
259                 }
260                 printerRenderer = new StyledTextRenderer(printer, null);
261                 printerRenderer.setContent(copyContent(styledText.getContent()));
262                 cacheLineData(styledText);
263         }
264         /**
265          * Caches all line data that needs to be requested from a listener.
266          *
267          * @param printerContent <code>StyledTextContent</code> to request
268          *      line data for.
269          */
270         void cacheLineData(StyledText styledText) {
271                 StyledTextRenderer renderer = styledText.renderer;
272                 renderer.copyInto(printerRenderer);
273                 fontData = styledText.getFont().getFontData()[0];
274                 tabLength = styledText.tabLength;
275                 int lineCount = printerRenderer.lineCount;
276                 if (styledText.isListening(ST.LineGetBackground) || (styledText.isListening(ST.LineGetSegments)) || styledText.isListening(ST.LineGetStyle)) {
277                         StyledTextContent content = printerRenderer.content;
278                         for (int i = 0; i < lineCount; i++) {
279                                 String line = content.getLine(i);
280                                 int lineOffset = content.getOffsetAtLine(i);
281                                 StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
282                                 if (event != null && event.lineBackground != null) {
283                                         printerRenderer.setLineBackground(i, 1, event.lineBackground);
284                                 }
285                                 event = styledText.getBidiSegments(lineOffset, line);
286                                 if (event != null) {
287                                         printerRenderer.setLineSegments(i, 1, event.segments);
288                                         printerRenderer.setLineSegmentChars(i, 1, event.segmentsChars);
289                                 }
290                                 event = styledText.getLineStyleData(lineOffset, line);
291                                 if (event != null) {
292                                         printerRenderer.setLineIndent(i, 1, event.indent);
293                                         printerRenderer.setLineAlignment(i, 1, event.alignment);
294                                         printerRenderer.setLineJustify(i, 1, event.justify);
295                                         printerRenderer.setLineBullet(i, 1, event.bullet);
296                                         StyleRange[] styles = event.styles;
297                                         if (styles != null && styles.length > 0) {
298                                                 printerRenderer.setStyleRanges(event.ranges, styles);
299                                         }
300                                 }
301                         }
302                 }
303                 Point screenDPI = styledText.getDisplay().getDPI();
304                 Point printerDPI = printer.getDPI();
305                 resources = new HashMap<> ();
306                 for (int i = 0; i < lineCount; i++) {
307                         Color color = printerRenderer.getLineBackground(i, null);
308                         if (color != null) {
309                                 if (printOptions.printLineBackground) {
310                                         Color printerColor = (Color)resources.get(color);
311                                         if (printerColor == null) {
312                                                 printerColor = new Color (printer, color.getRGB());
313                                                 resources.put(color, printerColor);
314                                         }
315                                         printerRenderer.setLineBackground(i, 1, printerColor);
316                                 } else {
317                                         printerRenderer.setLineBackground(i, 1, null);
318                                 }
319                         }
320                         int indent = printerRenderer.getLineIndent(i, 0);
321                         if (indent != 0) {
322                                 printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
323                         }
324                 }
325                 StyleRange[] styles = printerRenderer.styles;
326                 for (int i = 0; i < printerRenderer.styleCount; i++) {
327                         StyleRange style = styles[i];
328                         Font font = style.font;
329                         if (style.font != null) {
330                                 Font printerFont = (Font)resources.get(font);
331                                 if (printerFont == null) {
332                                         printerFont = new Font (printer, font.getFontData());
333                                         resources.put(font, printerFont);
334                                 }
335                                 style.font = printerFont;
336                         }
337                         Color color = style.foreground;
338                         if (color != null) {
339                                 Color printerColor = (Color)resources.get(color);
340                                 if (printOptions.printTextForeground) {
341                                         if (printerColor == null) {
342                                                 printerColor = new Color (printer, color.getRGB());
343                                                 resources.put(color, printerColor);
344                                         }
345                                         style.foreground = printerColor;
346                                 } else {
347                                         style.foreground = null;
348                                 }
349                         }
350                         color = style.background;
351                         if (color != null) {
352                                 Color printerColor = (Color)resources.get(color);
353                                 if (printOptions.printTextBackground) {
354                                         if (printerColor == null) {
355                                                 printerColor = new Color (printer, color.getRGB());
356                                                 resources.put(color, printerColor);
357                                         }
358                                         style.background = printerColor;
359                                 } else {
360                                         style.background = null;
361                                 }
362                         }
363                         if (!printOptions.printTextFontStyle) {
364                                 style.fontStyle = SWT.NORMAL;
365                         }
366                         style.rise = style.rise * printerDPI.y / screenDPI.y;
367                         GlyphMetrics metrics = style.metrics;
368                         if (metrics != null) {
369                                 metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
370                                 metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
371                                 metrics.width = metrics.width * printerDPI.x / screenDPI.x;
372                         }
373                 }
374                 lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
375                 if (printOptions.printLineNumbers) {
376                         printMargin = 3 * printerDPI.x / screenDPI.x;
377                 }
378         }
379         /**
380          * Copies the text of the specified <code>StyledTextContent</code>.
381          *
382          * @param original the <code>StyledTextContent</code> to copy.
383          */
384         StyledTextContent copyContent(StyledTextContent original) {
385                 StyledTextContent printerContent = new DefaultContent();
386                 int insertOffset = 0;
387                 for (int i = 0; i < original.getLineCount(); i++) {
388                         int insertEndOffset;
389                         if (i < original.getLineCount() - 1) {
390                                 insertEndOffset = original.getOffsetAtLine(i + 1);
391                         } else {
392                                 insertEndOffset = original.getCharCount();
393                         }
394                         printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset));
395                         insertOffset = insertEndOffset;
396                 }
397                 return printerContent;
398         }
399         /**
400          * Disposes of the resources and the <code>PrintRenderer</code>.
401          */
402         void dispose() {
403                 if (gc != null) {
404                         gc.dispose();
405                         gc = null;
406                 }
407                 if (resources != null) {
408                         for (Resource resource : resources.values()) {
409                                 resource.dispose();
410                         }
411                         resources = null;
412                 }
413                 if (printerFont != null) {
414                         printerFont.dispose();
415                         printerFont = null;
416                 }
417                 if (printerRenderer != null) {
418                         printerRenderer.dispose();
419                         printerRenderer = null;
420                 }
421         }
422         void init() {
423                 Rectangle trim = printer.computeTrim(0, 0, 0, 0);
424                 Point dpi = printer.getDPI();
425
426                 printerFont = new Font(printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL);
427                 clientArea = printer.getClientArea();
428                 pageWidth = clientArea.width;
429                 // one inch margin around text
430                 clientArea.x = dpi.x + trim.x;
431                 clientArea.y = dpi.y + trim.y;
432                 clientArea.width -= (clientArea.x + trim.width);
433                 clientArea.height -= (clientArea.y + trim.height);
434
435                 int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
436                 gc = new GC(printer, style);
437                 gc.setFont(printerFont);
438                 printerRenderer.setFont(printerFont, tabLength);
439                 int lineHeight = printerRenderer.getLineHeight();
440                 if (printOptions.header != null) {
441                         clientArea.y += lineHeight * 2;
442                         clientArea.height -= lineHeight * 2;
443                 }
444                 if (printOptions.footer != null) {
445                         clientArea.height -= lineHeight * 2;
446                 }
447
448                 // TODO not wrapped
449                 StyledTextContent content = printerRenderer.content;
450                 startLine = 0;
451                 endLine = singleLine ? 0 : content.getLineCount() - 1;
452                 if (scope == PrinterData.PAGE_RANGE) {
453                         int pageSize = clientArea.height / lineHeight;//WRONG
454                         startLine = (startPage - 1) * pageSize;
455                 } else if (scope == PrinterData.SELECTION) {
456                         startLine = content.getLineAtOffset(selection.x);
457                         if (selection.y > 0) {
458                                 endLine = content.getLineAtOffset(selection.x + selection.y - 1);
459                         } else {
460                                 endLine = startLine - 1;
461                         }
462                 }
463         }
464         /**
465          * Prints the lines in the specified page range.
466          */
467         void print() {
468                 Color background = gc.getBackground();
469                 Color foreground = gc.getForeground();
470                 int paintY = clientArea.y;
471                 int paintX = clientArea.x;
472                 int width = clientArea.width;
473                 int page = startPage;
474                 int pageBottom = clientArea.y + clientArea.height;
475                 int orientation =  gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT);
476                 TextLayout printLayout = null;
477                 if (printOptions.printLineNumbers || printOptions.header != null || printOptions.footer != null) {
478                         printLayout = new TextLayout(printer);
479                         printLayout.setFont(printerFont);
480                 }
481                 if (printOptions.printLineNumbers) {
482                         int numberingWidth = 0;
483                         int count = endLine - startLine + 1;
484                         String[] lineLabels = printOptions.lineLabels;
485                         if (lineLabels != null) {
486                                 for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
487                                         if (lineLabels[i] != null) {
488                                                 printLayout.setText(lineLabels[i]);
489                                                 int lineWidth = printLayout.getBounds().width;
490                                                 numberingWidth = Math.max(numberingWidth, lineWidth);
491                                         }
492                                 }
493                         } else {
494                                 StringBuilder buffer = new StringBuilder("0");
495                                 while ((count /= 10) > 0) buffer.append("0");
496                                 printLayout.setText(buffer.toString());
497                                 numberingWidth = printLayout.getBounds().width;
498                         }
499                         numberingWidth += printMargin;
500                         if (numberingWidth > width) numberingWidth = width;
501                         paintX += numberingWidth;
502                         width -= numberingWidth;
503                 }
504                 for (int i = startLine; i <= endLine && page <= endPage; i++) {
505                         if (paintY == clientArea.y) {
506                                 printer.startPage();
507                                 printDecoration(page, true, printLayout);
508                         }
509                         TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
510                         Color lineBackground = printerRenderer.getLineBackground(i, background);
511                         int paragraphBottom = paintY + layout.getBounds().height;
512                         if (paragraphBottom <= pageBottom) {
513                                 //normal case, the whole paragraph fits in the current page
514                                 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
515                                 paintY = paragraphBottom;
516                         } else {
517                                 int lineCount = layout.getLineCount();
518                                 while (paragraphBottom > pageBottom && lineCount > 0) {
519                                         lineCount--;
520                                         paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
521                                 }
522                                 if (lineCount == 0) {
523                                         //the whole paragraph goes to the next page
524                                         printDecoration(page, false, printLayout);
525                                         printer.endPage();
526                                         page++;
527                                         if (page <= endPage) {
528                                                 printer.startPage();
529                                                 printDecoration(page, true, printLayout);
530                                                 paintY = clientArea.y;
531                                                 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
532                                                 paintY += layout.getBounds().height;
533                                         }
534                                 } else {
535                                         //draw paragraph top in the current page and paragraph bottom in the next
536                                         int height = paragraphBottom - paintY;
537                                         gc.setClipping(clientArea.x, paintY, clientArea.width, height);
538                                         printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
539                                         gc.setClipping((Rectangle)null);
540                                         printDecoration(page, false, printLayout);
541                                         printer.endPage();
542                                         page++;
543                                         if (page <= endPage) {
544                                                 printer.startPage();
545                                                 printDecoration(page, true, printLayout);
546                                                 paintY = clientArea.y - height;
547                                                 int layoutHeight = layout.getBounds().height;
548                                                 gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
549                                                 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
550                                                 gc.setClipping((Rectangle)null);
551                                                 paintY += layoutHeight;
552                                         }
553                                 }
554                         }
555                         printerRenderer.disposeTextLayout(layout);
556                 }
557                 if (page <= endPage && paintY > clientArea.y) {
558                         // close partial page
559                         printDecoration(page, false, printLayout);
560                         printer.endPage();
561                 }
562                 if (printLayout != null) printLayout.dispose();
563         }
564         /**
565          * Print header or footer decorations.
566          *
567          * @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
568          * @param header true = print the header, false = print the footer
569          */
570         void printDecoration(int page, boolean header, TextLayout layout) {
571                 String text = header ? printOptions.header : printOptions.footer;
572                 if (text == null) return;
573                 int lastSegmentIndex = 0;
574                 for (int i = 0; i < 3; i++) {
575                         int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
576                         String segment;
577                         if (segmentIndex == -1) {
578                                 segment = text.substring(lastSegmentIndex);
579                                 printDecorationSegment(segment, i, page, header, layout);
580                                 break;
581                         } else {
582                                 segment = text.substring(lastSegmentIndex, segmentIndex);
583                                 printDecorationSegment(segment, i, page, header, layout);
584                                 lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length();
585                         }
586                 }
587         }
588         /**
589          * Print one segment of a header or footer decoration.
590          * Headers and footers have three different segments.
591          * One each for left aligned, centered, and right aligned text.
592          *
593          * @param segment decoration segment to print
594          * @param alignment alignment of the segment. 0=left, 1=center, 2=right
595          * @param page page number to print, if specified in the decoration segment.
596          * @param header true = print the header, false = print the footer
597          */
598         void printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout) {
599                 int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
600                 if (pageIndex != -1) {
601                         int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
602                         StringBuilder buffer = new StringBuilder(segment.substring (0, pageIndex));
603                         buffer.append (page);
604                         buffer.append (segment.substring(pageIndex + pageTagLength));
605                         segment = buffer.toString();
606                 }
607                 if (segment.length() > 0) {
608                         layout.setText(segment);
609                         int segmentWidth = layout.getBounds().width;
610                         int segmentHeight = printerRenderer.getLineHeight();
611                         int drawX = 0, drawY;
612                         if (alignment == LEFT) {
613                                 drawX = clientArea.x;
614                         } else if (alignment == CENTER) {
615                                 drawX = (pageWidth - segmentWidth) / 2;
616                         } else if (alignment == RIGHT) {
617                                 drawX = clientArea.x + clientArea.width - segmentWidth;
618                         }
619                         if (header) {
620                                 drawY = clientArea.y - segmentHeight * 2;
621                         } else {
622                                 drawY = clientArea.y + clientArea.height + segmentHeight;
623                         }
624                         layout.draw(gc, drawX, drawY);
625                 }
626         }
627         void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) {
628                 if (background != null) {
629                         Rectangle rect = layout.getBounds();
630                         gc.setBackground(background);
631                         gc.fillRectangle(x, y, rect.width, rect.height);
632
633 //                      int lineCount = layout.getLineCount();
634 //                      for (int i = 0; i < lineCount; i++) {
635 //                              Rectangle rect = layout.getLineBounds(i);
636 //                              rect.x += paintX;
637 //                              rect.y += paintY + layout.getSpacing();
638 //                              rect.width = width;//layout bounds
639 //                              gc.fillRectangle(rect);
640 //                      }
641                 }
642                 if (printOptions.printLineNumbers) {
643                         FontMetrics metrics = layout.getLineMetrics(0);
644                         printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
645                         printLayout.setDescent(metrics.getDescent());
646                         String[] lineLabels = printOptions.lineLabels;
647                         if (lineLabels != null) {
648                                 if (0 <= index && index < lineLabels.length && lineLabels[index] != null) {
649                                         printLayout.setText(lineLabels[index]);
650                                 } else {
651                                         printLayout.setText("");
652                                 }
653                         } else {
654                                 printLayout.setText(String.valueOf(index));
655                         }
656                         int paintX = x - printMargin - printLayout.getBounds().width;
657                         printLayout.draw(gc, paintX, y);
658                         printLayout.setAscent(-1);
659                         printLayout.setDescent(-1);
660                 }
661                 gc.setForeground(foreground);
662                 layout.draw(gc, x, y);
663         }
664         /**
665          * Starts a print job and prints the pages specified in the constructor.
666          */
667         @Override
668         public void run() {
669                 String jobName = printOptions.jobName;
670                 if (jobName == null) {
671                         jobName = "Printing";
672                 }
673                 if (printer.startJob(jobName)) {
674                         init();
675                         print();
676                         dispose();
677                         printer.endJob();
678                 }
679         }
680         }
681         /**
682          * The <code>RTFWriter</code> class is used to write widget content as
683          * rich text. The implementation complies with the RTF specification
684          * version 1.5.
685          * <p>
686          * toString() is guaranteed to return a valid RTF string only after
687          * close() has been called.
688          * </p><p>
689          * Whole and partial lines and line breaks can be written. Lines will be
690          * formatted using the styles queried from the LineStyleListener, if
691          * set, or those set directly in the widget. All styles are applied to
692          * the RTF stream like they are rendered by the widget. In addition, the
693          * widget font name and size is used for the whole text.
694          * </p>
695          */
696         class RTFWriter extends TextWriter {
697                 static final int DEFAULT_FOREGROUND = 0;
698                 static final int DEFAULT_BACKGROUND = 1;
699                 List<Color> colorTable;
700                 List<Font> fontTable;
701
702         /**
703          * Creates a RTF writer that writes content starting at offset "start"
704          * in the document.  <code>start</code> and <code>length</code>can be set to specify partial
705          * lines.
706          *
707          * @param start start offset of content to write, 0 based from
708          *      beginning of document
709          * @param length length of content to write
710          */
711         public RTFWriter(int start, int length) {
712                 super(start, length);
713                 colorTable = new ArrayList<>();
714                 fontTable = new ArrayList<>();
715                 colorTable.add(getForeground());
716                 colorTable.add(getBackground());
717                 fontTable.add(getFont());
718         }
719         /**
720          * Closes the RTF writer. Once closed no more content can be written.
721          * <b>NOTE:</b>  <code>toString()</code> does not return a valid RTF string until
722          * <code>close()</code> has been called.
723          */
724         @Override
725         public void close() {
726                 if (!isClosed()) {
727                         writeHeader();
728                         write("\n}}\0");
729                         super.close();
730                 }
731         }
732         /**
733          * Returns the index of the specified color in the RTF color table.
734          *
735          * @param color the color
736          * @param defaultIndex return value if color is null
737          * @return the index of the specified color in the RTF color table
738          *      or "defaultIndex" if "color" is null.
739          */
740         int getColorIndex(Color color, int defaultIndex) {
741                 if (color == null) return defaultIndex;
742                 int index = colorTable.indexOf(color);
743                 if (index == -1) {
744                         index = colorTable.size();
745                         colorTable.add(color);
746                 }
747                 return index;
748         }
749         /**
750          * Returns the index of the specified color in the RTF color table.
751          *
752          * @param color the color
753          * @param defaultIndex return value if color is null
754          * @return the index of the specified color in the RTF color table
755          *      or "defaultIndex" if "color" is null.
756          */
757         int getFontIndex(Font font) {
758                 int index = fontTable.indexOf(font);
759                 if (index == -1) {
760                         index = fontTable.size();
761                         fontTable.add(font);
762                 }
763                 return index;
764         }
765         /**
766          * Appends the specified segment of "string" to the RTF data.
767          * Copy from <code>start</code> up to, but excluding, <code>end</code>.
768          *
769          * @param string string to copy a segment from. Must not contain
770          *      line breaks. Line breaks should be written using writeLineDelimiter()
771          * @param start start offset of segment. 0 based.
772          * @param end end offset of segment
773          */
774         void write(String string, int start, int end) {
775                 for (int index = start; index < end; index++) {
776                         char ch = string.charAt(index);
777                         if (ch > 0x7F) {
778                                 // write the sub string from the last escaped character
779                                 // to the current one. Fixes bug 21698.
780                                 if (index > start) {
781                                         write(string.substring(start, index));
782                                 }
783                                 write("\\u");
784                                 write(Integer.toString((short) ch));
785                                 write('?');                                             // ANSI representation (1 byte long, \\uc1)
786                                 start = index + 1;
787                         } else if (ch == '}' || ch == '{' || ch == '\\') {
788                                 // write the sub string from the last escaped character
789                                 // to the current one. Fixes bug 21698.
790                                 if (index > start) {
791                                         write(string.substring(start, index));
792                                 }
793                                 write('\\');
794                                 write(ch);
795                                 start = index + 1;
796                         }
797                 }
798                 // write from the last escaped character to the end.
799                 // Fixes bug 21698.
800                 if (start < end) {
801                         write(string.substring(start, end));
802                 }
803         }
804         /**
805          * Writes the RTF header including font table and color table.
806          */
807         void writeHeader() {
808                 StringBuilder header = new StringBuilder();
809                 FontData fontData = getFont().getFontData()[0];
810                 header.append("{\\rtf1\\ansi");
811                 // specify code page, necessary for copy to work in bidi
812                 // systems that don't support Unicode RTF.
813                 String cpg = System.getProperty("file.encoding").toLowerCase();
814                 if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
815                         cpg = cpg.substring(2, cpg.length());
816                         header.append("\\ansicpg");
817                         header.append(cpg);
818                 }
819                 header.append("\\uc1\\deff0{\\fonttbl{\\f0\\fnil ");
820                 header.append(fontData.getName());
821                 header.append(";");
822                 for (int i = 1; i < fontTable.size(); i++) {
823                         header.append("\\f");
824                         header.append(i);
825                         header.append(" ");
826                         FontData fd = fontTable.get(i).getFontData()[0];
827                         header.append(fd.getName());
828                         header.append(";");
829                 }
830                 header.append("}}\n{\\colortbl");
831                 for (int i = 0; i < colorTable.size(); i++) {
832                         Color color = colorTable.get(i);
833                         header.append("\\red");
834                         header.append(color.getRed());
835                         header.append("\\green");
836                         header.append(color.getGreen());
837                         header.append("\\blue");
838                         header.append(color.getBlue());
839                         header.append(";");
840                 }
841                 // some RTF readers ignore the deff0 font tag. Explicitly
842                 // set the font for the whole document to work around this.
843                 header.append("}\n{\\f0\\fs");
844                 // font size is specified in half points
845                 header.append(fontData.getHeight() * 2);
846                 header.append(" ");
847                 write(header.toString(), 0);
848         }
849         /**
850          * Appends the specified line text to the RTF data.  Lines will be formatted
851          * using the styles queried from the LineStyleListener, if set, or those set
852          * directly in the widget.
853          *
854          * @param line line text to write as RTF. Must not contain line breaks
855          *      Line breaks should be written using writeLineDelimiter()
856          * @param lineOffset offset of the line. 0 based from the start of the
857          *      widget document. Any text occurring before the start offset or after the
858          *      end offset specified during object creation is ignored.
859          * @exception SWTException <ul>
860          *   <li>ERROR_IO when the writer is closed.</li>
861          * </ul>
862          */
863         @Override
864         public void writeLine(String line, int lineOffset) {
865                 if (isClosed()) {
866                         SWT.error(SWT.ERROR_IO);
867                 }
868                 int lineIndex = content.getLineAtOffset(lineOffset);
869                 int lineAlignment, lineIndent;
870                 boolean lineJustify;
871                 int[] ranges;
872                 StyleRange[] styles;
873                 StyledTextEvent event = getLineStyleData(lineOffset, line);
874                 if (event != null) {
875                         lineAlignment = event.alignment;
876                         lineIndent = event.indent;
877                         lineJustify = event.justify;
878                         ranges = event.ranges;
879                         styles = event.styles;
880                 } else {
881                         lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
882                         lineIndent =  renderer.getLineIndent(lineIndex, indent);
883                         lineJustify = renderer.getLineJustify(lineIndex, justify);
884                         ranges = renderer.getRanges(lineOffset, line.length());
885                         styles = renderer.getStyleRanges(lineOffset, line.length(), false);
886                 }
887                 if (styles == null) styles = new StyleRange[0];
888                 Color lineBackground = renderer.getLineBackground(lineIndex, null);
889                 event = getLineBackgroundData(lineOffset, line);
890                 if (event != null && event.lineBackground != null) lineBackground = event.lineBackground;
891                 writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify);
892         }
893         /**
894          * Appends the specified line delimiter to the RTF data.
895          *
896          * @param lineDelimiter line delimiter to write as RTF.
897          * @exception SWTException <ul>
898          *   <li>ERROR_IO when the writer is closed.</li>
899          * </ul>
900          */
901         @Override
902         public void writeLineDelimiter(String lineDelimiter) {
903                 if (isClosed()) {
904                         SWT.error(SWT.ERROR_IO);
905                 }
906                 write(lineDelimiter, 0, lineDelimiter.length());
907                 write("\\par ");
908         }
909         /**
910          * Appends the specified line text to the RTF data.
911          * <p>
912          * Use the colors and font styles specified in "styles" and "lineBackground".
913          * Formatting is written to reflect the text rendering by the text widget.
914          * Style background colors take precedence over the line background color.
915          * Background colors are written using the \chshdng0\chcbpat tag (vs. the \cb tag).
916          * </p>
917          *
918          * @param line line text to write as RTF. Must not contain line breaks
919          *      Line breaks should be written using writeLineDelimiter()
920          * @param lineOffset offset of the line. 0 based from the start of the
921          *      widget document. Any text occurring before the start offset or after the
922          *      end offset specified during object creation is ignored.
923          * @param styles styles to use for formatting. Must not be null.
924          * @param lineBackground line background color to use for formatting.
925          *      May be null.
926          */
927         void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify) {
928                 int lineLength = line.length();
929                 int startOffset = getStart();
930                 int writeOffset = startOffset - lineOffset;
931                 if (writeOffset >= lineLength) return;
932                 int lineIndex = Math.max(0, writeOffset);
933
934                 write("\\fi");
935                 write(indent);
936                 switch (alignment) {
937                         case SWT.LEFT: write("\\ql"); break;
938                         case SWT.CENTER: write("\\qc"); break;
939                         case SWT.RIGHT: write("\\qr"); break;
940                 }
941                 if (justify) write("\\qj");
942                 write(" ");
943
944                 if (lineBackground != null) {
945                         write("{\\chshdng0\\chcbpat");
946                         write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
947                         write(" ");
948                 }
949                 int endOffset = startOffset + super.getCharCount();
950                 int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
951                 for (int i = 0; i < styles.length; i++) {
952                         StyleRange style = styles[i];
953                         int start, end;
954                         if (ranges != null) {
955                                 start = ranges[i << 1] - lineOffset;
956                                 end = start + ranges[(i << 1) + 1];
957                         } else {
958                                 start = style.start - lineOffset;
959                                 end = start + style.length;
960                         }
961                         // skip over partial first line
962                         if (end < writeOffset) {
963                                 continue;
964                         }
965                         // style starts beyond line end or RTF write end
966                         if (start >= lineEndOffset) {
967                                 break;
968                         }
969                         // write any unstyled text
970                         if (lineIndex < start) {
971                                 // copy to start of style
972                                 // style starting beyond end of write range or end of line
973                                 // is guarded against above.
974                                 write(line, lineIndex, start);
975                                 lineIndex = start;
976                         }
977                         // write styled text
978                         write("{\\cf");
979                         write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
980                         int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
981                         if (colorIndex != DEFAULT_BACKGROUND) {
982                                 write("\\chshdng0\\chcbpat");
983                                 write(colorIndex);
984                         }
985                         int fontStyle = style.fontStyle;
986                         Font font = style.font;
987                         if (font != null) {
988                                 int fontIndex = getFontIndex(font);
989                                 write("\\f");
990                                 write(fontIndex);
991                                 FontData fontData = font.getFontData()[0];
992                                 write("\\fs");
993                                 write(fontData.getHeight() * 2);
994                                 fontStyle = fontData.getStyle();
995                         }
996                         if ((fontStyle & SWT.BOLD) != 0) {
997                                 write("\\b");
998                         }
999                         if ((fontStyle & SWT.ITALIC) != 0) {
1000                                 write("\\i");
1001                         }
1002                         if (style.underline) {
1003                                 write("\\ul");
1004                         }
1005                         if (style.strikeout) {
1006                                 write("\\strike");
1007                         }
1008                         write(" ");
1009                         // copy to end of style or end of write range or end of line
1010                         int copyEnd = Math.min(end, lineEndOffset);
1011                         // guard against invalid styles and let style processing continue
1012                         copyEnd = Math.max(copyEnd, lineIndex);
1013                         write(line, lineIndex, copyEnd);
1014                         if ((fontStyle & SWT.BOLD) != 0) {
1015                                 write("\\b0");
1016                         }
1017                         if ((style.fontStyle & SWT.ITALIC) != 0) {
1018                                 write("\\i0");
1019                         }
1020                         if (style.underline) {
1021                                 write("\\ul0");
1022                         }
1023                         if (style.strikeout) {
1024                                 write("\\strike0");
1025                         }
1026                         write("}");
1027                         lineIndex = copyEnd;
1028                 }
1029                 // write unstyled text at the end of the line
1030                 if (lineIndex < lineEndOffset) {
1031                         write(line, lineIndex, lineEndOffset);
1032                 }
1033                 if (lineBackground != null) write("}");
1034         }
1035         }
1036         /**
1037          * The <code>TextWriter</code> class is used to write widget content to
1038          * a string.  Whole and partial lines and line breaks can be written. To write
1039          * partial lines, specify the start and length of the desired segment
1040          * during object creation.
1041          * <p>
1042          * <b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close()
1043          * has been called.
1044          * </p>
1045          */
1046         class TextWriter {
1047                 private StringBuilder buffer;
1048                 private int startOffset;        // offset of first character that will be written
1049                 private int endOffset;          // offset of last character that will be written.
1050                                                                         // 0 based from the beginning of the widget text.
1051                 private boolean isClosed = false;
1052
1053         /**
1054          * Creates a writer that writes content starting at offset "start"
1055          * in the document.  <code>start</code> and <code>length</code> can be set to specify partial lines.
1056          *
1057          * @param start start offset of content to write, 0 based from beginning of document
1058          * @param length length of content to write
1059          */
1060         public TextWriter(int start, int length) {
1061                 buffer = new StringBuilder(length);
1062                 startOffset = start;
1063                 endOffset = start + length;
1064         }
1065         /**
1066          * Closes the writer. Once closed no more content can be written.
1067          * <b>NOTE:</b>  <code>toString()</code> is not guaranteed to return a valid string unless
1068          * the writer is closed.
1069          */
1070         public void close() {
1071                 if (!isClosed) {
1072                         isClosed = true;
1073                 }
1074         }
1075         /**
1076          * Returns the number of characters to write.
1077          * @return the integer number of characters to write
1078          */
1079         public int getCharCount() {
1080                 return endOffset - startOffset;
1081         }
1082         /**
1083          * Returns the offset where writing starts. 0 based from the start of
1084          * the widget text. Used to write partial lines.
1085          * @return the integer offset where writing starts
1086          */
1087         public int getStart() {
1088                 return startOffset;
1089         }
1090         /**
1091          * Returns whether the writer is closed.
1092          * @return a boolean specifying whether or not the writer is closed
1093          */
1094         public boolean isClosed() {
1095                 return isClosed;
1096         }
1097         /**
1098          * Returns the string.  <code>close()</code> must be called before <code>toString()</code>
1099          * is guaranteed to return a valid string.
1100          *
1101          * @return the string
1102          */
1103         @Override
1104         public String toString() {
1105                 return buffer.toString();
1106         }
1107         /**
1108          * Appends the given string to the data.
1109          */
1110         void write(String string) {
1111                 buffer.append(string);
1112         }
1113         /**
1114          * Inserts the given string to the data at the specified offset.
1115          * <p>
1116          * Do nothing if "offset" is &lt; 0 or &gt; getCharCount()
1117          * </p>
1118          *
1119          * @param string text to insert
1120          * @param offset offset in the existing data to insert "string" at.
1121          */
1122         void write(String string, int offset) {
1123                 if (offset < 0 || offset > buffer.length()) {
1124                         return;
1125                 }
1126                 buffer.insert(offset, string);
1127         }
1128         /**
1129          * Appends the given int to the data.
1130          */
1131         void write(int i) {
1132                 buffer.append(i);
1133         }
1134         /**
1135          * Appends the given character to the data.
1136          */
1137         void write(char i) {
1138                 buffer.append(i);
1139         }
1140         /**
1141          * Appends the specified line text to the data.
1142          *
1143          * @param line line text to write. Must not contain line breaks
1144          *      Line breaks should be written using writeLineDelimiter()
1145          * @param lineOffset offset of the line. 0 based from the start of the
1146          *      widget document. Any text occurring before the start offset or after the
1147          *      end offset specified during object creation is ignored.
1148          * @exception SWTException <ul>
1149          *   <li>ERROR_IO when the writer is closed.</li>
1150          * </ul>
1151          */
1152         public void writeLine(String line, int lineOffset) {
1153                 if (isClosed) {
1154                         SWT.error(SWT.ERROR_IO);
1155                 }
1156                 int writeOffset = startOffset - lineOffset;
1157                 int lineLength = line.length();
1158                 int lineIndex;
1159                 if (writeOffset >= lineLength) {
1160                         return;                                                 // whole line is outside write range
1161                 } else if (writeOffset > 0) {
1162                         lineIndex = writeOffset;                // line starts before write start
1163                 } else {
1164                         lineIndex = 0;
1165                 }
1166                 int copyEnd = Math.min(lineLength, endOffset - lineOffset);
1167                 if (lineIndex < copyEnd) {
1168                         write(line.substring(lineIndex, copyEnd));
1169                 }
1170         }
1171         /**
1172          * Appends the specified line delimiter to the data.
1173          *
1174          * @param lineDelimiter line delimiter to write
1175          * @exception SWTException <ul>
1176          *   <li>ERROR_IO when the writer is closed.</li>
1177          * </ul>
1178          */
1179         public void writeLineDelimiter(String lineDelimiter) {
1180                 if (isClosed) {
1181                         SWT.error(SWT.ERROR_IO);
1182                 }
1183                 write(lineDelimiter);
1184         }
1185         }
1186
1187 /**
1188  * Constructs a new instance of this class given its parent
1189  * and a style value describing its behavior and appearance.
1190  * <p>
1191  * The style value is either one of the style constants defined in
1192  * class <code>SWT</code> which is applicable to instances of this
1193  * class, or must be built by <em>bitwise OR</em>'ing together
1194  * (that is, using the <code>int</code> "|" operator) two or more
1195  * of those <code>SWT</code> style constants. The class description
1196  * lists the style constants that are applicable to the class.
1197  * Style bits are also inherited from superclasses.
1198  * </p>
1199  *
1200  * @param parent a widget which will be the parent of the new instance (cannot be null)
1201  * @param style the style of widget to construct
1202  *
1203  * @exception IllegalArgumentException <ul>
1204  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
1205  * </ul>
1206  * @exception SWTException <ul>
1207  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
1208  * </ul>
1209  *
1210  * @see SWT#FULL_SELECTION
1211  * @see SWT#MULTI
1212  * @see SWT#READ_ONLY
1213  * @see SWT#SINGLE
1214  * @see SWT#WRAP
1215  * @see #getStyle
1216  */
1217 public StyledText(Composite parent, int style) {
1218         super(parent, checkStyle(style));
1219         // set the fg in the OS to ensure that these are the same as StyledText, necessary
1220         // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
1221         super.setForeground(getForeground());
1222         super.setDragDetect(false);
1223         Display display = getDisplay();
1224         if ((style & SWT.READ_ONLY) != 0) {
1225                 setEditable(false);
1226         }
1227         leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
1228         if ((style & SWT.SINGLE) != 0 && (style & SWT.BORDER) != 0) {
1229                 leftMargin = topMargin = rightMargin = bottomMargin = 2;
1230         }
1231         alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
1232         if (alignment == 0) alignment = SWT.LEFT;
1233         clipboard = new Clipboard(display);
1234         installDefaultContent();
1235         renderer = new StyledTextRenderer(getDisplay(), this);
1236         renderer.setContent(content);
1237         renderer.setFont(getFont(), tabLength);
1238         ime = new IME(this, SWT.NONE);
1239         defaultCaret = new Caret(this, SWT.NONE);
1240         if ((style & SWT.WRAP) != 0) {
1241                 setWordWrap(true);
1242         }
1243         if (isBidiCaret()) {
1244                 createCaretBitmaps();
1245                 Runnable runnable = () -> {
1246                         int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
1247                         if (direction == caretDirection) return;
1248                         if (getCaret() != defaultCaret) return;
1249                         Point newCaretPos = getPointAtOffset(caretOffset);
1250                         setCaretLocation(newCaretPos, direction);
1251                 };
1252                 BidiUtil.addLanguageListener(this, runnable);
1253         }
1254         setCaret(defaultCaret);
1255         calculateScrollBars();
1256         createKeyBindings();
1257         super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
1258         installListeners();
1259         initializeAccessible();
1260         setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
1261         if (IS_MAC) setData(STYLEDTEXT_KEY);
1262 }
1263 /**
1264  * Adds an extended modify listener. An ExtendedModify event is sent by the
1265  * widget when the widget text has changed.
1266  *
1267  * @param extendedModifyListener the listener
1268  * @exception SWTException <ul>
1269  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1270  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1271  * </ul>
1272  * @exception IllegalArgumentException <ul>
1273  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1274  * </ul>
1275  */
1276 public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
1277         checkWidget();
1278         if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1279         StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
1280         addListener(ST.ExtendedModify, typedListener);
1281 }
1282 /**
1283  * Adds a bidirectional segment listener.
1284  * <p>
1285  * A BidiSegmentEvent is sent
1286  * whenever a line of text is measured or rendered. You can
1287  * specify text ranges in the line that should be treated as if they
1288  * had a different direction than the surrounding text.
1289  * This may be used when adjacent segments of right-to-left text should
1290  * not be reordered relative to each other.
1291  * E.g., multiple Java string literals in a right-to-left language
1292  * should generally remain in logical order to each other, that is, the
1293  * way they are stored.
1294  * </p>
1295  *
1296  * @param listener the listener
1297  * @exception SWTException <ul>
1298  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1299  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1300  * </ul>
1301  * @exception IllegalArgumentException <ul>
1302  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1303  * </ul>
1304  * @see BidiSegmentEvent
1305  * @since 2.0
1306  */
1307 public void addBidiSegmentListener(BidiSegmentListener listener) {
1308         checkWidget();
1309         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1310         addListener(ST.LineGetSegments, new StyledTextListener(listener));
1311         resetCache(0, content.getLineCount());
1312         setCaretLocation();
1313         super.redraw();
1314 }
1315 /**
1316  * Adds a caret listener. CaretEvent is sent when the caret offset changes.
1317  *
1318  * @param listener the listener
1319  * @exception SWTException <ul>
1320  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1321  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1322  * </ul>
1323  * @exception IllegalArgumentException <ul>
1324  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1325  * </ul>
1326  *
1327  * @since 3.5
1328  */
1329 public void addCaretListener(CaretListener listener) {
1330         checkWidget();
1331         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1332         addListener(ST.CaretMoved, new StyledTextListener(listener));
1333 }
1334 /**
1335  * Adds a line background listener. A LineGetBackground event is sent by the
1336  * widget to determine the background color for a line.
1337  *
1338  * @param listener the listener
1339  * @exception SWTException <ul>
1340  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1341  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1342  * </ul>
1343  * @exception IllegalArgumentException <ul>
1344  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1345  * </ul>
1346  */
1347 public void addLineBackgroundListener(LineBackgroundListener listener) {
1348         checkWidget();
1349         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1350         if (!isListening(ST.LineGetBackground)) {
1351                 renderer.clearLineBackground(0, content.getLineCount());
1352         }
1353         addListener(ST.LineGetBackground, new StyledTextListener(listener));
1354 }
1355 /**
1356  * Adds a line style listener. A LineGetStyle event is sent by the widget to
1357  * determine the styles for a line.
1358  *
1359  * @param listener the listener
1360  * @exception SWTException <ul>
1361  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1362  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1363  * </ul>
1364  * @exception IllegalArgumentException <ul>
1365  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1366  * </ul>
1367  */
1368 public void addLineStyleListener(LineStyleListener listener) {
1369         checkWidget();
1370         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1371         if (!isListening(ST.LineGetStyle)) {
1372                 setStyleRanges(0, 0, null, null, true);
1373                 renderer.clearLineStyle(0, content.getLineCount());
1374         }
1375         addListener(ST.LineGetStyle, new StyledTextListener(listener));
1376         setCaretLocation();
1377 }
1378 /**
1379  * Adds a modify listener. A Modify event is sent by the widget when the widget text
1380  * has changed.
1381  *
1382  * @param modifyListener the listener
1383  * @exception SWTException <ul>
1384  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1385  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1386  * </ul>
1387  * @exception IllegalArgumentException <ul>
1388  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1389  * </ul>
1390  */
1391 public void addModifyListener(ModifyListener modifyListener) {
1392         checkWidget();
1393         if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1394         addListener(SWT.Modify, new TypedListener(modifyListener));
1395 }
1396 /**
1397  * Adds a paint object listener. A paint object event is sent by the widget when an object
1398  * needs to be drawn.
1399  *
1400  * @param listener the listener
1401  * @exception SWTException <ul>
1402  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1403  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1404  * </ul>
1405  * @exception IllegalArgumentException <ul>
1406  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1407  * </ul>
1408  *
1409  * @since 3.2
1410  *
1411  * @see PaintObjectListener
1412  * @see PaintObjectEvent
1413  */
1414 public void addPaintObjectListener(PaintObjectListener listener) {
1415         checkWidget();
1416         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1417         addListener(ST.PaintObject, new StyledTextListener(listener));
1418 }
1419 /**
1420  * Adds a selection listener. A Selection event is sent by the widget when the
1421  * user changes the selection.
1422  * <p>
1423  * When <code>widgetSelected</code> is called, the event x and y fields contain
1424  * the start and end caret indices of the selection. The selection values returned are visual
1425  * (i.e., x will always always be &lt;= y).
1426  * No event is sent when the caret is moved while the selection length is 0.
1427  * </p><p>
1428  * <code>widgetDefaultSelected</code> is not called for StyledTexts.
1429  * </p>
1430  *
1431  * @param listener the listener which should be notified when the user changes the receiver's selection
1432
1433  * @exception IllegalArgumentException <ul>
1434  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
1435  * </ul>
1436  * @exception SWTException <ul>
1437  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1438  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1439  * </ul>
1440  *
1441  * @see SelectionListener
1442  * @see #removeSelectionListener
1443  * @see SelectionEvent
1444  */
1445 public void addSelectionListener(SelectionListener listener) {
1446         checkWidget();
1447         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1448         addListener(SWT.Selection, new TypedListener(listener));
1449 }
1450 /**
1451  * Adds a verify key listener. A VerifyKey event is sent by the widget when a key
1452  * is pressed. The widget ignores the key press if the listener sets the doit field
1453  * of the event to false.
1454  *
1455  * @param listener the listener
1456  * @exception SWTException <ul>
1457  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1458  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1459  * </ul>
1460  * @exception IllegalArgumentException <ul>
1461  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1462  * </ul>
1463  */
1464 public void addVerifyKeyListener(VerifyKeyListener listener) {
1465         checkWidget();
1466         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1467         addListener(ST.VerifyKey, new StyledTextListener(listener));
1468 }
1469 /**
1470  * Adds a verify listener. A Verify event is sent by the widget when the widget text
1471  * is about to change. The listener can set the event text and the doit field to
1472  * change the text that is set in the widget or to force the widget to ignore the
1473  * text change.
1474  *
1475  * @param verifyListener the listener
1476  * @exception SWTException <ul>
1477  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1478  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1479  * </ul>
1480  * @exception IllegalArgumentException <ul>
1481  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1482  * </ul>
1483  */
1484 public void addVerifyListener(VerifyListener verifyListener) {
1485         checkWidget();
1486         if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1487         addListener(SWT.Verify, new TypedListener(verifyListener));
1488 }
1489 /**
1490  * Adds a word movement listener. A movement event is sent when the boundary
1491  * of a word is needed. For example, this occurs during word next and word
1492  * previous actions.
1493  *
1494  * @param movementListener the listener
1495  * @exception SWTException <ul>
1496  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1497  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1498  * </ul>
1499  * @exception IllegalArgumentException <ul>
1500  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1501  * </ul>
1502  *
1503  * @see MovementEvent
1504  * @see MovementListener
1505  * @see #removeWordMovementListener
1506  *
1507  * @since 3.3
1508  */
1509 public void addWordMovementListener(MovementListener movementListener) {
1510         checkWidget();
1511         if (movementListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1512         addListener(ST.WordNext, new StyledTextListener(movementListener));
1513         addListener(ST.WordPrevious, new StyledTextListener(movementListener));
1514 }
1515 /**
1516  * Appends a string to the text at the end of the widget.
1517  *
1518  * @param string the string to be appended
1519  * @see #replaceTextRange(int,int,String)
1520  * @exception SWTException <ul>
1521  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1522  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1523  * </ul>
1524  * @exception IllegalArgumentException <ul>
1525  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1526  * </ul>
1527  */
1528 public void append(String string) {
1529         checkWidget();
1530         if (string == null) {
1531                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
1532         }
1533         int lastChar = Math.max(getCharCount(), 0);
1534         replaceTextRange(lastChar, 0, string);
1535 }
1536 /**
1537  * Calculates the scroll bars
1538  */
1539 void calculateScrollBars() {
1540         ScrollBar horizontalBar = getHorizontalBar();
1541         ScrollBar verticalBar = getVerticalBar();
1542         setScrollBars(true);
1543         if (verticalBar != null) {
1544                 verticalBar.setIncrement(getVerticalIncrement());
1545         }
1546         if (horizontalBar != null) {
1547                 horizontalBar.setIncrement(getHorizontalIncrement());
1548         }
1549 }
1550 /**
1551  * Calculates the top index based on the current vertical scroll offset.
1552  * The top index is the index of the topmost fully visible line or the
1553  * topmost partially visible line if no line is fully visible.
1554  * The top index starts at 0.
1555  */
1556 void calculateTopIndex(int delta) {
1557         int oldDelta = delta;
1558         int oldTopIndex = topIndex;
1559         int oldTopIndexY = topIndexY;
1560         if (isFixedLineHeight()) {
1561                 int verticalIncrement = getVerticalIncrement();
1562                 if (verticalIncrement == 0) {
1563                         return;
1564                 }
1565                 topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement);
1566                 // Set top index to partially visible top line if no line is fully
1567                 // visible but at least some of the widget client area is visible.
1568                 // Fixes bug 15088.
1569                 if (topIndex > 0) {
1570                         if (clientAreaHeight > 0) {
1571                                 int bottomPixel = getVerticalScrollOffset() + clientAreaHeight;
1572                                 int fullLineTopPixel = topIndex * verticalIncrement;
1573                                 int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
1574                                 // set top index to partially visible line if no line fully fits in
1575                                 // client area or if space is available but not used (the latter should
1576                                 // never happen because we use claimBottomFreeSpace)
1577                                 if (fullLineVisibleHeight < verticalIncrement) {
1578                                         topIndex = getVerticalScrollOffset() / verticalIncrement;
1579                                 }
1580                         } else if (topIndex >= content.getLineCount()) {
1581                                 topIndex = content.getLineCount() - 1;
1582                         }
1583                 }
1584         } else {
1585                 if (delta >= 0) {
1586                         delta -= topIndexY;
1587                         int lineIndex = topIndex;
1588                         int lineCount = content.getLineCount();
1589                         while (lineIndex < lineCount) {
1590                                 if (delta <= 0) break;
1591                                 delta -= renderer.getLineHeight(lineIndex++);
1592                         }
1593                         if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1594                                 topIndex = lineIndex;
1595                                 topIndexY = -delta;
1596                         } else {
1597                                 topIndex = lineIndex - 1;
1598                                 topIndexY = -renderer.getLineHeight(topIndex) - delta;
1599                         }
1600                 } else {
1601                         delta -= topIndexY;
1602                         int lineIndex = topIndex;
1603                         while (lineIndex > 0) {
1604                                 int lineHeight = renderer.getLineHeight(lineIndex - 1);
1605                                 if (delta + lineHeight > 0) break;
1606                                 delta += lineHeight;
1607                                 lineIndex--;
1608                         }
1609                         if (lineIndex == 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1610                                 topIndex = lineIndex;
1611                                 topIndexY = - delta;
1612                         } else {
1613                                 topIndex = lineIndex - 1;
1614                                 topIndexY = - renderer.getLineHeight(topIndex) - delta;
1615                         }
1616                 }
1617         }
1618         if (topIndex < 0) {
1619                 // TODO: This logging is in place to determine why topIndex is getting set to negative values.
1620                 // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
1621                 System.err.println("StyledText: topIndex was " + topIndex
1622                                 + ", isFixedLineHeight() = " + isFixedLineHeight()
1623                                 + ", delta = " + delta
1624                                 + ", content.getLineCount() = " + content.getLineCount()
1625                                 + ", clientAreaHeight = " + clientAreaHeight
1626                                 + ", oldTopIndex = " + oldTopIndex
1627                                 + ", oldTopIndexY = " + oldTopIndexY
1628                                 + ", getVerticalScrollOffset = " + getVerticalScrollOffset()
1629                                 + ", oldDelta = " + oldDelta
1630                                 + ", getVerticalIncrement() = " + getVerticalIncrement());
1631                 topIndex = 0;
1632         }
1633         if (topIndex != oldTopIndex || oldTopIndexY != topIndexY) {
1634                 int width = renderer.getWidth();
1635                 renderer.calculateClientArea();
1636                 if (width != renderer.getWidth()) {
1637                         setScrollBars(false);
1638                 }
1639         }
1640 }
1641 /**
1642  * Hides the scroll bars if widget is created in single line mode.
1643  */
1644 static int checkStyle(int style) {
1645         if ((style & SWT.SINGLE) != 0) {
1646                 style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI);
1647         } else {
1648                 style |= SWT.MULTI;
1649                 if ((style & SWT.WRAP) != 0) {
1650                         style &= ~SWT.H_SCROLL;
1651                 }
1652         }
1653         style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND;
1654         /* Clear SWT.CENTER to avoid the conflict with SWT.EMBEDDED */
1655         return style & ~SWT.CENTER;
1656 }
1657 /**
1658  * Scrolls down the text to use new space made available by a resize or by
1659  * deleted lines.
1660  */
1661 void claimBottomFreeSpace() {
1662         if (ime.getCompositionOffset() != -1) return;
1663         if (isFixedLineHeight()) {
1664                 int newVerticalOffset = Math.max(0, renderer.getHeight() - clientAreaHeight);
1665                 if (newVerticalOffset < getVerticalScrollOffset()) {
1666                         scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
1667                 }
1668         } else {
1669                 int bottomIndex = getPartialBottomIndex();
1670                 int height = getLinePixel(bottomIndex + 1);
1671                 if (clientAreaHeight > height) {
1672                         scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
1673                 }
1674         }
1675 }
1676 /**
1677  * Scrolls text to the right to use new space made available by a resize.
1678  */
1679 void claimRightFreeSpace() {
1680         int newHorizontalOffset = Math.max(0, renderer.getWidth() - clientAreaWidth);
1681         if (newHorizontalOffset < horizontalScrollOffset) {
1682                 // item is no longer drawn past the right border of the client area
1683                 // align the right end of the item with the right border of the
1684                 // client area (window is scrolled right).
1685                 scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
1686         }
1687 }
1688 void clearBlockSelection(boolean reset, boolean sendEvent) {
1689         if (reset) resetSelection();
1690         blockXAnchor = blockYAnchor = -1;
1691         blockXLocation = blockYLocation = -1;
1692         caretDirection = SWT.NULL;
1693         updateCaretVisibility();
1694         super.redraw();
1695         if (sendEvent) sendSelectionEvent();
1696 }
1697 /**
1698  * Removes the widget selection.
1699  *
1700  * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
1701  */
1702 void clearSelection(boolean sendEvent) {
1703         int selectionStart = selection.x;
1704         int selectionEnd = selection.y;
1705         resetSelection();
1706         // redraw old selection, if any
1707         if (selectionEnd - selectionStart > 0) {
1708                 int length = content.getCharCount();
1709                 // called internally to remove selection after text is removed
1710                 // therefore make sure redraw range is valid.
1711                 int redrawStart = Math.min(selectionStart, length);
1712                 int redrawEnd = Math.min(selectionEnd, length);
1713                 if (redrawEnd - redrawStart > 0) {
1714                         internalRedrawRange(redrawStart, redrawEnd - redrawStart);
1715                 }
1716                 if (sendEvent) {
1717                         sendSelectionEvent();
1718                 }
1719         }
1720 }
1721 @Override
1722 public Point computeSize (int wHint, int hHint, boolean changed) {
1723         checkWidget();
1724         int lineCount = (getStyle() & SWT.SINGLE) != 0 ? 1 : content.getLineCount();
1725         int width = 0;
1726         int height = 0;
1727         if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
1728                 Display display = getDisplay();
1729                 int maxHeight = display.getClientArea().height;
1730                 for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
1731                         TextLayout layout = renderer.getTextLayout(lineIndex);
1732                         int wrapWidth = layout.getWidth();
1733                         if (wordWrap) layout.setWidth(wHint == 0 ? 1 : wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(1, wHint - leftMargin - rightMargin));
1734                         Rectangle rect = layout.getBounds();
1735                         height += rect.height;
1736                         width = Math.max(width, rect.width);
1737                         layout.setWidth(wrapWidth);
1738                         renderer.disposeTextLayout(layout);
1739                         if (isFixedLineHeight() && height > maxHeight) break;
1740                 }
1741                 if (isFixedLineHeight()) {
1742                         height = lineCount * renderer.getLineHeight();
1743                 }
1744         }
1745         // Use default values if no text is defined.
1746         if (width == 0) width = DEFAULT_WIDTH;
1747         if (height == 0) height = DEFAULT_HEIGHT;
1748         if (wHint != SWT.DEFAULT) width = wHint;
1749         if (hHint != SWT.DEFAULT) height = hHint;
1750         int wTrim = getLeftMargin() + rightMargin + getCaretWidth();
1751         int hTrim = topMargin + bottomMargin;
1752         Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
1753         return new Point (rect.width, rect.height);
1754 }
1755 /**
1756  * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
1757  * <p>
1758  * The text will be put on the clipboard in plain text format and RTF format.
1759  * The <code>DND.CLIPBOARD</code> clipboard is used for data that is
1760  * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
1761  * by menu action.
1762  * </p>
1763  *
1764  * @exception SWTException <ul>
1765  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1766  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1767  * </ul>
1768  */
1769 public void copy() {
1770         checkWidget();
1771         copySelection(DND.CLIPBOARD);
1772 }
1773 /**
1774  * Copies the selected text to the specified clipboard.  The text will be put in the
1775  * clipboard in plain text format and RTF format.
1776  * <p>
1777  * The clipboardType is  one of the clipboard constants defined in class
1778  * <code>DND</code>.  The <code>DND.CLIPBOARD</code>  clipboard is
1779  * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
1780  * or by menu action.  The <code>DND.SELECTION_CLIPBOARD</code>
1781  * clipboard is used for data that is transferred by selecting text and pasting
1782  * with the middle mouse button.
1783  * </p>
1784  *
1785  * @param clipboardType indicates the type of clipboard
1786  *
1787  * @exception SWTException <ul>
1788  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1789  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1790  * </ul>
1791  *
1792  * @since 3.1
1793  */
1794 public void copy(int clipboardType) {
1795         checkWidget();
1796         copySelection(clipboardType);
1797 }
1798 boolean copySelection(int type) {
1799         if (type != DND.CLIPBOARD && type != DND.SELECTION_CLIPBOARD) return false;
1800         try {
1801                 if (blockSelection && blockXLocation != -1) {
1802                         String text = getBlockSelectionText(PlatformLineDelimiter);
1803                         if (text.length() > 0) {
1804                                 //TODO RTF support
1805                                 TextTransfer plainTextTransfer = TextTransfer.getInstance();
1806                                 Object[] data = new Object[]{text};
1807                                 Transfer[] types = new Transfer[]{plainTextTransfer};
1808                                 clipboard.setContents(data, types, type);
1809                                 return true;
1810                         }
1811                 } else {
1812                         int length = selection.y - selection.x;
1813                         if (length > 0) {
1814                                 setClipboardContent(selection.x, length, type);
1815                                 return true;
1816                         }
1817                 }
1818         } catch (SWTError error) {
1819                 // Copy to clipboard failed. This happens when another application
1820                 // is accessing the clipboard while we copy. Ignore the error.
1821                 // Rethrow all other errors. Fixes bug 17578.
1822                 if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
1823                         throw error;
1824                 }
1825         }
1826         return false;
1827 }
1828 /**
1829  * Returns the alignment of the widget.
1830  *
1831  * @return the alignment
1832  *
1833  * @exception SWTException <ul>
1834  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1835  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1836  * </ul>
1837  *
1838  * @see #getLineAlignment(int)
1839  *
1840  * @since 3.2
1841  */
1842 public int getAlignment() {
1843         checkWidget();
1844         return alignment;
1845 }
1846 /**
1847  * Returns the Always Show Scrollbars flag.  True if the scrollbars are
1848  * always shown even if they are not required.  False if the scrollbars are only
1849  * visible when some part of the content needs to be scrolled to be seen.
1850  * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
1851  * horizontal and vertical directions.
1852  *
1853  * @return the Always Show Scrollbars flag value
1854  *
1855  * @exception SWTException <ul>
1856  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1857  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1858  * </ul>
1859  *
1860  * @since 3.8
1861  */
1862 public boolean getAlwaysShowScrollBars() {
1863         checkWidget();
1864         return alwaysShowScroll;
1865 }
1866 int getAvailableHeightAbove(int height) {
1867         int maxHeight = verticalScrollOffset;
1868         if (maxHeight == -1) {
1869                 int lineIndex = topIndex - 1;
1870                 maxHeight = -topIndexY;
1871                 if (topIndexY > 0) {
1872                         maxHeight += renderer.getLineHeight(lineIndex--);
1873                 }
1874                 while (height > maxHeight && lineIndex >= 0) {
1875                         maxHeight += renderer.getLineHeight(lineIndex--);
1876                 }
1877         }
1878         return Math.min(height, maxHeight);
1879 }
1880 int getAvailableHeightBellow(int height) {
1881         int partialBottomIndex = getPartialBottomIndex();
1882         int topY = getLinePixel(partialBottomIndex);
1883         int lineHeight = renderer.getLineHeight(partialBottomIndex);
1884         int availableHeight = 0;
1885         int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
1886         if (topY + lineHeight > clientAreaHeight) {
1887                 availableHeight = lineHeight - (clientAreaHeight - topY);
1888         }
1889         int lineIndex = partialBottomIndex + 1;
1890         int lineCount = content.getLineCount();
1891         while (height > availableHeight && lineIndex < lineCount) {
1892                 availableHeight += renderer.getLineHeight(lineIndex++);
1893         }
1894         return Math.min(height, availableHeight);
1895 }
1896 /**
1897  * Returns the color of the margins.
1898  *
1899  * @return the color of the margins.
1900  * @exception SWTException <ul>
1901  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1902  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1903  * </ul>
1904  *
1905  * @since 3.5
1906  */
1907 public Color getMarginColor() {
1908         checkWidget();
1909         return marginColor != null ? marginColor : getBackground();
1910 }
1911 /**
1912  * Returns a string that uses only the line delimiter specified by the
1913  * StyledTextContent implementation.
1914  * <p>
1915  * Returns only the first line if the widget has the SWT.SINGLE style.
1916  * </p>
1917  *
1918  * @param text the text that may have line delimiters that don't
1919  *      match the model line delimiter. Possible line delimiters
1920  *      are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
1921  * @return the converted text that only uses the line delimiter
1922  *      specified by the model. Returns only the first line if the widget
1923  *      has the SWT.SINGLE style.
1924  */
1925 String getModelDelimitedText(String text) {
1926         int length = text.length();
1927         if (length == 0) {
1928                 return text;
1929         }
1930         int crIndex = 0;
1931         int lfIndex = 0;
1932         int i = 0;
1933         StringBuilder convertedText = new StringBuilder(length);
1934         String delimiter = getLineDelimiter();
1935         while (i < length) {
1936                 if (crIndex != -1) {
1937                         crIndex = text.indexOf(SWT.CR, i);
1938                 }
1939                 if (lfIndex != -1) {
1940                         lfIndex = text.indexOf(SWT.LF, i);
1941                 }
1942                 if (lfIndex == -1 && crIndex == -1) {   // no more line breaks?
1943                         break;
1944                 } else if ((crIndex < lfIndex && crIndex != -1) || lfIndex == -1) {
1945                         convertedText.append(text.substring(i, crIndex));
1946                         if (lfIndex == crIndex + 1) {           // CR/LF combination?
1947                                 i = lfIndex + 1;
1948                         } else {
1949                                 i = crIndex + 1;
1950                         }
1951                 } else {                                                                        // LF occurs before CR!
1952                         convertedText.append(text.substring(i, lfIndex));
1953                         i = lfIndex + 1;
1954                 }
1955                 if (isSingleLine()) {
1956                         break;
1957                 }
1958                 convertedText.append(delimiter);
1959         }
1960         // copy remaining text if any and if not in single line mode or no
1961         // text copied thus far (because there only is one line)
1962         if (i < length && (!isSingleLine() || convertedText.length() == 0)) {
1963                 convertedText.append(text.substring(i));
1964         }
1965         return convertedText.toString();
1966 }
1967 boolean checkDragDetect(Event event) {
1968         if (!isListening(SWT.DragDetect)) return false;
1969         if (event.button != 1) return false;
1970         if (blockSelection && blockXLocation != -1) {
1971                 Rectangle rect = getBlockSelectionRectangle();
1972                 if (rect.contains(event.x, event.y)) {
1973                         return dragDetect(event);
1974                 }
1975         } else {
1976                 if (selection.x == selection.y) return false;
1977                 int offset = getOffsetAtPoint(event.x, event.y, null, true);
1978                 if (selection.x <= offset && offset < selection.y) {
1979                         return dragDetect(event);
1980                 }
1981
1982         }
1983         return false;
1984 }
1985
1986 /**
1987  * Creates default key bindings.
1988  */
1989 void createKeyBindings() {
1990         int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT;
1991         int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
1992
1993         // Navigation
1994         setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
1995         setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
1996         if (IS_MAC) {
1997                 setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START);
1998                 setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END);
1999                 setKeyBinding(SWT.HOME, ST.TEXT_START);
2000                 setKeyBinding(SWT.END, ST.TEXT_END);
2001                 setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START);
2002                 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END);
2003                 setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT);
2004                 setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS);
2005         } else {
2006                 setKeyBinding(SWT.HOME, ST.LINE_START);
2007                 setKeyBinding(SWT.END, ST.LINE_END);
2008                 setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
2009                 setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
2010                 setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
2011                 setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
2012         }
2013         setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
2014         setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
2015         setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START);
2016         setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END);
2017         setKeyBinding(nextKey, ST.COLUMN_NEXT);
2018         setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
2019
2020         // Selection
2021         setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
2022         setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
2023         if (IS_MAC) {
2024                 setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START);
2025                 setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END);
2026                 setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START);
2027                 setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END);
2028                 setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2029                 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2030                 setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT);
2031                 setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS);
2032         } else  {
2033                 setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
2034                 setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
2035                 setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2036                 setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2037                 setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
2038                 setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
2039         }
2040         setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
2041         setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
2042         setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START);
2043         setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END);
2044         setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT);
2045         setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
2046
2047         // Modification
2048         // Cut, Copy, Paste
2049         setKeyBinding('X' | SWT.MOD1, ST.CUT);
2050         setKeyBinding('C' | SWT.MOD1, ST.COPY);
2051         setKeyBinding('V' | SWT.MOD1, ST.PASTE);
2052         if (IS_MAC) {
2053                 setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT);
2054                 setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS);
2055                 setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT);
2056         } else {
2057                 // Cut, Copy, Paste Wordstar style
2058                 setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT);
2059                 setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY);
2060                 setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE);
2061         }
2062         setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS);
2063         setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
2064         setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
2065         setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS);
2066         setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT);
2067
2068         // Miscellaneous
2069         setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
2070 }
2071 /**
2072  * Create the bitmaps to use for the caret in bidi mode.  This
2073  * method only needs to be called upon widget creation and when the
2074  * font changes (the caret bitmap height needs to match font height).
2075  */
2076 void createCaretBitmaps() {
2077         int caretWidth = BIDI_CARET_WIDTH;
2078         Display display = getDisplay();
2079         if (leftCaretBitmap != null) {
2080                 if (defaultCaret != null && leftCaretBitmap.equals(defaultCaret.getImage())) {
2081                         defaultCaret.setImage(null);
2082                 }
2083                 leftCaretBitmap.dispose();
2084         }
2085         int lineHeight = renderer.getLineHeight();
2086         leftCaretBitmap = new Image(display, caretWidth, lineHeight);
2087         GC gc = new GC (leftCaretBitmap);
2088         gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2089         gc.fillRectangle(0, 0, caretWidth, lineHeight);
2090         gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2091         gc.drawLine(0,0,0,lineHeight);
2092         gc.drawLine(0,0,caretWidth-1,0);
2093         gc.drawLine(0,1,1,1);
2094         gc.dispose();
2095
2096         if (rightCaretBitmap != null) {
2097                 if (defaultCaret != null && rightCaretBitmap.equals(defaultCaret.getImage())) {
2098                         defaultCaret.setImage(null);
2099                 }
2100                 rightCaretBitmap.dispose();
2101         }
2102         rightCaretBitmap = new Image(display, caretWidth, lineHeight);
2103         gc = new GC (rightCaretBitmap);
2104         gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2105         gc.fillRectangle(0, 0, caretWidth, lineHeight);
2106         gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2107         gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight);
2108         gc.drawLine(0,0,caretWidth-1,0);
2109         gc.drawLine(caretWidth-1,1,1,1);
2110         gc.dispose();
2111 }
2112 /**
2113  * Moves the selected text to the clipboard.  The text will be put in the
2114  * clipboard in plain text format and RTF format.
2115  *
2116  * @exception SWTException <ul>
2117  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2118  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2119  * </ul>
2120  */
2121 public void cut() {
2122         checkWidget();
2123         // Abort cut operation if copy to clipboard fails.
2124         // Fixes bug 21030.
2125         if (copySelection(DND.CLIPBOARD)) {
2126                 if (blockSelection && blockXLocation != -1) {
2127                         insertBlockSelectionText((char)0, SWT.NULL);
2128                 } else {
2129                         doDelete();
2130                 }
2131         }
2132 }
2133 /**
2134  * A mouse move event has occurred.  See if we should start autoscrolling.  If
2135  * the move position is outside of the client area, initiate autoscrolling.
2136  * Otherwise, we've moved back into the widget so end autoscrolling.
2137  */
2138 void doAutoScroll(Event event) {
2139         int caretLine = getCaretLine();
2140         if (event.y > clientAreaHeight - bottomMargin && caretLine != content.getLineCount() - 1) {
2141                 doAutoScroll(SWT.DOWN, event.y - (clientAreaHeight - bottomMargin));
2142         } else if (event.y < topMargin && caretLine != 0) {
2143                 doAutoScroll(SWT.UP, topMargin - event.y);
2144         } else if (event.x < leftMargin && !wordWrap) {
2145                 doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
2146         } else if (event.x > clientAreaWidth - rightMargin && !wordWrap) {
2147                 doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - rightMargin));
2148         } else {
2149                 endAutoScroll();
2150         }
2151 }
2152 /**
2153  * Initiates autoscrolling.
2154  *
2155  * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
2156  */
2157 void doAutoScroll(int direction, int distance) {
2158         autoScrollDistance = distance;
2159         // If we're already autoscrolling in the given direction do nothing
2160         if (autoScrollDirection == direction) {
2161                 return;
2162         }
2163
2164         Runnable timer = null;
2165         final Display display = getDisplay();
2166         // Set a timer that will simulate the user pressing and holding
2167         // down a cursor key (i.e., arrowUp, arrowDown).
2168         if (direction == SWT.UP) {
2169                 timer = new Runnable() {
2170                         @Override
2171                         public void run() {
2172                                 /* Bug 437357 - NPE in StyledText.getCaretLine
2173                                  * StyledText.content is null at times, probably because the
2174                                  * widget itself has been disposed.
2175                                  */
2176                                 if (isDisposed()) return;
2177                                 if (autoScrollDirection == SWT.UP) {
2178                                         if (blockSelection) {
2179                                                 int verticalScrollOffset = getVerticalScrollOffset();
2180                                                 int y = blockYLocation - verticalScrollOffset;
2181                                                 int pixels = Math.max(-autoScrollDistance, -verticalScrollOffset);
2182                                                 if (pixels != 0) {
2183                                                         setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
2184                                                         scrollVertical(pixels, true);
2185                                                 }
2186                                         } else {
2187                                                 doSelectionPageUp(autoScrollDistance);
2188                                         }
2189                                         display.timerExec(V_SCROLL_RATE, this);
2190                                 }
2191                         }
2192                 };
2193                 autoScrollDirection = direction;
2194                 display.timerExec(V_SCROLL_RATE, timer);
2195         } else if (direction == SWT.DOWN) {
2196                 timer = new Runnable() {
2197                         @Override
2198                         public void run() {
2199                                 /* Bug 437357 - NPE in StyledText.getCaretLine
2200                                  * StyledText.content is null at times, probably because the
2201                                  * widget itself has been disposed.
2202                                  */
2203                                 if (isDisposed()) return;
2204                                 if (autoScrollDirection == SWT.DOWN) {
2205                                         if (blockSelection) {
2206                                                 int verticalScrollOffset = getVerticalScrollOffset();
2207                                                 int y = blockYLocation - verticalScrollOffset;
2208                                                 int max = renderer.getHeight() - verticalScrollOffset - clientAreaHeight;
2209                                                 int pixels = Math.min(autoScrollDistance, Math.max(0,max));
2210                                                 if (pixels != 0) {
2211                                                         setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
2212                                                         scrollVertical(pixels, true);
2213                                                 }
2214                                         } else {
2215                                                 doSelectionPageDown(autoScrollDistance);
2216                                         }
2217                                         display.timerExec(V_SCROLL_RATE, this);
2218                                 }
2219                         }
2220                 };
2221                 autoScrollDirection = direction;
2222                 display.timerExec(V_SCROLL_RATE, timer);
2223         } else if (direction == ST.COLUMN_NEXT) {
2224                 timer = new Runnable() {
2225                         @Override
2226                         public void run() {
2227                                 /* Bug 437357 - NPE in StyledText.getCaretLine
2228                                  * StyledText.content is null at times, probably because the
2229                                  * widget itself has been disposed.
2230                                  */
2231                                 if (isDisposed()) return;
2232                                 if (autoScrollDirection == ST.COLUMN_NEXT) {
2233                                         if (blockSelection) {
2234                                                 int x = blockXLocation - horizontalScrollOffset;
2235                                                 int max = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
2236                                                 int pixels = Math.min(autoScrollDistance, Math.max(0,max));
2237                                                 if (pixels != 0) {
2238                                                         setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
2239                                                         scrollHorizontal(pixels, true);
2240                                                 }
2241                                         } else {
2242                                                 doVisualNext();
2243                                                 setMouseWordSelectionAnchor();
2244                                                 doMouseSelection();
2245                                         }
2246                                         display.timerExec(H_SCROLL_RATE, this);
2247                                 }
2248                         }
2249                 };
2250                 autoScrollDirection = direction;
2251                 display.timerExec(H_SCROLL_RATE, timer);
2252         } else if (direction == ST.COLUMN_PREVIOUS) {
2253                 timer = new Runnable() {
2254                         @Override
2255                         public void run() {
2256                                 /* Bug 437357 - NPE in StyledText.getCaretLine
2257                                  * StyledText.content is null at times, probably because the
2258                                  * widget itself has been disposed.
2259                                  */
2260                                 if (isDisposed()) return;
2261                                 if (autoScrollDirection == ST.COLUMN_PREVIOUS) {
2262                                         if (blockSelection) {
2263                                                 int x = blockXLocation - horizontalScrollOffset;
2264                                                 int pixels = Math.max(-autoScrollDistance, -horizontalScrollOffset);
2265                                                 if (pixels != 0) {
2266                                                         setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
2267                                                         scrollHorizontal(pixels, true);
2268                                                 }
2269                                         } else {
2270                                                 doVisualPrevious();
2271                                                 setMouseWordSelectionAnchor();
2272                                                 doMouseSelection();
2273                                         }
2274                                         display.timerExec(H_SCROLL_RATE, this);
2275                                 }
2276                         }
2277                 };
2278                 autoScrollDirection = direction;
2279                 display.timerExec(H_SCROLL_RATE, timer);
2280         }
2281 }
2282 /**
2283  * Deletes the previous character. Delete the selected text if any.
2284  * Move the caret in front of the deleted text.
2285  */
2286 void doBackspace() {
2287         Event event = new Event();
2288         event.text = "";
2289         if (selection.x != selection.y) {
2290                 event.start = selection.x;
2291                 event.end = selection.y;
2292                 sendKeyEvent(event);
2293         } else if (caretOffset > 0) {
2294                 int lineIndex = content.getLineAtOffset(caretOffset);
2295                 int lineOffset = content.getOffsetAtLine(lineIndex);
2296                 if (caretOffset == lineOffset) {
2297                         lineOffset = content.getOffsetAtLine(lineIndex - 1);
2298                         event.start = lineOffset + content.getLine(lineIndex - 1).length();
2299                         event.end = caretOffset;
2300                 } else {
2301                         boolean isSurrogate = false;
2302                         String lineText = content.getLine(lineIndex);
2303                         char ch = lineText.charAt(caretOffset - lineOffset - 1);
2304                         if (0xDC00 <= ch && ch <= 0xDFFF) {
2305                                 if (caretOffset - lineOffset - 2 >= 0) {
2306                                         ch = lineText.charAt(caretOffset - lineOffset - 2);
2307                                         isSurrogate = 0xD800 <= ch && ch <= 0xDBFF;
2308                                 }
2309                         }
2310                         TextLayout layout = renderer.getTextLayout(lineIndex);
2311                         int start = layout.getPreviousOffset(caretOffset - lineOffset, isSurrogate ? SWT.MOVEMENT_CLUSTER : SWT.MOVEMENT_CHAR);
2312                         renderer.disposeTextLayout(layout);
2313                         event.start = start + lineOffset;
2314                         event.end = caretOffset;
2315                 }
2316                 sendKeyEvent(event);
2317         }
2318 }
2319 void doBlockColumn(boolean next) {
2320         if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2321         int x = blockXLocation - horizontalScrollOffset;
2322         int y = blockYLocation - getVerticalScrollOffset();
2323         int[] trailing = new int[1];
2324         int offset = getOffsetAtPoint(x, y, trailing, true);
2325         if (offset != -1) {
2326                 offset += trailing[0];
2327                 int lineIndex = content.getLineAtOffset(offset);
2328                 int newOffset;
2329                 if (next) {
2330                         newOffset = getClusterNext(offset, lineIndex);
2331                 } else {
2332                         newOffset = getClusterPrevious(offset, lineIndex);
2333                 }
2334                 offset = newOffset != offset ? newOffset : -1;
2335         }
2336         if (offset != -1) {
2337                 setBlockSelectionOffset(offset, true);
2338                 showCaret();
2339         } else {
2340                 int width = next ? renderer.averageCharWidth : -renderer.averageCharWidth;
2341                 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2342                 x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
2343                 setBlockSelectionLocation(x, y, true);
2344                 Rectangle rect = new Rectangle(x, y, 0, 0);
2345                 showLocation(rect, true);
2346         }
2347 }
2348 void doBlockContentStartEnd(boolean end) {
2349         if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2350         int offset = end ? content.getCharCount() : 0;
2351         setBlockSelectionOffset(offset, true);
2352         showCaret();
2353 }
2354 void doBlockWord(boolean next) {
2355         if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2356         int x = blockXLocation - horizontalScrollOffset;
2357         int y = blockYLocation - getVerticalScrollOffset();
2358         int[] trailing = new int[1];
2359         int offset = getOffsetAtPoint(x, y, trailing, true);
2360         if (offset != -1) {
2361                 offset += trailing[0];
2362                 int lineIndex = content.getLineAtOffset(offset);
2363                 int lineOffset = content.getOffsetAtLine(lineIndex);
2364                 String lineText = content.getLine(lineIndex);
2365                 int lineLength = lineText.length();
2366                 int newOffset = offset;
2367                 if (next) {
2368                         if (offset < lineOffset + lineLength) {
2369                                 newOffset = getWordNext(offset, SWT.MOVEMENT_WORD);
2370                         }
2371                 } else {
2372                         if (offset > lineOffset) {
2373                                 newOffset = getWordPrevious(offset, SWT.MOVEMENT_WORD);
2374                         }
2375                 }
2376                 offset = newOffset != offset ? newOffset : -1;
2377         }
2378         if (offset != -1) {
2379                 setBlockSelectionOffset(offset, true);
2380                 showCaret();
2381         } else {
2382                 int width = (next ? renderer.averageCharWidth : -renderer.averageCharWidth) * 6;
2383                 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2384                 x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
2385                 setBlockSelectionLocation(x, y, true);
2386                 Rectangle rect = new Rectangle(x, y, 0, 0);
2387                 showLocation(rect, true);
2388         }
2389 }
2390 void doBlockLineVertical(boolean up) {
2391         if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2392         int y = blockYLocation - getVerticalScrollOffset();
2393         int lineIndex = getLineIndex(y);
2394         if (up) {
2395                 if (lineIndex > 0) {
2396                         y = getLinePixel(lineIndex - 1);
2397                         setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
2398                         if (y < topMargin) {
2399                                 scrollVertical(y - topMargin, true);
2400                         }
2401                 }
2402         } else {
2403                 int lineCount = content.getLineCount();
2404                 if (lineIndex + 1 < lineCount) {
2405                         y = getLinePixel(lineIndex + 2) - 1;
2406                         setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
2407                         int bottom = clientAreaHeight - bottomMargin;
2408                         if (y > bottom) {
2409                                 scrollVertical(y - bottom, true);
2410                         }
2411                 }
2412         }
2413 }
2414 void doBlockLineHorizontal(boolean end) {
2415         if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2416         int x = blockXLocation - horizontalScrollOffset;
2417         int y = blockYLocation - getVerticalScrollOffset();
2418         int lineIndex = getLineIndex(y);
2419         int lineOffset = content.getOffsetAtLine(lineIndex);
2420         String lineText = content.getLine(lineIndex);
2421         int lineLength = lineText.length();
2422         int[] trailing = new int[1];
2423         int offset = getOffsetAtPoint(x, y, trailing, true);
2424         if (offset != -1) {
2425                 offset += trailing[0];
2426                 int newOffset = offset;
2427                 if (end) {
2428                         if (offset < lineOffset + lineLength) {
2429                                 newOffset = lineOffset + lineLength;
2430                         }
2431                 } else {
2432                         if (offset > lineOffset) {
2433                                 newOffset = lineOffset;
2434                         }
2435                 }
2436                 offset = newOffset != offset ? newOffset : -1;
2437         } else {
2438                 if (!end) offset = lineOffset + lineLength;
2439         }
2440         if (offset != -1) {
2441                 setBlockSelectionOffset(offset, true);
2442                 showCaret();
2443         } else {
2444                 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2445                 x = (end ? maxWidth : 0) - horizontalScrollOffset;
2446                 setBlockSelectionLocation(x, y, true);
2447                 Rectangle rect = new Rectangle(x, y, 0, 0);
2448                 showLocation(rect, true);
2449         }
2450 }
2451 void doBlockSelection(boolean sendEvent) {
2452         if (caretOffset > selectionAnchor) {
2453                 selection.x = selectionAnchor;
2454                 selection.y = caretOffset;
2455         } else {
2456                 selection.x = caretOffset;
2457                 selection.y = selectionAnchor;
2458         }
2459         updateCaretVisibility();
2460         setCaretLocation();
2461         super.redraw();
2462         if (sendEvent) {
2463                 sendSelectionEvent();
2464         }
2465         sendAccessibleTextCaretMoved();
2466 }
2467 /**
2468  * Replaces the selection with the character or insert the character at the
2469  * current caret position if no selection exists.
2470  * <p>
2471  * If a carriage return was typed replace it with the line break character
2472  * used by the widget on this platform.
2473  * </p>
2474  *
2475  * @param key the character typed by the user
2476  */
2477 void doContent(char key) {
2478         if (blockSelection && blockXLocation != -1) {
2479                 insertBlockSelectionText(key, SWT.NULL);
2480                 return;
2481         }
2482
2483         Event event = new Event();
2484         event.start = selection.x;
2485         event.end = selection.y;
2486         // replace a CR line break with the widget line break
2487         // CR does not make sense on Windows since most (all?) applications
2488         // don't recognize CR as a line break.
2489         if (key == SWT.CR || key == SWT.LF) {
2490                 if (!isSingleLine()) {
2491                         event.text = getLineDelimiter();
2492                 }
2493         } else if (selection.x == selection.y && overwrite && key != TAB) {
2494                 // no selection and overwrite mode is on and the typed key is not a
2495                 // tab character (tabs are always inserted without overwriting)?
2496                 int lineIndex = content.getLineAtOffset(event.end);
2497                 int lineOffset = content.getOffsetAtLine(lineIndex);
2498                 String line = content.getLine(lineIndex);
2499                 // replace character at caret offset if the caret is not at the
2500                 // end of the line
2501                 if (event.end < lineOffset + line.length()) {
2502                         event.end++;
2503                 }
2504                 event.text = new String(new char[] {key});
2505         } else {
2506                 event.text = new String(new char[] {key});
2507         }
2508         if (event.text != null) {
2509                 if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
2510                         return;
2511                 }
2512                 sendKeyEvent(event);
2513         }
2514 }
2515 /**
2516  * Moves the caret after the last character of the widget content.
2517  */
2518 void doContentEnd() {
2519         // place caret at end of first line if receiver is in single
2520         // line mode. fixes 4820.
2521         if (isSingleLine()) {
2522                 doLineEnd();
2523         } else {
2524                 int length = content.getCharCount();
2525                 setCaretOffset(length, SWT.DEFAULT);
2526                 showCaret();
2527         }
2528 }
2529 /**
2530  * Moves the caret in front of the first character of the widget content.
2531  */
2532 void doContentStart() {
2533         setCaretOffset(0, SWT.DEFAULT);
2534         showCaret();
2535 }
2536 /**
2537  * Moves the caret to the start of the selection if a selection exists.
2538  * Otherwise, if no selection exists move the cursor according to the
2539  * cursor selection rules.
2540  *
2541  * @see #doSelectionCursorPrevious
2542  */
2543 void doCursorPrevious() {
2544         if (selection.y - selection.x > 0) {
2545                 setCaretOffset(selection.x, OFFSET_LEADING);
2546                 showCaret();
2547         } else {
2548                 doSelectionCursorPrevious();
2549         }
2550 }
2551 /**
2552  * Moves the caret to the end of the selection if a selection exists.
2553  * Otherwise, if no selection exists move the cursor according to the
2554  * cursor selection rules.
2555  *
2556  * @see #doSelectionCursorNext
2557  */
2558 void doCursorNext() {
2559         if (selection.y - selection.x > 0) {
2560                 setCaretOffset(selection.y, PREVIOUS_OFFSET_TRAILING);
2561                 showCaret();
2562         } else {
2563                 doSelectionCursorNext();
2564         }
2565 }
2566 /**
2567  * Deletes the next character. Delete the selected text if any.
2568  */
2569 void doDelete() {
2570         Event event = new Event();
2571         event.text = "";
2572         if (selection.x != selection.y) {
2573                 event.start = selection.x;
2574                 event.end = selection.y;
2575                 sendKeyEvent(event);
2576         } else if (caretOffset < content.getCharCount()) {
2577                 int line = content.getLineAtOffset(caretOffset);
2578                 int lineOffset = content.getOffsetAtLine(line);
2579                 int lineLength = content.getLine(line).length();
2580                 if (caretOffset == lineOffset + lineLength) {
2581                         event.start = caretOffset;
2582                         event.end = content.getOffsetAtLine(line + 1);
2583                 } else {
2584                         event.start = caretOffset;
2585                         event.end = getClusterNext(caretOffset, line);
2586                 }
2587                 sendKeyEvent(event);
2588         }
2589 }
2590 /**
2591  * Deletes the next word.
2592  */
2593 void doDeleteWordNext() {
2594         if (selection.x != selection.y) {
2595                 // if a selection exists, treat the as if
2596                 // only the delete key was pressed
2597                 doDelete();
2598         } else {
2599                 Event event = new Event();
2600                 event.text = "";
2601                 event.start = caretOffset;
2602                 event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
2603                 sendKeyEvent(event);
2604         }
2605 }
2606 /**
2607  * Deletes the previous word.
2608  */
2609 void doDeleteWordPrevious() {
2610         if (selection.x != selection.y) {
2611                 // if a selection exists, treat as if
2612                 // only the backspace key was pressed
2613                 doBackspace();
2614         } else {
2615                 Event event = new Event();
2616                 event.text = "";
2617                 event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
2618                 event.end = caretOffset;
2619                 sendKeyEvent(event);
2620         }
2621 }
2622 /**
2623  * Moves the caret one line down and to the same character offset relative
2624  * to the beginning of the line. Move the caret to the end of the new line
2625  * if the new line is shorter than the character offset. Moves the caret to
2626  * the end of the text if the caret already is on the last line.
2627  */
2628 void doLineDown(boolean select) {
2629         int caretLine = getCaretLine();
2630         int lineCount = content.getLineCount();
2631         int y = 0;
2632         boolean lastLine = false;
2633         if (isWordWrap()) {
2634                 int lineOffset = content.getOffsetAtLine(caretLine);
2635                 int offsetInLine = caretOffset - lineOffset;
2636                 TextLayout layout = renderer.getTextLayout(caretLine);
2637                 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2638                 int layoutLineCount = layout.getLineCount();
2639                 if (lineIndex == layoutLineCount - 1) {
2640                         lastLine = caretLine == lineCount - 1;
2641                         caretLine++;
2642                 } else {
2643                         y = layout.getLineBounds(lineIndex + 1).y;
2644                         y++; // bug 485722: workaround for fractional line heights
2645                 }
2646                 renderer.disposeTextLayout(layout);
2647         } else {
2648                 lastLine = caretLine == lineCount - 1;
2649                 caretLine++;
2650         }
2651         if (lastLine) {
2652                 setCaretOffset(content.getCharCount(), SWT.DEFAULT);
2653         } else {
2654                 int[] alignment = new int[1];
2655                 int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
2656                 setCaretOffset(offset, alignment[0]);
2657         }
2658         int oldColumnX = columnX;
2659         int oldHScrollOffset = horizontalScrollOffset;
2660         if (select) {
2661                 setMouseWordSelectionAnchor();
2662                 // select first and then scroll to reduce flash when key
2663                 // repeat scrolls lots of lines
2664                 doSelection(ST.COLUMN_NEXT);
2665         }
2666         showCaret();
2667         int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2668         columnX = oldColumnX + hScrollChange;
2669 }
2670 /**
2671  * Moves the caret to the end of the line.
2672  */
2673 void doLineEnd() {
2674         int caretLine = getCaretLine();
2675         int lineOffset = content.getOffsetAtLine(caretLine);
2676         int lineEndOffset;
2677         if (isWordWrap()) {
2678                 TextLayout layout = renderer.getTextLayout(caretLine);
2679                 int offsetInLine = caretOffset - lineOffset;
2680                 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2681                 int[] offsets = layout.getLineOffsets();
2682                 lineEndOffset = lineOffset + offsets[lineIndex + 1];
2683                 renderer.disposeTextLayout(layout);
2684         } else {
2685                 int lineLength = content.getLine(caretLine).length();
2686                 lineEndOffset = lineOffset + lineLength;
2687         }
2688         setCaretOffset(lineEndOffset, PREVIOUS_OFFSET_TRAILING);
2689         showCaret();
2690 }
2691 /**
2692  * Moves the caret to the beginning of the line.
2693  */
2694 void doLineStart() {
2695         int caretLine = getCaretLine();
2696         int lineOffset = content.getOffsetAtLine(caretLine);
2697         if (isWordWrap()) {
2698                 TextLayout layout = renderer.getTextLayout(caretLine);
2699                 int offsetInLine = caretOffset - lineOffset;
2700                 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2701                 int[] offsets = layout.getLineOffsets();
2702                 lineOffset += offsets[lineIndex];
2703                 renderer.disposeTextLayout(layout);
2704         }
2705         setCaretOffset(lineOffset, OFFSET_LEADING);
2706         showCaret();
2707 }
2708 /**
2709  * Moves the caret one line up and to the same character offset relative
2710  * to the beginning of the line. Move the caret to the end of the new line
2711  * if the new line is shorter than the character offset. Moves the caret to
2712  * the beginning of the document if it is already on the first line.
2713  */
2714 void doLineUp(boolean select) {
2715         int caretLine = getCaretLine(), y = 0;
2716         boolean firstLine = false;
2717         if (isWordWrap()) {
2718                 int lineOffset = content.getOffsetAtLine(caretLine);
2719                 int offsetInLine = caretOffset - lineOffset;
2720                 TextLayout layout = renderer.getTextLayout(caretLine);
2721                 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2722                 if (lineIndex == 0) {
2723                         firstLine = caretLine == 0;
2724                         if (!firstLine) {
2725                                 caretLine--;
2726                                 y = renderer.getLineHeight(caretLine) - 1;
2727                                 y--; // bug 485722: workaround for fractional line heights
2728                         }
2729                 } else {
2730                         y = layout.getLineBounds(lineIndex - 1).y;
2731                         y++; // bug 485722: workaround for fractional line heights
2732                 }
2733                 renderer.disposeTextLayout(layout);
2734         } else {
2735                 firstLine = caretLine == 0;
2736                 caretLine--;
2737         }
2738         if (firstLine) {
2739                 setCaretOffset(0, SWT.DEFAULT);
2740         } else {
2741                 int[] alignment = new int[1];
2742                 int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
2743                 setCaretOffset(offset, alignment[0]);
2744         }
2745         int oldColumnX = columnX;
2746         int oldHScrollOffset = horizontalScrollOffset;
2747         if (select) setMouseWordSelectionAnchor();
2748         showCaret();
2749         if (select) doSelection(ST.COLUMN_PREVIOUS);
2750         int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2751         columnX = oldColumnX + hScrollChange;
2752 }
2753 void doMouseLinkCursor() {
2754         Display display = getDisplay();
2755         Point point = display.getCursorLocation();
2756         point = display.map(null, this, point);
2757         doMouseLinkCursor(point.x, point.y);
2758 }
2759 void doMouseLinkCursor(int x, int y) {
2760         int offset = getOffsetAtPoint(x, y, null, true);
2761         Display display = getDisplay();
2762         Cursor newCursor = cursor;
2763         if (renderer.hasLink(offset)) {
2764                 newCursor = display.getSystemCursor(SWT.CURSOR_HAND);
2765         } else {
2766                 if (cursor == null) {
2767                         int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
2768                         newCursor = display.getSystemCursor(type);
2769                 }
2770         }
2771         if (newCursor != getCursor()) super.setCursor(newCursor);
2772 }
2773 /**
2774  * Moves the caret to the specified location.
2775  *
2776  * @param x x location of the new caret position
2777  * @param y y location of the new caret position
2778  * @param select the location change is a selection operation.
2779  *      include the line delimiter in the selection
2780  */
2781 void doMouseLocationChange(int x, int y, boolean select) {
2782         int line = getLineIndex(y);
2783
2784         updateCaretDirection = true;
2785
2786         if (blockSelection) {
2787                 x = Math.max(leftMargin, Math.min(x, clientAreaWidth - rightMargin));
2788                 y = Math.max(topMargin, Math.min(y, clientAreaHeight - bottomMargin));
2789                 if (doubleClickEnabled && clickCount > 1) {
2790                         boolean wordSelect = (clickCount & 1) == 0;
2791                         if (wordSelect) {
2792                                 Point left = getPointAtOffset(doubleClickSelection.x);
2793                                 int[] trailing = new int[1];
2794                                 int offset = getOffsetAtPoint(x, y, trailing, true);
2795                                 if (offset != -1) {
2796                                         if (x > left.x) {
2797                                                 offset = getWordNext(offset + trailing[0], SWT.MOVEMENT_WORD_END);
2798                                                 setBlockSelectionOffset(doubleClickSelection.x, offset, true);
2799                                         } else {
2800                                                 offset = getWordPrevious(offset + trailing[0], SWT.MOVEMENT_WORD_START);
2801                                                 setBlockSelectionOffset(doubleClickSelection.y, offset, true);
2802                                         }
2803                                 } else {
2804                                         if (x > left.x) {
2805                                                 setBlockSelectionLocation(left.x, left.y, x, y, true);
2806                                         } else {
2807                                                 Point right = getPointAtOffset(doubleClickSelection.y);
2808                                                 setBlockSelectionLocation(right.x, right.y, x, y, true);
2809                                         }
2810                                 }
2811                         } else {
2812                                 setBlockSelectionLocation(blockXLocation, y, true);
2813                         }
2814                         return;
2815                 } else {
2816                         if (select) {
2817                                 if (blockXLocation == -1) {
2818                                         setBlockSelectionOffset(caretOffset, false);
2819                                 }
2820                         } else {
2821                                 clearBlockSelection(true, false);
2822                         }
2823                         int[] trailing = new int[1];
2824                         int offset = getOffsetAtPoint(x, y, trailing, true);
2825                         if (offset != -1) {
2826                                 if (select) {
2827                                         setBlockSelectionOffset(offset + trailing[0], true);
2828                                         return;
2829                                 }
2830                         } else {
2831                                 if (isFixedLineHeight() && renderer.fixedPitch) {
2832                                         int avg = renderer.averageCharWidth;
2833                                         x = ((x + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
2834                                 }
2835                                 setBlockSelectionLocation(x, y, true);
2836                                 return;
2837                         }
2838                 }
2839         }
2840
2841         // allow caret to be placed below first line only if receiver is
2842         // not in single line mode. fixes 4820.
2843         if (line < 0 || (isSingleLine() && line > 0)) {
2844                 return;
2845         }
2846         int[] alignment = new int[1];
2847         int newCaretOffset = getOffsetAtPoint(x, y, alignment);
2848         int newCaretAlignemnt = alignment[0];
2849
2850         if (doubleClickEnabled && clickCount > 1) {
2851                 newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
2852         }
2853
2854         int newCaretLine = content.getLineAtOffset(newCaretOffset);
2855
2856         // Is the mouse within the left client area border or on
2857         // a different line? If not the autoscroll selection
2858         // could be incorrectly reset. Fixes 1GKM3XS
2859         boolean vchange = 0 <= y && y < clientAreaHeight || newCaretLine == 0 || newCaretLine == content.getLineCount() - 1;
2860         boolean hchange = 0 <= x && x < clientAreaWidth || wordWrap || newCaretLine != content.getLineAtOffset(caretOffset);
2861         if (vchange && hchange && (newCaretOffset != caretOffset || newCaretAlignemnt != caretAlignment)) {
2862                 setCaretOffset(newCaretOffset, newCaretAlignemnt);
2863                 if (select) doMouseSelection();
2864                 showCaret();
2865         }
2866         if (!select) {
2867                 setCaretOffset(newCaretOffset, newCaretAlignemnt);
2868                 clearSelection(true);
2869         }
2870 }
2871 /**
2872  * Updates the selection based on the caret position
2873  */
2874 void doMouseSelection() {
2875         if (caretOffset <= selection.x ||
2876                 (caretOffset > selection.x &&
2877                  caretOffset < selection.y && selectionAnchor == selection.x)) {
2878                 doSelection(ST.COLUMN_PREVIOUS);
2879         } else {
2880                 doSelection(ST.COLUMN_NEXT);
2881         }
2882 }
2883 /**
2884  * Returns the offset of the word at the specified offset.
2885  * If the current selection extends from high index to low index
2886  * (i.e., right to left, or caret is at left border of selection on
2887  * non-bidi platforms) the start offset of the word preceding the
2888  * selection is returned. If the current selection extends from
2889  * low index to high index the end offset of the word following
2890  * the selection is returned.
2891  *
2892  * @param x mouse x location
2893  * @param newCaretOffset caret offset of the mouse cursor location
2894  * @param line line index of the mouse cursor location
2895  */
2896 int doMouseWordSelect(int x, int newCaretOffset, int line) {
2897         // flip selection anchor based on word selection direction from
2898         // base double click. Always do this here (and don't rely on doAutoScroll)
2899         // because auto scroll only does not cover all possible mouse selections
2900         // (e.g., mouse x < 0 && mouse y > caret line y)
2901         if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) {
2902                 selectionAnchor = doubleClickSelection.y;
2903         } else if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) {
2904                 selectionAnchor = doubleClickSelection.x;
2905         }
2906         if (0 <= x && x < clientAreaWidth) {
2907                 boolean wordSelect = (clickCount & 1) == 0;
2908                 if (caretOffset == selection.x) {
2909                         if (wordSelect) {
2910                                 newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START);
2911                         } else {
2912                                 newCaretOffset = content.getOffsetAtLine(line);
2913                         }
2914                 } else {
2915                         if (wordSelect) {
2916                                 newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END);
2917                         } else {
2918                                 int lineEnd = content.getCharCount();
2919                                 if (line + 1 < content.getLineCount()) {
2920                                         lineEnd = content.getOffsetAtLine(line + 1);
2921                                 }
2922                                 newCaretOffset = lineEnd;
2923                         }
2924                 }
2925         }
2926         return newCaretOffset;
2927 }
2928 /**
2929  * Scrolls one page down so that the last line (truncated or whole)
2930  * of the current page becomes the fully visible top line.
2931  * <p>
2932  * The caret is scrolled the same number of lines so that its location
2933  * relative to the top line remains the same. The exception is the end
2934  * of the text where a full page scroll is not possible. In this case
2935  * the caret is moved after the last character.
2936  * </p>
2937  *
2938  * @param select whether or not to select the page
2939  */
2940 void doPageDown(boolean select, int height) {
2941         if (isSingleLine()) return;
2942         int oldColumnX = columnX;
2943         int oldHScrollOffset = horizontalScrollOffset;
2944         if (isFixedLineHeight()) {
2945                 int lineCount = content.getLineCount();
2946                 int caretLine = getCaretLine();
2947                 if (caretLine < lineCount - 1) {
2948                         int lineHeight = renderer.getLineHeight();
2949                         int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
2950                         int scrollLines = Math.min(lineCount - caretLine - 1, lines);
2951                         // ensure that scrollLines never gets negative and at least one
2952                         // line is scrolled. fixes bug 5602.
2953                         scrollLines = Math.max(1, scrollLines);
2954                         int[] alignment = new int[1];
2955                         int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines), alignment);
2956                         setCaretOffset(offset, alignment[0]);
2957                         if (select) {
2958                                 doSelection(ST.COLUMN_NEXT);
2959                         }
2960                         // scroll one page down or to the bottom
2961                         int verticalMaximum = lineCount * getVerticalIncrement();
2962                         int pageSize = clientAreaHeight;
2963                         int verticalScrollOffset = getVerticalScrollOffset();
2964                         int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
2965                         if (scrollOffset + pageSize > verticalMaximum) {
2966                                 scrollOffset = verticalMaximum - pageSize;
2967                         }
2968                         if (scrollOffset > verticalScrollOffset) {
2969                                 scrollVertical(scrollOffset - verticalScrollOffset, true);
2970                         }
2971                 }
2972         } else {
2973                 int lineCount = content.getLineCount();
2974                 int caretLine = getCaretLine();
2975                 int lineIndex, lineHeight;
2976                 if (height == -1) {
2977                         lineIndex = getPartialBottomIndex();
2978                         int topY = getLinePixel(lineIndex);
2979                         lineHeight = renderer.getLineHeight(lineIndex);
2980                         height = topY;
2981                         if (topY + lineHeight <= clientAreaHeight) {
2982                                 height += lineHeight;
2983                         } else {
2984                                 if (isWordWrap()) {
2985                                         TextLayout layout = renderer.getTextLayout(lineIndex);
2986                                         int y = clientAreaHeight - topY;
2987                                         for (int i = 0; i < layout.getLineCount(); i++) {
2988                                                 Rectangle bounds = layout.getLineBounds(i);
2989                                                 if (bounds.contains(bounds.x, y)) {
2990                                                         height += bounds.y;
2991                                                         break;
2992                                                 }
2993                                         }
2994                                         renderer.disposeTextLayout(layout);
2995                                 }
2996                         }
2997                 } else {
2998                         lineIndex = getLineIndex(height);
2999                         int topLineY = getLinePixel(lineIndex);
3000                         if (isWordWrap()) {
3001                                 TextLayout layout = renderer.getTextLayout(lineIndex);
3002                                 int y = height - topLineY;
3003                                 for (int i = 0; i < layout.getLineCount(); i++) {
3004                                         Rectangle bounds = layout.getLineBounds(i);
3005                                         if (bounds.contains(bounds.x, y)) {
3006                                                 height = topLineY + bounds.y + bounds.height;
3007                                                 break;
3008                                         }
3009                                 }
3010                                 renderer.disposeTextLayout(layout);
3011                         } else {
3012                                 height = topLineY + renderer.getLineHeight(lineIndex);
3013                         }
3014                 }
3015                 int caretHeight = height;
3016                 if (isWordWrap()) {
3017                         TextLayout layout = renderer.getTextLayout(caretLine);
3018                         int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
3019                         lineIndex = getVisualLineIndex(layout, offsetInLine);
3020                         caretHeight += layout.getLineBounds(lineIndex).y;
3021                         renderer.disposeTextLayout(layout);
3022                 }
3023                 lineIndex = caretLine;
3024                 lineHeight = renderer.getLineHeight(lineIndex);
3025                 while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
3026                         caretHeight -= lineHeight;
3027                         lineHeight = renderer.getLineHeight(++lineIndex);
3028                 }
3029                 int[] alignment = new int[1];
3030                 int offset = getOffsetAtPoint(columnX, caretHeight, lineIndex, alignment);
3031                 setCaretOffset(offset, alignment[0]);
3032                 if (select) doSelection(ST.COLUMN_NEXT);
3033                 height = getAvailableHeightBellow(height);
3034                 scrollVertical(height, true);
3035                 if (height == 0) setCaretLocation();
3036         }
3037         showCaret();
3038         int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
3039         columnX = oldColumnX + hScrollChange;
3040 }
3041 /**
3042  * Moves the cursor to the end of the last fully visible line.
3043  */
3044 void doPageEnd() {
3045         // go to end of line if in single line mode. fixes 5673
3046         if (isSingleLine()) {
3047                 doLineEnd();
3048         } else {
3049                 int bottomOffset;
3050                 if (isWordWrap()) {
3051                         int lineIndex = getPartialBottomIndex();
3052                         TextLayout layout = renderer.getTextLayout(lineIndex);
3053                         int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);
3054                         int index = layout.getLineCount() - 1;
3055                         while (index >= 0) {
3056                                 Rectangle bounds = layout.getLineBounds(index);
3057                                 if (y >= bounds.y + bounds.height) break;
3058                                 index--;
3059                         }
3060                         if (index == -1 && lineIndex > 0) {
3061                                 bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length();
3062                         } else {
3063                                 bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1);
3064                         }
3065                         renderer.disposeTextLayout(layout);
3066                 } else {
3067                         int lineIndex = getBottomIndex();
3068                         bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length();
3069                 }
3070                 if (caretOffset < bottomOffset) {
3071                         setCaretOffset(bottomOffset, OFFSET_LEADING);
3072                         showCaret();
3073                 }
3074         }
3075 }
3076 /**
3077  * Moves the cursor to the beginning of the first fully visible line.
3078  */
3079 void doPageStart() {
3080         int topOffset;
3081         if (isWordWrap()) {
3082                 int y, lineIndex;
3083                 if (topIndexY > 0) {
3084                         lineIndex = topIndex - 1;
3085                         y = renderer.getLineHeight(lineIndex) - topIndexY;
3086                 } else {
3087                         lineIndex = topIndex;
3088                         y = -topIndexY;
3089                 }
3090                 TextLayout layout = renderer.getTextLayout(lineIndex);
3091                 int index = 0;
3092                 int lineCount = layout.getLineCount();
3093                 while (index < lineCount) {
3094                         Rectangle bounds = layout.getLineBounds(index);
3095                         if (y <= bounds.y) break;
3096                         index++;
3097                 }
3098                 if (index == lineCount) {
3099                         topOffset = content.getOffsetAtLine(lineIndex + 1);
3100                 } else {
3101                         topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
3102                 }
3103                 renderer.disposeTextLayout(layout);
3104         } else {
3105                 topOffset = content.getOffsetAtLine(topIndex);
3106         }
3107         if (caretOffset > topOffset) {
3108                 setCaretOffset(topOffset, OFFSET_LEADING);
3109                 showCaret();
3110         }
3111 }
3112 /**
3113  * Scrolls one page up so that the first line (truncated or whole)
3114  * of the current page becomes the fully visible last line.
3115  * The caret is scrolled the same number of lines so that its location
3116  * relative to the top line remains the same. The exception is the beginning
3117  * of the text where a full page scroll is not possible. In this case the
3118  * caret is moved in front of the first character.
3119  */
3120 void doPageUp(boolean select, int height) {
3121         if (isSingleLine()) return;
3122         int oldHScrollOffset = horizontalScrollOffset;
3123         int oldColumnX = columnX;
3124         if (isFixedLineHeight()) {
3125                 int caretLine = getCaretLine();
3126                 if (caretLine > 0) {
3127                         int lineHeight = renderer.getLineHeight();
3128                         int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
3129                         int scrollLines = Math.max(1, Math.min(caretLine, lines));
3130                         caretLine -= scrollLines;
3131                         int[] alignment = new int[1];
3132                         int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine), alignment);
3133                         setCaretOffset(offset, alignment[0]);
3134                         if (select) {
3135                                 doSelection(ST.COLUMN_PREVIOUS);
3136                         }
3137                         int verticalScrollOffset = getVerticalScrollOffset();
3138                         int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
3139                         if (scrollOffset < verticalScrollOffset) {
3140                                 scrollVertical(scrollOffset - verticalScrollOffset, true);
3141                         }
3142                 }
3143         } else {
3144                 int caretLine = getCaretLine();
3145                 int lineHeight, lineIndex;
3146                 if (height == -1) {
3147                         if (topIndexY == 0) {
3148                                 height = clientAreaHeight;
3149                         } else {
3150                                 int y;
3151                                 if (topIndex > 0) {
3152                                         lineIndex = topIndex - 1;
3153                                         lineHeight = renderer.getLineHeight(lineIndex);
3154                                         height = clientAreaHeight - topIndexY;
3155                                         y = lineHeight - topIndexY;
3156                                 } else {
3157                                         lineIndex = topIndex;
3158                                         lineHeight = renderer.getLineHeight(lineIndex);
3159                                         height = clientAreaHeight - (lineHeight + topIndexY);
3160                                         y = -topIndexY;
3161                                 }
3162                                 if (isWordWrap()) {
3163                                         TextLayout layout = renderer.getTextLayout(lineIndex);
3164                                         for (int i = 0; i < layout.getLineCount(); i++) {
3165                                                 Rectangle bounds = layout.getLineBounds(i);
3166                                                 if (bounds.contains(bounds.x, y)) {
3167                                                         height += lineHeight - (bounds.y + bounds.height);
3168                                                         break;
3169                                                 }
3170                                         }
3171                                         renderer.disposeTextLayout(layout);
3172                                 }
3173                         }
3174                 } else {
3175                         lineIndex = getLineIndex(clientAreaHeight - height);
3176                         int topLineY = getLinePixel(lineIndex);
3177                         if (isWordWrap()) {
3178                                 TextLayout layout = renderer.getTextLayout(lineIndex);
3179                                 int y = topLineY;
3180                                 for (int i = 0; i < layout.getLineCount(); i++) {
3181                                         Rectangle bounds = layout.getLineBounds(i);
3182                                         if (bounds.contains(bounds.x, y)) {
3183                                                 height = clientAreaHeight - (topLineY + bounds.y);
3184                                                 break;
3185                                         }
3186                                 }
3187                                 renderer.disposeTextLayout(layout);
3188                         } else {
3189                                 height = clientAreaHeight - topLineY;
3190                         }
3191                 }
3192                 int caretHeight = height;
3193                 if (isWordWrap()) {
3194                         TextLayout layout = renderer.getTextLayout(caretLine);
3195                         int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
3196                         lineIndex = getVisualLineIndex(layout, offsetInLine);
3197                         caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
3198                         renderer.disposeTextLayout(layout);
3199                 }
3200                 lineIndex = caretLine;
3201                 lineHeight = renderer.getLineHeight(lineIndex);
3202                 while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
3203                         caretHeight -= lineHeight;
3204                         lineHeight = renderer.getLineHeight(--lineIndex);
3205                 }
3206                 lineHeight = renderer.getLineHeight(lineIndex);
3207                 int[] alignment = new int[1];
3208                 int offset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex, alignment);
3209                 setCaretOffset(offset, alignment[0]);
3210                 if (select) doSelection(ST.COLUMN_PREVIOUS);
3211                 height = getAvailableHeightAbove(height);
3212                 scrollVertical(-height, true);
3213                 if (height == 0) setCaretLocation();
3214         }
3215         showCaret();
3216         int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
3217         columnX = oldColumnX + hScrollChange;
3218 }
3219 /**
3220  * Updates the selection to extend to the current caret position.
3221  */
3222 void doSelection(int direction) {
3223         int redrawStart = -1;
3224         int redrawEnd = -1;
3225         if (selectionAnchor == -1) {
3226                 selectionAnchor = selection.x;
3227         }
3228         if (direction == ST.COLUMN_PREVIOUS) {
3229                 if (caretOffset < selection.x) {
3230                         // grow selection
3231                         redrawEnd = selection.x;
3232                         redrawStart = selection.x = caretOffset;
3233                         // check if selection has reversed direction
3234                         if (selection.y != selectionAnchor) {
3235                                 redrawEnd = selection.y;
3236                                 selection.y = selectionAnchor;
3237                         }
3238                 // test whether selection actually changed. Fixes 1G71EO1
3239                 } else if (selectionAnchor == selection.x && caretOffset < selection.y) {
3240                         // caret moved towards selection anchor (left side of selection).
3241                         // shrink selection
3242                         redrawEnd = selection.y;
3243                         redrawStart = selection.y = caretOffset;
3244                 }
3245         } else {
3246                 if (caretOffset > selection.y) {
3247                         // grow selection
3248                         redrawStart = selection.y;
3249                         redrawEnd = selection.y = caretOffset;
3250                         // check if selection has reversed direction
3251                         if (selection.x != selectionAnchor) {
3252                                 redrawStart = selection.x;
3253                                 selection.x = selectionAnchor;
3254                         }
3255                 // test whether selection actually changed. Fixes 1G71EO1
3256                 } else if (selectionAnchor == selection.y && caretOffset > selection.x) {
3257                         // caret moved towards selection anchor (right side of selection).
3258                         // shrink selection
3259                         redrawStart = selection.x;
3260                         redrawEnd = selection.x = caretOffset;
3261                 }
3262         }
3263         if (redrawStart != -1 && redrawEnd != -1) {
3264                 internalRedrawRange(redrawStart, redrawEnd - redrawStart);
3265                 sendSelectionEvent();
3266         }
3267         sendAccessibleTextCaretMoved();
3268 }
3269 /**
3270  * Moves the caret to the next character or to the beginning of the
3271  * next line if the cursor is at the end of a line.
3272  */
3273 void doSelectionCursorNext() {
3274         int caretLine = getCaretLine();
3275         int lineOffset = content.getOffsetAtLine(caretLine);
3276         int offsetInLine = caretOffset - lineOffset;
3277         int offset, alignment;
3278         if (offsetInLine < content.getLine(caretLine).length()) {
3279                 TextLayout layout = renderer.getTextLayout(caretLine);
3280                 offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
3281                 int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
3282                 renderer.disposeTextLayout(layout);
3283                 offset = offsetInLine + lineOffset;
3284                 alignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
3285                 setCaretOffset(offset, alignment);
3286                 showCaret();
3287         } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
3288                 caretLine++;
3289                 offset = content.getOffsetAtLine(caretLine);
3290                 alignment = PREVIOUS_OFFSET_TRAILING;
3291                 setCaretOffset(offset, alignment);
3292                 showCaret();
3293         }
3294 }
3295 /**
3296  * Moves the caret to the previous character or to the end of the previous
3297  * line if the cursor is at the beginning of a line.
3298  */
3299 void doSelectionCursorPrevious() {
3300         int caretLine = getCaretLine();
3301         int lineOffset = content.getOffsetAtLine(caretLine);
3302         int offsetInLine = caretOffset - lineOffset;
3303         if (offsetInLine > 0) {
3304                 int offset = getClusterPrevious(caretOffset, caretLine);
3305                 setCaretOffset(offset, OFFSET_LEADING);
3306                 showCaret();
3307         } else if (caretLine > 0) {
3308                 caretLine--;
3309                 lineOffset = content.getOffsetAtLine(caretLine);
3310                 int offset = lineOffset + content.getLine(caretLine).length();
3311                 setCaretOffset(offset, OFFSET_LEADING);
3312                 showCaret();
3313         }
3314 }
3315 /**
3316  * Moves the caret one line down and to the same character offset relative
3317  * to the beginning of the line. Moves the caret to the end of the new line
3318  * if the new line is shorter than the character offset.
3319  * Moves the caret to the end of the text if the caret already is on the
3320  * last line.
3321  * Adjusts the selection according to the caret change. This can either add
3322  * to or subtract from the old selection, depending on the previous selection
3323  * direction.
3324  */
3325 void doSelectionLineDown() {
3326         int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3327         doLineDown(true);
3328         columnX = oldColumnX;
3329 }
3330 /**
3331  * Moves the caret one line up and to the same character offset relative
3332  * to the beginning of the line. Moves the caret to the end of the new line
3333  * if the new line is shorter than the character offset.
3334  * Moves the caret to the beginning of the document if it is already on the
3335  * first line.
3336  * Adjusts the selection according to the caret change. This can either add
3337  * to or subtract from the old selection, depending on the previous selection
3338  * direction.
3339  */
3340 void doSelectionLineUp() {
3341         int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3342         doLineUp(true);
3343         columnX = oldColumnX;
3344 }
3345 /**
3346  * Scrolls one page down so that the last line (truncated or whole)
3347  * of the current page becomes the fully visible top line.
3348  * <p>
3349  * The caret is scrolled the same number of lines so that its location
3350  * relative to the top line remains the same. The exception is the end
3351  * of the text where a full page scroll is not possible. In this case
3352  * the caret is moved after the last character.
3353  * </p><p>
3354  * Adjusts the selection according to the caret change. This can either add
3355  * to or subtract from the old selection, depending on the previous selection
3356  * direction.
3357  * </p>
3358  */
3359 void doSelectionPageDown(int pixels) {
3360         int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3361         doPageDown(true, pixels);
3362         columnX = oldColumnX;
3363 }
3364 /**
3365  * Scrolls one page up so that the first line (truncated or whole)
3366  * of the current page becomes the fully visible last line.
3367  * <p>
3368  * The caret is scrolled the same number of lines so that its location
3369  * relative to the top line remains the same. The exception is the beginning
3370  * of the text where a full page scroll is not possible. In this case the
3371  * caret is moved in front of the first character.
3372  * </p><p>
3373  * Adjusts the selection according to the caret change. This can either add
3374  * to or subtract from the old selection, depending on the previous selection
3375  * direction.
3376  * </p>
3377  */
3378 void doSelectionPageUp(int pixels) {
3379         int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3380         doPageUp(true, pixels);
3381         columnX = oldColumnX;
3382 }
3383 /**
3384  * Moves the caret to the end of the next word .
3385  */
3386 void doSelectionWordNext() {
3387         int offset = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
3388         // don't change caret position if in single line mode and the cursor
3389         // would be on a different line. fixes 5673
3390         if (!isSingleLine() ||
3391                 content.getLineAtOffset(caretOffset) == content.getLineAtOffset(offset)) {
3392                 // Force symmetrical movement for word next and previous. Fixes 14536
3393                 setCaretOffset(offset, OFFSET_LEADING);
3394                 showCaret();
3395         }
3396 }
3397 /**
3398  * Moves the caret to the start of the previous word.
3399  */
3400 void doSelectionWordPrevious() {
3401         int offset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
3402         setCaretOffset(offset, OFFSET_LEADING);
3403         showCaret();
3404 }
3405 /**
3406  * Moves the caret one character to the left.  Do not go to the previous line.
3407  * When in a bidi locale and at a R2L character the caret is moved to the
3408  * beginning of the R2L segment (visually right) and then one character to the
3409  * left (visually left because it's now in a L2R segment).
3410  */
3411 void doVisualPrevious() {
3412         int offset = getClusterPrevious(caretOffset, getCaretLine());
3413         setCaretOffset(offset, SWT.DEFAULT);
3414         showCaret();
3415 }
3416 /**
3417  * Moves the caret one character to the right.  Do not go to the next line.
3418  * When in a bidi locale and at a R2L character the caret is moved to the
3419  * end of the R2L segment (visually left) and then one character to the
3420  * right (visually right because it's now in a L2R segment).
3421  */
3422 void doVisualNext() {
3423         int offset = getClusterNext(caretOffset, getCaretLine());
3424         setCaretOffset(offset, SWT.DEFAULT);
3425         showCaret();
3426 }
3427 /**
3428  * Moves the caret to the end of the next word.
3429  * If a selection exists, move the caret to the end of the selection
3430  * and remove the selection.
3431  */
3432 void doWordNext() {
3433         if (selection.y - selection.x > 0) {
3434                 setCaretOffset(selection.y, SWT.DEFAULT);
3435                 showCaret();
3436         } else {
3437                 doSelectionWordNext();
3438         }
3439 }
3440 /**
3441  * Moves the caret to the start of the previous word.
3442  * If a selection exists, move the caret to the start of the selection
3443  * and remove the selection.
3444  */
3445 void doWordPrevious() {
3446         if (selection.y - selection.x > 0) {
3447                 setCaretOffset(selection.x, SWT.DEFAULT);
3448                 showCaret();
3449         } else {
3450                 doSelectionWordPrevious();
3451         }
3452 }
3453 /**
3454  * Ends the autoscroll process.
3455  */
3456 void endAutoScroll() {
3457         autoScrollDirection = SWT.NULL;
3458 }
3459 @Override
3460 public Color getBackground() {
3461         checkWidget();
3462         if (background == null) {
3463                 return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
3464         }
3465         return background;
3466 }
3467 /**
3468  * Returns the baseline, in points.
3469  *
3470  * Note: this API should not be used if a StyleRange attribute causes lines to
3471  * have different heights (i.e. different fonts, rise, etc).
3472  *
3473  * @return baseline the baseline
3474  * @exception SWTException <ul>
3475  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3476  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3477  * </ul>
3478  * @since 3.0
3479  *
3480  * @see #getBaseline(int)
3481  */
3482 public int getBaseline() {
3483         checkWidget();
3484         return renderer.getBaseline();
3485 }
3486 /**
3487  * Returns the baseline at the given offset, in points.
3488  *
3489  * @param offset the offset
3490  *
3491  * @return baseline the baseline
3492  *
3493  * @exception SWTException <ul>
3494  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3495  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3496  * </ul>
3497  * @exception IllegalArgumentException <ul>
3498  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
3499  * </ul>
3500  *
3501  * @since 3.2
3502  */
3503 public int getBaseline(int offset) {
3504         checkWidget();
3505         if (!(0 <= offset && offset <= content.getCharCount())) {
3506                 SWT.error(SWT.ERROR_INVALID_RANGE);
3507         }
3508         if (isFixedLineHeight()) {
3509                 return renderer.getBaseline();
3510         }
3511         int lineIndex = content.getLineAtOffset(offset);
3512         int lineOffset = content.getOffsetAtLine(lineIndex);
3513         TextLayout layout = renderer.getTextLayout(lineIndex);
3514         int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
3515         FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
3516         renderer.disposeTextLayout(layout);
3517         return metrics.getAscent() + metrics.getLeading();
3518 }
3519 /**
3520  * Gets the BIDI coloring mode.  When true the BIDI text display
3521  * algorithm is applied to segments of text that are the same
3522  * color.
3523  *
3524  * @return the current coloring mode
3525  * @exception SWTException <ul>
3526  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3527  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3528  * </ul>
3529  *
3530  * @deprecated use BidiSegmentListener instead.
3531  */
3532 @Deprecated
3533 public boolean getBidiColoring() {
3534         checkWidget();
3535         return bidiColoring;
3536 }
3537 /**
3538  * Returns whether the widget is in block selection mode.
3539  *
3540  * @return true if widget is in block selection mode, false otherwise
3541  *
3542  * @exception SWTException <ul>
3543  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3544  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3545  * </ul>
3546  *
3547  * @since 3.5
3548  */
3549 public boolean getBlockSelection() {
3550         checkWidget();
3551         return blockSelection;
3552 }
3553 Rectangle getBlockSelectionPosition() {
3554         int firstLine = getLineIndex(blockYAnchor - getVerticalScrollOffset());
3555         int lastLine = getLineIndex(blockYLocation - getVerticalScrollOffset());
3556         if (firstLine > lastLine) {
3557                 int temp = firstLine;
3558                 firstLine = lastLine;
3559                 lastLine = temp;
3560         }
3561         int left = blockXAnchor;
3562         int right = blockXLocation;
3563         if (left > right) {
3564                 left = blockXLocation;
3565                 right = blockXAnchor;
3566         }
3567         return new Rectangle (left - horizontalScrollOffset, firstLine, right - horizontalScrollOffset, lastLine);
3568 }
3569 /**
3570  * Returns the block selection bounds. The bounds is
3571  * relative to the upper left corner of the document.
3572  *
3573  * @return the block selection bounds
3574  *
3575  * @exception SWTException <ul>
3576  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3577  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3578  * </ul>
3579  *
3580  * @since 3.5
3581  */
3582 public Rectangle getBlockSelectionBounds() {
3583         Rectangle rect;
3584         if (blockSelection && blockXLocation != -1) {
3585                 rect = getBlockSelectionRectangle();
3586         } else {
3587                 Point startPoint = getPointAtOffset(selection.x);
3588                 Point endPoint = getPointAtOffset(selection.y);
3589                 int height = getLineHeight(selection.y);
3590                 rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y);
3591                 if (selection.x == selection.y) {
3592                         rect.width = getCaretWidth();
3593                 }
3594         }
3595         rect.x += horizontalScrollOffset;
3596         rect.y += getVerticalScrollOffset();
3597         return rect;
3598 }
3599 Rectangle getBlockSelectionRectangle() {
3600         Rectangle rect = getBlockSelectionPosition();
3601         rect.y = getLinePixel(rect.y);
3602         rect.width = rect.width - rect.x;
3603         rect.height =  getLinePixel(rect.height + 1) - rect.y;
3604         return rect;
3605 }
3606 String getBlockSelectionText(String delimiter) {
3607         Rectangle rect = getBlockSelectionPosition();
3608         int firstLine = rect.y;
3609         int lastLine = rect.height;
3610         int left = rect.x;
3611         int right = rect.width;
3612         StringBuilder buffer = new StringBuilder();
3613         for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
3614                 int start = getOffsetAtPoint(left, 0, lineIndex, null);
3615                 int end = getOffsetAtPoint(right, 0, lineIndex, null);
3616                 if (start > end) {
3617                         int temp = start;
3618                         start = end;
3619                         end = temp;
3620                 }
3621                 String text = content.getTextRange(start, end - start);
3622                 buffer.append(text);
3623                 if (lineIndex < lastLine) buffer.append(delimiter);
3624         }
3625         return buffer.toString();
3626 }
3627 /**
3628  * Returns the index of the last fully visible line.
3629  *
3630  * @return index of the last fully visible line.
3631  */
3632 int getBottomIndex() {
3633         int bottomIndex;
3634         if (isFixedLineHeight()) {
3635                 int lineCount = 1;
3636                 int lineHeight = renderer.getLineHeight();
3637                 if (lineHeight != 0) {
3638                         // calculate the number of lines that are fully visible
3639                         int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
3640                         lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
3641                 }
3642                 bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
3643         } else {
3644                 int clientAreaHeight = this.clientAreaHeight - bottomMargin;
3645                 bottomIndex = getLineIndex(clientAreaHeight);
3646                 if (bottomIndex > 0) {
3647                         int linePixel = getLinePixel(bottomIndex);
3648                         int lineHeight = renderer.getLineHeight(bottomIndex);
3649                         if (linePixel + lineHeight > clientAreaHeight) {
3650                                 if (getLinePixel(bottomIndex - 1) >= topMargin) {
3651                                         bottomIndex--;
3652                                 }
3653                         }
3654                 }
3655         }
3656         return bottomIndex;
3657 }
3658 /**
3659  * Returns the bottom margin.
3660  *
3661  * @return the bottom margin.
3662  * @exception SWTException <ul>
3663  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3664  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3665  * </ul>
3666  *
3667  * @since 3.5
3668  */
3669 public int getBottomMargin() {
3670         checkWidget();
3671         return bottomMargin;
3672 }
3673 Rectangle getBoundsAtOffset(int offset) {
3674         int lineIndex = content.getLineAtOffset(offset);
3675         int lineOffset = content.getOffsetAtLine(lineIndex);
3676         String line = content.getLine(lineIndex);
3677         Rectangle bounds;
3678         if (line.length() != 0) {
3679                 TextLayout layout = renderer.getTextLayout(lineIndex);
3680                 int offsetInLine = Math.min (layout.getText().length(), Math.max (0, offset - lineOffset));
3681                 bounds = layout.getBounds(offsetInLine, offsetInLine);
3682                 if (getListeners(ST.LineGetSegments).length > 0 && caretAlignment == PREVIOUS_OFFSET_TRAILING && offsetInLine != 0) {
3683                         offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
3684                         Point point = layout.getLocation(offsetInLine, true);
3685                         bounds = new Rectangle (point.x, point.y, 0, bounds.height);
3686                 }
3687                 renderer.disposeTextLayout(layout);
3688         } else {
3689                 bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
3690         }
3691         if (offset == caretOffset && !isWordWrap()) {
3692                 int lineEnd = lineOffset + line.length();
3693                 if (offset == lineEnd) {
3694                         bounds.width += getCaretWidth();
3695                 }
3696         }
3697         bounds.x += leftMargin - horizontalScrollOffset;
3698         bounds.y += getLinePixel(lineIndex);
3699         return bounds;
3700 }
3701 /**
3702  * Returns the caret position relative to the start of the text.
3703  *
3704  * @return the caret position relative to the start of the text.
3705  * @exception SWTException <ul>
3706  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3707  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3708  * </ul>
3709  */
3710 public int getCaretOffset() {
3711         checkWidget();
3712         return caretOffset;
3713 }
3714 /**
3715  * Returns the caret width.
3716  *
3717  * @return the caret width, 0 if caret is null.
3718  */
3719 int getCaretWidth() {
3720         Caret caret = getCaret();
3721         if (caret == null) return 0;
3722         return caret.getSize().x;
3723 }
3724 Object getClipboardContent(int clipboardType) {
3725         TextTransfer plainTextTransfer = TextTransfer.getInstance();
3726         return clipboard.getContents(plainTextTransfer, clipboardType);
3727 }
3728 int getClusterNext(int offset, int lineIndex) {
3729         int lineOffset = content.getOffsetAtLine(lineIndex);
3730         TextLayout layout = renderer.getTextLayout(lineIndex);
3731         offset -= lineOffset;
3732         offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
3733         offset += lineOffset;
3734         renderer.disposeTextLayout(layout);
3735         return offset;
3736 }
3737 int getClusterPrevious(int offset, int lineIndex) {
3738         int lineOffset = content.getOffsetAtLine(lineIndex);
3739         TextLayout layout = renderer.getTextLayout(lineIndex);
3740         offset -= lineOffset;
3741         offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
3742         offset += lineOffset;
3743         renderer.disposeTextLayout(layout);
3744         return offset;
3745 }
3746 /**
3747  * Returns the content implementation that is used for text storage.
3748  *
3749  * @return content the user defined content implementation that is used for
3750  * text storage or the default content implementation if no user defined
3751  * content implementation has been set.
3752  * @exception SWTException <ul>
3753  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3754  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3755  * </ul>
3756  */
3757 public StyledTextContent getContent() {
3758         checkWidget();
3759         return content;
3760 }
3761 @Override
3762 public boolean getDragDetect () {
3763         checkWidget ();
3764         return dragDetect;
3765 }
3766 /**
3767  * Returns whether the widget implements double click mouse behavior.
3768  *
3769  * @return true if double clicking a word selects the word, false if double clicks
3770  * have the same effect as regular mouse clicks
3771  * @exception SWTException <ul>
3772  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3773  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3774  * </ul>
3775  */
3776 public boolean getDoubleClickEnabled() {
3777         checkWidget();
3778         return doubleClickEnabled;
3779 }
3780 /**
3781  * Returns whether the widget content can be edited.
3782  *
3783  * @return true if content can be edited, false otherwise
3784  * @exception SWTException <ul>
3785  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3786  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3787  * </ul>
3788  */
3789 public boolean getEditable() {
3790         checkWidget();
3791         return editable;
3792 }
3793 @Override
3794 public Color getForeground() {
3795         checkWidget();
3796         if (foreground == null) {
3797                 return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
3798         }
3799         return foreground;
3800 }
3801 /**
3802  * Returns the horizontal scroll increment.
3803  *
3804  * @return horizontal scroll increment.
3805  */
3806 int getHorizontalIncrement() {
3807         return renderer.averageCharWidth;
3808 }
3809 /**
3810  * Returns the horizontal scroll offset relative to the start of the line.
3811  *
3812  * @return horizontal scroll offset relative to the start of the line,
3813  * measured in character increments starting at 0, if &gt; 0 the content is scrolled
3814  * @exception SWTException <ul>
3815  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3816  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3817  * </ul>
3818  */
3819 public int getHorizontalIndex() {
3820         checkWidget();
3821         return horizontalScrollOffset / getHorizontalIncrement();
3822 }
3823 /**
3824  * Returns the horizontal scroll offset relative to the start of the line.
3825  *
3826  * @return the horizontal scroll offset relative to the start of the line,
3827  * measured in SWT logical point starting at 0, if &gt; 0 the content is scrolled.
3828  * @exception SWTException <ul>
3829  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3830  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3831  * </ul>
3832  */
3833 public int getHorizontalPixel() {
3834         checkWidget();
3835         return horizontalScrollOffset;
3836 }
3837 /**
3838  * Returns the line indentation of the widget.
3839  *
3840  * @return the line indentation
3841  *
3842  * @exception SWTException <ul>
3843  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3844  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3845  * </ul>
3846  *
3847  * @see #getLineIndent(int)
3848  *
3849  * @since 3.2
3850  */
3851 public int getIndent() {
3852         checkWidget();
3853         return indent;
3854 }
3855 /**
3856  * Returns whether the widget justifies lines.
3857  *
3858  * @return whether lines are justified
3859  *
3860  * @exception SWTException <ul>
3861  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3862  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3863  * </ul>
3864  *
3865  * @see #getLineJustify(int)
3866  *
3867  * @since 3.2
3868  */
3869 public boolean getJustify() {
3870         checkWidget();
3871         return justify;
3872 }
3873 /**
3874  * Returns the action assigned to the key.
3875  * Returns SWT.NULL if there is no action associated with the key.
3876  *
3877  * @param key a key code defined in SWT.java or a character.
3878  *      Optionally ORd with a state mask.  Preferred state masks are one or more of
3879  *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
3880  *  differences.  However, there may be cases where using the specific state masks
3881  *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
3882  * @return one of the predefined actions defined in ST.java or SWT.NULL
3883  *      if there is no action associated with the key.
3884  * @exception SWTException <ul>
3885  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3886  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3887  * </ul>
3888  */
3889 public int getKeyBinding(int key) {
3890         checkWidget();
3891         Integer action = keyActionMap.get(key);
3892         return action == null ? SWT.NULL : action.intValue();
3893 }
3894 /**
3895  * Gets the number of characters.
3896  *
3897  * @return number of characters in the widget
3898  * @exception SWTException <ul>
3899  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3900  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3901  * </ul>
3902  */
3903 public int getCharCount() {
3904         checkWidget();
3905         return content.getCharCount();
3906 }
3907 /**
3908  * Returns the line at the given line index without delimiters.
3909  * Index 0 is the first line of the content. When there are not
3910  * any lines, getLine(0) is a valid call that answers an empty string.
3911  * <p>
3912  *
3913  * @param lineIndex index of the line to return.
3914  * @return the line text without delimiters
3915  *
3916  * @exception SWTException <ul>
3917  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3918  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3919  * </ul>
3920  * @exception IllegalArgumentException <ul>
3921  *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (&lt; 0 or &gt;= getLineCount())</li>
3922  * </ul>
3923  * @since 3.4
3924  */
3925 public String getLine(int lineIndex) {
3926         checkWidget();
3927         if (lineIndex < 0 ||
3928                 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
3929                 SWT.error(SWT.ERROR_INVALID_RANGE);
3930         }
3931         return content.getLine(lineIndex);
3932 }
3933 /**
3934  * Returns the alignment of the line at the given index.
3935  *
3936  * @param index the index of the line
3937  *
3938  * @return the line alignment
3939  *
3940  * @exception SWTException <ul>
3941  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3942  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3943  * </ul>
3944  * @exception IllegalArgumentException <ul>
3945  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3946  * </ul>
3947  *
3948  * @see #getAlignment()
3949  *
3950  * @since 3.2
3951  */
3952 public int getLineAlignment(int index) {
3953         checkWidget();
3954         if (index < 0 || index > content.getLineCount()) {
3955                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3956         }
3957         return renderer.getLineAlignment(index, alignment);
3958 }
3959 /**
3960  * Returns the line at the specified offset in the text
3961  * where 0 &lt; offset &lt; getCharCount() so that getLineAtOffset(getCharCount())
3962  * returns the line of the insert location.
3963  *
3964  * @param offset offset relative to the start of the content.
3965  *      0 &lt;= offset &lt;= getCharCount()
3966  * @return line at the specified offset in the text
3967  * @exception SWTException <ul>
3968  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3969  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3970  * </ul>
3971  * @exception IllegalArgumentException <ul>
3972  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
3973  * </ul>
3974  */
3975 public int getLineAtOffset(int offset) {
3976         checkWidget();
3977         if (offset < 0 || offset > getCharCount()) {
3978                 SWT.error(SWT.ERROR_INVALID_RANGE);
3979         }
3980         return content.getLineAtOffset(offset);
3981 }
3982 /**
3983  * Returns the background color of the line at the given index.
3984  * Returns null if a LineBackgroundListener has been set or if no background
3985  * color has been specified for the line. Should not be called if a
3986  * LineBackgroundListener has been set since the listener maintains the
3987  * line background colors.
3988  *
3989  * @param index the index of the line
3990  * @return the background color of the line at the given index.
3991  *
3992  * @exception SWTException <ul>
3993  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3994  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3995  * </ul>
3996  * @exception IllegalArgumentException <ul>
3997  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3998  * </ul>
3999  */
4000 public Color getLineBackground(int index) {
4001         checkWidget();
4002         if (index < 0 || index > content.getLineCount()) {
4003                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4004         }
4005         return isListening(ST.LineGetBackground) ? null : renderer.getLineBackground(index, null);
4006 }
4007 /**
4008  * Returns the bullet of the line at the given index.
4009  *
4010  * @param index the index of the line
4011  *
4012  * @return the line bullet
4013  *
4014  * @exception SWTException <ul>
4015  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4016  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4017  * </ul>
4018  * @exception IllegalArgumentException <ul>
4019  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4020  * </ul>
4021  *
4022  * @since 3.2
4023  */
4024 public Bullet getLineBullet(int index) {
4025         checkWidget();
4026         if (index < 0 || index > content.getLineCount()) {
4027                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4028         }
4029         return isListening(ST.LineGetStyle) ? null : renderer.getLineBullet(index, null);
4030 }
4031 /**
4032  * Returns the line background data for the given line or null if
4033  * there is none.
4034  *
4035  * @param lineOffset offset of the line start relative to the start
4036  *      of the content.
4037  * @param line line to get line background data for
4038  * @return line background data for the given line.
4039  */
4040 StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
4041         return sendLineEvent(ST.LineGetBackground, lineOffset, line);
4042 }
4043 /**
4044  * Gets the number of text lines.
4045  *
4046  * @return the number of lines in the widget
4047  * @exception SWTException <ul>
4048  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4049  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4050  * </ul>
4051  */
4052 public int getLineCount() {
4053         checkWidget();
4054         return content.getLineCount();
4055 }
4056 /**
4057  * Returns the number of lines that can be completely displayed in the
4058  * widget client area.
4059  *
4060  * @return number of lines that can be completely displayed in the widget
4061  *      client area.
4062  */
4063 int getLineCountWhole() {
4064         if (isFixedLineHeight()) {
4065                 int lineHeight = renderer.getLineHeight();
4066                 return lineHeight != 0 ? clientAreaHeight / lineHeight : 1;
4067         }
4068         return getBottomIndex() - topIndex + 1;
4069 }
4070 /**
4071  * Returns the line delimiter used for entering new lines by key down
4072  * or paste operation.
4073  *
4074  * @return line delimiter used for entering new lines by key down
4075  * or paste operation.
4076  * @exception SWTException <ul>
4077  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4078  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4079  * </ul>
4080  */
4081 public String getLineDelimiter() {
4082         checkWidget();
4083         return content.getLineDelimiter();
4084 }
4085 /**
4086  * Returns the line height.
4087  * <p>
4088  * Note: this API should not be used if a StyleRange attribute causes lines to
4089  * have different heights (i.e. different fonts, rise, etc).
4090  * </p>
4091  *
4092  * @return line height in points.
4093  * @exception SWTException <ul>
4094  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4095  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4096  * </ul>
4097  * @see #getLineHeight(int)
4098  */
4099 public int getLineHeight() {
4100         checkWidget();
4101         return renderer.getLineHeight();
4102 }
4103 /**
4104  * Returns the line height at the given offset.
4105  *
4106  * @param offset the offset
4107  *
4108  * @return line height in points
4109  *
4110  * @exception SWTException <ul>
4111  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4112  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4113  * </ul>
4114  * @exception IllegalArgumentException <ul>
4115  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
4116  * </ul>
4117  *
4118  * @since 3.2
4119  */
4120 public int getLineHeight(int offset) {
4121         checkWidget();
4122         if (!(0 <= offset && offset <= content.getCharCount())) {
4123                 SWT.error(SWT.ERROR_INVALID_RANGE);
4124         }
4125         if (isFixedLineHeight()) {
4126                 return renderer.getLineHeight();
4127         }
4128         int lineIndex = content.getLineAtOffset(offset);
4129         int lineOffset = content.getOffsetAtLine(lineIndex);
4130         TextLayout layout = renderer.getTextLayout(lineIndex);
4131         int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
4132         int height = layout.getLineBounds(lineInParagraph).height;
4133         renderer.disposeTextLayout(layout);
4134         return height;
4135 }
4136 /**
4137  * Returns the indentation of the line at the given index.
4138  *
4139  * @param index the index of the line
4140  *
4141  * @return the line indentation
4142  *
4143  * @exception SWTException <ul>
4144  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4145  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4146  * </ul>
4147  * @exception IllegalArgumentException <ul>
4148  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4149  * </ul>
4150  *
4151  * @see #getIndent()
4152  *
4153  * @since 3.2
4154  */
4155 public int getLineIndent(int index) {
4156         checkWidget();
4157         if (index < 0 || index > content.getLineCount()) {
4158                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4159         }
4160         return isListening(ST.LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
4161 }
4162 /**
4163  * Returns the vertical indentation of the line at the given index.
4164  *
4165  * @param index the index of the line
4166  *
4167  * @return the line vertical indentation
4168  *
4169  * @exception SWTException <ul>
4170  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4171  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4172  * </ul>
4173  * @exception IllegalArgumentException <ul>
4174  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4175  * </ul>
4176  *
4177  * @since 3.109
4178  */
4179 public int getLineVerticalIndent(int index) {
4180         checkWidget();
4181         if (index < 0 || index >= content.getLineCount()) {
4182                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4183         }
4184         return isListening(ST.LineGetStyle) ? 0 : renderer.getLineVerticalIndent(index);
4185 }
4186 /**
4187  * Returns whether the line at the given index is justified.
4188  *
4189  * @param index the index of the line
4190  *
4191  * @return whether the line is justified
4192  *
4193  * @exception SWTException <ul>
4194  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4195  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4196  * </ul>
4197  * @exception IllegalArgumentException <ul>
4198  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4199  * </ul>
4200  *
4201  * @see #getJustify()
4202  *
4203  * @since 3.2
4204  */
4205 public boolean getLineJustify(int index) {
4206         checkWidget();
4207         if (index < 0 || index > content.getLineCount()) {
4208                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4209         }
4210         return isListening(ST.LineGetStyle) ? false : renderer.getLineJustify(index, justify);
4211 }
4212 /**
4213  * Returns the line spacing of the widget.
4214  *
4215  * @return the line spacing
4216  *
4217  * @exception SWTException <ul>
4218  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4219  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4220  * </ul>
4221  *
4222  * @since 3.2
4223  */
4224 public int getLineSpacing() {
4225         checkWidget();
4226         return lineSpacing;
4227 }
4228 /**
4229  * Returns the line style data for the given line or null if there is
4230  * none.
4231  * <p>
4232  * If there is a LineStyleListener but it does not set any styles,
4233  * the StyledTextEvent.styles field will be initialized to an empty
4234  * array.
4235  * </p>
4236  *
4237  * @param lineOffset offset of the line start relative to the start of
4238  *      the content.
4239  * @param line line to get line styles for
4240  * @return line style data for the given line. Styles may start before
4241  *      line start and end after line end
4242  */
4243 StyledTextEvent getLineStyleData(int lineOffset, String line) {
4244         return sendLineEvent(ST.LineGetStyle, lineOffset, line);
4245 }
4246 /**
4247  * Returns the top SWT logical point, relative to the client area, of a given line.
4248  * Clamps out of ranges index.
4249  *
4250  * @param lineIndex the line index, the max value is lineCount. If
4251  * lineIndex == lineCount it returns the bottom SWT logical point of the last line.
4252  * It means this function can be used to retrieve the bottom SWT logical point of any line.
4253  *
4254  * @return the top SWT logical point of a given line index
4255  *
4256  * @since 3.2
4257  */
4258 public int getLinePixel(int lineIndex) {
4259         checkWidget();
4260         int lineCount = content.getLineCount();
4261         lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
4262         if (isFixedLineHeight()) {
4263                 int lineHeight = renderer.getLineHeight();
4264                 return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
4265         }
4266         if (lineIndex == topIndex) return topIndexY + topMargin;
4267         int height = topIndexY;
4268         if (lineIndex > topIndex) {
4269                 for (int i = topIndex; i < lineIndex; i++) {
4270                         height += renderer.getLineHeight(i);
4271                 }
4272         } else {
4273                 for (int i = topIndex - 1; i >= lineIndex; i--) {
4274                         height -= renderer.getLineHeight(i);
4275                 }
4276         }
4277         return height + topMargin;
4278 }
4279 /**
4280  * Returns the line index for a y, relative to the client area.
4281  * The line index returned is always in the range 0..lineCount - 1.
4282  *
4283  * @param y the y-coordinate point
4284  *
4285  * @return the line index for a given y-coordinate point
4286  *
4287  * @since 3.2
4288  */
4289 public int getLineIndex(int y) {
4290         checkWidget();
4291         y -= topMargin;
4292         if (isFixedLineHeight()) {
4293                 int lineHeight = renderer.getLineHeight();
4294                 int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
4295                 int lineCount = content.getLineCount();
4296                 lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
4297                 return lineIndex;
4298         }
4299         if (y == topIndexY) return topIndex;
4300         int line = topIndex;
4301         if (y < topIndexY) {
4302                 while (y < topIndexY && line > 0) {
4303                         y += renderer.getLineHeight(--line);
4304                 }
4305         } else {
4306                 int lineCount = content.getLineCount();
4307                 int lineHeight = renderer.getLineHeight(line);
4308                 while (y - lineHeight >= topIndexY && line < lineCount - 1) {
4309                         y -= lineHeight;
4310                         lineHeight = renderer.getLineHeight(++line);
4311                 }
4312         }
4313         return line;
4314 }
4315 /**
4316  * Returns the tab stops of the line at the given <code>index</code>.
4317  *
4318  * @param index the index of the line
4319  *
4320  * @return the tab stops for the line
4321  *
4322  * @exception SWTException <ul>
4323  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4324  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4325  * </ul>
4326  * @exception IllegalArgumentException <ul>
4327  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4328  * </ul>
4329  *
4330  * @see #getTabStops()
4331  *
4332  * @since 3.6
4333  */
4334 public int[] getLineTabStops(int index) {
4335         checkWidget();
4336         if (index < 0 || index > content.getLineCount()) {
4337                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4338         }
4339         if (isListening(ST.LineGetStyle)) return null;
4340         int[] tabs = renderer.getLineTabStops(index, null);
4341         if (tabs == null) tabs = this.tabs;
4342         if (tabs == null) return new int [] {renderer.tabWidth};
4343         int[] result = new int[tabs.length];
4344         System.arraycopy(tabs, 0, result, 0, tabs.length);
4345         return result;
4346 }
4347 /**
4348  * Returns the wrap indentation of the line at the given <code>index</code>.
4349  *
4350  * @param index the index of the line
4351  *
4352  * @return the wrap indentation
4353  *
4354  * @exception SWTException <ul>
4355  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4356  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4357  * </ul>
4358  * @exception IllegalArgumentException <ul>
4359  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4360  * </ul>
4361  *
4362  * @see #getWrapIndent()
4363  *
4364  * @since 3.6
4365  */
4366 public int getLineWrapIndent(int index) {
4367         checkWidget();
4368         if (index < 0 || index > content.getLineCount()) {
4369                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4370         }
4371         return isListening(ST.LineGetStyle) ? 0 : renderer.getLineWrapIndent(index, wrapIndent);
4372 }
4373 /**
4374  * Returns the left margin.
4375  *
4376  * @return the left margin.
4377  * @exception SWTException <ul>
4378  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4379  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4380  * </ul>
4381  *
4382  * @since 3.5
4383  */
4384 public int getLeftMargin() {
4385         checkWidget();
4386         return leftMargin - alignmentMargin;
4387 }
4388 /**
4389  * Returns the x, y location of the upper left corner of the character
4390  * bounding box at the specified offset in the text. The point is
4391  * relative to the upper left corner of the widget client area.
4392  *
4393  * @param offset offset relative to the start of the content.
4394  *      0 &lt;= offset &lt;= getCharCount()
4395  * @return x, y location of the upper left corner of the character
4396  *      bounding box at the specified offset in the text.
4397  * @exception SWTException <ul>
4398  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4399  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4400  * </ul>
4401  * @exception IllegalArgumentException <ul>
4402  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (&lt; 0 or &gt; getCharCount())</li>
4403  * </ul>
4404  */
4405 public Point getLocationAtOffset(int offset) {
4406         checkWidget();
4407         if (offset < 0 || offset > getCharCount()) {
4408                 SWT.error(SWT.ERROR_INVALID_RANGE);
4409         }
4410         return getPointAtOffset(offset);
4411 }
4412 /**
4413  * Returns <code>true</code> if the mouse navigator is enabled.
4414  * When mouse navigator is enabled, the user can navigate through the widget by pressing the middle button and moving the cursor
4415  *
4416  * @return the mouse navigator's enabled state
4417  *
4418  * @exception SWTException <ul>
4419  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4420  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4421  * </ul>
4422  *
4423  * @see #getEnabled
4424  * @since 3.110
4425  */
4426 public boolean getMouseNavigatorEnabled () {
4427         checkWidget ();
4428         return mouseNavigator != null;
4429 }
4430 /**
4431  * Returns the character offset of the first character of the given line.
4432  *
4433  * @param lineIndex index of the line, 0 based relative to the first
4434  *      line in the content. 0 &lt;= lineIndex &lt; getLineCount(), except
4435  *      lineIndex may always be 0
4436  * @return offset offset of the first character of the line, relative to
4437  *      the beginning of the document. The first character of the document is
4438  *      at offset 0.
4439  *  When there are not any lines, getOffsetAtLine(0) is a valid call that
4440  *      answers 0.
4441  * @exception SWTException <ul>
4442  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4443  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4444  * </ul>
4445  * @exception IllegalArgumentException <ul>
4446  *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (&lt; 0 or &gt;= getLineCount())</li>
4447  * </ul>
4448  * @since 2.0
4449  */
4450 public int getOffsetAtLine(int lineIndex) {
4451         checkWidget();
4452         if (lineIndex < 0 ||
4453                 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
4454                 SWT.error(SWT.ERROR_INVALID_RANGE);
4455         }
4456         return content.getOffsetAtLine(lineIndex);
4457 }
4458 /**
4459  * Returns the offset of the character at the given location relative
4460  * to the first character in the document.
4461  * <p>
4462  * The return value reflects the character offset that the caret will
4463  * be placed at if a mouse click occurred at the specified location.
4464  * If the x coordinate of the location is beyond the center of a character
4465  * the returned offset will be behind the character.
4466  * </p>
4467  *
4468  * @param point the origin of character bounding box relative to
4469  *  the origin of the widget client area.
4470  * @return offset of the character at the given location relative
4471  *  to the first character in the document.
4472  * @exception SWTException <ul>
4473  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4474  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4475  * </ul>
4476  * @exception IllegalArgumentException <ul>
4477  *   <li>ERROR_NULL_ARGUMENT when point is null</li>
4478  *   <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
4479  * </ul>
4480  *
4481  * @deprecated Use {@link #getOffsetAtPoint(Point)} instead for better performance
4482  */
4483 @Deprecated
4484 public int getOffsetAtLocation(Point point) {
4485         checkWidget();
4486         if (point == null) {
4487                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
4488         }
4489         int[] trailing = new int[1];
4490         int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
4491         if (offset == -1) {
4492                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4493         }
4494         return offset + trailing[0];
4495 }
4496
4497 /**
4498  * Returns the offset of the character at the given point relative
4499  * to the first character in the document.
4500  * <p>
4501  * The return value reflects the character offset that the caret will
4502  * be placed at if a mouse click occurred at the specified point.
4503  * If the x coordinate of the point is beyond the center of a character
4504  * the returned offset will be behind the character.
4505  * </p>
4506  * Note: This method is functionally similar to {@link #getOffsetAtLocation(Point)} except that
4507  * it does not throw an exception when no character is found and thus performs faster.
4508  *
4509  * @param point the origin of character bounding box relative to
4510  *  the origin of the widget client area.
4511  * @return offset of the character at the given point relative
4512  *  to the first character in the document.
4513  * -1 when there is no character at the specified location.
4514  * @exception SWTException <ul>
4515  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4516  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4517  * </ul>
4518  * @exception IllegalArgumentException <ul>
4519  *   <li>ERROR_NULL_ARGUMENT when point is <code>null</code></li>
4520  * </ul>
4521  *
4522  * @since 3.107
4523  */
4524 public int getOffsetAtPoint(Point point) {
4525         checkWidget();
4526         if (point == null) {
4527                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
4528         }
4529         int[] trailing = new int[1];
4530         int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
4531         return offset != -1 ? offset + trailing[0] : -1;
4532 }
4533
4534 int getOffsetAtPoint(int x, int y, int[] alignment) {
4535         int lineIndex = getLineIndex(y);
4536         y -= getLinePixel(lineIndex);
4537         return getOffsetAtPoint(x, y, lineIndex, alignment);
4538 }
4539 int getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment) {
4540         TextLayout layout = renderer.getTextLayout(lineIndex);
4541         x += horizontalScrollOffset - leftMargin;
4542         int[] trailing = new int[1];
4543         int offsetInLine = layout.getOffset(x, y, trailing);
4544         if (alignment != null) alignment[0] = OFFSET_LEADING;
4545         if (trailing[0] != 0) {
4546                 int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
4547                 int lineStart = layout.getLineOffsets()[lineInParagraph];
4548                 if (offsetInLine + trailing[0] == lineStart) {
4549                         offsetInLine += trailing[0];
4550                         if (alignment != null) alignment[0] = PREVIOUS_OFFSET_TRAILING;
4551                 } else {
4552                         String line = content.getLine(lineIndex);
4553                         int level = 0;
4554                         if (alignment != null) {
4555                                 int offset = offsetInLine;
4556                                 while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
4557                                 if (offset == 0 && Character.isDigit(line.charAt(offset))) {
4558                                         level = isMirrored() ? 1 : 0;
4559                                 } else {
4560                                         level = layout.getLevel(offset) & 0x1;
4561                                 }
4562                         }
4563                         offsetInLine += trailing[0];
4564                         if (alignment != null) {
4565                                 int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
4566                                 if (level != trailingLevel) {
4567                                         alignment[0] = PREVIOUS_OFFSET_TRAILING;
4568                                 } else {
4569                                         alignment[0] = OFFSET_LEADING;
4570                                 }
4571                         }
4572                 }
4573         }
4574         renderer.disposeTextLayout(layout);
4575         return offsetInLine + content.getOffsetAtLine(lineIndex);
4576 }
4577 int getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly) {
4578         if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
4579                 return -1;
4580         }
4581         int bottomIndex = getPartialBottomIndex();
4582         int height = getLinePixel(bottomIndex + 1);
4583         if (inTextOnly && y > height) {
4584                 return -1;
4585         }
4586         int lineIndex = getLineIndex(y);
4587         int lineOffset = content.getOffsetAtLine(lineIndex);
4588         TextLayout layout = renderer.getTextLayout(lineIndex);
4589         x += horizontalScrollOffset - leftMargin;
4590         y -= getLinePixel(lineIndex);
4591         int offset = layout.getOffset(x, y, trailing);
4592         Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
4593         renderer.disposeTextLayout(layout);
4594         if (inTextOnly && !(rect.x  <= x && x <=  rect.x + rect.width)) {
4595                 return -1;
4596         }
4597         return offset + lineOffset;
4598 }
4599 /**
4600  * Returns the orientation of the receiver.
4601  *
4602  * @return the orientation style
4603  *
4604  * @exception SWTException <ul>
4605  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4606  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4607  * </ul>
4608  *
4609  * @since 2.1.2
4610  */
4611 @Override
4612 public int getOrientation () {
4613         return super.getOrientation ();
4614 }
4615 /**
4616  * Returns the index of the last partially visible line.
4617  *
4618  * @return index of the last partially visible line.
4619  */
4620 int getPartialBottomIndex() {
4621         if (isFixedLineHeight()) {
4622                 int lineHeight = renderer.getLineHeight();
4623                 int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
4624                 return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
4625         }
4626         return getLineIndex(clientAreaHeight - bottomMargin);
4627 }
4628 /**
4629  * Returns the index of the first partially visible line.
4630  *
4631  * @return index of the first partially visible line.
4632  */
4633 int getPartialTopIndex() {
4634         if (isFixedLineHeight()) {
4635                 int lineHeight = renderer.getLineHeight();
4636                 return getVerticalScrollOffset() / lineHeight;
4637         }
4638         return topIndexY <= 0 ? topIndex : topIndex - 1;
4639 }
4640 /**
4641  * Returns the content in the specified range using the platform line
4642  * delimiter to separate lines.
4643  *
4644  * @param writer the TextWriter to write line text into
4645  * @return the content in the specified range using the platform line
4646  *      delimiter to separate lines as written by the specified TextWriter.
4647  */
4648 String getPlatformDelimitedText(TextWriter writer) {
4649         int end = writer.getStart() + writer.getCharCount();
4650         int startLine = content.getLineAtOffset(writer.getStart());
4651         int endLine = content.getLineAtOffset(end);
4652         String endLineText = content.getLine(endLine);
4653         int endLineOffset = content.getOffsetAtLine(endLine);
4654
4655         for (int i = startLine; i <= endLine; i++) {
4656                 writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
4657                 if (i < endLine) {
4658                         writer.writeLineDelimiter(PlatformLineDelimiter);
4659                 }
4660         }
4661         if (end > endLineOffset + endLineText.length()) {
4662                 writer.writeLineDelimiter(PlatformLineDelimiter);
4663         }
4664         writer.close();
4665         return writer.toString();
4666 }
4667 /**
4668  * Returns all the ranges of text that have an associated StyleRange.
4669  * Returns an empty array if a LineStyleListener has been set.
4670  * Should not be called if a LineStyleListener has been set since the
4671  * listener maintains the styles.
4672  * <p>
4673  * The ranges array contains start and length pairs.  Each pair refers to
4674  * the corresponding style in the styles array.  For example, the pair
4675  * that starts at ranges[n] with length ranges[n+1] uses the style
4676  * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
4677  * </p>
4678  *
4679  * @return the ranges or an empty array if a LineStyleListener has been set.
4680  *
4681  * @exception SWTException <ul>
4682  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4683  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4684  * </ul>
4685  *
4686  * @since 3.2
4687  *
4688  * @see #getStyleRanges(boolean)
4689  */
4690 public int[] getRanges() {
4691         checkWidget();
4692         if (!isListening(ST.LineGetStyle)) {
4693                 int[] ranges = renderer.getRanges(0, content.getCharCount());
4694                 if (ranges != null) return ranges;
4695         }
4696         return new int[0];
4697 }
4698 /**
4699  * Returns the ranges of text that have an associated StyleRange.
4700  * Returns an empty array if a LineStyleListener has been set.
4701  * Should not be called if a LineStyleListener has been set since the
4702  * listener maintains the styles.
4703  * <p>
4704  * The ranges array contains start and length pairs.  Each pair refers to
4705  * the corresponding style in the styles array.  For example, the pair
4706  * that starts at ranges[n] with length ranges[n+1] uses the style
4707  * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
4708  * </p>
4709  *
4710  * @param start the start offset of the style ranges to return
4711  * @param length the number of style ranges to return
4712  *
4713  * @return the ranges or an empty array if a LineStyleListener has been set.
4714  *
4715  * @exception SWTException <ul>
4716  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4717  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4718  * </ul>
4719  * @exception IllegalArgumentException <ul>
4720  *   <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
4721  * </ul>
4722  *
4723  * @since 3.2
4724  *
4725  * @see #getStyleRanges(int, int, boolean)
4726  */
4727 public int[] getRanges(int start, int length) {
4728         checkWidget();
4729         int contentLength = getCharCount();
4730         int end = start + length;
4731         if (start > end || start < 0 || end > contentLength) {
4732                 SWT.error(SWT.ERROR_INVALID_RANGE);
4733         }
4734         if (!isListening(ST.LineGetStyle)) {
4735                 int[] ranges = renderer.getRanges(start, length);
4736                 if (ranges != null) return ranges;
4737         }
4738         return new int[0];
4739 }
4740 /**
4741  * Returns the right margin.
4742  *
4743  * @return the right margin.
4744  * @exception SWTException <ul>
4745  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4746  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4747  * </ul>
4748  *
4749  * @since 3.5
4750  */
4751 public int getRightMargin() {
4752         checkWidget();
4753         return rightMargin;
4754 }
4755 /**
4756  * Returns the selection.
4757  * <p>
4758  * Text selections are specified in terms of caret positions.  In a text
4759  * widget that contains N characters, there are N+1 caret positions,
4760  * ranging from 0..N
4761  * </p>
4762  *
4763  * @return start and end of the selection, x is the offset of the first
4764  *      selected character, y is the offset after the last selected character.
4765  *  The selection values returned are visual (i.e., x will always always be
4766  *  &lt;= y).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
4767  *  (LtoR), compare the caretOffset to the start and end of the selection
4768  *  (e.g., caretOffset == start of selection implies that the selection is RtoL).
4769  * @see #getSelectionRange
4770  * @exception SWTException <ul>
4771  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4772  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4773  * </ul>
4774  */
4775 public Point getSelection() {
4776         checkWidget();
4777         return new Point(selection.x, selection.y);
4778 }
4779 /**
4780  * Returns the selection.
4781  *
4782  * @return start and length of the selection, x is the offset of the
4783  *      first selected character, relative to the first character of the
4784  *      widget content. y is the length of the selection.
4785  *  The selection values returned are visual (i.e., length will always always be
4786  *  positive).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
4787  *  (LtoR), compare the caretOffset to the start and end of the selection
4788  *  (e.g., caretOffset == start of selection implies that the selection is RtoL).
4789  * @exception SWTException <ul>
4790  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4791  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4792  * </ul>
4793  */
4794 public Point getSelectionRange() {
4795         checkWidget();
4796         return new Point(selection.x, selection.y - selection.x);
4797 }
4798 /**
4799  * Returns the ranges of text that are inside the block selection rectangle.
4800  * <p>
4801  * The ranges array contains start and length pairs. When the receiver is not
4802  * in block selection mode the return arrays contains the start and length of
4803  * the regular selection.
4804  *
4805  * @return the ranges array
4806  *
4807  * @exception SWTException <ul>
4808  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4809  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4810  * </ul>
4811  *
4812  * @since 3.5
4813  */
4814 public int[] getSelectionRanges() {
4815         checkWidget();
4816         if (blockSelection && blockXLocation != -1) {
4817                 Rectangle rect = getBlockSelectionPosition();
4818                 int firstLine = rect.y;
4819                 int lastLine = rect.height;
4820                 int left = rect.x;
4821                 int right = rect.width;
4822                 int[] ranges = new int[(lastLine - firstLine + 1) * 2];
4823                 int index = 0;
4824                 for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
4825                         int start = getOffsetAtPoint(left, 0, lineIndex, null);
4826                         int end = getOffsetAtPoint(right, 0, lineIndex, null);
4827                         if (start > end) {
4828                                 int temp = start;
4829                                 start = end;
4830                                 end = temp;
4831                         }
4832                         ranges[index++] = start;
4833                         ranges[index++] = end - start;
4834                 }
4835                 return ranges;
4836         }
4837         return new int[] {selection.x, selection.y - selection.x};
4838 }
4839 /**
4840  * Returns the receiver's selection background color.
4841  *
4842  * @return the selection background color
4843  *
4844  * @exception SWTException <ul>
4845  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4846  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4847  * </ul>
4848  * @since 2.1
4849  */
4850 public Color getSelectionBackground() {
4851         checkWidget();
4852         if (selectionBackground == null) {
4853                 return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
4854         }
4855         return selectionBackground;
4856 }
4857 /**
4858  * Gets the number of selected characters.
4859  *
4860  * @return the number of selected characters.
4861  * @exception SWTException <ul>
4862  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4863  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4864  * </ul>
4865  */
4866 public int getSelectionCount() {
4867         checkWidget();
4868         if (blockSelection && blockXLocation != -1) {
4869                 return getBlockSelectionText(content.getLineDelimiter()).length();
4870         }
4871         return getSelectionRange().y;
4872 }
4873 /**
4874  * Returns the receiver's selection foreground color.
4875  *
4876  * @return the selection foreground color
4877  *
4878  * @exception SWTException <ul>
4879  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4880  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4881  * </ul>
4882  * @since 2.1
4883  */
4884 public Color getSelectionForeground() {
4885         checkWidget();
4886         if (selectionForeground == null) {
4887                 return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
4888         }
4889         return selectionForeground;
4890 }
4891 /**
4892  * Returns the selected text.
4893  *
4894  * @return selected text, or an empty String if there is no selection.
4895  * @exception SWTException <ul>
4896  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4897  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4898  * </ul>
4899  */
4900 public String getSelectionText() {
4901         checkWidget();
4902         if (blockSelection && blockXLocation != -1) {
4903                 return getBlockSelectionText(content.getLineDelimiter());
4904         }
4905         return content.getTextRange(selection.x, selection.y - selection.x);
4906 }
4907 StyledTextEvent getBidiSegments(int lineOffset, String line) {
4908         if (!isListening(ST.LineGetSegments)) {
4909                 if (!bidiColoring) return null;
4910                 StyledTextEvent event = new StyledTextEvent(content);
4911                 event.segments = getBidiSegmentsCompatibility(line, lineOffset);
4912                 return event;
4913         }
4914         StyledTextEvent event = sendLineEvent(ST.LineGetSegments, lineOffset, line);
4915         if (event == null || event.segments == null || event.segments.length == 0) return null;
4916         int lineLength = line.length();
4917         int[] segments = event.segments;
4918         if (segments[0] > lineLength) {
4919                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4920         }
4921         char[] segmentsChars = event.segmentsChars;
4922         boolean hasSegmentsChars = segmentsChars != null;
4923         for (int i = 1; i < segments.length; i++) {
4924                 if ((hasSegmentsChars ? segments[i] < segments[i - 1] : segments[i] <= segments[i - 1]) || segments[i] > lineLength) {
4925                         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4926                 }
4927         }
4928         if (hasSegmentsChars && !visualWrap) {
4929                 for (int i= 0; i < segmentsChars.length; i++) {
4930                         if (segmentsChars[i] == '\n' || segmentsChars[i] == '\r') {
4931                                 visualWrap = true;
4932                                 break;
4933                         }
4934                 }
4935         }
4936         return event;
4937 }
4938 /**
4939  * @see #getBidiSegments
4940  * Supports deprecated setBidiColoring API. Remove when API is removed.
4941  */
4942 int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
4943         int lineLength = line.length();
4944         StyleRange [] styles = null;
4945         StyledTextEvent event = getLineStyleData(lineOffset, line);
4946         if (event != null) {
4947                 styles = event.styles;
4948         } else {
4949                 styles = renderer.getStyleRanges(lineOffset, lineLength, true);
4950         }
4951         if (styles == null || styles.length == 0) {
4952                 return new int[] {0, lineLength};
4953         }
4954         int k=0, count = 1;
4955         while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) {
4956                 k++;
4957         }
4958         int[] offsets = new int[(styles.length - k) * 2 + 2];
4959         for (int i = k; i < styles.length; i++) {
4960                 StyleRange style = styles[i];
4961                 int styleLineStart = Math.max(style.start - lineOffset, 0);
4962                 int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
4963                 styleLineEnd = Math.min (styleLineEnd, line.length ());
4964                 if (i > 0 && count > 1 &&
4965                         ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
4966                          (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
4967                          style.similarTo(styles[i-1])) {
4968                         offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
4969                         offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
4970                 } else {
4971                         if (styleLineStart > offsets[count - 1]) {
4972                                 offsets[count] = styleLineStart;
4973                                 count++;
4974                         }
4975                         offsets[count] = styleLineEnd;
4976                         count++;
4977                 }
4978         }
4979         // add offset for last non-colored segment in line, if any
4980         if (lineLength > offsets[count-1]) {
4981                 offsets [count] = lineLength;
4982                 count++;
4983         }
4984         if (count == offsets.length) {
4985                 return offsets;
4986         }
4987         int [] result = new int [count];
4988         System.arraycopy (offsets, 0, result, 0, count);
4989         return result;
4990 }
4991 /**
4992  * Returns the style range at the given offset.
4993  * <p>
4994  * Returns null if a LineStyleListener has been set or if a style is not set
4995  * for the offset.
4996  * Should not be called if a LineStyleListener has been set since the
4997  * listener maintains the styles.
4998  * </p>
4999  *
5000  * @param offset the offset to return the style for.
5001  *      0 &lt;= offset &lt; getCharCount() must be true.
5002  * @return a StyleRange with start == offset and length == 1, indicating
5003  *      the style at the given offset. null if a LineStyleListener has been set
5004  *      or if a style is not set for the given offset.
5005  * @exception SWTException <ul>
5006  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5007  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5008  * </ul>
5009  * @exception IllegalArgumentException <ul>
5010  *   <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
5011  * </ul>
5012  */
5013 public StyleRange getStyleRangeAtOffset(int offset) {
5014         checkWidget();
5015         if (offset < 0 || offset >= getCharCount()) {
5016                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
5017         }
5018         if (!isListening(ST.LineGetStyle)) {
5019                 StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
5020                 if (ranges != null) return ranges[0];
5021         }
5022         return null;
5023 }
5024 /**
5025  * Returns the styles.
5026  * <p>
5027  * Returns an empty array if a LineStyleListener has been set.
5028  * Should not be called if a LineStyleListener has been set since the
5029  * listener maintains the styles.
5030  * </p><p>
5031  * Note: Because a StyleRange includes the start and length, the
5032  * same instance cannot occur multiple times in the array of styles.
5033  * If the same style attributes, such as font and color, occur in
5034  * multiple StyleRanges, <code>getStyleRanges(boolean)</code>
5035  * can be used to get the styles without the ranges.
5036  * </p>
5037  *
5038  * @return the styles or an empty array if a LineStyleListener has been set.
5039  *
5040  * @exception SWTException <ul>
5041  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5042  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5043  * </ul>
5044  *
5045  * @see #getStyleRanges(boolean)
5046  */
5047 public StyleRange[] getStyleRanges() {
5048         checkWidget();
5049         return getStyleRanges(0, content.getCharCount(), true);
5050 }
5051 /**
5052  * Returns the styles.
5053  * <p>
5054  * Returns an empty array if a LineStyleListener has been set.
5055  * Should not be called if a LineStyleListener has been set since the
5056  * listener maintains the styles.
5057  * </p><p>
5058  * Note: When <code>includeRanges</code> is true, the start and length
5059  * fields of each StyleRange will be valid, however the StyleRange
5060  * objects may need to be cloned. When <code>includeRanges</code> is
5061  * false, <code>getRanges(int, int)</code> can be used to get the
5062  * associated ranges.
5063  * </p>
5064  *
5065  * @param includeRanges whether the start and length field of the StyleRanges should be set.
5066  *
5067  * @return the styles or an empty array if a LineStyleListener has been set.
5068  *
5069  * @exception SWTException <ul>
5070  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5071  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5072  * </ul>
5073  *
5074  * @since 3.2
5075  *
5076  * @see #getRanges(int, int)
5077  * @see #setStyleRanges(int[], StyleRange[])
5078  */
5079 public StyleRange[] getStyleRanges(boolean includeRanges) {
5080         checkWidget();
5081         return getStyleRanges(0, content.getCharCount(), includeRanges);
5082 }
5083 /**
5084  * Returns the styles for the given text range.
5085  * <p>
5086  * Returns an empty array if a LineStyleListener has been set.
5087  * Should not be called if a LineStyleListener has been set since the
5088  * listener maintains the styles.
5089  * </p><p>
5090  * Note: Because the StyleRange includes the start and length, the
5091  * same instance cannot occur multiple times in the array of styles.
5092  * If the same style attributes, such as font and color, occur in
5093  * multiple StyleRanges, <code>getStyleRanges(int, int, boolean)</code>
5094  * can be used to get the styles without the ranges.
5095  * </p>
5096  * @param start the start offset of the style ranges to return
5097  * @param length the number of style ranges to return
5098  *
5099  * @return the styles or an empty array if a LineStyleListener has
5100  *  been set.  The returned styles will reflect the given range.  The first
5101  *  returned <code>StyleRange</code> will have a starting offset &gt;= start
5102  *  and the last returned <code>StyleRange</code> will have an ending
5103  *  offset &lt;= start + length - 1
5104  *
5105  * @exception SWTException <ul>
5106  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5107  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5108  * </ul>
5109  * @exception IllegalArgumentException <ul>
5110  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5111  * </ul>
5112  *
5113  * @see #getStyleRanges(int, int, boolean)
5114  *
5115  * @since 3.0
5116  */
5117 public StyleRange[] getStyleRanges(int start, int length) {
5118         checkWidget();
5119         return getStyleRanges(start, length, true);
5120 }
5121 /**
5122  * Returns the styles for the given text range.
5123  * <p>
5124  * Returns an empty array if a LineStyleListener has been set.
5125  * Should not be called if a LineStyleListener has been set since the
5126  * listener maintains the styles.
5127  * </p><p>
5128  * Note: When <code>includeRanges</code> is true, the start and length
5129  * fields of each StyleRange will be valid, however the StyleRange
5130  * objects may need to be cloned. When <code>includeRanges</code> is
5131  * false, <code>getRanges(int, int)</code> can be used to get the
5132  * associated ranges.
5133  * </p>
5134  *
5135  * @param start the start offset of the style ranges to return
5136  * @param length the number of style ranges to return
5137  * @param includeRanges whether the start and length field of the StyleRanges should be set.
5138  *
5139  * @return the styles or an empty array if a LineStyleListener has
5140  *  been set.  The returned styles will reflect the given range.  The first
5141  *  returned <code>StyleRange</code> will have a starting offset &gt;= start
5142  *  and the last returned <code>StyleRange</code> will have an ending
5143  *  offset &gt;= start + length - 1
5144  *
5145  * @exception SWTException <ul>
5146  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5147  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5148  * </ul>
5149  * @exception IllegalArgumentException <ul>
5150  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5151  * </ul>
5152  *
5153  * @since 3.2
5154  *
5155  * @see #getRanges(int, int)
5156  * @see #setStyleRanges(int[], StyleRange[])
5157  */
5158 public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) {
5159         checkWidget();
5160         int contentLength = getCharCount();
5161         int end = start + length;
5162         if (start > end || start < 0 || end > contentLength) {
5163                 SWT.error(SWT.ERROR_INVALID_RANGE);
5164         }
5165         if (!isListening(ST.LineGetStyle)) {
5166                 StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
5167                 if (ranges != null) return ranges;
5168         }
5169         return new StyleRange[0];
5170 }
5171 /**
5172  * Returns the tab width measured in characters.
5173  *
5174  * @return tab width measured in characters
5175  * @exception SWTException <ul>
5176  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5177  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5178  * </ul>
5179  *
5180  * @see #getTabStops()
5181  */
5182 public int getTabs() {
5183         checkWidget();
5184         return tabLength;
5185 }
5186
5187 /**
5188  * Returns the tab list of the receiver.
5189  *
5190  * @return the tab list
5191  * @exception SWTException <ul>
5192  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5193  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5194  * </ul>
5195  *
5196  * @since 3.6
5197  */
5198 public int[] getTabStops() {
5199         checkWidget();
5200         if (tabs == null) return new int [] {renderer.tabWidth};
5201         int[] result = new int[tabs.length];
5202         System.arraycopy(tabs, 0, result, 0, tabs.length);
5203         return result;
5204 }
5205
5206 /**
5207  * Returns a copy of the widget content.
5208  *
5209  * @return copy of the widget content
5210  * @exception SWTException <ul>
5211  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5212  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5213  * </ul>
5214  */
5215 public String getText() {
5216         checkWidget();
5217         return content.getTextRange(0, getCharCount());
5218 }
5219 /**
5220  * Returns the widget content between the two offsets.
5221  *
5222  * @param start offset of the first character in the returned String
5223  * @param end offset of the last character in the returned String
5224  * @return widget content starting at start and ending at end
5225  * @see #getTextRange(int,int)
5226  * @exception SWTException <ul>
5227  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5228  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5229  * </ul>
5230  * @exception IllegalArgumentException <ul>
5231  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5232  * </ul>
5233  */
5234 public String getText(int start, int end) {
5235         checkWidget();
5236         int contentLength = getCharCount();
5237         if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
5238                 SWT.error(SWT.ERROR_INVALID_RANGE);
5239         }
5240         return content.getTextRange(start, end - start + 1);
5241 }
5242 /**
5243  * Returns the smallest bounding rectangle that includes the characters between two offsets.
5244  *
5245  * @param start offset of the first character included in the bounding box
5246  * @param end offset of the last character included in the bounding box
5247  * @return bounding box of the text between start and end
5248  * @exception SWTException <ul>
5249  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5250  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5251  * </ul>
5252  * @exception IllegalArgumentException <ul>
5253  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5254  * </ul>
5255  * @since 3.1
5256  */
5257 public Rectangle getTextBounds(int start, int end) {
5258         checkWidget();
5259         int contentLength = getCharCount();
5260         if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
5261                 SWT.error(SWT.ERROR_INVALID_RANGE);
5262         }
5263         int lineStart = content.getLineAtOffset(start);
5264         int lineEnd = content.getLineAtOffset(end);
5265         Rectangle rect;
5266         int y = getLinePixel(lineStart);
5267         int height = 0;
5268         int left = 0x7fffffff, right = 0;
5269         for (int i = lineStart; i <= lineEnd; i++) {
5270                 int lineOffset = content.getOffsetAtLine(i);
5271                 TextLayout layout = renderer.getTextLayout(i);
5272                 int length = layout.getText().length();
5273                 if (length > 0) {
5274                         if (i == lineStart) {
5275                                 if (i == lineEnd) {
5276                                         rect = layout.getBounds(start - lineOffset, end - lineOffset);
5277                                 } else {
5278                                         rect = layout.getBounds(start - lineOffset, length);
5279                                 }
5280                                 y += rect.y;
5281                         } else if (i == lineEnd) {
5282                                 rect = layout.getBounds(0, end - lineOffset);
5283                         } else {
5284                                 rect = layout.getBounds();
5285                         }
5286                         left = Math.min(left, rect.x);
5287                         right = Math.max(right, rect.x + rect.width);
5288                         height += rect.height;
5289                 } else {
5290                         height += renderer.getLineHeight();
5291                 }
5292                 renderer.disposeTextLayout(layout);
5293         }
5294         rect = new Rectangle (left, y, right-left, height);
5295         rect.x += leftMargin - horizontalScrollOffset;
5296         return rect;
5297 }
5298 /**
5299  * Returns the widget content starting at start for length characters.
5300  *
5301  * @param start offset of the first character in the returned String
5302  * @param length number of characters to return
5303  * @return widget content starting at start and extending length characters.
5304  * @exception SWTException <ul>
5305  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5306  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5307  * </ul>
5308  * @exception IllegalArgumentException <ul>
5309  *   <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
5310  * </ul>
5311  */
5312 public String getTextRange(int start, int length) {
5313         checkWidget();
5314         int contentLength = getCharCount();
5315         int end = start + length;
5316         if (start > end || start < 0 || end > contentLength) {
5317                 SWT.error(SWT.ERROR_INVALID_RANGE);
5318         }
5319         return content.getTextRange(start, length);
5320 }
5321 /**
5322  * Returns the maximum number of characters that the receiver is capable of holding.
5323  *
5324  * @return the text limit
5325  *
5326  * @exception SWTException <ul>
5327  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5328  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5329  * </ul>
5330  */
5331 public int getTextLimit() {
5332         checkWidget();
5333         return textLimit;
5334 }
5335 /**
5336  * Gets the top index.
5337  * <p>
5338  * The top index is the index of the fully visible line that is currently
5339  * at the top of the widget or the topmost partially visible line if no line is fully visible.
5340  * The top index changes when the widget is scrolled. Indexing is zero based.
5341  * </p>
5342  *
5343  * @return the index of the top line
5344  * @exception SWTException <ul>
5345  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5346  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5347  * </ul>
5348  */
5349 public int getTopIndex() {
5350         checkWidget();
5351         return topIndex;
5352 }
5353 /**
5354  * Returns the top margin.
5355  *
5356  * @return the top margin.
5357  * @exception SWTException <ul>
5358  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5359  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5360  * </ul>
5361  *
5362  * @since 3.5
5363  */
5364 public int getTopMargin() {
5365         checkWidget();
5366         return topMargin;
5367 }
5368 /**
5369  * Gets the top SWT logical point.
5370  * <p>
5371  * The top point is the SWT logical point position of the line that is
5372  * currently at the top of the widget. The text widget can be scrolled by points
5373  * by dragging the scroll thumb so that a partial line may be displayed at the top
5374  * the widget.  The top point changes when the widget is scrolled.  The top point
5375  * does not include the widget trimming.
5376  * </p>
5377  *
5378  * @return SWT logical point position of the top line
5379  * @exception SWTException <ul>
5380  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5381  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5382  * </ul>
5383  */
5384 public int getTopPixel() {
5385         checkWidget();
5386         return getVerticalScrollOffset();
5387 }
5388 /**
5389  * Returns the vertical scroll increment.
5390  *
5391  * @return vertical scroll increment.
5392  */
5393 int getVerticalIncrement() {
5394         return renderer.getLineHeight();
5395 }
5396 int getVerticalScrollOffset() {
5397         if (verticalScrollOffset == -1) {
5398                 renderer.calculate(0, topIndex);
5399                 int height = 0;
5400                 for (int i = 0; i < topIndex; i++) {
5401                         height += renderer.getLineHeight(i);
5402                 }
5403                 height -= topIndexY;
5404                 verticalScrollOffset = height;
5405         }
5406         return verticalScrollOffset;
5407 }
5408 int getVisualLineIndex(TextLayout layout, int offsetInLine) {
5409         int lineIndex = layout.getLineIndex(offsetInLine);
5410         int[] offsets = layout.getLineOffsets();
5411         Caret caret = getCaret();
5412         if (caret != null && lineIndex != 0 && offsetInLine == offsets[lineIndex]) {
5413                 int lineY = layout.getLineBounds(lineIndex).y;
5414                 int caretY = caret.getLocation().y - getLinePixel(getCaretLine());
5415                 if (lineY > caretY) lineIndex--;
5416                 caretAlignment = OFFSET_LEADING;
5417         }
5418         return lineIndex;
5419 }
5420 int getCaretDirection() {
5421         if (!isBidiCaret()) return SWT.DEFAULT;
5422         if (ime.getCompositionOffset() != -1) return SWT.DEFAULT;
5423         if (!updateCaretDirection && caretDirection != SWT.NULL) return caretDirection;
5424         updateCaretDirection = false;
5425         int caretLine = getCaretLine();
5426         int lineOffset = content.getOffsetAtLine(caretLine);
5427         String line = content.getLine(caretLine);
5428         int offset = caretOffset - lineOffset;
5429         int lineLength = line.length();
5430         if (lineLength == 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT;
5431         if (caretAlignment == PREVIOUS_OFFSET_TRAILING && offset > 0) offset--;
5432         if (offset == lineLength && offset > 0) offset--;
5433         while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
5434         if (offset == 0 && Character.isDigit(line.charAt(offset))) {
5435                 return isMirrored() ? SWT.RIGHT : SWT.LEFT;
5436         }
5437         TextLayout layout = renderer.getTextLayout(caretLine);
5438         int level = layout.getLevel(offset);
5439         renderer.disposeTextLayout(layout);
5440         return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT;
5441 }
5442 /*
5443  * Returns the index of the line the caret is on.
5444  */
5445 int getCaretLine() {
5446         return content.getLineAtOffset(caretOffset);
5447 }
5448 int getWrapWidth () {
5449         if (wordWrap && !isSingleLine()) {
5450                 int width = clientAreaWidth - leftMargin - rightMargin;
5451                 return width > 0 ? width : 1;
5452         }
5453         return -1;
5454 }
5455 int getWordNext (int offset, int movement) {
5456         return getWordNext(offset, movement, false);
5457 }
5458 int getWordNext (int offset, int movement, boolean ignoreListener) {
5459         int newOffset, lineOffset;
5460         String lineText;
5461         if (offset >= getCharCount()) {
5462                 newOffset = offset;
5463                 int lineIndex = content.getLineCount() - 1;
5464                 lineOffset = content.getOffsetAtLine(lineIndex);
5465                 lineText = content.getLine(lineIndex);
5466         } else {
5467                 int lineIndex = content.getLineAtOffset(offset);
5468                 lineOffset = content.getOffsetAtLine(lineIndex);
5469                 lineText = content.getLine(lineIndex);
5470                 int lineLength = lineText.length();
5471                 if (offset >= lineOffset + lineLength) {
5472                         newOffset = content.getOffsetAtLine(lineIndex + 1);
5473                 } else {
5474                         TextLayout layout = renderer.getTextLayout(lineIndex);
5475                         newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
5476                         renderer.disposeTextLayout(layout);
5477                 }
5478         }
5479         if (ignoreListener) return newOffset;
5480         return sendWordBoundaryEvent(ST.WordNext, movement, offset, newOffset, lineText, lineOffset);
5481 }
5482 int getWordPrevious(int offset, int movement) {
5483         return getWordPrevious(offset, movement, false);
5484 }
5485 int getWordPrevious(int offset, int movement, boolean ignoreListener) {
5486         int newOffset, lineOffset;
5487         String lineText;
5488         if (offset <= 0) {
5489                 newOffset = 0;
5490                 int lineIndex = content.getLineAtOffset(newOffset);
5491                 lineOffset = content.getOffsetAtLine(lineIndex);
5492                 lineText = content.getLine(lineIndex);
5493         } else {
5494                 int lineIndex = content.getLineAtOffset(offset);
5495                 lineOffset = content.getOffsetAtLine(lineIndex);
5496                 lineText = content.getLine(lineIndex);
5497                 if (offset == lineOffset) {
5498                         String nextLineText = content.getLine(lineIndex - 1);
5499                         int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
5500                         newOffset = nextLineOffset + nextLineText.length();
5501                 } else {
5502                         int layoutOffset = Math.min(offset - lineOffset, lineText.length());
5503                         TextLayout layout = renderer.getTextLayout(lineIndex);
5504                         newOffset = lineOffset + layout.getPreviousOffset(layoutOffset, movement);
5505                         renderer.disposeTextLayout(layout);
5506                 }
5507         }
5508         if (ignoreListener) return newOffset;
5509         return sendWordBoundaryEvent(ST.WordPrevious, movement, offset, newOffset, lineText, lineOffset);
5510 }
5511 /**
5512  * Returns whether the widget wraps lines.
5513  *
5514  * @return true if widget wraps lines, false otherwise
5515  * @since 2.0
5516  */
5517 public boolean getWordWrap() {
5518         checkWidget();
5519         return wordWrap;
5520 }
5521 /**
5522  * Returns the wrap indentation of the widget.
5523  *
5524  * @return the wrap indentation
5525  *
5526  * @exception SWTException <ul>
5527  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5528  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5529  * </ul>
5530  *
5531  * @see #getLineWrapIndent(int)
5532  *
5533  * @since 3.6
5534  */
5535 public int getWrapIndent() {
5536         checkWidget();
5537         return wrapIndent;
5538 }
5539 /**
5540  * Returns the location of the given offset.
5541  * <p>
5542  * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
5543  * </p>
5544  *
5545  * @return location of the character at the given offset in the line.
5546  */
5547 Point getPointAtOffset(int offset) {
5548         int lineIndex = content.getLineAtOffset(offset);
5549         String line = content.getLine(lineIndex);
5550         int lineOffset = content.getOffsetAtLine(lineIndex);
5551         int offsetInLine = Math.max (0, offset - lineOffset);
5552         int lineLength = line.length();
5553         if (lineIndex < content.getLineCount() - 1) {
5554                 int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
5555                 if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
5556                         offsetInLine = lineLength;
5557                 }
5558         }
5559         Point point;
5560         TextLayout layout = renderer.getTextLayout(lineIndex);
5561         if (lineLength != 0  && offsetInLine <= lineLength) {
5562                 if (offsetInLine == lineLength) {
5563                         offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
5564                         point = layout.getLocation(offsetInLine, true);
5565                 } else {
5566                         switch (caretAlignment) {
5567                                 case OFFSET_LEADING:
5568                                         point = layout.getLocation(offsetInLine, false);
5569                                         break;
5570                                 case PREVIOUS_OFFSET_TRAILING:
5571                                 default:
5572                                         boolean lineBegin = offsetInLine == 0;
5573                                         // If word wrap is enabled, we should also consider offsets
5574                                         // of wrapped line parts as line begin and do NOT go back.
5575                                         // This prevents clients to jump one line higher than
5576                                         // expected, see bug 488172.
5577                                         // Respect caretAlignment at the caretOffset, unless there's
5578                                         // a non-empty selection, see bug 488172 comment 6.
5579                                         if (wordWrap && !lineBegin && (offset != caretOffset || selection.x != selection.y)) {
5580                                                 int[] offsets = layout.getLineOffsets();
5581                                                 for (int i : offsets) {
5582                                                         if (i == offsetInLine) {
5583                                                                 lineBegin = true;
5584                                                                 break;
5585                                                         }
5586                                                 }
5587                                         }
5588                                         if (lineBegin) {
5589                                                 point = layout.getLocation(offsetInLine, false);
5590                                         } else {
5591                                                 offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
5592                                                 point = layout.getLocation(offsetInLine, true);
5593                                         }
5594                                         break;
5595                         }
5596                 }
5597         } else {
5598                 point = new Point(layout.getIndent(), layout.getVerticalIndent());
5599         }
5600         renderer.disposeTextLayout(layout);
5601         point.x += leftMargin - horizontalScrollOffset;
5602         point.y += getLinePixel(lineIndex);
5603         return point;
5604 }
5605 /**
5606  * Inserts a string.  The old selection is replaced with the new text.
5607  *
5608  * @param string the string
5609  * @see #replaceTextRange(int,int,String)
5610  * @exception SWTException <ul>
5611  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5612  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5613  * </ul>
5614  * @exception IllegalArgumentException <ul>
5615  *    <li>ERROR_NULL_ARGUMENT when string is null</li>
5616  * </ul>
5617  */
5618 public void insert(String string) {
5619         checkWidget();
5620         if (string == null) {
5621                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
5622         }
5623         if (blockSelection) {
5624                 insertBlockSelectionText(string, false);
5625         } else {
5626                 Point sel = getSelectionRange();
5627                 replaceTextRange(sel.x, sel.y, string);
5628         }
5629 }
5630 int insertBlockSelectionText(String text, boolean fillWithSpaces) {
5631         int lineCount = 1;
5632         for (int i = 0; i < text.length(); i++) {
5633                 char ch = text.charAt(i);
5634                 if (ch == '\n' || ch == '\r') {
5635                         lineCount++;
5636                         if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
5637                                 i++;
5638                         }
5639                 }
5640         }
5641         String[] lines = new String[lineCount];
5642         int start = 0;
5643         lineCount = 0;
5644         for (int i = 0; i < text.length(); i++) {
5645                 char ch = text.charAt(i);
5646                 if (ch == '\n' || ch == '\r') {
5647                         lines[lineCount++] = text.substring(start, i);
5648                         if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
5649                                 i++;
5650                         }
5651                         start = i + 1;
5652                 }
5653         }
5654         lines[lineCount++] = text.substring(start);
5655         if (fillWithSpaces) {
5656                 int maxLength = 0;
5657                 for (int i = 0; i < lines.length; i++) {
5658                         int length = lines[i].length();
5659                         maxLength = Math.max(maxLength, length);
5660                 }
5661                 for (int i = 0; i < lines.length; i++) {
5662                         String line = lines[i];
5663                         int length = line.length();
5664                         if (length < maxLength) {
5665                                 int numSpaces = maxLength - length;
5666                                 StringBuilder buffer = new StringBuilder(length + numSpaces);
5667                                 buffer.append(line);
5668                                 for (int j = 0; j < numSpaces; j++) buffer.append(' ');
5669                                 lines[i] = buffer.toString();
5670                         }
5671                 }
5672         }
5673         int firstLine, lastLine, left, right;
5674         if (blockXLocation != -1) {
5675                 Rectangle rect = getBlockSelectionPosition();
5676                 firstLine = rect.y;
5677                 lastLine = rect.height;
5678                 left = rect.x;
5679                 right = rect.width;
5680         } else {
5681                 firstLine = lastLine = getCaretLine();
5682                 left = right = getPointAtOffset(caretOffset).x;
5683         }
5684         start = caretOffset;
5685         int caretLine = getCaretLine();
5686         int index = 0, lineIndex = firstLine;
5687         while (lineIndex <= lastLine) {
5688                 String string = index < lineCount ? lines[index++] : "";
5689                 int lineStart = sendTextEvent(left, right, lineIndex, string, fillWithSpaces);
5690                 if (lineIndex == caretLine) start = lineStart;
5691                 lineIndex++;
5692         }
5693         while (index < lineCount) {
5694                 int lineStart = sendTextEvent(left, left, lineIndex, lines[index++], fillWithSpaces);
5695                 if (lineIndex == caretLine) start = lineStart;
5696                 lineIndex++;
5697         }
5698         return start;
5699 }
5700 void insertBlockSelectionText(char key, int action) {
5701         if (key == SWT.CR || key == SWT.LF) return;
5702         Rectangle rect = getBlockSelectionPosition();
5703         int firstLine = rect.y;
5704         int lastLine = rect.height;
5705         int left = rect.x;
5706         int right = rect.width;
5707         int[] trailing = new int[1];
5708         int offset = 0, delta = 0;
5709         String text = key != 0 ? new String(new char[] {key}) : "";
5710         int length = text.length();
5711         for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
5712                 String line = content.getLine(lineIndex);
5713                 int lineOffset = content.getOffsetAtLine(lineIndex);
5714                 int lineEndOffset = lineOffset + line.length();
5715                 int linePixel = getLinePixel(lineIndex);
5716                 int start = getOffsetAtPoint(left, linePixel, trailing, true);
5717                 boolean outOfLine = start == -1;
5718                 if (outOfLine) {
5719                         start = left < leftMargin ? lineOffset : lineEndOffset;
5720                 } else {
5721                         start += trailing[0];
5722                 }
5723                 int end = getOffsetAtPoint(right, linePixel, trailing, true);
5724                 if (end == -1) {
5725                         end = right < leftMargin ? lineOffset : lineEndOffset;
5726                 } else {
5727                         end += trailing[0];
5728                 }
5729                 if (start > end) {
5730                         int temp = start;
5731                         start = end;
5732                         end = temp;
5733                 }
5734                 if (start == end && !outOfLine) {
5735                         switch (action) {
5736                                 case ST.DELETE_PREVIOUS:
5737                                         if (start > lineOffset) start = getClusterPrevious(start, lineIndex);
5738                                         break;
5739                                 case ST.DELETE_NEXT:
5740                                         if (end < lineEndOffset) end = getClusterNext(end, lineIndex);
5741                                         break;
5742                         }
5743                 }
5744                 if (outOfLine) {
5745                         if (line.length() >= delta) {
5746                                 delta = line.length();
5747                                 offset = lineEndOffset + length;
5748                         }
5749                 } else {
5750                         offset = start + length;
5751                         delta = content.getCharCount();
5752                 }
5753                 Event event = new Event();
5754                 event.text = text;
5755                 event.start = start;
5756                 event.end = end;
5757                 sendKeyEvent(event);
5758         }
5759         int x = getPointAtOffset(offset).x;
5760         int verticalScrollOffset = getVerticalScrollOffset();
5761         setBlockSelectionLocation(x, blockYAnchor - verticalScrollOffset, x, blockYLocation - verticalScrollOffset, false);
5762 }
5763 /**
5764  * Creates content change listeners and set the default content model.
5765  */
5766 void installDefaultContent() {
5767         textChangeListener = new TextChangeListener() {
5768                 @Override
5769                 public void textChanging(TextChangingEvent event) {
5770                         handleTextChanging(event);
5771                 }
5772                 @Override
5773                 public void textChanged(TextChangedEvent event) {
5774                         handleTextChanged(event);
5775                 }
5776                 @Override
5777                 public void textSet(TextChangedEvent event) {
5778                         handleTextSet(event);
5779                 }
5780         };
5781         content = new DefaultContent();
5782         content.addTextChangeListener(textChangeListener);
5783 }
5784 /**
5785  * Adds event listeners
5786  */
5787 void installListeners() {
5788         ScrollBar verticalBar = getVerticalBar();
5789         ScrollBar horizontalBar = getHorizontalBar();
5790
5791         listener = event -> {
5792                 switch (event.type) {
5793                         case SWT.Dispose: handleDispose(event); break;
5794                         case SWT.KeyDown: handleKeyDown(event); break;
5795                         case SWT.KeyUp: handleKeyUp(event); break;
5796                         case SWT.MenuDetect: handleMenuDetect(event); break;
5797                         case SWT.MouseDown: handleMouseDown(event); break;
5798                         case SWT.MouseUp: handleMouseUp(event); break;
5799                         case SWT.MouseMove: handleMouseMove(event); break;
5800                         case SWT.Paint: handlePaint(event); break;
5801                         case SWT.Resize: handleResize(event); break;
5802                         case SWT.Traverse: handleTraverse(event); break;
5803                 }
5804         };
5805         addListener(SWT.Dispose, listener);
5806         addListener(SWT.KeyDown, listener);
5807         addListener(SWT.KeyUp, listener);
5808         addListener(SWT.MenuDetect, listener);
5809         addListener(SWT.MouseDown, listener);
5810         addListener(SWT.MouseUp, listener);
5811         addListener(SWT.MouseMove, listener);
5812         addListener(SWT.Paint, listener);
5813         addListener(SWT.Resize, listener);
5814         addListener(SWT.Traverse, listener);
5815         ime.addListener(SWT.ImeComposition, event -> {
5816                 if (!editable) {
5817                         event.doit = false;
5818                         event.start = 0;
5819                         event.end = 0;
5820                         event.text = "";
5821                         return;
5822                 }
5823
5824                 switch (event.detail) {
5825                         case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break;
5826                         case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break;
5827                         case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break;
5828                 }
5829         });
5830         if (verticalBar != null) {
5831                 verticalBar.addListener(SWT.Selection, event -> handleVerticalScroll(event));
5832         }
5833         if (horizontalBar != null) {
5834                 horizontalBar.addListener(SWT.Selection, event -> handleHorizontalScroll(event));
5835         }
5836 }
5837 void internalRedrawRange(int start, int length) {
5838         if (length <= 0) return;
5839         int end = start + length;
5840         int startLine = content.getLineAtOffset(start);
5841         int endLine = content.getLineAtOffset(end);
5842         int partialBottomIndex = getPartialBottomIndex();
5843         int partialTopIndex = getPartialTopIndex();
5844         if (startLine > partialBottomIndex || endLine < partialTopIndex) {
5845                 return;
5846         }
5847         if (partialTopIndex > startLine) {
5848                 startLine = partialTopIndex;
5849                 start = 0;
5850         } else {
5851                 start -= content.getOffsetAtLine(startLine);
5852         }
5853         if (partialBottomIndex < endLine) {
5854                 endLine = partialBottomIndex + 1;
5855                 end = 0;
5856         } else {
5857                 end -= content.getOffsetAtLine(endLine);
5858         }
5859
5860         TextLayout layout = renderer.getTextLayout(startLine);
5861         int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
5862         int[] offsets = layout.getLineOffsets();
5863         int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length()));
5864
5865         /* Redraw end of line before start line if wrapped and start offset is first char */
5866         if (isWordWrap() && startIndex > 0 && offsets[startIndex] == start) {
5867                 Rectangle rect = layout.getLineBounds(startIndex - 1);
5868                 rect.x = rect.width;
5869                 rect.width = clientAreaWidth - rightMargin - rect.x;
5870                 rect.x += lineX;
5871                 rect.y += startLineY;
5872                 super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5873         }
5874
5875         if (startLine == endLine) {
5876                 int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
5877                 if (startIndex == endIndex) {
5878                         /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
5879                         Rectangle rect = layout.getBounds(start, end - 1);
5880                         rect.x += lineX;
5881                         rect.y += startLineY;
5882                         super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5883                         renderer.disposeTextLayout(layout);
5884                         return;
5885                 }
5886         }
5887
5888         /* Redraw start line from the start offset to the end of client area */
5889         Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1);
5890         if (startRect.height == 0) {
5891                 Rectangle bounds = layout.getLineBounds(startIndex);
5892                 startRect.x = bounds.width;
5893                 startRect.y = bounds.y;
5894                 startRect.height = bounds.height;
5895         }
5896         startRect.x += lineX;
5897         startRect.y += startLineY;
5898         startRect.width = clientAreaWidth - rightMargin - startRect.x;
5899         super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);
5900
5901         /* Redraw end line from the beginning of the line to the end offset */
5902         if (startLine != endLine) {
5903                 renderer.disposeTextLayout(layout);
5904                 layout = renderer.getTextLayout(endLine);
5905                 offsets = layout.getLineOffsets();
5906         }
5907         int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
5908         Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1);
5909         if (endRect.height == 0) {
5910                 Rectangle bounds = layout.getLineBounds(endIndex);
5911                 endRect.y = bounds.y;
5912                 endRect.height = bounds.height;
5913         }
5914         endRect.x += lineX;
5915         endRect.y += getLinePixel(endLine);
5916         super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
5917         renderer.disposeTextLayout(layout);
5918
5919         /* Redraw all lines in between start and end line */
5920         int y = startRect.y + startRect.height;
5921         if (endRect.y > y) {
5922                 super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
5923         }
5924 }
5925 void handleCompositionOffset (Event event) {
5926         int[] trailing = new int [1];
5927         event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
5928         event.count = trailing[0];
5929 }
5930 void handleCompositionSelection (Event event) {
5931         if (event.start != event.end) {
5932                 int charCount = getCharCount();
5933                 event.start = Math.max(0, Math.min(event.start, charCount));
5934                 event.end = Math.max(0, Math.min(event.end, charCount));
5935                 if (event.text != null) {
5936                         setSelection(event.start, event.end);
5937                 } else {
5938                         event.text = getTextRange(event.start, event.end - event.start);
5939                 }
5940         } else {
5941                 event.start = selection.x;
5942                 event.end = selection.y;
5943                 event.text = getSelectionText();
5944         }
5945 }
5946 void handleCompositionChanged(Event event) {
5947         String text = event.text;
5948         int start = event.start;
5949         int end = event.end;
5950         int charCount = content.getCharCount();
5951         start = Math.min(start, charCount);
5952         end = Math.min(end, charCount);
5953         int length = text.length();
5954         if (length == ime.getCommitCount()) {
5955                 content.replaceTextRange(start, end - start, "");
5956                 setCaretOffset(ime.getCompositionOffset(), SWT.DEFAULT);
5957                 caretWidth = 0;
5958                 caretDirection = SWT.NULL;
5959         } else {
5960                 content.replaceTextRange(start, end - start, text);
5961                 int alignment = SWT.DEFAULT;
5962                 if (ime.getWideCaret()) {
5963                         start = ime.getCompositionOffset();
5964                         int lineIndex = getCaretLine();
5965                         int lineOffset = content.getOffsetAtLine(lineIndex);
5966                         TextLayout layout = renderer.getTextLayout(lineIndex);
5967                         caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width;
5968                         renderer.disposeTextLayout(layout);
5969                         alignment = OFFSET_LEADING;
5970                 }
5971                 setCaretOffset(ime.getCaretOffset(), alignment);
5972         }
5973         resetSelection();
5974         showCaret();
5975 }
5976 /**
5977  * Frees resources.
5978  */
5979 void handleDispose(Event event) {
5980         removeListener(SWT.Dispose, listener);
5981         notifyListeners(SWT.Dispose, event);
5982         event.type = SWT.None;
5983
5984         clipboard.dispose();
5985         if (renderer != null) {
5986                 renderer.dispose();
5987                 renderer = null;
5988         }
5989         if (content != null) {
5990                 content.removeTextChangeListener(textChangeListener);
5991                 content = null;
5992         }
5993         if (defaultCaret != null) {
5994                 defaultCaret.dispose();
5995                 defaultCaret = null;
5996         }
5997         if (leftCaretBitmap != null) {
5998                 leftCaretBitmap.dispose();
5999                 leftCaretBitmap = null;
6000         }
6001         if (rightCaretBitmap != null) {
6002                 rightCaretBitmap.dispose();
6003                 rightCaretBitmap = null;
6004         }
6005         if (isBidiCaret()) {
6006                 BidiUtil.removeLanguageListener(this);
6007         }
6008         selectionBackground = null;
6009         selectionForeground = null;
6010         marginColor = null;
6011         textChangeListener = null;
6012         selection = null;
6013         doubleClickSelection = null;
6014         keyActionMap = null;
6015         background = null;
6016         foreground = null;
6017         clipboard = null;
6018         tabs = null;
6019 }
6020 /**
6021  * Scrolls the widget horizontally.
6022  */
6023 void handleHorizontalScroll(Event event) {
6024         int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
6025         scrollHorizontal(scrollPixel, false);
6026 }
6027 /**
6028  * If an action has been registered for the key stroke execute the action.
6029  * Otherwise, if a character has been entered treat it as new content.
6030  *
6031  * @param event keyboard event
6032  */
6033 void handleKey(Event event) {
6034         int action;
6035         caretAlignment = PREVIOUS_OFFSET_TRAILING;
6036         if (event.keyCode != 0) {
6037                 // special key pressed (e.g., F1)
6038                 action = getKeyBinding(event.keyCode | event.stateMask);
6039         } else {
6040                 // character key pressed
6041                 action = getKeyBinding(event.character | event.stateMask);
6042                 if (action == SWT.NULL) {
6043                         // see if we have a control character
6044                         if ((event.stateMask & SWT.CTRL) != 0 && event.character <= 31) {
6045                                 // get the character from the CTRL+char sequence, the control
6046                                 // key subtracts 64 from the value of the key that it modifies
6047                                 int c = event.character + 64;
6048                                 action = getKeyBinding(c | event.stateMask);
6049                         }
6050                 }
6051         }
6052         if (action == SWT.NULL) {
6053                 boolean ignore = false;
6054
6055                 if (IS_MAC) {
6056                         // Ignore accelerator key combinations (we do not want to
6057                         // insert a character in the text in this instance).
6058                         ignore = (event.stateMask & (SWT.COMMAND | SWT.CTRL)) != 0;
6059                 } else {
6060                         // Ignore accelerator key combinations (we do not want to
6061                         // insert a character in the text in this instance). Don't
6062                         // ignore CTRL+ALT combinations since that is the Alt Gr
6063                         // key on some keyboards.  See bug 20953.
6064                         ignore = event.stateMask == SWT.ALT ||
6065                                         event.stateMask == SWT.CTRL ||
6066                                         event.stateMask == (SWT.ALT | SWT.SHIFT) ||
6067                                         event.stateMask == (SWT.CTRL | SWT.SHIFT);
6068                 }
6069                 // -ignore anything below SPACE except for line delimiter keys and tab.
6070                 // -ignore DEL
6071                 if (!ignore && event.character > 31 && event.character != SWT.DEL ||
6072                         event.character == SWT.CR || event.character == SWT.LF ||
6073                         event.character == TAB) {
6074                         doContent(event.character);
6075                         update();
6076                 }
6077         } else {
6078                 invokeAction(action);
6079         }
6080 }
6081 /**
6082  * If a VerifyKey listener exists, verify that the key that was entered
6083  * should be processed.
6084  *
6085  * @param event keyboard event
6086  */
6087 void handleKeyDown(Event event) {
6088         if (clipboardSelection == null) {
6089                 clipboardSelection = new Point(selection.x, selection.y);
6090         }
6091         newOrientation = SWT.NONE;
6092         event.stateMask &= SWT.MODIFIER_MASK;
6093
6094         Event verifyEvent = new Event();
6095         verifyEvent.character = event.character;
6096         verifyEvent.keyCode = event.keyCode;
6097         verifyEvent.keyLocation = event.keyLocation;
6098         verifyEvent.stateMask = event.stateMask;
6099         verifyEvent.doit = event.doit;
6100         notifyListeners(ST.VerifyKey, verifyEvent);
6101         if (verifyEvent.doit) {
6102                 if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL && event.keyCode == SWT.SHIFT && isBidiCaret()) {
6103                         newOrientation = event.keyLocation == SWT.LEFT ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT;
6104                 }
6105                 handleKey(event);
6106         }
6107 }
6108 /**
6109  * Update the Selection Clipboard.
6110  *
6111  * @param event keyboard event
6112  */
6113 void handleKeyUp(Event event) {
6114         if (clipboardSelection != null) {
6115                 if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) {
6116                         copySelection(DND.SELECTION_CLIPBOARD);
6117                 }
6118         }
6119         clipboardSelection = null;
6120
6121         if (newOrientation != SWT.NONE) {
6122                 if (newOrientation != getOrientation()) {
6123                         Event e = new Event();
6124                         e.doit = true;
6125                         notifyListeners(SWT.OrientationChange, e);
6126                         if (e.doit) {
6127                                 setOrientation(newOrientation);
6128                         }
6129                 }
6130                 newOrientation = SWT.NONE;
6131         }
6132 }
6133 /**
6134  * Update the event location for focus-based context menu triggers.
6135  *
6136  * @param event menu detect event
6137  */
6138 void handleMenuDetect(Event event) {
6139         if (event.detail == SWT.MENU_KEYBOARD) {
6140                 Point point = getDisplay().map(this, null, getPointAtOffset(caretOffset));
6141                 event.x = point.x;
6142                 event.y = point.y + getLineHeight(caretOffset);
6143         }
6144 }
6145 /**
6146  * Updates the caret location and selection if mouse button 1 has been
6147  * pressed.
6148  */
6149 void handleMouseDown(Event event) {
6150         //force focus (object support)
6151         forceFocus();
6152
6153         //drag detect
6154         if (dragDetect && checkDragDetect(event)) return;
6155
6156         //paste clipboard selection
6157         if (event.button == 2) {
6158                 // On GTK, if mouseNavigator is enabled we have to distinguish a short middle-click (to paste content) from
6159                 // a long middle-click (mouse navigation started)
6160                 if (IS_GTK && mouseNavigator != null) {
6161                         middleClickPressed = true;
6162                         getDisplay().timerExec(200, ()->{
6163                                 boolean click = middleClickPressed;
6164                                 middleClickPressed = false;
6165                                 if (click && mouseNavigator !=null) {
6166                                         mouseNavigator.onMouseDown(event);
6167                                 } else {
6168                                         pasteOnMiddleClick(event);
6169                                 }
6170                         });
6171                         return;
6172                 } else {
6173                         pasteOnMiddleClick(event);
6174                 }
6175         }
6176
6177         //set selection
6178         if ((event.button != 1) || (IS_MAC && (event.stateMask & SWT.MOD4) != 0)) {
6179                 return;
6180         }
6181         clickCount = event.count;
6182         if (clickCount == 1) {
6183                 boolean select = (event.stateMask & SWT.MOD2) != 0;
6184                 doMouseLocationChange(event.x, event.y, select);
6185         } else {
6186                 if (doubleClickEnabled) {
6187                         boolean wordSelect = (clickCount & 1) == 0;
6188                         int offset = getOffsetAtPoint(event.x, event.y, null);
6189                         int lineIndex = content.getLineAtOffset(offset);
6190                         int lineOffset = content.getOffsetAtLine(lineIndex);
6191                         if (wordSelect) {
6192                                 int min = blockSelection ? lineOffset : 0;
6193                                 int max = blockSelection ? lineOffset + content.getLine(lineIndex).length() : content.getCharCount();
6194                                 int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
6195                                 int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END));
6196                                 setSelection(start, end - start, false, true);
6197                                 sendSelectionEvent();
6198                         } else {
6199                                 if (blockSelection) {
6200                                         setBlockSelectionLocation(leftMargin, event.y, clientAreaWidth - rightMargin, event.y, true);
6201                                 } else {
6202                                         int lineEnd = content.getCharCount();
6203                                         if (lineIndex + 1 < content.getLineCount()) {
6204                                                 lineEnd = content.getOffsetAtLine(lineIndex + 1);
6205                                         }
6206                                         setSelection(lineOffset, lineEnd - lineOffset, false, false);
6207                                         sendSelectionEvent();
6208                                 }
6209                         }
6210                         doubleClickSelection = new Point(selection.x, selection.y);
6211                         showCaret();
6212                 }
6213         }
6214 }
6215 /**
6216  * Updates the caret location and selection if mouse button 1 is pressed
6217  * during the mouse move.
6218  */
6219 void handleMouseMove(Event event) {
6220         if (clickCount > 0) {
6221                 update();
6222                 doAutoScroll(event);
6223                 doMouseLocationChange(event.x, event.y, true);
6224         }
6225         if (renderer.hasLinks) {
6226                 doMouseLinkCursor(event.x, event.y);
6227         }
6228 }
6229 /**
6230  * Autoscrolling ends when the mouse button is released.
6231  */
6232 void handleMouseUp(Event event) {
6233         middleClickPressed = false;
6234         clickCount = 0;
6235         endAutoScroll();
6236         if (event.button == 1) {
6237                 copySelection(DND.SELECTION_CLIPBOARD);
6238         }
6239 }
6240 /**
6241  * Renders the invalidated area specified in the paint event.
6242  *
6243  * @param event paint event
6244  */
6245 void handlePaint(Event event) {
6246         if (event.width == 0 || event.height == 0) return;
6247         if (clientAreaWidth == 0 || clientAreaHeight == 0) return;
6248
6249         int startLine = getLineIndex(event.y);
6250         int y = getLinePixel(startLine);
6251         int endY = event.y + event.height;
6252         GC gc = event.gc;
6253         Color background = getBackground();
6254         Color foreground = getForeground();
6255         if (endY > 0) {
6256                 int lineCount = isSingleLine() ? 1 : content.getLineCount();
6257                 int x = leftMargin - horizontalScrollOffset;
6258                 for (int i = startLine; y < endY && i < lineCount; i++) {
6259                         y += renderer.drawLine(i, x, y, gc, background, foreground);
6260                 }
6261                 if (y < endY) {
6262                         gc.setBackground(background);
6263                         drawBackground(gc, 0, y, clientAreaWidth, endY - y);
6264                 }
6265         }
6266         if (blockSelection && blockXLocation != -1) {
6267                 gc.setBackground(getSelectionBackground());
6268                 Rectangle rect = getBlockSelectionRectangle();
6269                 gc.drawRectangle(rect.x, rect.y, Math.max(1, rect.width - 1), Math.max(1, rect.height - 1));
6270                 gc.setAdvanced(true);
6271                 if (gc.getAdvanced()) {
6272                         gc.setAlpha(100);
6273                         gc.fillRectangle(rect);
6274                         gc.setAdvanced(false);
6275                 }
6276         }
6277
6278         // fill the margin background
6279         gc.setBackground(marginColor != null ? marginColor : background);
6280         if (topMargin > 0) {
6281                 drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
6282         }
6283         if (bottomMargin > 0) {
6284                 drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
6285         }
6286         if (leftMargin - alignmentMargin > 0) {
6287                 drawBackground(gc, 0, 0, leftMargin - alignmentMargin, clientAreaHeight);
6288         }
6289         if (rightMargin > 0) {
6290                 drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
6291         }
6292 }
6293 /**
6294  * Recalculates the scroll bars. Rewraps all lines when in word
6295  * wrap mode.
6296  *
6297  * @param event resize event
6298  */
6299 void handleResize(Event event) {
6300         int oldHeight = clientAreaHeight;
6301         int oldWidth = clientAreaWidth;
6302         Rectangle clientArea = getClientArea();
6303         clientAreaHeight = clientArea.height;
6304         clientAreaWidth = clientArea.width;
6305         if (!alwaysShowScroll && ignoreResize != 0) return;
6306
6307         redrawMargins(oldHeight, oldWidth);
6308         if (wordWrap) {
6309                 if (oldWidth != clientAreaWidth) {
6310                         renderer.reset(0, content.getLineCount());
6311                         verticalScrollOffset = -1;
6312                         renderer.calculateIdle();
6313                         super.redraw();
6314                 }
6315                 if (oldHeight != clientAreaHeight) {
6316                         if (oldHeight == 0) topIndexY = 0;
6317                         setScrollBars(true);
6318                 }
6319                 setCaretLocation();
6320         } else  {
6321                 renderer.calculateClientArea();
6322                 setScrollBars(true);
6323                 claimRightFreeSpace();
6324                 // StyledText allows any value for horizontalScrollOffset when clientArea is zero
6325                 // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
6326                 if (clientAreaWidth != 0) {
6327                         ScrollBar horizontalBar = getHorizontalBar();
6328                         if (horizontalBar != null && horizontalBar.getVisible()) {
6329                                 if (horizontalScrollOffset != horizontalBar.getSelection()) {
6330                                         horizontalBar.setSelection(horizontalScrollOffset);
6331                                         horizontalScrollOffset = horizontalBar.getSelection();
6332                                 }
6333                         }
6334                 }
6335         }
6336         updateCaretVisibility();
6337         claimBottomFreeSpace();
6338         setAlignment();
6339         //TODO FIX TOP INDEX DURING RESIZE
6340 //      if (oldHeight != clientAreaHeight || wordWrap) {
6341 //              calculateTopIndex(0);
6342 //      }
6343 }
6344 /**
6345  * Updates the caret position and selection and the scroll bars to reflect
6346  * the content change.
6347  */
6348 void handleTextChanged(TextChangedEvent event) {
6349         int offset = ime.getCompositionOffset();
6350         if (offset != -1 && lastTextChangeStart < offset) {
6351                 ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount);
6352         }
6353         int firstLine = content.getLineAtOffset(lastTextChangeStart);
6354         resetCache(firstLine, 0);
6355         if (!isFixedLineHeight() && topIndex > firstLine) {
6356                 topIndex = firstLine;
6357                 if (topIndex < 0) {
6358                         // TODO: This logging is in place to determine why topIndex is getting set to negative values.
6359                         // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
6360                         System.err.println("StyledText: topIndex was " + topIndex
6361                                         + ", lastTextChangeStart = " + lastTextChangeStart
6362                                         + ", content.getClass() = " + content.getClass()
6363                                         );
6364                         topIndex = 0;
6365                 }
6366                 topIndexY = 0;
6367                 super.redraw();
6368         } else {
6369                 int lastLine = firstLine + lastTextChangeNewLineCount;
6370                 int firstLineTop = getLinePixel(firstLine);
6371                 int newLastLineBottom = getLinePixel(lastLine + 1);
6372                 if (lastLineBottom != newLastLineBottom) {
6373                         super.redraw();
6374                 } else {
6375                         super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
6376                         redrawLinesBullet(renderer.redrawLines);
6377                 }
6378         }
6379         renderer.redrawLines = null;
6380         // update selection/caret location after styles have been changed.
6381         // otherwise any text measuring could be incorrect
6382         //
6383         // also, this needs to be done after all scrolling. Otherwise,
6384         // selection redraw would be flushed during scroll which is wrong.
6385         // in some cases new text would be drawn in scroll source area even
6386         // though the intent is to scroll it.
6387         if (!(blockSelection && blockXLocation != -1)) {
6388                 updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
6389         }
6390         if (lastTextChangeReplaceLineCount > 0 || wordWrap || visualWrap) {
6391                 claimBottomFreeSpace();
6392         }
6393         if (lastTextChangeReplaceCharCount > 0) {
6394                 claimRightFreeSpace();
6395         }
6396
6397         sendAccessibleTextChanged(lastTextChangeStart, lastTextChangeNewCharCount, 0);
6398         lastCharCount += lastTextChangeNewCharCount;
6399         lastCharCount -= lastTextChangeReplaceCharCount;
6400         setAlignment();
6401 }
6402 /**
6403  * Updates the screen to reflect a pending content change.
6404  *
6405  * @param event .start the start offset of the change
6406  * @param event .newText text that is going to be inserted or empty String
6407  *      if no text will be inserted
6408  * @param event .replaceCharCount length of text that is going to be replaced
6409  * @param event .newCharCount length of text that is going to be inserted
6410  * @param event .replaceLineCount number of lines that are going to be replaced
6411  * @param event .newLineCount number of new lines that are going to be inserted
6412  */
6413 void handleTextChanging(TextChangingEvent event) {
6414         if (event.replaceCharCount < 0) {
6415                 event.start += event.replaceCharCount;
6416                 event.replaceCharCount *= -1;
6417         }
6418         lastTextChangeStart = event.start;
6419         lastTextChangeNewLineCount = event.newLineCount;
6420         lastTextChangeNewCharCount = event.newCharCount;
6421         lastTextChangeReplaceLineCount = event.replaceLineCount;
6422         lastTextChangeReplaceCharCount = event.replaceCharCount;
6423         int lineIndex = content.getLineAtOffset(event.start);
6424         int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
6425         int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
6426         lastLineBottom = destY;
6427         if (srcY < 0 && destY < 0) {
6428                 lastLineBottom += srcY - destY;
6429                 verticalScrollOffset += destY - srcY;
6430                 calculateTopIndex(destY - srcY);
6431                 setScrollBars(true);
6432         } else {
6433                 scrollText(srcY, destY);
6434         }
6435         sendAccessibleTextChanged(lastTextChangeStart, 0, lastTextChangeReplaceCharCount);
6436         renderer.textChanging(event);
6437
6438         // Update the caret offset if it is greater than the length of the content.
6439         // This is necessary since style range API may be called between the
6440         // handleTextChanging and handleTextChanged events and this API sets the
6441         // caretOffset.
6442         int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
6443         if (caretOffset > newEndOfText) setCaretOffset(newEndOfText, SWT.DEFAULT);
6444 }
6445 /**
6446  * Called when the widget content is set programmatically, overwriting
6447  * the old content. Resets the caret position, selection and scroll offsets.
6448  * Recalculates the content width and scroll bars. Redraws the widget.
6449  *
6450  * @param event text change event.
6451  */
6452 void handleTextSet(TextChangedEvent event) {
6453         reset();
6454         int newCharCount = getCharCount();
6455         sendAccessibleTextChanged(0, newCharCount, lastCharCount);
6456         lastCharCount = newCharCount;
6457         setAlignment();
6458 }
6459 /**
6460  * Called when a traversal key is pressed.
6461  * Allow tab next traversal to occur when the widget is in single
6462  * line mode or in multi line and non-editable mode .
6463  * When in editable multi line mode we want to prevent the tab
6464  * traversal and receive the tab key event instead.
6465  *
6466  * @param event the event
6467  */
6468 void handleTraverse(Event event) {
6469         switch (event.detail) {
6470                 case SWT.TRAVERSE_ESCAPE:
6471                 case SWT.TRAVERSE_PAGE_NEXT:
6472                 case SWT.TRAVERSE_PAGE_PREVIOUS:
6473                         event.doit = true;
6474                         break;
6475                 case SWT.TRAVERSE_RETURN:
6476                 case SWT.TRAVERSE_TAB_NEXT:
6477                 case SWT.TRAVERSE_TAB_PREVIOUS:
6478                         if ((getStyle() & SWT.SINGLE) != 0) {
6479                                 event.doit = true;
6480                         } else {
6481                                 if (!editable || (event.stateMask & SWT.MODIFIER_MASK) != 0) {
6482                                         event.doit = true;
6483                                 }
6484                         }
6485                         break;
6486         }
6487 }
6488 /**
6489  * Scrolls the widget vertically.
6490  */
6491 void handleVerticalScroll(Event event) {
6492         int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
6493         scrollVertical(scrollPixel, false);
6494 }
6495 /**
6496  * Add accessibility support for the widget.
6497  */
6498 void initializeAccessible() {
6499         acc = getAccessible();
6500
6501         accAdapter = new AccessibleAdapter() {
6502                 @Override
6503                 public void getName (AccessibleEvent e) {
6504                         String name = null;
6505                         String text = getAssociatedLabel ();
6506                         if (text != null) {
6507                                 name = stripMnemonic (text);
6508                         }
6509                         e.result = name;
6510                 }
6511                 @Override
6512                 public void getHelp(AccessibleEvent e) {
6513                         e.result = getToolTipText();
6514                 }
6515                 @Override
6516                 public void getKeyboardShortcut(AccessibleEvent e) {
6517                         String shortcut = null;
6518                         String text = getAssociatedLabel ();
6519                         if (text != null) {
6520                                 char mnemonic = _findMnemonic (text);
6521                                 if (mnemonic != '\0') {
6522                                         shortcut = "Alt+"+mnemonic; //$NON-NLS-1$
6523                                 }
6524                         }
6525                         e.result = shortcut;
6526                 }
6527         };
6528         acc.addAccessibleListener(accAdapter);
6529
6530         accTextExtendedAdapter = new AccessibleTextExtendedAdapter() {
6531                 @Override
6532                 public void getCaretOffset(AccessibleTextEvent e) {
6533                         e.offset = StyledText.this.getCaretOffset();
6534                 }
6535                 @Override
6536                 public void setCaretOffset(AccessibleTextEvent e) {
6537                         StyledText.this.setCaretOffset(e.offset);
6538                         e.result = ACC.OK;
6539                 }
6540                 @Override
6541                 public void getSelectionRange(AccessibleTextEvent e) {
6542                         Point selection = StyledText.this.getSelectionRange();
6543                         e.offset = selection.x;
6544                         e.length = selection.y;
6545                 }
6546                 @Override
6547                 public void addSelection(AccessibleTextEvent e) {
6548                         StyledText st = StyledText.this;
6549                         Point point = st.getSelection();
6550                         if (point.x == point.y) {
6551                                 int end = e.end;
6552                                 if (end == -1) end = st.getCharCount();
6553                                 st.setSelection(e.start, end);
6554                                 e.result = ACC.OK;
6555                         }
6556                 }
6557                 @Override
6558                 public void getSelection(AccessibleTextEvent e) {
6559                         StyledText st = StyledText.this;
6560                         if (st.blockSelection && st.blockXLocation != -1) {
6561                                 Rectangle rect = st.getBlockSelectionPosition();
6562                                 int lineIndex = rect.y + e.index;
6563                                 int linePixel = st.getLinePixel(lineIndex);
6564                                 e.ranges = getRanges(rect.x, linePixel, rect.width, linePixel);
6565                                 if (e.ranges.length > 0) {
6566                                         e.start = e.ranges[0];
6567                                         e.end = e.ranges[e.ranges.length - 1];
6568                                 }
6569                         } else {
6570                                 if (e.index == 0) {
6571                                         Point point = st.getSelection();
6572                                         e.start = point.x;
6573                                         e.end = point.y;
6574                                         if (e.start > e.end) {
6575                                                 int temp = e.start;
6576                                                 e.start = e.end;
6577                                                 e.end = temp;
6578                                         }
6579                                 }
6580                         }
6581                 }
6582                 @Override
6583                 public void getSelectionCount(AccessibleTextEvent e) {
6584                         StyledText st = StyledText.this;
6585                         if (st.blockSelection && st.blockXLocation != -1) {
6586                                 Rectangle rect = st.getBlockSelectionPosition();
6587                                 e.count = rect.height - rect.y + 1;
6588                         } else {
6589                                 Point point = st.getSelection();
6590                                 e.count = point.x == point.y ? 0 : 1;
6591                         }
6592                 }
6593                 @Override
6594                 public void removeSelection(AccessibleTextEvent e) {
6595                         StyledText st = StyledText.this;
6596                         if (e.index == 0) {
6597                                 if (st.blockSelection) {
6598                                         st.clearBlockSelection(true, false);
6599                                 } else {
6600                                         st.clearSelection(false);
6601                                 }
6602                                 e.result = ACC.OK;
6603                         }
6604                 }
6605                 @Override
6606                 public void setSelection(AccessibleTextEvent e) {
6607                         if (e.index != 0) return;
6608                         StyledText st = StyledText.this;
6609                         Point point = st.getSelection();
6610                         if (point.x == point.y) return;
6611                         int end = e.end;
6612                         if (end == -1) end = st.getCharCount();
6613                         st.setSelection(e.start, end);
6614                         e.result = ACC.OK;
6615                 }
6616                 @Override
6617                 public void getCharacterCount(AccessibleTextEvent e) {
6618                         e.count = StyledText.this.getCharCount();
6619                 }
6620                 @Override
6621                 public void getOffsetAtPoint(AccessibleTextEvent e) {
6622                         StyledText st = StyledText.this;
6623                         Point point = new Point (e.x, e.y);
6624                         Display display = st.getDisplay();
6625                         point = display.map(null, st, point);
6626                         e.offset = st.getOffsetAtPoint(point.x, point.y, null, true);
6627                 }
6628                 @Override
6629                 public void getTextBounds(AccessibleTextEvent e) {
6630                         StyledText st = StyledText.this;
6631                         int start = e.start;
6632                         int end = e.end;
6633                         int contentLength = st.getCharCount();
6634                         start = Math.max(0, Math.min(start, contentLength));
6635                         end = Math.max(0, Math.min(end, contentLength));
6636                         if (start > end) {
6637                                 int temp = start;
6638                                 start = end;
6639                                 end = temp;
6640                         }
6641                         int startLine = st.getLineAtOffset(start);
6642                         int endLine = st.getLineAtOffset(end);
6643                         Rectangle[] rects = new Rectangle[endLine - startLine + 1];
6644                         Rectangle bounds = null;
6645                         int index = 0;
6646                         Display display = st.getDisplay();
6647                         for (int lineIndex = startLine; lineIndex <= endLine; lineIndex++) {
6648                                 Rectangle rect = new Rectangle(0, 0, 0, 0);
6649                                 rect.y = st.getLinePixel(lineIndex);
6650                                 rect.height = st.renderer.getLineHeight(lineIndex);
6651                                 if (lineIndex == startLine) {
6652                                         rect.x = st.getPointAtOffset(start).x;
6653                                 } else {
6654                                         rect.x = st.leftMargin - st.horizontalScrollOffset;
6655                                 }
6656                                 if (lineIndex == endLine) {
6657                                         rect.width = st.getPointAtOffset(end).x - rect.x;
6658                                 } else {
6659                                         TextLayout layout = st.renderer.getTextLayout(lineIndex);
6660                                         rect.width = layout.getBounds().width - rect.x;
6661                                         st.renderer.disposeTextLayout(layout);
6662                                 }
6663                                 rects [index++] = rect = display.map(st, null, rect);
6664                                 if (bounds == null) {
6665                                         bounds = new Rectangle(rect.x, rect.y, rect.width, rect.height);
6666                                 } else {
6667                                         bounds.add(rect);
6668                                 }
6669                         }
6670                         e.rectangles = rects;
6671                         if (bounds != null) {
6672                                 e.x = bounds.x;
6673                                 e.y = bounds.y;
6674                                 e.width = bounds.width;
6675                                 e.height = bounds.height;
6676                         }
6677                 }
6678                 int[] getRanges(int left, int top, int right, int bottom) {
6679                         StyledText st = StyledText.this;
6680                         int lineStart = st.getLineIndex(top);
6681                         int lineEnd = st.getLineIndex(bottom);
6682                         int count = lineEnd - lineStart + 1;
6683                         int[] ranges = new int [count * 2];
6684                         int index = 0;
6685                         for (int lineIndex = lineStart; lineIndex <= lineEnd; lineIndex++) {
6686                                 String line = st.content.getLine(lineIndex);
6687                                 int lineOffset = st.content.getOffsetAtLine(lineIndex);
6688                                 int lineEndOffset = lineOffset + line.length();
6689                                 int linePixel = st.getLinePixel(lineIndex);
6690                                 int start = st.getOffsetAtPoint(left, linePixel, null, true);
6691                                 if (start == -1) {
6692                                         start = left < st.leftMargin ? lineOffset : lineEndOffset;
6693                                 }
6694                                 int[] trailing = new int[1];
6695                                 int end = st.getOffsetAtPoint(right, linePixel, trailing, true);
6696                                 if (end == -1) {
6697                                         end = right < st.leftMargin ? lineOffset : lineEndOffset;
6698                                 } else {
6699                                         end += trailing[0];
6700                                 }
6701                                 if (start > end) {
6702                                         int temp = start;
6703                                         start = end;
6704                                         end = temp;
6705                                 }
6706                                 ranges[index++] = start;
6707                                 ranges[index++] = end;
6708                         }
6709                         return ranges;
6710                 }
6711                 @Override
6712                 public void getRanges(AccessibleTextEvent e) {
6713                         StyledText st = StyledText.this;
6714                         Point point = new Point (e.x, e.y);
6715                         Display display = st.getDisplay();
6716                         point = display.map(null, st, point);
6717                         e.ranges = getRanges(point.x, point.y, point.x + e.width, point.y + e.height);
6718                         if (e.ranges.length > 0) {
6719                                 e.start = e.ranges[0];
6720                                 e.end = e.ranges[e.ranges.length - 1];
6721                         }
6722                 }
6723                 @Override
6724                 public void getText(AccessibleTextEvent e) {
6725                         StyledText st = StyledText.this;
6726                         int start = e.start;
6727                         int end = e.end;
6728                         int contentLength = st.getCharCount();
6729                         if (end == -1) end = contentLength;
6730                         start = Math.max(0, Math.min(start, contentLength));
6731                         end = Math.max(0, Math.min(end, contentLength));
6732                         if (start > end) {
6733                                 int temp = start;
6734                                 start = end;
6735                                 end = temp;
6736                         }
6737                         int count = e.count;
6738                         switch (e.type) {
6739                                 case ACC.TEXT_BOUNDARY_ALL:
6740                                         //nothing to do
6741                                         break;
6742                                 case ACC.TEXT_BOUNDARY_CHAR: {
6743                                         int newCount = 0;
6744                                         if (count > 0) {
6745                                                 while (count-- > 0) {
6746                                                         int newEnd = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
6747                                                         if (newEnd == contentLength) break;
6748                                                         if (newEnd == end) break;
6749                                                         end = newEnd;
6750                                                         newCount++;
6751                                                 }
6752                                                 start = end;
6753                                                 end = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
6754                                         } else {
6755                                                 while (count++ < 0) {
6756                                                         int newStart = st.getWordPrevious(start, SWT.MOVEMENT_CLUSTER);
6757                                                         if (newStart == start) break;
6758                                                         start = newStart;
6759                                                         newCount--;
6760                                                 }
6761                                                 end = st.getWordNext(start, SWT.MOVEMENT_CLUSTER);
6762                                         }
6763                                         count = newCount;
6764                                         break;
6765                                 }
6766                                 case ACC.TEXT_BOUNDARY_WORD: {
6767                                         int newCount = 0;
6768                                         if (count > 0) {
6769                                                 while (count-- > 0) {
6770                                                         int newEnd = st.getWordNext(end, SWT.MOVEMENT_WORD_START, true);
6771                                                         if (newEnd == end) break;
6772                                                         newCount++;
6773                                                         end = newEnd;
6774                                                 }
6775                                                 start = end;
6776                                                 end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
6777                                         } else {
6778                                                 if (st.getWordPrevious(Math.min(start + 1, contentLength), SWT.MOVEMENT_WORD_START, true) == start) {
6779                                                         //start is a word start already
6780                                                         count++;
6781                                                 }
6782                                                 while (count <= 0) {
6783                                                         int newStart = st.getWordPrevious(start, SWT.MOVEMENT_WORD_START, true);
6784                                                         if (newStart == start) break;
6785                                                         count++;
6786                                                         start = newStart;
6787                                                         if (count != 0) newCount--;
6788                                                 }
6789                                                 if (count <= 0 && start == 0) {
6790                                                         end = start;
6791                                                 } else {
6792                                                         end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
6793                                                 }
6794                                         }
6795                                         count = newCount;
6796                                         break;
6797                                 }
6798                                 case ACC.TEXT_BOUNDARY_LINE:
6799                                         //TODO implement line
6800                                 case ACC.TEXT_BOUNDARY_PARAGRAPH:
6801                                 case ACC.TEXT_BOUNDARY_SENTENCE: {
6802                                         int offset = count > 0 ? end : start;
6803                                         int lineIndex = st.getLineAtOffset(offset) + count;
6804                                         lineIndex = Math.max(0, Math.min(lineIndex, st.getLineCount() - 1));
6805                                         start = st.getOffsetAtLine(lineIndex);
6806                                         String line = st.getLine(lineIndex);
6807                                         end = start + line.length();
6808                                         count = lineIndex - st.getLineAtOffset(offset);
6809                                         break;
6810                                 }
6811                         }
6812                         e.start = start;
6813                         e.end = end;
6814                         e.count = count;
6815                         e.result = st.content.getTextRange(start, end - start);
6816                 }
6817                 @Override
6818                 public void getVisibleRanges(AccessibleTextEvent e) {
6819                         e.ranges = getRanges(leftMargin, topMargin, clientAreaWidth - rightMargin, clientAreaHeight - bottomMargin);
6820                         if (e.ranges.length > 0) {
6821                                 e.start = e.ranges[0];
6822                                 e.end = e.ranges[e.ranges.length - 1];
6823                         }
6824                 }
6825                 @Override
6826                 public void scrollText(AccessibleTextEvent e) {
6827                         StyledText st = StyledText.this;
6828                         int topPixel = getTopPixel(), horizontalPixel = st.getHorizontalPixel();
6829                         switch (e.type) {
6830                                 case ACC.SCROLL_TYPE_ANYWHERE:
6831                                 case ACC.SCROLL_TYPE_TOP_LEFT:
6832                                 case ACC.SCROLL_TYPE_LEFT_EDGE:
6833                                 case ACC.SCROLL_TYPE_TOP_EDGE: {
6834                                         Rectangle rect = st.getBoundsAtOffset(e.start);
6835                                         if (e.type != ACC.SCROLL_TYPE_TOP_EDGE) {
6836                                                 horizontalPixel = horizontalPixel + rect.x - st.leftMargin;
6837                                         }
6838                                         if (e.type != ACC.SCROLL_TYPE_LEFT_EDGE) {
6839                                                 topPixel = topPixel + rect.y - st.topMargin;
6840                                         }
6841                                         break;
6842                                 }
6843                                 case ACC.SCROLL_TYPE_BOTTOM_RIGHT:
6844                                 case ACC.SCROLL_TYPE_BOTTOM_EDGE:
6845                                 case ACC.SCROLL_TYPE_RIGHT_EDGE: {
6846                                         Rectangle rect = st.getBoundsAtOffset(e.end - 1);
6847                                         if (e.type != ACC.SCROLL_TYPE_BOTTOM_EDGE) {
6848                                                 horizontalPixel = horizontalPixel - st.clientAreaWidth + rect.x + rect.width + st.rightMargin;
6849                                         }
6850                                         if (e.type != ACC.SCROLL_TYPE_RIGHT_EDGE) {
6851                                                 topPixel = topPixel - st.clientAreaHeight + rect.y +rect.height + st.bottomMargin;
6852                                         }
6853                                         break;
6854                                 }
6855                                 case ACC.SCROLL_TYPE_POINT: {
6856                                         Point point = new Point(e.x, e.y);
6857                                         Display display = st.getDisplay();
6858                                         point = display.map(null, st, point);
6859                                         Rectangle rect = st.getBoundsAtOffset(e.start);
6860                                         topPixel = topPixel - point.y + rect.y;
6861                                         horizontalPixel = horizontalPixel - point.x + rect.x;
6862                                         break;
6863                                 }
6864                         }
6865                         st.setTopPixel(topPixel);
6866                         st.setHorizontalPixel(horizontalPixel);
6867                         e.result = ACC.OK;
6868                 }
6869         };
6870         acc.addAccessibleTextListener(accTextExtendedAdapter);
6871
6872         accEditableTextListener = new AccessibleEditableTextListener() {
6873                 @Override
6874                 public void setTextAttributes(AccessibleTextAttributeEvent e) {
6875                         // This method must be implemented by the application
6876                         e.result = ACC.OK;
6877                 }
6878                 @Override
6879                 public void replaceText(AccessibleEditableTextEvent e) {
6880                         StyledText st = StyledText.this;
6881                         st.replaceTextRange(e.start, e.end - e.start, e.string);
6882                         e.result = ACC.OK;
6883                 }
6884                 @Override
6885                 public void pasteText(AccessibleEditableTextEvent e) {
6886                         StyledText st = StyledText.this;
6887                         st.setSelection(e.start);
6888                         st.paste();
6889                         e.result = ACC.OK;
6890                 }
6891                 @Override
6892                 public void cutText(AccessibleEditableTextEvent e) {
6893                         StyledText st = StyledText.this;
6894                         st.setSelection(e.start, e.end);
6895                         st.cut();
6896                         e.result = ACC.OK;
6897                 }
6898                 @Override
6899                 public void copyText(AccessibleEditableTextEvent e) {
6900                         StyledText st = StyledText.this;
6901                         st.setSelection(e.start, e.end);
6902                         st.copy();
6903                         e.result = ACC.OK;
6904                 }
6905         };
6906         acc.addAccessibleEditableTextListener(accEditableTextListener);
6907
6908         accAttributeAdapter = new AccessibleAttributeAdapter() {
6909                 @Override
6910                 public void getAttributes(AccessibleAttributeEvent e) {
6911                         StyledText st = StyledText.this;
6912                         e.leftMargin = st.getLeftMargin();
6913                         e.topMargin = st.getTopMargin();
6914                         e.rightMargin = st.getRightMargin();
6915                         e.bottomMargin = st.getBottomMargin();
6916                         e.tabStops = st.getTabStops();
6917                         e.justify = st.getJustify();
6918                         e.alignment = st.getAlignment();
6919                         e.indent = st.getIndent();
6920                 }
6921                 @Override
6922                 public void getTextAttributes(AccessibleTextAttributeEvent e) {
6923                         StyledText st = StyledText.this;
6924                         int contentLength = st.getCharCount();
6925                         if (!isListening(ST.LineGetStyle) && st.renderer.styleCount == 0) {
6926                                 e.start = 0;
6927                                 e.end = contentLength;
6928                                 e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
6929                                 return;
6930                         }
6931                         int offset = Math.max(0, Math.min(e.offset, contentLength - 1));
6932                         int lineIndex = st.getLineAtOffset(offset);
6933                         int lineOffset = st.getOffsetAtLine(lineIndex);
6934                         int lineCount = st.getLineCount();
6935                         offset = offset - lineOffset;
6936
6937                         TextLayout layout = st.renderer.getTextLayout(lineIndex);
6938                         int lineLength = layout.getText().length();
6939                         if (lineLength > 0) {
6940                                 e.textStyle = layout.getStyle(Math.max(0, Math.min(offset, lineLength - 1)));
6941                         }
6942
6943                         // If no override info available, use defaults. Don't supply default colors, though.
6944                         if (e.textStyle == null) {
6945                                 e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
6946                         } else {
6947                                 if (e.textStyle.foreground == null || e.textStyle.background == null || e.textStyle.font == null) {
6948                                         TextStyle textStyle = new TextStyle(e.textStyle);
6949                                         if (textStyle.foreground == null) textStyle.foreground = st.foreground;
6950                                         if (textStyle.background == null) textStyle.background = st.background;
6951                                         if (textStyle.font == null) textStyle.font = st.getFont();
6952                                         e.textStyle = textStyle;
6953                                 }
6954                         }
6955
6956                         //offset at line delimiter case
6957                         if (offset >= lineLength) {
6958                                 e.start = lineOffset + lineLength;
6959                                 if (lineIndex + 1 < lineCount) {
6960                                         e.end = st.getOffsetAtLine(lineIndex + 1);
6961                                 } else  {
6962                                         e.end = contentLength;
6963                                 }
6964                                 return;
6965                         }
6966
6967                         int[] ranges = layout.getRanges();
6968                         st.renderer.disposeTextLayout(layout);
6969                         int index = 0;
6970                         int end = 0;
6971                         while (index < ranges.length) {
6972                                 int styleStart = ranges[index++];
6973                                 int styleEnd = ranges[index++];
6974                                 if (styleStart <= offset && offset <= styleEnd) {
6975                                         e.start = lineOffset + styleStart;
6976                                         e.end = lineOffset + styleEnd + 1;
6977                                         return;
6978                                 }
6979                                 if (styleStart > offset) {
6980                                         e.start = lineOffset + end;
6981                                         e.end = lineOffset + styleStart;
6982                                         return;
6983                                 }
6984                                 end = styleEnd + 1;
6985                         }
6986                         if (index == ranges.length) {
6987                                 e.start = lineOffset + end;
6988                                 if (lineIndex + 1 < lineCount) {
6989                                         e.end = st.getOffsetAtLine(lineIndex + 1);
6990                                 } else  {
6991                                         e.end = contentLength;
6992                                 }
6993                         }
6994                 }
6995         };
6996         acc.addAccessibleAttributeListener(accAttributeAdapter);
6997
6998         accControlAdapter = new AccessibleControlAdapter() {
6999                 @Override
7000                 public void getRole(AccessibleControlEvent e) {
7001                         e.detail = ACC.ROLE_TEXT;
7002                 }
7003                 @Override
7004                 public void getState(AccessibleControlEvent e) {
7005                         int state = 0;
7006                         if (isEnabled()) state |= ACC.STATE_FOCUSABLE;
7007                         if (isFocusControl()) state |= ACC.STATE_FOCUSED;
7008                         if (!isVisible()) state |= ACC.STATE_INVISIBLE;
7009                         if (!getEditable()) state |= ACC.STATE_READONLY;
7010                         if (isSingleLine()) state |= ACC.STATE_SINGLELINE;
7011                         else state |= ACC.STATE_MULTILINE;
7012                         e.detail = state;
7013                 }
7014                 @Override
7015                 public void getValue(AccessibleControlEvent e) {
7016                         e.result = StyledText.this.getText();
7017                 }
7018         };
7019         acc.addAccessibleControlListener(accControlAdapter);
7020
7021         addListener(SWT.FocusIn, event -> acc.setFocus(ACC.CHILDID_SELF));
7022 }
7023
7024 @Override
7025 public void dispose() {
7026         /*
7027          * Note: It is valid to attempt to dispose a widget more than once.
7028          * Added check for this.
7029          */
7030         if (!isDisposed()) {
7031                 acc.removeAccessibleControlListener(accControlAdapter);
7032                 acc.removeAccessibleAttributeListener(accAttributeAdapter);
7033                 acc.removeAccessibleEditableTextListener(accEditableTextListener);
7034                 acc.removeAccessibleTextListener(accTextExtendedAdapter);
7035                 acc.removeAccessibleListener(accAdapter);
7036         }
7037         super.dispose();
7038 }
7039
7040 /*
7041  * Return the Label immediately preceding the receiver in the z-order,
7042  * or null if none.
7043  */
7044 String getAssociatedLabel () {
7045         Control[] siblings = getParent ().getChildren ();
7046         for (int i = 0; i < siblings.length; i++) {
7047                 if (siblings [i] == StyledText.this) {
7048                         if (i > 0) {
7049                                 Control sibling = siblings [i-1];
7050                                 if (sibling instanceof Label) return ((Label) sibling).getText();
7051                                 if (sibling instanceof CLabel) return ((CLabel) sibling).getText();
7052                         }
7053                         break;
7054                 }
7055         }
7056         return null;
7057 }
7058 String stripMnemonic (String string) {
7059         int index = 0;
7060         int length = string.length ();
7061         do {
7062                 while ((index < length) && (string.charAt (index) != '&')) index++;
7063                 if (++index >= length) return string;
7064                 if (string.charAt (index) != '&') {
7065                         return string.substring(0, index-1) + string.substring(index, length);
7066                 }
7067                 index++;
7068         } while (index < length);
7069         return string;
7070 }
7071 /*
7072  * Return the lowercase of the first non-'&' character following
7073  * an '&' character in the given string. If there are no '&'
7074  * characters in the given string, return '\0'.
7075  */
7076 char _findMnemonic (String string) {
7077         if (string == null) return '\0';
7078         int index = 0;
7079         int length = string.length ();
7080         do {
7081                 while (index < length && string.charAt (index) != '&') index++;
7082                 if (++index >= length) return '\0';
7083                 if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index));
7084                 index++;
7085         } while (index < length);
7086         return '\0';
7087 }
7088 /**
7089  * Executes the action.
7090  *
7091  * @param action one of the actions defined in ST.java
7092  */
7093 public void invokeAction(int action) {
7094         checkWidget();
7095         if (blockSelection && invokeBlockAction(action)) return;
7096         updateCaretDirection = true;
7097         switch (action) {
7098                 // Navigation
7099                 case ST.LINE_UP:
7100                         doLineUp(false);
7101                         clearSelection(true);
7102                         break;
7103                 case ST.LINE_DOWN:
7104                         doLineDown(false);
7105                         clearSelection(true);
7106                         break;
7107                 case ST.LINE_START:
7108                         doLineStart();
7109                         clearSelection(true);
7110                         break;
7111                 case ST.LINE_END:
7112                         doLineEnd();
7113                         clearSelection(true);
7114                         break;
7115                 case ST.COLUMN_PREVIOUS:
7116                         doCursorPrevious();
7117                         clearSelection(true);
7118                         break;
7119                 case ST.COLUMN_NEXT:
7120                         doCursorNext();
7121                         clearSelection(true);
7122                         break;
7123                 case ST.PAGE_UP:
7124                         doPageUp(false, -1);
7125                         clearSelection(true);
7126                         break;
7127                 case ST.PAGE_DOWN:
7128                         doPageDown(false, -1);
7129                         clearSelection(true);
7130                         break;
7131                 case ST.WORD_PREVIOUS:
7132                         doWordPrevious();
7133                         clearSelection(true);
7134                         break;
7135                 case ST.WORD_NEXT:
7136                         doWordNext();
7137                         clearSelection(true);
7138                         break;
7139                 case ST.TEXT_START:
7140                         doContentStart();
7141                         clearSelection(true);
7142                         break;
7143                 case ST.TEXT_END:
7144                         doContentEnd();
7145                         clearSelection(true);
7146                         break;
7147                 case ST.WINDOW_START:
7148                         doPageStart();
7149                         clearSelection(true);
7150                         break;
7151                 case ST.WINDOW_END:
7152                         doPageEnd();
7153                         clearSelection(true);
7154                         break;
7155                 // Selection
7156                 case ST.SELECT_LINE_UP:
7157                         doSelectionLineUp();
7158                         break;
7159                 case ST.SELECT_ALL:
7160                         selectAll();
7161                         break;
7162                 case ST.SELECT_LINE_DOWN:
7163                         doSelectionLineDown();
7164                         break;
7165                 case ST.SELECT_LINE_START:
7166                         doLineStart();
7167                         doSelection(ST.COLUMN_PREVIOUS);
7168                         break;
7169                 case ST.SELECT_LINE_END:
7170                         doLineEnd();
7171                         doSelection(ST.COLUMN_NEXT);
7172                         break;
7173                 case ST.SELECT_COLUMN_PREVIOUS:
7174                         doSelectionCursorPrevious();
7175                         doSelection(ST.COLUMN_PREVIOUS);
7176                         break;
7177                 case ST.SELECT_COLUMN_NEXT:
7178                         doSelectionCursorNext();
7179                         doSelection(ST.COLUMN_NEXT);
7180                         break;
7181                 case ST.SELECT_PAGE_UP:
7182                         doSelectionPageUp(-1);
7183                         break;
7184                 case ST.SELECT_PAGE_DOWN:
7185                         doSelectionPageDown(-1);
7186                         break;
7187                 case ST.SELECT_WORD_PREVIOUS:
7188                         doSelectionWordPrevious();
7189                         doSelection(ST.COLUMN_PREVIOUS);
7190                         break;
7191                 case ST.SELECT_WORD_NEXT:
7192                         doSelectionWordNext();
7193                         doSelection(ST.COLUMN_NEXT);
7194                         break;
7195                 case ST.SELECT_TEXT_START:
7196                         doContentStart();
7197                         doSelection(ST.COLUMN_PREVIOUS);
7198                         break;
7199                 case ST.SELECT_TEXT_END:
7200                         doContentEnd();
7201                         doSelection(ST.COLUMN_NEXT);
7202                         break;
7203                 case ST.SELECT_WINDOW_START:
7204                         doPageStart();
7205                         doSelection(ST.COLUMN_PREVIOUS);
7206                         break;
7207                 case ST.SELECT_WINDOW_END:
7208                         doPageEnd();
7209                         doSelection(ST.COLUMN_NEXT);
7210                         break;
7211                 // Modification
7212                 case ST.CUT:
7213                         cut();
7214                         break;
7215                 case ST.COPY:
7216                         copy();
7217                         break;
7218                 case ST.PASTE:
7219                         paste();
7220                         break;
7221                 case ST.DELETE_PREVIOUS:
7222                         doBackspace();
7223                         break;
7224                 case ST.DELETE_NEXT:
7225                         doDelete();
7226                         break;
7227                 case ST.DELETE_WORD_PREVIOUS:
7228                         doDeleteWordPrevious();
7229                         break;
7230                 case ST.DELETE_WORD_NEXT:
7231                         doDeleteWordNext();
7232                         break;
7233                 // Miscellaneous
7234                 case ST.TOGGLE_OVERWRITE:
7235                         overwrite = !overwrite;         // toggle insert/overwrite mode
7236                         break;
7237                 case ST.TOGGLE_BLOCKSELECTION:
7238                         setBlockSelection(!blockSelection);
7239                         break;
7240         }
7241 }
7242 /**
7243 * Returns true if an action should not be performed when block
7244 * selection in active
7245 */
7246 boolean invokeBlockAction(int action) {
7247         switch (action) {
7248                 // Navigation
7249                 case ST.LINE_UP:
7250                 case ST.LINE_DOWN:
7251                 case ST.LINE_START:
7252                 case ST.LINE_END:
7253                 case ST.COLUMN_PREVIOUS:
7254                 case ST.COLUMN_NEXT:
7255                 case ST.PAGE_UP:
7256                 case ST.PAGE_DOWN:
7257                 case ST.WORD_PREVIOUS:
7258                 case ST.WORD_NEXT:
7259                 case ST.TEXT_START:
7260                 case ST.TEXT_END:
7261                 case ST.WINDOW_START:
7262                 case ST.WINDOW_END:
7263                         clearBlockSelection(false, false);
7264                         return false;
7265                 // Selection
7266                 case ST.SELECT_LINE_UP:
7267                         doBlockLineVertical(true);
7268                         return true;
7269                 case ST.SELECT_LINE_DOWN:
7270                         doBlockLineVertical(false);
7271                         return true;
7272                 case ST.SELECT_LINE_START:
7273                         doBlockLineHorizontal(false);
7274                         return true;
7275                 case ST.SELECT_LINE_END:
7276                         doBlockLineHorizontal(true);
7277                         return false;
7278                 case ST.SELECT_COLUMN_PREVIOUS:
7279                         doBlockColumn(false);
7280                         return true;
7281                 case ST.SELECT_COLUMN_NEXT:
7282                         doBlockColumn(true);
7283                         return true;
7284                 case ST.SELECT_WORD_PREVIOUS:
7285                         doBlockWord(false);
7286                         return true;
7287                 case ST.SELECT_WORD_NEXT:
7288                         doBlockWord(true);
7289                         return true;
7290                 case ST.SELECT_ALL:
7291                         return false;
7292                 case ST.SELECT_TEXT_START:
7293                         doBlockContentStartEnd(false);
7294                         break;
7295                 case ST.SELECT_TEXT_END:
7296                         doBlockContentStartEnd(true);
7297                         break;
7298                 case ST.SELECT_PAGE_UP:
7299                 case ST.SELECT_PAGE_DOWN:
7300                 case ST.SELECT_WINDOW_START:
7301                 case ST.SELECT_WINDOW_END:
7302                         //blocked actions
7303                         return true;
7304                 // Modification
7305                 case ST.CUT:
7306                 case ST.COPY:
7307                 case ST.PASTE:
7308                         return false;
7309                 case ST.DELETE_PREVIOUS:
7310                 case ST.DELETE_NEXT:
7311                         if (blockXLocation != -1) {
7312                                 insertBlockSelectionText((char)0, action);
7313                                 return true;
7314                         }
7315                         return false;
7316                 case ST.DELETE_WORD_PREVIOUS:
7317                 case ST.DELETE_WORD_NEXT:
7318                         //blocked actions
7319                         return blockXLocation != -1;
7320         }
7321         return false;
7322 }
7323 boolean isBidiCaret() {
7324         return BidiUtil.isBidiPlatform();
7325 }
7326 boolean isFixedLineHeight() {
7327         return !isWordWrap() && lineSpacing == 0 && renderer.lineSpacingProvider == null && !hasStyleWithVariableHeight && !hasVerticalIndent;
7328 }
7329 /**
7330  * Returns whether the given offset is inside a multi byte line delimiter.
7331  * Example:
7332  * "Line1\r\n" isLineDelimiter(5) == false but isLineDelimiter(6) == true
7333  *
7334  * @return true if the given offset is inside a multi byte line delimiter.
7335  * false if the given offset is before or after a line delimiter.
7336  */
7337 boolean isLineDelimiter(int offset) {
7338         int line = content.getLineAtOffset(offset);
7339         int lineOffset = content.getOffsetAtLine(line);
7340         int offsetInLine = offset - lineOffset;
7341         // offsetInLine will be greater than line length if the line
7342         // delimiter is longer than one character and the offset is set
7343         // in between parts of the line delimiter.
7344         return offsetInLine > content.getLine(line).length();
7345 }
7346 /**
7347  * Returns whether the widget is mirrored (right oriented/right to left
7348  * writing order).
7349  *
7350  * @return isMirrored true=the widget is right oriented, false=the widget
7351  *      is left oriented
7352  */
7353 boolean isMirrored() {
7354         return (getStyle() & SWT.MIRRORED) != 0;
7355 }
7356 /**
7357  * Returns <code>true</code> if any text in the widget is selected,
7358  * and <code>false</code> otherwise.
7359  *
7360  * @return the text selection state
7361  * @exception SWTException <ul>
7362  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7363  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7364  * </ul>
7365  *
7366  * @since 3.103
7367  */
7368 public boolean isTextSelected() {
7369         checkWidget();
7370         if (blockSelection && blockXLocation != -1) {
7371                 Rectangle rect = getBlockSelectionPosition();
7372                 return !rect.isEmpty();
7373         }
7374         return selection.y != selection.x;
7375 }
7376 /**
7377  * Returns whether the widget can have only one line.
7378  *
7379  * @return true if widget can have only one line, false if widget can have
7380  *      multiple lines
7381  */
7382 boolean isSingleLine() {
7383         return (getStyle() & SWT.SINGLE) != 0;
7384 }
7385
7386 /**
7387  * Sends the specified verify event, replace/insert text as defined by
7388  * the event and send a modify event.
7389  *
7390  * @param event the text change event.
7391  *      <ul>
7392  *      <li>event.start - the replace start offset</li>
7393  *      <li>event.end - the replace end offset</li>
7394  *      <li>event.text - the new text</li>
7395  *      </ul>
7396  * @param updateCaret whether or not he caret should be set behind
7397  *      the new text
7398  */
7399 void modifyContent(Event event, boolean updateCaret) {
7400         event.doit = true;
7401         notifyListeners(SWT.Verify, event);
7402         if (event.doit) {
7403                 StyledTextEvent styledTextEvent = null;
7404                 int replacedLength = event.end - event.start;
7405                 if (isListening(ST.ExtendedModify)) {
7406                         styledTextEvent = new StyledTextEvent(content);
7407                         styledTextEvent.start = event.start;
7408                         styledTextEvent.end = event.start + event.text.length();
7409                         styledTextEvent.text = content.getTextRange(event.start, replacedLength);
7410                 }
7411                 if (updateCaret) {
7412                         //Fix advancing flag for delete/backspace key on direction boundary
7413                         if (event.text.length() == 0) {
7414                                 int lineIndex = content.getLineAtOffset(event.start);
7415                                 int lineOffset = content.getOffsetAtLine(lineIndex);
7416                                 TextLayout layout = renderer.getTextLayout(lineIndex);
7417                                 int levelStart = layout.getLevel(event.start - lineOffset);
7418                                 int lineIndexEnd = content.getLineAtOffset(event.end);
7419                                 if (lineIndex != lineIndexEnd) {
7420                                         renderer.disposeTextLayout(layout);
7421                                         lineOffset = content.getOffsetAtLine(lineIndexEnd);
7422                                         layout = renderer.getTextLayout(lineIndexEnd);
7423                                 }
7424                                 int levelEnd = layout.getLevel(event.end - lineOffset);
7425                                 renderer.disposeTextLayout(layout);
7426                                 if (levelStart != levelEnd) {
7427                                         caretAlignment = PREVIOUS_OFFSET_TRAILING;
7428                                 } else {
7429                                         caretAlignment = OFFSET_LEADING;
7430                                 }
7431                         }
7432                 }
7433                 content.replaceTextRange(event.start, replacedLength, event.text);
7434                 // set the caret position prior to sending the modify event.
7435                 // fixes 1GBB8NJ
7436                 if (updateCaret && !(blockSelection && blockXLocation != -1)) {
7437                         // always update the caret location. fixes 1G8FODP
7438                         setSelection(event.start + event.text.length(), 0, true, false);
7439                         showCaret();
7440                 }
7441                 notifyListeners(SWT.Modify, event);
7442                 if (isListening(ST.ExtendedModify)) {
7443                         notifyListeners(ST.ExtendedModify, styledTextEvent);
7444                 }
7445         }
7446 }
7447 void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) {
7448         if (isListening(ST.PaintObject)) {
7449                 StyledTextEvent event = new StyledTextEvent (content) ;
7450                 event.gc = gc;
7451                 event.x = x;
7452                 event.y = y;
7453                 event.ascent = ascent;
7454                 event.descent = descent;
7455                 event.style = style;
7456                 event.bullet = bullet;
7457                 event.bulletIndex = bulletIndex;
7458                 notifyListeners(ST.PaintObject, event);
7459         }
7460 }
7461 /**
7462  * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
7463  * clipboard  or, if there is no selection,  inserts the text at the current
7464  * caret offset.   If the widget has the SWT.SINGLE style and the
7465  * clipboard text contains more than one line, only the first line without
7466  * line delimiters is  inserted in the widget.
7467  *
7468  * @exception SWTException <ul>
7469  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7470  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7471  * </ul>
7472  */
7473 public void paste(){
7474         checkWidget();
7475         String text = (String) getClipboardContent(DND.CLIPBOARD);
7476         if (text != null && text.length() > 0) {
7477                 if (blockSelection) {
7478                         boolean fillWithSpaces = isFixedLineHeight() && renderer.fixedPitch;
7479                         int offset = insertBlockSelectionText(text, fillWithSpaces);
7480                         setCaretOffset(offset, SWT.DEFAULT);
7481                         clearBlockSelection(true, true);
7482                         setCaretLocation();
7483                         return;
7484                 }
7485                 Event event = new Event();
7486                 event.start = selection.x;
7487                 event.end = selection.y;
7488                 String delimitedText = getModelDelimitedText(text);
7489                 if (textLimit > 0) {
7490                         int uneditedTextLength = getCharCount() - (selection.y - selection.x);
7491                         if ((uneditedTextLength + delimitedText.length()) > textLimit) {
7492                                 int endIndex = textLimit - uneditedTextLength;
7493                                 delimitedText = delimitedText.substring(0, Math.max(endIndex, 0));
7494                         }
7495                 }
7496                 event.text = delimitedText;
7497                 sendKeyEvent(event);
7498         }
7499 }
7500 private void pasteOnMiddleClick(Event event) {
7501         String text = (String)getClipboardContent(DND.SELECTION_CLIPBOARD);
7502         if (text != null && text.length() > 0) {
7503                 // position cursor
7504                 doMouseLocationChange(event.x, event.y, false);
7505                 // insert text
7506                 Event e = new Event();
7507                 e.start = selection.x;
7508                 e.end = selection.y;
7509                 e.text = getModelDelimitedText(text);
7510                 sendKeyEvent(e);
7511         }
7512 }
7513 /**
7514  * Prints the widget's text to the default printer.
7515  *
7516  * @exception SWTException <ul>
7517  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7518  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7519  * </ul>
7520  */
7521 public void print() {
7522         checkWidget();
7523         Printer printer = new Printer();
7524         StyledTextPrintOptions options = new StyledTextPrintOptions();
7525         options.printTextForeground = true;
7526         options.printTextBackground = true;
7527         options.printTextFontStyle = true;
7528         options.printLineBackground = true;
7529         new Printing(this, printer, options).run();
7530         printer.dispose();
7531 }
7532 /**
7533  * Returns a runnable that will print the widget's text
7534  * to the specified printer.
7535  * <p>
7536  * The runnable may be run in a non-UI thread.
7537  * </p>
7538  *
7539  * @param printer the printer to print to
7540  *
7541  * @return a <code>Runnable</code> for printing the receiver's text
7542  *
7543  * @exception SWTException <ul>
7544  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7545  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7546  * </ul>
7547  * @exception IllegalArgumentException <ul>
7548  *    <li>ERROR_NULL_ARGUMENT when printer is null</li>
7549  * </ul>
7550  */
7551 public Runnable print(Printer printer) {
7552         checkWidget();
7553         if (printer == null) {
7554                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
7555         }
7556         StyledTextPrintOptions options = new StyledTextPrintOptions();
7557         options.printTextForeground = true;
7558         options.printTextBackground = true;
7559         options.printTextFontStyle = true;
7560         options.printLineBackground = true;
7561         return print(printer, options);
7562 }
7563 /**
7564  * Returns a runnable that will print the widget's text
7565  * to the specified printer.
7566  * <p>
7567  * The runnable may be run in a non-UI thread.
7568  * </p>
7569  *
7570  * @param printer the printer to print to
7571  * @param options print options to use during printing
7572  *
7573  * @return a <code>Runnable</code> for printing the receiver's text
7574  *
7575  * @exception SWTException <ul>
7576  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7577  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7578  * </ul>
7579  * @exception IllegalArgumentException <ul>
7580  *    <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
7581  * </ul>
7582  * @since 2.1
7583  */
7584 public Runnable print(Printer printer, StyledTextPrintOptions options) {
7585         checkWidget();
7586         if (printer == null || options == null) {
7587                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
7588         }
7589         return new Printing(this, printer, options);
7590 }
7591 /**
7592  * Causes the entire bounds of the receiver to be marked
7593  * as needing to be redrawn. The next time a paint request
7594  * is processed, the control will be completely painted.
7595  * <p>
7596  * Recalculates the content width for all lines in the bounds.
7597  * When a <code>LineStyleListener</code> is used a redraw call
7598  * is the only notification to the widget that styles have changed
7599  * and that the content width may have changed.
7600  * </p>
7601  *
7602  * @exception SWTException <ul>
7603  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7604  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7605  * </ul>
7606  *
7607  * @see Control#update()
7608  */
7609 @Override
7610 public void redraw() {
7611         super.redraw();
7612         int itemCount = getPartialBottomIndex() - topIndex + 1;
7613         renderer.reset(topIndex, itemCount);
7614         renderer.calculate(topIndex, itemCount);
7615         setScrollBars(false);
7616         doMouseLinkCursor();
7617 }
7618 /**
7619  * Causes the rectangular area of the receiver specified by
7620  * the arguments to be marked as needing to be redrawn.
7621  * The next time a paint request is processed, that area of
7622  * the receiver will be painted. If the <code>all</code> flag
7623  * is <code>true</code>, any children of the receiver which
7624  * intersect with the specified area will also paint their
7625  * intersecting areas. If the <code>all</code> flag is
7626  * <code>false</code>, the children will not be painted.
7627  * <p>
7628  * Marks the content width of all lines in the specified rectangle
7629  * as unknown. Recalculates the content width of all visible lines.
7630  * When a <code>LineStyleListener</code> is used a redraw call
7631  * is the only notification to the widget that styles have changed
7632  * and that the content width may have changed.
7633  * </p>
7634  *
7635  * @param x the x coordinate of the area to draw
7636  * @param y the y coordinate of the area to draw
7637  * @param width the width of the area to draw
7638  * @param height the height of the area to draw
7639  * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
7640  *
7641  * @exception SWTException <ul>
7642  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7643  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7644  * </ul>
7645  *
7646  * @see Control#update()
7647  */
7648 @Override
7649 public void redraw(int x, int y, int width, int height, boolean all) {
7650         super.redraw(x, y, width, height, all);
7651         if (height > 0) {
7652                 int firstLine = getLineIndex(y);
7653                 int lastLine = getLineIndex(y + height);
7654                 resetCache(firstLine, lastLine - firstLine + 1);
7655                 doMouseLinkCursor();
7656         }
7657 }
7658 void redrawLines(int startLine, int lineCount, boolean bottomChanged) {
7659         // do nothing if redraw range is completely invisible
7660         int endLine = startLine + lineCount - 1;
7661         int partialBottomIndex = getPartialBottomIndex();
7662         int partialTopIndex = getPartialTopIndex();
7663         if (startLine > partialBottomIndex || endLine < partialTopIndex) {
7664                 return;
7665         }
7666         // only redraw visible lines
7667         if (startLine < partialTopIndex) {
7668                 startLine = partialTopIndex;
7669         }
7670         if (endLine > partialBottomIndex) {
7671                 endLine = partialBottomIndex;
7672         }
7673         int redrawTop = getLinePixel(startLine);
7674         int redrawBottom = getLinePixel(endLine + 1);
7675         if (bottomChanged) redrawBottom = clientAreaHeight - bottomMargin;
7676         int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
7677         super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
7678 }
7679 void redrawLinesBullet (int[] redrawLines) {
7680         if (redrawLines == null) return;
7681         int topIndex = getPartialTopIndex();
7682         int bottomIndex = getPartialBottomIndex();
7683         for (int i = 0; i < redrawLines.length; i++) {
7684                 int lineIndex = redrawLines[i];
7685                 if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue;
7686                 int width = -1;
7687                 Bullet bullet = renderer.getLineBullet(lineIndex, null);
7688                 if (bullet != null) {
7689                         StyleRange style = bullet.style;
7690                         GlyphMetrics metrics = style.metrics;
7691                         width = metrics.width;
7692                 }
7693                 if (width == -1) width = getClientArea().width;
7694                 int height = renderer.getLineHeight(lineIndex);
7695                 int y = getLinePixel(lineIndex);
7696                 super.redraw(0, y, width, height, false);
7697         }
7698 }
7699 void redrawMargins(int oldHeight, int oldWidth) {
7700         /* Redraw the old or new right/bottom margin if needed */
7701         if (oldWidth != clientAreaWidth) {
7702                 if (rightMargin > 0) {
7703                         int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
7704                         super.redraw(x, 0, rightMargin, oldHeight, false);
7705                 }
7706         }
7707         if (oldHeight != clientAreaHeight) {
7708                 if (bottomMargin > 0) {
7709                         int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
7710                         super.redraw(0, y, oldWidth, bottomMargin, false);
7711                 }
7712         }
7713 }
7714 /**
7715  * Redraws the specified text range.
7716  *
7717  * @param start offset of the first character to redraw
7718  * @param length number of characters to redraw
7719  * @param clearBackground true if the background should be cleared as
7720  *  part of the redraw operation.  If true, the entire redraw range will
7721  *  be cleared before anything is redrawn.  If the redraw range includes
7722  *      the last character of a line (i.e., the entire line is redrawn) the
7723  *      line is cleared all the way to the right border of the widget.
7724  *      The redraw operation will be faster and smoother if clearBackground
7725  *      is set to false.  Whether or not the flag can be set to false depends
7726  *      on the type of change that has taken place.  If font styles or
7727  *      background colors for the redraw range have changed, clearBackground
7728  *      should be set to true.  If only foreground colors have changed for
7729  *      the redraw range, clearBackground can be set to false.
7730  * @exception SWTException <ul>
7731  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7732  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7733  * </ul>
7734  * @exception IllegalArgumentException <ul>
7735  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
7736  * </ul>
7737  */
7738 public void redrawRange(int start, int length, boolean clearBackground) {
7739         checkWidget();
7740         int end = start + length;
7741         int contentLength = content.getCharCount();
7742         if (start > end || start < 0 || end > contentLength) {
7743                 SWT.error(SWT.ERROR_INVALID_RANGE);
7744         }
7745         int firstLine = content.getLineAtOffset(start);
7746         int lastLine = content.getLineAtOffset(end);
7747         resetCache(firstLine, lastLine - firstLine + 1);
7748         internalRedrawRange(start, length);
7749         doMouseLinkCursor();
7750 }
7751 /**
7752  * Removes the specified bidirectional segment listener.
7753  *
7754  * @param listener the listener which should no longer be notified
7755  *
7756  * @exception SWTException <ul>
7757  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7758  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7759  * </ul>
7760  * @exception IllegalArgumentException <ul>
7761  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7762  * </ul>
7763  *
7764  * @since 2.0
7765  */
7766 public void removeBidiSegmentListener(BidiSegmentListener listener) {
7767         checkWidget();
7768         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7769         removeListener(ST.LineGetSegments, listener);
7770         resetCache(0, content.getLineCount());
7771         setCaretLocation();
7772         super.redraw();
7773 }
7774 /**
7775  * Removes the specified caret listener.
7776  *
7777  * @param listener the listener which should no longer be notified
7778  *
7779  * @exception SWTException <ul>
7780  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7781  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7782  * </ul>
7783  * @exception IllegalArgumentException <ul>
7784  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7785  * </ul>
7786  *
7787  * @since 3.5
7788  */
7789 public void removeCaretListener(CaretListener listener) {
7790         checkWidget();
7791         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7792         removeListener(ST.CaretMoved, listener);
7793 }
7794 /**
7795  * Removes the specified extended modify listener.
7796  *
7797  * @param extendedModifyListener the listener which should no longer be notified
7798  *
7799  * @exception SWTException <ul>
7800  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7801  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7802  * </ul>
7803  * @exception IllegalArgumentException <ul>
7804  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7805  * </ul>
7806  */
7807 public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
7808         checkWidget();
7809         if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7810         removeListener(ST.ExtendedModify, extendedModifyListener);
7811 }
7812 /**
7813  * Removes the specified line background listener.
7814  *
7815  * @param listener the listener which should no longer be notified
7816  *
7817  * @exception SWTException <ul>
7818  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7819  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7820  * </ul>
7821  * @exception IllegalArgumentException <ul>
7822  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7823  * </ul>
7824  */
7825 public void removeLineBackgroundListener(LineBackgroundListener listener) {
7826         checkWidget();
7827         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7828         removeListener(ST.LineGetBackground, listener);
7829 }
7830 /**
7831  * Removes the specified line style listener.
7832  *
7833  * @param listener the listener which should no longer be notified
7834  *
7835  * @exception SWTException <ul>
7836  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7837  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7838  * </ul>
7839  * @exception IllegalArgumentException <ul>
7840  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7841  * </ul>
7842  */
7843 public void removeLineStyleListener(LineStyleListener listener) {
7844         checkWidget();
7845         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7846         removeListener(ST.LineGetStyle, listener);
7847         setCaretLocation();
7848 }
7849 /**
7850  * Removes the specified modify listener.
7851  *
7852  * @param modifyListener the listener which should no longer be notified
7853  *
7854  * @exception SWTException <ul>
7855  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7856  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7857  * </ul>
7858  * @exception IllegalArgumentException <ul>
7859  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7860  * </ul>
7861  */
7862 public void removeModifyListener(ModifyListener modifyListener) {
7863         checkWidget();
7864         if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7865         removeListener(SWT.Modify, modifyListener);
7866 }
7867 /**
7868  * Removes the specified listener.
7869  *
7870  * @param listener the listener which should no longer be notified
7871  *
7872  * @exception SWTException <ul>
7873  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7874  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7875  * </ul>
7876  * @exception IllegalArgumentException <ul>
7877  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7878  * </ul>
7879  * @since 3.2
7880  */
7881 public void removePaintObjectListener(PaintObjectListener listener) {
7882         checkWidget();
7883         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7884         removeListener(ST.PaintObject, listener);
7885 }
7886 /**
7887  * Removes the listener from the collection of listeners who will
7888  * be notified when the user changes the receiver's selection.
7889  *
7890  * @param listener the listener which should no longer be notified
7891  *
7892  * @exception IllegalArgumentException <ul>
7893  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
7894  * </ul>
7895  * @exception SWTException <ul>
7896  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7897  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7898  * </ul>
7899  *
7900  * @see SelectionListener
7901  * @see #addSelectionListener
7902  */
7903 public void removeSelectionListener(SelectionListener listener) {
7904         checkWidget();
7905         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7906         removeListener(SWT.Selection, listener);
7907 }
7908 /**
7909  * Removes the specified verify listener.
7910  *
7911  * @param verifyListener the listener which should no longer be notified
7912  *
7913  * @exception SWTException <ul>
7914  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7915  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7916  * </ul>
7917  * @exception IllegalArgumentException <ul>
7918  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7919  * </ul>
7920  */
7921 public void removeVerifyListener(VerifyListener verifyListener) {
7922         checkWidget();
7923         if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7924         removeListener(SWT.Verify, verifyListener);
7925 }
7926 /**
7927  * Removes the specified key verify listener.
7928  *
7929  * @param listener the listener which should no longer be notified
7930  *
7931  * @exception SWTException <ul>
7932  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7933  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7934  * </ul>
7935  * @exception IllegalArgumentException <ul>
7936  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7937  * </ul>
7938  */
7939 public void removeVerifyKeyListener(VerifyKeyListener listener) {
7940         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7941         removeListener(ST.VerifyKey, listener);
7942 }
7943 /**
7944  * Removes the specified word movement listener.
7945  *
7946  * @param listener the listener which should no longer be notified
7947  *
7948  * @exception SWTException <ul>
7949  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7950  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7951  * </ul>
7952  * @exception IllegalArgumentException <ul>
7953  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7954  * </ul>
7955  *
7956  * @see MovementEvent
7957  * @see MovementListener
7958  * @see #addWordMovementListener
7959  *
7960  * @since 3.3
7961  */
7962
7963 public void removeWordMovementListener(MovementListener listener) {
7964         checkWidget();
7965         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7966         removeListener(ST.WordNext, listener);
7967         removeListener(ST.WordPrevious, listener);
7968 }
7969 /**
7970  * Replaces the styles in the given range with new styles.  This method
7971  * effectively deletes the styles in the given range and then adds the
7972  * the new styles.
7973  * <p>
7974  * Note: Because a StyleRange includes the start and length, the
7975  * same instance cannot occur multiple times in the array of styles.
7976  * If the same style attributes, such as font and color, occur in
7977  * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
7978  * can be used to share styles and reduce memory usage.
7979  * </p><p>
7980  * Should not be called if a LineStyleListener has been set since the
7981  * listener maintains the styles.
7982  * </p>
7983  *
7984  * @param start offset of first character where styles will be deleted
7985  * @param length length of the range to delete styles in
7986  * @param ranges StyleRange objects containing the new style information.
7987  * The ranges should not overlap and should be within the specified start
7988  * and length. The style rendering is undefined if the ranges do overlap
7989  * or are ill-defined. Must not be null.
7990  * @exception SWTException <ul>
7991  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7992  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7993  * </ul>
7994  * @exception IllegalArgumentException <ul>
7995  *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 &lt;= offset &lt;= getCharCount())</li>
7996  *   <li>ERROR_NULL_ARGUMENT when ranges is null</li>
7997  * </ul>
7998  *
7999  * @since 2.0
8000  *
8001  * @see #setStyleRanges(int, int, int[], StyleRange[])
8002  */
8003 public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
8004         checkWidget();
8005         if (isListening(ST.LineGetStyle)) return;
8006         if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
8007         setStyleRanges(start, length, null, ranges, false);
8008 }
8009 /**
8010  * Replaces the given text range with new text.
8011  * If the widget has the SWT.SINGLE style and "text" contains more than
8012  * one line, only the first line is rendered but the text is stored
8013  * unchanged. A subsequent call to getText will return the same text
8014  * that was set. Note that only a single line of text should be set when
8015  * the SWT.SINGLE style is used.
8016  * <p>
8017  * <b>NOTE:</b> During the replace operation the current selection is
8018  * changed as follows:
8019  * </p>
8020  * <ul>
8021  * <li>selection before replaced text: selection unchanged
8022  * <li>selection after replaced text: adjust the selection so that same text
8023  * remains selected
8024  * <li>selection intersects replaced text: selection is cleared and caret
8025  * is placed after inserted text
8026  * </ul>
8027  *
8028  * @param start offset of first character to replace
8029  * @param length number of characters to replace. Use 0 to insert text
8030  * @param text new text. May be empty to delete text.
8031  * @exception SWTException <ul>
8032  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8033  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8034  * </ul>
8035  * @exception IllegalArgumentException <ul>
8036  *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 &lt;= offset &lt;= getCharCount())</li>
8037  *   <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
8038  *              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>
8039  *   <li>ERROR_NULL_ARGUMENT when string is null</li>
8040  * </ul>
8041  */
8042 public void replaceTextRange(int start, int length, String text) {
8043         checkWidget();
8044         if (text == null) {
8045                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
8046         }
8047         int contentLength = getCharCount();
8048         int end = start + length;
8049         if (start > end || start < 0 || end > contentLength) {
8050                 SWT.error(SWT.ERROR_INVALID_RANGE);
8051         }
8052         Event event = new Event();
8053         event.start = start;
8054         event.end = end;
8055         event.text = text;
8056         modifyContent(event, false);
8057 }
8058 /**
8059  * Resets the caret position, selection and scroll offsets. Recalculate
8060  * the content width and scroll bars. Redraw the widget.
8061  */
8062 void reset() {
8063         ScrollBar verticalBar = getVerticalBar();
8064         ScrollBar horizontalBar = getHorizontalBar();
8065         setCaretOffset(0, SWT.DEFAULT);
8066         topIndex = 0;
8067         topIndexY = 0;
8068         verticalScrollOffset = 0;
8069         horizontalScrollOffset = 0;
8070         resetSelection();
8071         renderer.setContent(content);
8072         if (verticalBar != null) {
8073                 verticalBar.setSelection(0);
8074         }
8075         if (horizontalBar != null) {
8076                 horizontalBar.setSelection(0);
8077         }
8078         resetCache(0, 0);
8079         setCaretLocation();
8080         super.redraw();
8081 }
8082 void resetBidiData() {
8083         caretDirection = SWT.NULL;
8084         resetCache(0, content.getLineCount());
8085         setCaretLocation();
8086         keyActionMap.clear();
8087         createKeyBindings();
8088         super.redraw();
8089 }
8090 void resetCache(SortedSet<Integer> lines) {
8091         if (lines == null || lines.isEmpty()) return;
8092         int maxLineIndex = renderer.maxWidthLineIndex;
8093         renderer.reset(lines);
8094         renderer.calculateClientArea();
8095         if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
8096                 renderer.calculate(maxLineIndex, 1);
8097         }
8098         setScrollBars(true);
8099         if (!isFixedLineHeight()) {
8100                 if (topIndex > lines.iterator().next()) {
8101                         verticalScrollOffset = -1;
8102                 }
8103                 renderer.calculateIdle();
8104         }
8105 }
8106 void resetCache(int firstLine, int count) {
8107         int maxLineIndex = renderer.maxWidthLineIndex;
8108         renderer.reset(firstLine, count);
8109         renderer.calculateClientArea();
8110         if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
8111                 renderer.calculate(maxLineIndex, 1);
8112         }
8113         setScrollBars(true);
8114         if (!isFixedLineHeight()) {
8115                 if (topIndex > firstLine) {
8116                         verticalScrollOffset = -1;
8117                 }
8118                 renderer.calculateIdle();
8119         }
8120 }
8121 /**
8122  * Resets the selection.
8123  */
8124 void resetSelection() {
8125         selection.x = selection.y = caretOffset;
8126         selectionAnchor = -1;
8127         sendAccessibleTextCaretMoved();
8128 }
8129
8130 @Override
8131 public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) {
8132         super.scroll(destX, destY, x, y, width, height, false);
8133         if (all) {
8134                 int deltaX = destX - x, deltaY = destY - y;
8135                 Control[] children = getChildren();
8136                 for (int i=0; i<children.length; i++) {
8137                         Control child = children[i];
8138                         Rectangle rect = child.getBounds();
8139                         child.setLocation(rect.x + deltaX, rect.y + deltaY);
8140                 }
8141         }
8142 }
8143
8144 /**
8145  * Scrolls the widget horizontally.
8146  *
8147  * @param pixels number of SWT logical points to scroll, &gt; 0 = scroll left,
8148  *      &lt; 0 scroll right
8149  * @param adjustScrollBar
8150  *      true= the scroll thumb will be moved to reflect the new scroll offset.
8151  *      false = the scroll thumb will not be moved
8152  * @return
8153  *      true=the widget was scrolled
8154  *      false=the widget was not scrolled, the given offset is not valid.
8155  */
8156 boolean scrollHorizontal(int pixels, boolean adjustScrollBar) {
8157         if (pixels == 0) return false;
8158         if (wordWrap) return false;
8159         ScrollBar horizontalBar = getHorizontalBar();
8160         if (horizontalBar != null && adjustScrollBar) {
8161                 horizontalBar.setSelection(horizontalScrollOffset + pixels);
8162         }
8163         int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
8164         if (pixels > 0) {
8165                 int sourceX = leftMargin + pixels;
8166                 int scrollWidth = clientAreaWidth - sourceX - rightMargin;
8167                 if (scrollWidth > 0) {
8168                         scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
8169                 }
8170                 if (sourceX > scrollWidth) {
8171                         super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
8172                 }
8173         } else {
8174                 int destinationX = leftMargin - pixels;
8175                 int scrollWidth = clientAreaWidth - destinationX - rightMargin;
8176                 if (scrollWidth > 0) {
8177                         scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
8178                 }
8179                 if (destinationX > scrollWidth) {
8180                         super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
8181                 }
8182         }
8183         horizontalScrollOffset += pixels;
8184         setCaretLocation();
8185         return true;
8186 }
8187 /**
8188  * Scrolls the widget vertically.
8189  *
8190  * @param pixel the new vertical scroll offset
8191  * @param adjustScrollBar
8192  *      true= the scroll thumb will be moved to reflect the new scroll offset.
8193  *      false = the scroll thumb will not be moved
8194  * @return
8195  *      true=the widget was scrolled
8196  *      false=the widget was not scrolled
8197  */
8198 boolean scrollVertical(int pixels, boolean adjustScrollBar) {
8199         if (pixels == 0) {
8200                 return false;
8201         }
8202         if (verticalScrollOffset != -1) {
8203                 ScrollBar verticalBar = getVerticalBar();
8204                 if (verticalBar != null && adjustScrollBar) {
8205                         verticalBar.setSelection(verticalScrollOffset + pixels);
8206                 }
8207                 int deltaY = 0;
8208                 if (pixels > 0) {
8209                         int sourceY = topMargin + pixels;
8210                         int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
8211                         if (scrollHeight > 0) {
8212                                 deltaY = -pixels;
8213                         }
8214                 } else {
8215                         int destinationY = topMargin - pixels;
8216                         int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
8217                         if (scrollHeight > 0) {
8218                                 deltaY = -pixels;
8219                         }
8220                 }
8221                 Control[] children = getChildren();
8222                 for (int i=0; i<children.length; i++) {
8223                         Control child = children[i];
8224                         Rectangle rect = child.getBounds();
8225                         child.setLocation(rect.x, rect.y + deltaY);
8226                 }
8227                 verticalScrollOffset += pixels;
8228                 calculateTopIndex(pixels);
8229                 super.redraw();
8230         } else {
8231                 calculateTopIndex(pixels);
8232                 super.redraw();
8233         }
8234         setCaretLocation();
8235         return true;
8236 }
8237 void scrollText(int srcY, int destY) {
8238         if (srcY == destY) return;
8239         int deltaY = destY - srcY;
8240         int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
8241         if (deltaY > 0) {
8242                 scrollHeight = clientAreaHeight - srcY - bottomMargin;
8243         } else {
8244                 scrollHeight = clientAreaHeight - destY - bottomMargin;
8245         }
8246         scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
8247         if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
8248                 super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
8249         }
8250         if ((0 < destY + scrollHeight) && (topMargin > destY)) {
8251                 super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
8252         }
8253         if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
8254                 super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
8255         }
8256         if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
8257                 super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
8258         }
8259 }
8260 void sendAccessibleTextCaretMoved() {
8261         if (caretOffset != accCaretOffset) {
8262                 accCaretOffset = caretOffset;
8263                 getAccessible().textCaretMoved(caretOffset);
8264         }
8265 }
8266 void sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount) {
8267         Accessible accessible = getAccessible();
8268         if (replaceCharCount != 0) {
8269                 accessible.textChanged(ACC.TEXT_DELETE, start, replaceCharCount);
8270         }
8271         if (newCharCount != 0) {
8272                 accessible.textChanged(ACC.TEXT_INSERT, start, newCharCount);
8273         }
8274 }
8275 /**
8276  * Selects all the text.
8277  *
8278  * @exception SWTException <ul>
8279  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8280  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8281  * </ul>
8282  */
8283 public void selectAll() {
8284         checkWidget();
8285         if (blockSelection) {
8286                 renderer.calculate(0, content.getLineCount());
8287                 setScrollBars(false);
8288                 int verticalScrollOffset = getVerticalScrollOffset();
8289                 int left = leftMargin - horizontalScrollOffset;
8290                 int top = topMargin - verticalScrollOffset;
8291                 int right = renderer.getWidth() - rightMargin - horizontalScrollOffset;
8292                 int bottom = renderer.getHeight() - bottomMargin - verticalScrollOffset;
8293                 setBlockSelectionLocation(left, top, right, bottom, false);
8294                 return;
8295         }
8296         setSelection(0, Math.max(getCharCount(),0));
8297 }
8298 /**
8299  * Replaces/inserts text as defined by the event.
8300  *
8301  * @param event the text change event.
8302  *      <ul>
8303  *      <li>event.start - the replace start offset</li>
8304  *      <li>event.end - the replace end offset</li>
8305  *      <li>event.text - the new text</li>
8306  *      </ul>
8307  */
8308 void sendKeyEvent(Event event) {
8309         if (editable) {
8310                 modifyContent(event, true);
8311         }
8312 }
8313 /**
8314  * Returns a StyledTextEvent that can be used to request data such
8315  * as styles and background color for a line.
8316  * <p>
8317  * The specified line may be a visual (wrapped) line if in word
8318  * wrap mode. The returned object will always be for a logical
8319  * (unwrapped) line.
8320  * </p>
8321  *
8322  * @param lineOffset offset of the line. This may be the offset of
8323  *      a visual line if the widget is in word wrap mode.
8324  * @param line line text. This may be the text of a visual line if
8325  *      the widget is in word wrap mode.
8326  * @return StyledTextEvent that can be used to request line data
8327  *      for the given line.
8328  */
8329 StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
8330         StyledTextEvent event = null;
8331         if (isListening(eventType)) {
8332                 event = new StyledTextEvent(content);
8333                 event.detail = lineOffset;
8334                 event.text = line;
8335                 event.alignment = alignment;
8336                 event.indent = indent;
8337                 event.wrapIndent = wrapIndent;
8338                 event.justify = justify;
8339                 notifyListeners(eventType, event);
8340         }
8341         return event;
8342 }
8343 /**
8344  * Sends the specified selection event.
8345  */
8346 void sendSelectionEvent() {
8347         getAccessible().textSelectionChanged();
8348         Event event = new Event();
8349         event.x = selection.x;
8350         event.y = selection.y;
8351         notifyListeners(SWT.Selection, event);
8352 }
8353 int sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces) {
8354         int lineWidth = 0, start, end;
8355         StringBuilder buffer = new StringBuilder();
8356         if (lineIndex < content.getLineCount()) {
8357                 int[] trailing = new int[1];
8358                 start = getOffsetAtPoint(left, getLinePixel(lineIndex), trailing, true);
8359                 if (start == -1) {
8360                         int lineOffset = content.getOffsetAtLine(lineIndex);
8361                         int lineLegth = content.getLine(lineIndex).length();
8362                         start = end = lineOffset + lineLegth;
8363                         if (fillWithSpaces) {
8364                                 TextLayout layout = renderer.getTextLayout(lineIndex);
8365                                 lineWidth = layout.getBounds().width;
8366                                 renderer.disposeTextLayout(layout);
8367                         }
8368                 } else {
8369                         start += trailing[0];
8370                         end = left == right ? start : getOffsetAtPoint(right, 0, lineIndex, null);
8371                         fillWithSpaces = false;
8372                 }
8373         } else {
8374                 start = end = content.getCharCount();
8375                 buffer.append(content.getLineDelimiter());
8376         }
8377         if (start > end) {
8378                 int temp = start;
8379                 start = end;
8380                 end = temp;
8381         }
8382         if (fillWithSpaces) {
8383                 int spacesWidth = left - lineWidth + horizontalScrollOffset - leftMargin;
8384                 int spacesCount = spacesWidth / renderer.averageCharWidth;
8385                 for (int i = 0; i < spacesCount; i++) {
8386                         buffer.append(' ');
8387                 }
8388         }
8389         buffer.append(text);
8390         Event event = new Event();
8391         event.start = start;
8392         event.end = end;
8393         event.text = buffer.toString();
8394         sendKeyEvent(event);
8395         return event.start + event.text.length();
8396 }
8397 int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) {
8398         if (isListening(eventType)) {
8399                 StyledTextEvent event = new StyledTextEvent(content);
8400                 event.detail = lineOffset;
8401                 event.text = lineText;
8402                 event.count = movement;
8403                 event.start = offset;
8404                 event.end = newOffset;
8405                 notifyListeners(eventType, event);
8406                 offset = event.end;
8407                 if (offset != newOffset) {
8408                         int length = getCharCount();
8409                         if (offset < 0) {
8410                                 offset = 0;
8411                         } else if (offset > length) {
8412                                 offset = length;
8413                         } else {
8414                                 if (isLineDelimiter(offset)) {
8415                                         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8416                                 }
8417                         }
8418                 }
8419                 return offset;
8420         }
8421         return newOffset;
8422 }
8423 void setAlignment() {
8424         if ((getStyle() & SWT.SINGLE) == 0) return;
8425         int alignment = renderer.getLineAlignment(0, this.alignment);
8426         int newAlignmentMargin = 0;
8427         if (alignment != SWT.LEFT) {
8428                 renderer.calculate(0, 1);
8429                 int width = renderer.getWidth() - alignmentMargin;
8430                 newAlignmentMargin = clientAreaWidth - width;
8431                 if (newAlignmentMargin < 0) newAlignmentMargin = 0;
8432                 if (alignment == SWT.CENTER) newAlignmentMargin /= 2;
8433         }
8434         if (alignmentMargin != newAlignmentMargin) {
8435                 leftMargin -= alignmentMargin;
8436                 leftMargin += newAlignmentMargin;
8437                 alignmentMargin = newAlignmentMargin;
8438                 resetCache(0, 1);
8439                 setCaretLocation();
8440                 super.redraw();
8441         }
8442 }
8443 /**
8444  * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>,
8445  * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines.
8446  * <p>
8447  * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
8448  * in order to stabilize the right edge before setting alignment.
8449  * </p>
8450  *
8451  * @param alignment the new alignment
8452  *
8453  * @exception SWTException <ul>
8454  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8455  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8456  * </ul>
8457  *
8458  * @see #setLineAlignment(int, int, int)
8459  *
8460  * @since 3.2
8461  */
8462 public void setAlignment(int alignment) {
8463         checkWidget();
8464         alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
8465         if (alignment == 0 || this.alignment == alignment) return;
8466         this.alignment = alignment;
8467         resetCache(0, content.getLineCount());
8468         setCaretLocation();
8469         setAlignment();
8470         super.redraw();
8471 }
8472 /**
8473  * Set the Always Show Scrollbars flag.  True if the scrollbars are
8474  * always shown even if they are not required (default value).  False if the scrollbars are only
8475  * visible when some part of the content needs to be scrolled to be seen.
8476  * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
8477  * horizontal and vertical directions.
8478  *
8479  * @param show true to show the scrollbars even when not required, false to show scrollbars only when required
8480  *
8481  * @exception SWTException <ul>
8482  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8483  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8484  * </ul>
8485  *
8486  * @since 3.8
8487  */
8488 public void setAlwaysShowScrollBars(boolean show) {
8489         checkWidget();
8490         if (show == alwaysShowScroll) return;
8491         alwaysShowScroll = show;
8492         setScrollBars(true);
8493 }
8494 /**
8495  * @see Control#setBackground(Color)
8496  */
8497 @Override
8498 public void setBackground(Color color) {
8499         checkWidget();
8500         boolean backgroundDisabled = false;
8501         if (!this.enabled && color == null) {
8502                 if (background != null) {
8503                         Color disabledBg = getDisplay().getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND);
8504                         if (background.equals(disabledBg)) {
8505                                 return;
8506                         } else {
8507                                 color = new Color (getDisplay(), disabledBg.getRGBA());
8508                                 backgroundDisabled = true;
8509                         }
8510                 }
8511         }
8512         customBackground = color != null && !this.insideSetEnableCall && !backgroundDisabled;
8513         background = color;
8514         super.setBackground(color);
8515         resetCache(0, content.getLineCount());
8516         setCaretLocation();
8517         super.redraw();
8518 }
8519 /**
8520  * Sets the block selection mode.
8521  *
8522  * @param blockSelection true=enable block selection, false=disable block selection
8523  *
8524  * @since 3.5
8525  */
8526 public void setBlockSelection(boolean blockSelection) {
8527         checkWidget();
8528         if ((getStyle() & SWT.SINGLE) != 0) return;
8529         if (blockSelection == this.blockSelection) return;
8530         if (wordWrap) return;
8531         this.blockSelection = blockSelection;
8532         if (cursor == null) {
8533                 Display display = getDisplay();
8534                 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
8535                 super.setCursor(display.getSystemCursor(type));
8536         }
8537         if (blockSelection) {
8538                 int start = selection.x;
8539                 int end = selection.y;
8540                 if (start != end) {
8541                         setBlockSelectionOffset(start, end, false);
8542                 }
8543         } else {
8544                 clearBlockSelection(false, false);
8545         }
8546 }
8547 /**
8548  * Sets the block selection bounds. The bounds is
8549  * relative to the upper left corner of the document.
8550  *
8551  * @param rect the new bounds for the block selection
8552  *
8553  * @see #setBlockSelectionBounds(int, int, int, int)
8554  * @exception SWTException <ul>
8555  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8556  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8557  * </ul>
8558  * @exception IllegalArgumentException <ul>
8559  *   <li>ERROR_NULL_ARGUMENT when point is null</li>
8560  * </ul>
8561  *
8562  * @since 3.5
8563  */
8564 public void setBlockSelectionBounds(Rectangle rect) {
8565         checkWidget();
8566         if (rect == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
8567         setBlockSelectionBounds(rect.x, rect.y, rect.width, rect.height);
8568 }
8569 /**
8570  * Sets the block selection bounds. The bounds is
8571  * relative to the upper left corner of the document.
8572  *
8573  * @param x the new x coordinate for the block selection
8574  * @param y the new y coordinate for the block selection
8575  * @param width the new width for the block selection
8576  * @param height the new height for the block selection
8577  *
8578  * @exception SWTException <ul>
8579  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8580  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8581  * </ul>
8582  *
8583  * @since 3.5
8584  */
8585 public void setBlockSelectionBounds(int x, int y, int width, int height) {
8586         checkWidget();
8587         int verticalScrollOffset = getVerticalScrollOffset();
8588         if (!blockSelection) {
8589                 x -= horizontalScrollOffset;
8590                 y -= verticalScrollOffset;
8591                 int start = getOffsetAtPoint(x, y, null);
8592                 int end = getOffsetAtPoint(x+width-1, y+height-1, null);
8593                 setSelection(start, end - start, false, false);
8594                 setCaretLocation();
8595                 return;
8596         }
8597         int minY = topMargin;
8598         int minX = leftMargin;
8599         int maxY = renderer.getHeight() - bottomMargin;
8600         int maxX = Math.max(clientAreaWidth, renderer.getWidth()) - rightMargin;
8601         int anchorX = Math.max(minX, Math.min(maxX, x)) - horizontalScrollOffset;
8602         int anchorY = Math.max(minY, Math.min(maxY, y)) - verticalScrollOffset;
8603         int locationX = Math.max(minX, Math.min(maxX, x + width)) - horizontalScrollOffset;
8604         int locationY = Math.max(minY, Math.min(maxY, y + height - 1)) - verticalScrollOffset;
8605         if (isFixedLineHeight() && renderer.fixedPitch) {
8606                 int avg = renderer.averageCharWidth;
8607                 anchorX = ((anchorX - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
8608                 locationX = ((locationX + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
8609         }
8610         setBlockSelectionLocation(anchorX, anchorY, locationX, locationY, false);
8611 }
8612 void setBlockSelectionLocation (int x, int y, boolean sendEvent) {
8613         int verticalScrollOffset = getVerticalScrollOffset();
8614         blockXLocation = x + horizontalScrollOffset;
8615         blockYLocation = y + verticalScrollOffset;
8616         int[] alignment = new int[1];
8617         int offset = getOffsetAtPoint(x, y, alignment);
8618         setCaretOffset(offset, alignment[0]);
8619         if (blockXAnchor == -1) {
8620                 blockXAnchor = blockXLocation;
8621                 blockYAnchor = blockYLocation;
8622                 selectionAnchor = caretOffset;
8623         }
8624         doBlockSelection(sendEvent);
8625 }
8626 void setBlockSelectionLocation (int anchorX, int anchorY, int x, int y, boolean sendEvent) {
8627         int verticalScrollOffset = getVerticalScrollOffset();
8628         blockXAnchor = anchorX + horizontalScrollOffset;
8629         blockYAnchor = anchorY + verticalScrollOffset;
8630         selectionAnchor = getOffsetAtPoint(anchorX, anchorY, null);
8631         setBlockSelectionLocation(x, y, sendEvent);
8632 }
8633 void setBlockSelectionOffset (int offset, boolean sendEvent) {
8634         Point point = getPointAtOffset(offset);
8635         int verticalScrollOffset = getVerticalScrollOffset();
8636         blockXLocation = point.x + horizontalScrollOffset;
8637         blockYLocation = point.y + verticalScrollOffset;
8638         setCaretOffset(offset, SWT.DEFAULT);
8639         if (blockXAnchor == -1) {
8640                 blockXAnchor = blockXLocation;
8641                 blockYAnchor = blockYLocation;
8642                 selectionAnchor = caretOffset;
8643         }
8644         doBlockSelection(sendEvent);
8645 }
8646 void setBlockSelectionOffset (int anchorOffset, int offset, boolean sendEvent) {
8647         int verticalScrollOffset = getVerticalScrollOffset();
8648         Point anchorPoint = getPointAtOffset(anchorOffset);
8649         blockXAnchor = anchorPoint.x + horizontalScrollOffset;
8650         blockYAnchor = anchorPoint.y + verticalScrollOffset;
8651         selectionAnchor = anchorOffset;
8652         setBlockSelectionOffset(offset, sendEvent);
8653 }
8654 /**
8655  * Sets the receiver's caret.  Set the caret's height and location.
8656  *
8657  * @param caret the new caret for the receiver
8658  *
8659  * @exception SWTException <ul>
8660  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8661  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8662  * </ul>
8663  */
8664 @Override
8665 public void setCaret(Caret caret) {
8666         checkWidget ();
8667         super.setCaret(caret);
8668         caretDirection = SWT.NULL;
8669         if (caret != null) {
8670                 setCaretLocation();
8671         }
8672 }
8673 /**
8674  * Sets the BIDI coloring mode.  When true the BIDI text display
8675  * algorithm is applied to segments of text that are the same
8676  * color.
8677  *
8678  * @param mode the new coloring mode
8679  * @exception SWTException <ul>
8680  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8681  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8682  * </ul>
8683  *
8684  * @deprecated use BidiSegmentListener instead.
8685  */
8686 @Deprecated
8687 public void setBidiColoring(boolean mode) {
8688         checkWidget();
8689         bidiColoring = mode;
8690 }
8691 /**
8692  * Sets the bottom margin.
8693  *
8694  * @param bottomMargin the bottom margin.
8695  * @exception SWTException <ul>
8696  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8697  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8698  * </ul>
8699  *
8700  * @since 3.5
8701  */
8702 public void setBottomMargin (int bottomMargin) {
8703         checkWidget();
8704         setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
8705 }
8706 /**
8707  * Moves the Caret to the current caret offset.
8708  */
8709 void setCaretLocation() {
8710         Point newCaretPos = getPointAtOffset(caretOffset);
8711         setCaretLocation(newCaretPos, getCaretDirection());
8712 }
8713 void setCaretLocation(final Point location, int direction) {
8714         Caret caret = getCaret();
8715         if (caret != null) {
8716                 final boolean isDefaultCaret = caret == defaultCaret;
8717                 final StyleRange styleAtOffset = content.getCharCount() > 0 ?
8718                         (caretOffset < content.getCharCount() ?
8719                                 getStyleRangeAtOffset(caretOffset) :
8720                                 getStyleRangeAtOffset(content.getCharCount() - 1)) : // caret after last char: use last char style
8721                         null;
8722                 final int caretLine = getCaretLine();
8723
8724                 int graphicalLineHeight = getLineHeight();
8725                 final int lineStartOffset = getOffsetAtLine(caretLine);
8726                 int graphicalLineFirstOffset = lineStartOffset;
8727                 final int lineEndOffset = lineStartOffset + getLine(caretLine).length();
8728                 int graphicalLineLastOffset = lineEndOffset;
8729                 if (caretLine < getLineCount() && renderer.getLineHeight(caretLine) != getLineHeight()) { // word wrap, metrics, styles...
8730                         graphicalLineHeight = getLineHeight(caretOffset);
8731                         final Rectangle characterBounds = getBoundsAtOffset(caretOffset);
8732                         graphicalLineFirstOffset = getOffsetAtPoint(new Point(leftMargin, characterBounds.y));
8733                         graphicalLineLastOffset = getOffsetAtPoint(new Point(leftMargin, characterBounds.y + graphicalLineHeight)) - 1;
8734                         if (graphicalLineLastOffset < graphicalLineFirstOffset) {
8735                                 graphicalLineLastOffset = getCharCount();
8736                         }
8737                 }
8738
8739                 int caretHeight = getLineHeight();
8740                 boolean isTextAlignedAtBottom = true;
8741                 if (graphicalLineFirstOffset >= 0) {
8742                         for (StyleRange style : getStyleRanges(graphicalLineFirstOffset, graphicalLineLastOffset - graphicalLineFirstOffset)) {
8743                                 isTextAlignedAtBottom &= (
8744                                         (style.font == null || Objects.equals(style.font, getFont())) &&
8745                                         style.rise >= 0 &&
8746                                         (style.metrics == null || style.metrics.descent <= 0)
8747                                 );
8748                         }
8749                 }
8750                 if (!isTextAlignedAtBottom || (styleAtOffset != null && styleAtOffset.isVariableHeight())) {
8751                         if (isDefaultCaret) {
8752                                 direction = SWT.DEFAULT;
8753                                 caretHeight = graphicalLineHeight;
8754                         } else {
8755                                 caretHeight = caret.getSize().y;
8756                         }
8757                 }
8758                 if (isTextAlignedAtBottom && caretHeight < graphicalLineHeight) {
8759                         location.y += (graphicalLineHeight - caretHeight);
8760                 }
8761
8762                 int imageDirection = direction;
8763                 if (isMirrored()) {
8764                         if (imageDirection == SWT.LEFT) {
8765                                 imageDirection = SWT.RIGHT;
8766                         } else if (imageDirection == SWT.RIGHT) {
8767                                 imageDirection = SWT.LEFT;
8768                         }
8769                 }
8770                 if (isDefaultCaret && imageDirection == SWT.RIGHT) {
8771                         location.x -= (caret.getSize().x - 1);
8772                 }
8773                 if (isDefaultCaret) {
8774                         caret.setBounds(location.x, location.y, caretWidth, caretHeight);
8775                 } else {
8776                         caret.setLocation(location);
8777                 }
8778                 if (direction != caretDirection) {
8779                         caretDirection = direction;
8780                         if (isDefaultCaret) {
8781                                 if (imageDirection == SWT.DEFAULT) {
8782                                         defaultCaret.setImage(null);
8783                                 } else if (imageDirection == SWT.LEFT) {
8784                                         defaultCaret.setImage(leftCaretBitmap);
8785                                 } else if (imageDirection == SWT.RIGHT) {
8786                                         defaultCaret.setImage(rightCaretBitmap);
8787                                 }
8788                         }
8789                         if (caretDirection == SWT.LEFT) {
8790                                 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
8791                         } else if (caretDirection == SWT.RIGHT) {
8792                                 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
8793                         }
8794                 }
8795                 updateCaretVisibility();
8796         }
8797         columnX = location.x;
8798 }
8799 /**
8800  * Sets the caret offset.
8801  *
8802  * @param offset caret offset, relative to the first character in the text.
8803  * @exception SWTException <ul>
8804  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8805  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8806  * </ul>
8807  * @exception IllegalArgumentException <ul>
8808  *   <li>ERROR_INVALID_ARGUMENT when the offset is inside a multi byte line
8809  *   delimiter (and thus neither clearly in front of or after the line delimiter)
8810  * </ul>
8811  */
8812 public void setCaretOffset(int offset) {
8813         checkWidget();
8814         int length = getCharCount();
8815         if (length > 0 && offset != caretOffset) {
8816                 if (offset < 0) {
8817                         offset = 0;
8818                 } else if (offset > length) {
8819                         offset = length;
8820                 } else {
8821                         if (isLineDelimiter(offset)) {
8822                                 // offset is inside a multi byte line delimiter. This is an
8823                                 // illegal operation and an exception is thrown. Fixes 1GDKK3R
8824                                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8825                         }
8826                 }
8827                 setCaretOffset(offset, PREVIOUS_OFFSET_TRAILING);
8828                 // clear the selection if the caret is moved.
8829                 // don't notify listeners about the selection change.
8830                 if (blockSelection) {
8831                         clearBlockSelection(true, false);
8832                 } else {
8833                         clearSelection(false);
8834                 }
8835         }
8836         setCaretLocation();
8837 }
8838 void setCaretOffset(int offset, int alignment) {
8839         if (caretOffset != offset) {
8840                 caretOffset = offset;
8841                 if (isListening(ST.CaretMoved)) {
8842                         StyledTextEvent event = new StyledTextEvent(content);
8843                         event.end = caretOffset;
8844                         notifyListeners(ST.CaretMoved, event);
8845                 }
8846         }
8847         if (alignment != SWT.DEFAULT) {
8848                 caretAlignment = alignment;
8849         }
8850 }
8851 /**
8852  * Copies the specified text range to the clipboard.  The text will be placed
8853  * in the clipboard in plain text format and RTF format.
8854  *
8855  * @param start start index of the text
8856  * @param length length of text to place in clipboard
8857  *
8858  * @exception SWTError
8859  * @see org.eclipse.swt.dnd.Clipboard#setContents
8860  */
8861 void setClipboardContent(int start, int length, int clipboardType) throws SWTError {
8862         if (clipboardType == DND.SELECTION_CLIPBOARD && !IS_GTK) return;
8863         TextTransfer plainTextTransfer = TextTransfer.getInstance();
8864         TextWriter plainTextWriter = new TextWriter(start, length);
8865         String plainText = getPlatformDelimitedText(plainTextWriter);
8866         Object[] data;
8867         Transfer[] types;
8868         if (clipboardType == DND.SELECTION_CLIPBOARD) {
8869                 data = new Object[]{plainText};
8870                 types = new Transfer[]{plainTextTransfer};
8871         } else {
8872                 RTFTransfer rtfTransfer = RTFTransfer.getInstance();
8873                 RTFWriter rtfWriter = new RTFWriter(start, length);
8874                 String rtfText = getPlatformDelimitedText(rtfWriter);
8875                 data = new Object[]{rtfText, plainText};
8876                 types = new Transfer[]{rtfTransfer, plainTextTransfer};
8877         }
8878         clipboard.setContents(data, types, clipboardType);
8879 }
8880 /**
8881  * Sets the content implementation to use for text storage.
8882  *
8883  * @param newContent StyledTextContent implementation to use for text storage.
8884  * @exception SWTException <ul>
8885  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8886  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8887  * </ul>
8888  * @exception IllegalArgumentException <ul>
8889  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
8890  * </ul>
8891  */
8892 public void setContent(StyledTextContent newContent) {
8893         checkWidget();
8894         if (newContent == null) {
8895                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
8896         }
8897         if (content != null) {
8898                 content.removeTextChangeListener(textChangeListener);
8899         }
8900         content = newContent;
8901         content.addTextChangeListener(textChangeListener);
8902         reset();
8903 }
8904 /**
8905  * Sets the receiver's cursor to the cursor specified by the
8906  * argument.  Overridden to handle the null case since the
8907  * StyledText widget uses an ibeam as its default cursor.
8908  *
8909  * @see Control#setCursor(Cursor)
8910  */
8911 @Override
8912 public void setCursor (Cursor cursor) {
8913         checkWidget();
8914         if (cursor != null && cursor.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8915         this.cursor = cursor;
8916         if (cursor == null) {
8917                 Display display = getDisplay();
8918                 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
8919                 super.setCursor(display.getSystemCursor(type));
8920         } else {
8921                 super.setCursor(cursor);
8922         }
8923 }
8924 /**
8925  * Sets whether the widget implements double click mouse behavior.
8926  *
8927  * @param enable if true double clicking a word selects the word, if false
8928  *      double clicks have the same effect as regular mouse clicks.
8929  * @exception SWTException <ul>
8930  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8931  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8932  * </ul>
8933  */
8934 public void setDoubleClickEnabled(boolean enable) {
8935         checkWidget();
8936         doubleClickEnabled = enable;
8937 }
8938 @Override
8939 public void setDragDetect (boolean dragDetect) {
8940         checkWidget ();
8941         this.dragDetect = dragDetect;
8942 }
8943 /**
8944  * Sets whether the widget content can be edited.
8945  *
8946  * @param editable if true content can be edited, if false content can not be
8947  *      edited
8948  * @exception SWTException <ul>
8949  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8950  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8951  * </ul>
8952  */
8953 public void setEditable(boolean editable) {
8954         checkWidget();
8955         this.editable = editable;
8956 }
8957 @Override
8958 public void setEnabled(boolean enabled) {
8959         super.setEnabled(enabled);
8960         Display display = getDisplay();
8961         this.enabled = enabled;
8962         this.insideSetEnableCall = true;
8963         try {
8964                 if (enabled) {
8965                         if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
8966                         if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
8967                 } else {
8968                         if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND));
8969                         if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND));
8970                 }
8971         }
8972         finally {
8973                 this.insideSetEnableCall = false;
8974         }
8975 }
8976 /**
8977  * Sets a new font to render text with.
8978  * <p>
8979  * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
8980  * and the same baseline as regular fonts.
8981  * </p>
8982  *
8983  * @param font new font
8984  * @exception SWTException <ul>
8985  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8986  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8987  * </ul>
8988  */
8989 @Override
8990 public void setFont(Font font) {
8991         checkWidget();
8992         int oldLineHeight = renderer.getLineHeight();
8993         super.setFont(font);
8994         renderer.setFont(getFont(), tabLength);
8995         // keep the same top line visible. fixes 5815
8996         if (isFixedLineHeight()) {
8997                 int lineHeight = renderer.getLineHeight();
8998                 if (lineHeight != oldLineHeight) {
8999                         int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
9000                         scrollVertical(vscroll, true);
9001                 }
9002         }
9003         resetCache(0, content.getLineCount());
9004         claimBottomFreeSpace();
9005         calculateScrollBars();
9006         if (isBidiCaret()) createCaretBitmaps();
9007         caretDirection = SWT.NULL;
9008         setCaretLocation();
9009         super.redraw();
9010 }
9011 @Override
9012 public void setForeground(Color color) {
9013         checkWidget();
9014         boolean foregroundDisabled = false;
9015         if (!this.enabled && color == null) {
9016                 if (foreground != null) {
9017                         Color disabledFg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND);
9018                         if (foreground.equals(disabledFg)) {
9019                                 return;
9020                         } else {
9021                                 color = new Color (getDisplay(), disabledFg.getRGBA());
9022                                 foregroundDisabled = true;
9023                         }
9024                 }
9025         }
9026         customForeground = color != null && !this.insideSetEnableCall && !foregroundDisabled;
9027         foreground = color;
9028         super.setForeground(color);
9029         resetCache(0, content.getLineCount());
9030         setCaretLocation();
9031         super.redraw();
9032 }
9033 /**
9034  * Sets the horizontal scroll offset relative to the start of the line.
9035  * Do nothing if there is no text set.
9036  * <p>
9037  * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
9038  * widget.
9039  * </p>
9040  *
9041  * @param offset horizontal scroll offset relative to the start
9042  *      of the line, measured in character increments starting at 0, if
9043  *      equal to 0 the content is not scrolled, if &gt; 0 = the content is scrolled.
9044  * @exception SWTException <ul>
9045  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9046  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9047  * </ul>
9048  */
9049 public void setHorizontalIndex(int offset) {
9050         checkWidget();
9051         if (getCharCount() == 0) {
9052                 return;
9053         }
9054         if (offset < 0) {
9055                 offset = 0;
9056         }
9057         offset *= getHorizontalIncrement();
9058         // allow any value if client area width is unknown or 0.
9059         // offset will be checked in resize handler.
9060         // don't use isVisible since width is known even if widget
9061         // is temporarily invisible
9062         if (clientAreaWidth > 0) {
9063                 int width = renderer.getWidth();
9064                 // prevent scrolling if the content fits in the client area.
9065                 // align end of longest line with right border of client area
9066                 // if offset is out of range.
9067                 if (offset > width - clientAreaWidth) {
9068                         offset = Math.max(0, width - clientAreaWidth);
9069                 }
9070         }
9071         scrollHorizontal(offset - horizontalScrollOffset, true);
9072 }
9073 /**
9074  * Sets the horizontal SWT logical point offset relative to the start of the line.
9075  * Do nothing if there is no text set.
9076  * <p>
9077  * <b>NOTE:</b> The horizontal SWT logical point offset is reset to 0 when new text
9078  * is set in the widget.
9079  * </p>
9080  *
9081  * @param pixel horizontal SWT logical point offset relative to the start
9082  *      of the line.
9083  * @exception SWTException <ul>
9084  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9085  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9086  * </ul>
9087  * @since 2.0
9088  */
9089 public void setHorizontalPixel(int pixel) {
9090         checkWidget();
9091         if (getCharCount() == 0) {
9092                 return;
9093         }
9094         if (pixel < 0) {
9095                 pixel = 0;
9096         }
9097         // allow any value if client area width is unknown or 0.
9098         // offset will be checked in resize handler.
9099         // don't use isVisible since width is known even if widget
9100         // is temporarily invisible
9101         if (clientAreaWidth > 0) {
9102                 int width = renderer.getWidth();
9103                 // prevent scrolling if the content fits in the client area.
9104                 // align end of longest line with right border of client area
9105                 // if offset is out of range.
9106                 if (pixel > width - clientAreaWidth) {
9107                         pixel = Math.max(0, width - clientAreaWidth);
9108                 }
9109         }
9110         scrollHorizontal(pixel - horizontalScrollOffset, true);
9111 }
9112 /**
9113  * Sets the line indentation of the widget.
9114  * <p>
9115  * It is the amount of blank space, in points, at the beginning of each line.
9116  * When a line wraps in several lines only the first one is indented.
9117  * </p>
9118  *
9119  * @param indent the new indent
9120  *
9121  * @exception SWTException <ul>
9122  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9123  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9124  * </ul>
9125  *
9126  * @see #setLineIndent(int, int, int)
9127  *
9128  * @since 3.2
9129  */
9130 public void setIndent(int indent) {
9131         checkWidget();
9132         if (this.indent == indent || indent < 0) return;
9133         this.indent = indent;
9134         resetCache(0, content.getLineCount());
9135         setCaretLocation();
9136         super.redraw();
9137 }
9138 /**
9139  * Sets whether the widget should justify lines.
9140  *
9141  * @param justify whether lines should be justified
9142  *
9143  * @exception SWTException <ul>
9144  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9145  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9146  * </ul>
9147  *
9148  * @see #setLineJustify(int, int, boolean)
9149  *
9150  * @since 3.2
9151  */
9152 public void setJustify(boolean justify) {
9153         checkWidget();
9154         if (this.justify == justify) return;
9155         this.justify = justify;
9156         resetCache(0, content.getLineCount());
9157         setCaretLocation();
9158         super.redraw();
9159 }
9160 /**
9161  * Maps a key to an action.
9162  * <p>
9163  * One action can be associated with N keys. However, each key can only
9164  * have one action (key:action is N:1 relation).
9165  * </p>
9166  *
9167  * @param key a key code defined in SWT.java or a character.
9168  *      Optionally ORd with a state mask.  Preferred state masks are one or more of
9169  *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
9170  *  differences.  However, there may be cases where using the specific state masks
9171  *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
9172  * @param action one of the predefined actions defined in ST.java.
9173  *      Use SWT.NULL to remove a key binding.
9174  * @exception SWTException <ul>
9175  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9176  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9177  * </ul>
9178  */
9179 public void setKeyBinding(int key, int action) {
9180         checkWidget();
9181         int modifierValue = key & SWT.MODIFIER_MASK;
9182         int keyInt = key & SWT.KEY_MASK;
9183         char keyChar = (char)keyInt;
9184         /**
9185          * Bug 440535: Make sure the key getting mapped to letter is in defiened
9186          * character range and filter out incorrect int to char typecasting. For
9187          * Example: SWT.KEYPAD_CR int gets wrongly type-cast to char letter 'p'
9188          */
9189         if (Character.isDefined(keyInt) && Character.isLetter(keyChar)) {
9190                 // make the keybinding case insensitive by adding it
9191                 // in its upper and lower case form
9192                 char ch = Character.toUpperCase(keyChar);
9193                 int newKey = ch | modifierValue;
9194                 if (action == SWT.NULL) {
9195                         keyActionMap.remove(newKey);
9196                 } else {
9197                         keyActionMap.put(newKey, action);
9198                 }
9199                 ch = Character.toLowerCase(keyChar);
9200                 newKey = ch | modifierValue;
9201                 if (action == SWT.NULL) {
9202                         keyActionMap.remove(newKey);
9203                 } else {
9204                         keyActionMap.put(newKey, action);
9205                 }
9206         } else {
9207                 if (action == SWT.NULL) {
9208                         keyActionMap.remove(key);
9209                 } else {
9210                         keyActionMap.put(key, action);
9211                 }
9212         }
9213 }
9214 /**
9215  * Sets the left margin.
9216  *
9217  * @param leftMargin the left margin.
9218  * @exception SWTException <ul>
9219  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9220  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9221  * </ul>
9222  *
9223  * @since 3.5
9224  */
9225 public void setLeftMargin (int leftMargin) {
9226         checkWidget();
9227         setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
9228 }
9229 /**
9230  * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>,
9231  * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>.
9232  * <p>
9233  * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
9234  * in order to stabilize the right edge before setting alignment.
9235  * </p><p>
9236  * Should not be called if a LineStyleListener has been set since the listener
9237  * maintains the line attributes.
9238  * </p><p>
9239  * All line attributes are maintained relative to the line text, not the
9240  * line index that is specified in this method call.
9241  * During text changes, when entire lines are inserted or removed, the line
9242  * attributes that are associated with the lines after the change
9243  * will "move" with their respective text. An entire line is defined as
9244  * extending from the first character on a line to the last and including the
9245  * line delimiter.
9246  * </p>
9247  * When two lines are joined by deleting a line delimiter, the top line
9248  * attributes take precedence and the attributes of the bottom line are deleted.
9249  * For all other text changes line attributes will remain unchanged.
9250  *
9251  * @param startLine first line the alignment is applied to, 0 based
9252  * @param lineCount number of lines the alignment applies to.
9253  * @param alignment line alignment
9254  *
9255  * @exception SWTException <ul>
9256  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9257  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9258  * </ul>
9259  * @exception IllegalArgumentException <ul>
9260  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9261  * </ul>
9262  * @see #setAlignment(int)
9263  * @since 3.2
9264  */
9265 public void setLineAlignment(int startLine, int lineCount, int alignment) {
9266         checkWidget();
9267         if (isListening(ST.LineGetStyle)) return;
9268         if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9269                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9270         }
9271
9272         renderer.setLineAlignment(startLine, lineCount, alignment);
9273         resetCache(startLine, lineCount);
9274         redrawLines(startLine, lineCount, false);
9275         int caretLine = getCaretLine();
9276         if (startLine <= caretLine && caretLine < startLine + lineCount) {
9277                 setCaretLocation();
9278         }
9279         setAlignment();
9280 }
9281 /**
9282  * Sets the background color of the specified lines.
9283  * <p>
9284  * The background color is drawn for the width of the widget. All
9285  * line background colors are discarded when setText is called.
9286  * The text background color if defined in a StyleRange overlays the
9287  * line background color.
9288  * </p><p>
9289  * Should not be called if a LineBackgroundListener has been set since the
9290  * listener maintains the line backgrounds.
9291  * </p><p>
9292  * All line attributes are maintained relative to the line text, not the
9293  * line index that is specified in this method call.
9294  * During text changes, when entire lines are inserted or removed, the line
9295  * attributes that are associated with the lines after the change
9296  * will "move" with their respective text. An entire line is defined as
9297  * extending from the first character on a line to the last and including the
9298  * line delimiter.
9299  * </p><p>
9300  * When two lines are joined by deleting a line delimiter, the top line
9301  * attributes take precedence and the attributes of the bottom line are deleted.
9302  * For all other text changes line attributes will remain unchanged.
9303  * </p>
9304  *
9305  * @param startLine first line the color is applied to, 0 based
9306  * @param lineCount number of lines the color applies to.
9307  * @param background line background color
9308  * @exception SWTException <ul>
9309  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9310  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9311  * </ul>
9312  * @exception IllegalArgumentException <ul>
9313  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9314  * </ul>
9315  */
9316 public void setLineBackground(int startLine, int lineCount, Color background) {
9317         checkWidget();
9318         if (isListening(ST.LineGetBackground)) return;
9319         if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9320                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9321         }
9322         if (background != null) {
9323                 renderer.setLineBackground(startLine, lineCount, background);
9324         } else {
9325                 renderer.clearLineBackground(startLine, lineCount);
9326         }
9327         redrawLines(startLine, lineCount, false);
9328 }
9329 /**
9330  * Sets the bullet of the specified lines.
9331  * <p>
9332  * Should not be called if a LineStyleListener has been set since the listener
9333  * maintains the line attributes.
9334  * </p><p>
9335  * All line attributes are maintained relative to the line text, not the
9336  * line index that is specified in this method call.
9337  * During text changes, when entire lines are inserted or removed, the line
9338  * attributes that are associated with the lines after the change
9339  * will "move" with their respective text. An entire line is defined as
9340  * extending from the first character on a line to the last and including the
9341  * line delimiter.
9342  * </p><p>
9343  * When two lines are joined by deleting a line delimiter, the top line
9344  * attributes take precedence and the attributes of the bottom line are deleted.
9345  * For all other text changes line attributes will remain unchanged.
9346  * </p>
9347  *
9348  * @param startLine first line the bullet is applied to, 0 based
9349  * @param lineCount number of lines the bullet applies to.
9350  * @param bullet line bullet
9351  *
9352  * @exception SWTException <ul>
9353  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9354  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9355  * </ul>
9356  * @exception IllegalArgumentException <ul>
9357  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9358  * </ul>
9359  * @since 3.2
9360  */
9361 public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
9362         checkWidget();
9363         if (isListening(ST.LineGetStyle)) return;
9364         if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9365                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9366         }
9367         int oldBottom = getLinePixel(startLine + lineCount);
9368         renderer.setLineBullet(startLine, lineCount, bullet);
9369         resetCache(startLine, lineCount);
9370         int newBottom = getLinePixel(startLine + lineCount);
9371         redrawLines(startLine, lineCount, oldBottom != newBottom);
9372         int caretLine = getCaretLine();
9373         if (startLine <= caretLine && caretLine < startLine + lineCount) {
9374                 setCaretLocation();
9375         }
9376 }
9377 /**
9378  * Returns true if StyledText is in word wrap mode and false otherwise.
9379  *
9380  * @return true if StyledText is in word wrap mode and false otherwise.
9381  */
9382 boolean isWordWrap() {
9383         return wordWrap || visualWrap;
9384 }
9385 /**
9386  * Sets the indent of the specified lines.
9387  * <p>
9388  * Should not be called if a LineStyleListener has been set since the listener
9389  * maintains the line attributes.
9390  * </p><p>
9391  * All line attributes are maintained relative to the line text, not the
9392  * line index that is specified in this method call.
9393  * During text changes, when entire lines are inserted or removed, the line
9394  * attributes that are associated with the lines after the change
9395  * will "move" with their respective text. An entire line is defined as
9396  * extending from the first character on a line to the last and including the
9397  * line delimiter.
9398  * </p><p>
9399  * When two lines are joined by deleting a line delimiter, the top line
9400  * attributes take precedence and the attributes of the bottom line are deleted.
9401  * For all other text changes line attributes will remain unchanged.
9402  * </p>
9403  *
9404  * @param startLine first line the indent is applied to, 0 based
9405  * @param lineCount number of lines the indent applies to.
9406  * @param indent line indent
9407  *
9408  * @exception SWTException <ul>
9409  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9410  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9411  * </ul>
9412  * @exception IllegalArgumentException <ul>
9413  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9414  * </ul>
9415  * @see #setIndent(int)
9416  * @since 3.2
9417  */
9418 public void setLineIndent(int startLine, int lineCount, int indent) {
9419         checkWidget();
9420         if (isListening(ST.LineGetStyle)) return;
9421         if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9422                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9423         }
9424         int oldBottom = getLinePixel(startLine + lineCount);
9425         renderer.setLineIndent(startLine, lineCount, indent);
9426         resetCache(startLine, lineCount);
9427         int newBottom = getLinePixel(startLine + lineCount);
9428         redrawLines(startLine, lineCount, oldBottom != newBottom);
9429         int caretLine = getCaretLine();
9430         if (startLine <= caretLine && caretLine < startLine + lineCount) {
9431                 setCaretLocation();
9432         }
9433 }
9434
9435 /**
9436  * Sets the vertical indent of the specified lines.
9437  * <p>
9438  * Should not be called if a LineStyleListener has been set since the listener
9439  * maintains the line attributes.
9440  * </p><p>
9441  * All line attributes are maintained relative to the line text, not the
9442  * line index that is specified in this method call.
9443  * During text changes, when entire lines are inserted or removed, the line
9444  * attributes that are associated with the lines after the change
9445  * will "move" with their respective text. An entire line is defined as
9446  * extending from the first character on a line to the last and including the
9447  * line delimiter.
9448  * </p><p>
9449  * When two lines are joined by deleting a line delimiter, the top line
9450  * attributes take precedence and the attributes of the bottom line are deleted.
9451  * For all other text changes line attributes will remain unchanged.
9452  * </p><p>
9453  * Setting both line spacing and vertical indent on a line would result in the
9454  * spacing and indent add up for the line.
9455  * </p>
9456  *
9457  * @param lineIndex line index the vertical indent is applied to, 0 based
9458  * @param verticalLineIndent vertical line indent
9459  *
9460  * @exception SWTException <ul>
9461  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9462  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9463  * </ul>
9464  * @exception IllegalArgumentException <ul>
9465  *   <li>ERROR_INVALID_ARGUMENT when the specified line index is invalid</li>
9466  * </ul>
9467  * @since 3.109
9468  */
9469 public void setLineVerticalIndent(int lineIndex, int verticalLineIndent) {
9470         checkWidget();
9471         if (isListening(ST.LineGetStyle)) return;
9472         if (lineIndex < 0 || lineIndex >= content.getLineCount()) {
9473                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9474         }
9475         if (verticalLineIndent == renderer.getLineVerticalIndent(lineIndex)) {
9476                         return;
9477         }
9478         int oldBottom = getLinePixel(lineIndex + 1);
9479         if (oldBottom <= getClientArea().height) {
9480                 verticalScrollOffset = -1;
9481         }
9482         renderer.setLineVerticalIndent(lineIndex, verticalLineIndent);
9483         hasVerticalIndent = verticalLineIndent != 0 || renderer.hasVerticalIndent();
9484         resetCache(lineIndex, 1);
9485         int newBottom = getLinePixel(lineIndex + 1);
9486         redrawLines(lineIndex, 1, oldBottom != newBottom);
9487         int caretLine = getCaretLine();
9488         if (lineIndex <= caretLine && caretLine < lineIndex + 1) {
9489                 setCaretLocation();
9490         }
9491 }
9492
9493 /**
9494  * Sets the justify of the specified lines.
9495  * <p>
9496  * Should not be called if a LineStyleListener has been set since the listener
9497  * maintains the line attributes.
9498  * </p><p>
9499  * All line attributes are maintained relative to the line text, not the
9500  * line index that is specified in this method call.
9501  * During text changes, when entire lines are inserted or removed, the line
9502  * attributes that are associated with the lines after the change
9503  * will "move" with their respective text. An entire line is defined as
9504  * extending from the first character on a line to the last and including the
9505  * line delimiter.
9506  * </p><p>
9507  * When two lines are joined by deleting a line delimiter, the top line
9508  * attributes take precedence and the attributes of the bottom line are deleted.
9509  * For all other text changes line attributes will remain unchanged.
9510  * </p>
9511  *
9512  * @param startLine first line the justify is applied to, 0 based
9513  * @param lineCount number of lines the justify applies to.
9514  * @param justify true if lines should be justified
9515  *
9516  * @exception SWTException <ul>
9517  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9518  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9519  * </ul>
9520  * @exception IllegalArgumentException <ul>
9521  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9522  * </ul>
9523  * @see #setJustify(boolean)
9524  * @since 3.2
9525  */
9526 public void setLineJustify(int startLine, int lineCount, boolean justify) {
9527         checkWidget();
9528         if (isListening(ST.LineGetStyle)) return;
9529         if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9530                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9531         }
9532
9533         renderer.setLineJustify(startLine, lineCount, justify);
9534         resetCache(startLine, lineCount);
9535         redrawLines(startLine, lineCount, false);
9536         int caretLine = getCaretLine();
9537         if (startLine <= caretLine && caretLine < startLine + lineCount) {
9538                 setCaretLocation();
9539         }
9540 }
9541 /**
9542  * Sets the line spacing of the widget. The line spacing applies for all lines.
9543  * In the case of #setLineSpacingProvider(StyledTextLineSpacingProvider) is customized,
9544  * the line spacing are applied only for the lines which are not managed by {@link StyledTextLineSpacingProvider}.
9545  *
9546  * @param lineSpacing the line spacing
9547  * @exception SWTException <ul>
9548  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9549  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9550  * </ul>
9551  * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
9552  * @since 3.2
9553  */
9554 public void setLineSpacing(int lineSpacing) {
9555         checkWidget();
9556         if (this.lineSpacing == lineSpacing || lineSpacing < 0) return;
9557         this.lineSpacing = lineSpacing;
9558         resetCache(0, content.getLineCount());
9559         setCaretLocation();
9560         super.redraw();
9561 }
9562 /**
9563  * Sets the line spacing provider of the widget. The line spacing applies for some lines with customized spacing
9564  * or reset the customized spacing if the argument is null.
9565  *
9566  * @param lineSpacingProvider the line spacing provider (or null)
9567  * @exception SWTException <ul>
9568  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9569  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9570  * </ul>
9571  * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
9572  * @since 3.107
9573  */
9574 public void setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider) {
9575         checkWidget();
9576         boolean wasFixedLineHeight = isFixedLineHeight();
9577         if (renderer.getLineSpacingProvider() == null && lineSpacingProvider == null
9578                         || (renderer.getLineSpacingProvider() != null
9579                                         && renderer.getLineSpacingProvider().equals(lineSpacingProvider)))
9580                 return;
9581         renderer.setLineSpacingProvider(lineSpacingProvider);
9582         // reset lines cache if needed
9583         if (lineSpacingProvider == null) {
9584                 if (!wasFixedLineHeight) {
9585                         resetCache(0, content.getLineCount());
9586                 }
9587         } else {
9588                 if (wasFixedLineHeight) {
9589                         int firstLine = -1;
9590                         for (int i = 0; i < content.getLineCount(); i++) {
9591                                 Integer lineSpacing = lineSpacingProvider.getLineSpacing(i);
9592                                 if (lineSpacing != null && lineSpacing > 0) {
9593                                         // there is a custom line spacing, set StyledText as variable line height mode
9594                                         // reset only the line size
9595                                         renderer.reset(i, 1);
9596                                         if (firstLine == -1) {
9597                                                 firstLine = i;
9598                                         }
9599                                 }
9600                         }
9601                         if (firstLine != -1) {
9602                                 // call reset cache for the first line which have changed to recompute scrollbars
9603                                 resetCache(firstLine, 0);
9604                         }
9605                 }
9606         }
9607         setCaretLocation();
9608         super.redraw();
9609 }
9610 /**
9611  * Sets the tab stops of the specified lines.
9612  * <p>
9613  * Should not be called if a <code>LineStyleListener</code> has been set since the listener
9614  * maintains the line attributes.
9615  * </p><p>
9616  * All line attributes are maintained relative to the line text, not the
9617  * line index that is specified in this method call.
9618  * During text changes, when entire lines are inserted or removed, the line
9619  * attributes that are associated with the lines after the change
9620  * will "move" with their respective text. An entire line is defined as
9621  * extending from the first character on a line to the last and including the
9622  * line delimiter.
9623  * </p><p>
9624  * When two lines are joined by deleting a line delimiter, the top line
9625  * attributes take precedence and the attributes of the bottom line are deleted.
9626  * For all other text changes line attributes will remain unchanged.
9627  * </p>
9628  *
9629  * @param startLine first line the justify is applied to, 0 based
9630  * @param lineCount number of lines the justify applies to.
9631  * @param tabStops tab stops
9632  *
9633  * @exception SWTException <ul>
9634  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9635  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9636  * </ul>
9637  * @exception IllegalArgumentException <ul>
9638  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9639  * </ul>
9640  * @see #setTabStops(int[])
9641  * @since 3.6
9642  */
9643 public void setLineTabStops(int startLine, int lineCount, int[] tabStops) {
9644         checkWidget();
9645         if (isListening(ST.LineGetStyle)) return;
9646         if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9647                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9648         }
9649         if (tabStops != null) {
9650                 int pos = 0;
9651                 int[] newTabs = new int[tabStops.length];
9652                 for (int i = 0; i < tabStops.length; i++) {
9653                         if (tabStops[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9654                         newTabs[i] = pos = tabStops[i];
9655                 }
9656                 renderer.setLineTabStops(startLine, lineCount, newTabs);
9657         } else {
9658                 renderer.setLineTabStops(startLine, lineCount, null);
9659         }
9660         resetCache(startLine, lineCount);
9661         redrawLines(startLine, lineCount, false);
9662         int caretLine = getCaretLine();
9663         if (startLine <= caretLine && caretLine < startLine + lineCount) {
9664                 setCaretLocation();
9665         }
9666 }
9667 /**
9668  * Sets the wrap indent of the specified lines.
9669  * <p>
9670  * Should not be called if a <code>LineStyleListener</code> has been set since the listener
9671  * maintains the line attributes.
9672  * </p><p>
9673  * All line attributes are maintained relative to the line text, not the
9674  * line index that is specified in this method call.
9675  * During text changes, when entire lines are inserted or removed, the line
9676  * attributes that are associated with the lines after the change
9677  * will "move" with their respective text. An entire line is defined as
9678  * extending from the first character on a line to the last and including the
9679  * line delimiter.
9680  * </p><p>
9681  * When two lines are joined by deleting a line delimiter, the top line
9682  * attributes take precedence and the attributes of the bottom line are deleted.
9683  * For all other text changes line attributes will remain unchanged.
9684  * </p>
9685  *
9686  * @param startLine first line the wrap indent is applied to, 0 based
9687  * @param lineCount number of lines the wrap indent applies to.
9688  * @param wrapIndent line wrap indent
9689  *
9690  * @exception SWTException <ul>
9691  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9692  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9693  * </ul>
9694  * @exception IllegalArgumentException <ul>
9695  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9696  * </ul>
9697  * @see #setWrapIndent(int)
9698  * @since 3.6
9699  */
9700 public void setLineWrapIndent(int startLine, int lineCount, int wrapIndent) {
9701         checkWidget();
9702         if (isListening(ST.LineGetStyle)) return;
9703         if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9704                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9705         }
9706         int oldBottom = getLinePixel(startLine + lineCount);
9707         renderer.setLineWrapIndent(startLine, lineCount, wrapIndent);
9708         resetCache(startLine, lineCount);
9709         int newBottom = getLinePixel(startLine + lineCount);
9710         redrawLines(startLine, lineCount, oldBottom != newBottom);
9711         int caretLine = getCaretLine();
9712         if (startLine <= caretLine && caretLine < startLine + lineCount) {
9713                 setCaretLocation();
9714         }
9715 }
9716
9717 /**
9718  * Sets the color of the margins.
9719  *
9720  * @param color the new color (or null)
9721  * @exception IllegalArgumentException <ul>
9722  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9723  * </ul>
9724  * @exception SWTException <ul>
9725  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9726  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9727  * </ul>
9728  *
9729  * @since 3.5
9730  */
9731 public void setMarginColor(Color color) {
9732         checkWidget();
9733         if (color != null && color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9734         marginColor = color;
9735         super.redraw();
9736 }
9737 /**
9738  * Sets the margins.
9739  *
9740  * @param leftMargin the left margin.
9741  * @param topMargin the top margin.
9742  * @param rightMargin the right margin.
9743  * @param bottomMargin the bottom margin.
9744  * @exception SWTException <ul>
9745  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9746  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9747  * </ul>
9748  *
9749  * @since 3.5
9750  */
9751 public void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
9752         checkWidget();
9753         this.leftMargin = Math.max(0, leftMargin) + alignmentMargin;
9754         this.topMargin = Math.max(0, topMargin);
9755         this.rightMargin = Math.max(0, rightMargin);
9756         this.bottomMargin = Math.max(0, bottomMargin);
9757         resetCache(0, content.getLineCount());
9758         setScrollBars(true);
9759         setCaretLocation();
9760         setAlignment();
9761         super.redraw();
9762 }
9763 /**
9764  * Sets the enabled state of the mouse navigator. When the mouse navigator is enabled, the user can navigate through the widget
9765  * by pressing the middle button and moving the cursor.
9766  *
9767  * @param enabled if true, the mouse navigator is enabled, if false the mouse navigator is deactivated
9768  * @exception SWTException <ul>
9769  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9770  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9771  * </ul>
9772  * @since 3.110
9773  */
9774 public void setMouseNavigatorEnabled(boolean enabled) {
9775         checkWidget();
9776         if ((enabled && mouseNavigator != null) || (!enabled && mouseNavigator == null)) {
9777                 return;
9778         }
9779         if (enabled) {
9780                 mouseNavigator = new MouseNavigator(this);
9781         } else {
9782                 mouseNavigator.dispose();
9783                 mouseNavigator = null;
9784         }
9785 }
9786 /**
9787  * Flips selection anchor based on word selection direction.
9788  */
9789 void setMouseWordSelectionAnchor() {
9790         if (doubleClickEnabled && clickCount > 1) {
9791                 if (caretOffset < doubleClickSelection.x) {
9792                         selectionAnchor = doubleClickSelection.y;
9793                 } else if (caretOffset > doubleClickSelection.y) {
9794                         selectionAnchor = doubleClickSelection.x;
9795                 }
9796         }
9797 }
9798 /**
9799  * Sets the orientation of the receiver, which must be one
9800  * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
9801  *
9802  * @param orientation new orientation style
9803  *
9804  * @exception SWTException <ul>
9805  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9806  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9807  * </ul>
9808  *
9809  * @since 2.1.2
9810  */
9811 @Override
9812 public void setOrientation(int orientation) {
9813         int oldOrientation = getOrientation();
9814         super.setOrientation(orientation);
9815         int newOrientation = getOrientation();
9816         if (oldOrientation != newOrientation) {
9817                 resetBidiData();
9818         }
9819 }
9820 /**
9821  * Sets the right margin.
9822  *
9823  * @param rightMargin the right margin.
9824  * @exception SWTException <ul>
9825  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9826  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9827  * </ul>
9828  *
9829  * @since 3.5
9830  */
9831 public void setRightMargin (int rightMargin) {
9832         checkWidget();
9833         setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
9834 }
9835 void setScrollBar(ScrollBar bar, int clientArea, int maximum, int margin) {
9836         int inactive = 1;
9837         if (clientArea < maximum) {
9838                 bar.setMaximum(maximum - margin);
9839                 bar.setThumb(clientArea - margin);
9840                 bar.setPageIncrement(clientArea - margin);
9841                 if (!alwaysShowScroll) bar.setVisible(true);
9842         } else if (bar.getThumb() != inactive || bar.getMaximum() != inactive) {
9843                 bar.setValues(bar.getSelection(), bar.getMinimum(), inactive, inactive, bar.getIncrement(), inactive);
9844         }
9845 }
9846 /**
9847  * Adjusts the maximum and the page size of the scroll bars to
9848  * reflect content width/length changes.
9849  *
9850  * @param vertical indicates if the vertical scrollbar also needs to be set
9851  */
9852 void setScrollBars(boolean vertical) {
9853         ignoreResize++;
9854         if (!isFixedLineHeight() || !alwaysShowScroll) vertical = true;
9855         ScrollBar verticalBar = vertical ? getVerticalBar() : null;
9856         ScrollBar horizontalBar = getHorizontalBar();
9857         int oldHeight = clientAreaHeight;
9858         int oldWidth = clientAreaWidth;
9859         if (!alwaysShowScroll) {
9860                 if (verticalBar != null) verticalBar.setVisible(false);
9861                 if (horizontalBar != null) horizontalBar.setVisible(false);
9862         }
9863         if (verticalBar != null) {
9864                 setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
9865         }
9866         if (horizontalBar != null && !wordWrap) {
9867                 setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
9868                 if (!alwaysShowScroll && horizontalBar.getVisible() && verticalBar != null) {
9869                         setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
9870                         if (verticalBar.getVisible()) {
9871                                 setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
9872                         }
9873                 }
9874         }
9875         if (!alwaysShowScroll) {
9876                 redrawMargins(oldHeight, oldWidth);
9877         }
9878         ignoreResize--;
9879 }
9880 /**
9881  * Sets the selection to the given position and scrolls it into view.  Equivalent to setSelection(start,start).
9882  *
9883  * @param start new caret position
9884  * @see #setSelection(int,int)
9885  * @exception SWTException <ul>
9886  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9887  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9888  * </ul>
9889  * @exception IllegalArgumentException <ul>
9890  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9891  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9892  * </ul>
9893  */
9894 public void setSelection(int start) {
9895         // checkWidget test done in setSelectionRange
9896         setSelection(start, start);
9897 }
9898 /**
9899  * Sets the selection and scrolls it into view.
9900  * <p>
9901  * Indexing is zero based.  Text selections are specified in terms of
9902  * caret positions.  In a text widget that contains N characters, there are
9903  * N+1 caret positions, ranging from 0..N
9904  * </p>
9905  *
9906  * @param point x=selection start offset, y=selection end offset
9907  *      The caret will be placed at the selection start when x &gt; y.
9908  * @see #setSelection(int,int)
9909  * @exception SWTException <ul>
9910  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9911  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9912  * </ul>
9913  * @exception IllegalArgumentException <ul>
9914  *   <li>ERROR_NULL_ARGUMENT when point is null</li>
9915  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9916  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9917  * </ul>
9918  */
9919 public void setSelection(Point point) {
9920         checkWidget();
9921         if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
9922         setSelection(point.x, point.y);
9923 }
9924 /**
9925  * Sets the receiver's selection background color to the color specified
9926  * by the argument, or to the default system color for the control
9927  * if the argument is null.
9928  *
9929  * @param color the new color (or null)
9930  *
9931  * @exception IllegalArgumentException <ul>
9932  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9933  * </ul>
9934  * @exception SWTException <ul>
9935  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9936  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9937  * </ul>
9938  * @since 2.1
9939  */
9940 public void setSelectionBackground (Color color) {
9941         checkWidget ();
9942         if (color != null) {
9943                 if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9944         }
9945         selectionBackground = color;
9946         resetCache(0, content.getLineCount());
9947         setCaretLocation();
9948         super.redraw();
9949 }
9950 /**
9951  * Sets the receiver's selection foreground color to the color specified
9952  * by the argument, or to the default system color for the control
9953  * if the argument is null.
9954  * <p>
9955  * Note that this is a <em>HINT</em>. Some platforms do not allow the application
9956  * to change the selection foreground color.
9957  * </p>
9958  * @param color the new color (or null)
9959  *
9960  * @exception IllegalArgumentException <ul>
9961  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9962  * </ul>
9963  * @exception SWTException <ul>
9964  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9965  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9966  * </ul>
9967  * @since 2.1
9968  */
9969 public void setSelectionForeground (Color color) {
9970         checkWidget ();
9971         if (color != null) {
9972                 if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9973         }
9974         selectionForeground = color;
9975         resetCache(0, content.getLineCount());
9976         setCaretLocation();
9977         super.redraw();
9978 }
9979 /**
9980  * Sets the selection and scrolls it into view.
9981  * <p>
9982  * Indexing is zero based.  Text selections are specified in terms of
9983  * caret positions.  In a text widget that contains N characters, there are
9984  * N+1 caret positions, ranging from 0..N
9985  * </p>
9986  *
9987  * @param start selection start offset. The caret will be placed at the
9988  *      selection start when start &gt; end.
9989  * @param end selection end offset
9990  * @see #setSelectionRange(int,int)
9991  * @exception SWTException <ul>
9992  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9993  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9994  * </ul>
9995  * @exception IllegalArgumentException <ul>
9996  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9997  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9998  * </ul>
9999  */
10000 public void setSelection(int start, int end) {
10001         setSelectionRange(start, end - start);
10002         showSelection();
10003 }
10004 /**
10005  * Sets the selection.
10006  * <p>
10007  * The new selection may not be visible. Call showSelection to scroll
10008  * the selection into view.
10009  * </p>
10010  *
10011  * @param start offset of the first selected character, start &gt;= 0 must be true.
10012  * @param length number of characters to select, 0 &lt;= start + length
10013  *      &lt;= getCharCount() must be true.
10014  *      A negative length places the caret at the selection start.
10015  * @param sendEvent a Selection event is sent when set to true and when
10016  *      the selection is reset.
10017  */
10018 void setSelection(int start, int length, boolean sendEvent, boolean doBlock) {
10019         int end = start + length;
10020         if (start > end) {
10021                 int temp = end;
10022                 end = start;
10023                 start = temp;
10024         }
10025         // is the selection range different or is the selection direction
10026         // different?
10027         if (selection.x != start || selection.y != end ||
10028                 (length > 0 && selectionAnchor != selection.x) ||
10029                 (length < 0 && selectionAnchor != selection.y)) {
10030                 if (blockSelection && doBlock) {
10031                         if (length < 0) {
10032                                 setBlockSelectionOffset(end, start, sendEvent);
10033                         } else {
10034                                 setBlockSelectionOffset(start, end, sendEvent);
10035                         }
10036                 } else {
10037                         int oldStart = selection.x;
10038                         int oldLength = selection.y - selection.x;
10039                         int charCount = content.getCharCount();
10040                         // called internally to remove selection after text is removed
10041                         // therefore make sure redraw range is valid.
10042                         int redrawX = Math.min(selection.x, charCount);
10043                         int redrawY = Math.min(selection.y, charCount);
10044                         if (length < 0) {
10045                                 selectionAnchor = selection.y = end;
10046                                 selection.x = start;
10047                                 setCaretOffset(start, PREVIOUS_OFFSET_TRAILING);
10048                         } else {
10049                                 selectionAnchor = selection.x = start;
10050                                 selection.y = end;
10051                                 setCaretOffset(end, PREVIOUS_OFFSET_TRAILING);
10052                         }
10053                         redrawX = Math.min(redrawX, selection.x);
10054                         redrawY = Math.max(redrawY, selection.y);
10055                         if (redrawY - redrawX > 0) {
10056                                 internalRedrawRange(redrawX, redrawY - redrawX);
10057                         }
10058                         if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) {
10059                                 sendSelectionEvent();
10060                         }
10061                         sendAccessibleTextCaretMoved();
10062                 }
10063         }
10064 }
10065 /**
10066  * Sets the selection.
10067  * <p>
10068  * The new selection may not be visible. Call showSelection to scroll the selection
10069  * into view. A negative length places the caret at the visual start of the selection.
10070  * </p>
10071  *
10072  * @param start offset of the first selected character
10073  * @param length number of characters to select
10074  *
10075  * @exception SWTException <ul>
10076  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10077  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10078  * </ul>
10079  * @exception IllegalArgumentException <ul>
10080  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
10081  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
10082  * </ul>
10083  */
10084 public void setSelectionRange(int start, int length) {
10085         checkWidget();
10086         int contentLength = getCharCount();
10087         start = Math.max(0, Math.min (start, contentLength));
10088         int end = start + length;
10089         if (end < 0) {
10090                 length = -start;
10091         } else {
10092                 if (end > contentLength) length = contentLength - start;
10093         }
10094         if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
10095                 // the start offset or end offset of the selection range is inside a
10096                 // multi byte line delimiter. This is an illegal operation and an exception
10097                 // is thrown. Fixes 1GDKK3R
10098                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10099         }
10100         setSelection(start, length, false, true);
10101         setCaretLocation();
10102 }
10103 /**
10104  * Adds the specified style.
10105  * <p>
10106  * The new style overwrites existing styles for the specified range.
10107  * Existing style ranges are adjusted if they partially overlap with
10108  * the new style. To clear an individual style, call setStyleRange
10109  * with a StyleRange that has null attributes.
10110  * </p><p>
10111  * Should not be called if a LineStyleListener has been set since the
10112  * listener maintains the styles.
10113  * </p>
10114  *
10115  * @param range StyleRange object containing the style information.
10116  * Overwrites the old style in the given range. May be null to delete
10117  * all styles.
10118  * @exception SWTException <ul>
10119  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10120  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10121  * </ul>
10122  * @exception IllegalArgumentException <ul>
10123  *   <li>ERROR_INVALID_RANGE when the style range is outside the valid range (&gt; getCharCount())</li>
10124  * </ul>
10125  */
10126 public void setStyleRange(StyleRange range) {
10127         checkWidget();
10128         if (isListening(ST.LineGetStyle)) return;
10129         if (range != null) {
10130                 if (range.isUnstyled()) {
10131                         setStyleRanges(range.start, range.length, null, null, false);
10132                 } else {
10133                         setStyleRanges(range.start, 0, null, new StyleRange[]{range}, false);
10134                 }
10135         } else {
10136                 setStyleRanges(0, 0, null, null, true);
10137         }
10138 }
10139 /**
10140  * Clears the styles in the range specified by <code>start</code> and
10141  * <code>length</code> and adds the new styles.
10142  * <p>
10143  * The ranges array contains start and length pairs.  Each pair refers to
10144  * the corresponding style in the styles array.  For example, the pair
10145  * that starts at ranges[n] with length ranges[n+1] uses the style
10146  * at styles[n/2].  The range fields within each StyleRange are ignored.
10147  * If ranges or styles is null, the specified range is cleared.
10148  * </p><p>
10149  * Note: It is expected that the same instance of a StyleRange will occur
10150  * multiple times within the styles array, reducing memory usage.
10151  * </p><p>
10152  * Should not be called if a LineStyleListener has been set since the
10153  * listener maintains the styles.
10154  * </p>
10155  *
10156  * @param start offset of first character where styles will be deleted
10157  * @param length length of the range to delete styles in
10158  * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
10159  * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
10160  *
10161  * @exception SWTException <ul>
10162  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10163  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10164  * </ul>
10165  * @exception IllegalArgumentException <ul>
10166  *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
10167  *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
10168  *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (&gt; getCharCount() or less than zero)</li>
10169  *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
10170  * </ul>
10171  *
10172  * @since 3.2
10173  */
10174 public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
10175         checkWidget();
10176         if (isListening(ST.LineGetStyle)) return;
10177         if (ranges == null || styles == null) {
10178                 setStyleRanges(start, length, null, null, false);
10179         } else {
10180                 setStyleRanges(start, length, ranges, styles, false);
10181         }
10182 }
10183 /**
10184  * Sets styles to be used for rendering the widget content.
10185  * <p>
10186  * All styles in the widget will be replaced with the given set of ranges and styles.
10187  * The ranges array contains start and length pairs.  Each pair refers to
10188  * the corresponding style in the styles array.  For example, the pair
10189  * that starts at ranges[n] with length ranges[n+1] uses the style
10190  * at styles[n/2].  The range fields within each StyleRange are ignored.
10191  * If either argument is null, the styles are cleared.
10192  * </p><p>
10193  * Note: It is expected that the same instance of a StyleRange will occur
10194  * multiple times within the styles array, reducing memory usage.
10195  * </p><p>
10196  * Should not be called if a LineStyleListener has been set since the
10197  * listener maintains the styles.
10198  * </p>
10199  *
10200  * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
10201  * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
10202  *
10203  * @exception SWTException <ul>
10204  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10205  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10206  * </ul>
10207  * @exception IllegalArgumentException <ul>
10208  *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
10209  *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
10210  *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (&gt; getCharCount() or less than zero)</li>
10211  *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
10212  * </ul>
10213  *
10214  * @since 3.2
10215  */
10216 public void setStyleRanges(int[] ranges, StyleRange[] styles) {
10217         checkWidget();
10218         if (isListening(ST.LineGetStyle)) return;
10219         if (ranges == null || styles == null) {
10220                 setStyleRanges(0, 0, null, null, true);
10221         } else {
10222                 setStyleRanges(0, 0, ranges, styles, true);
10223         }
10224 }
10225 void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) {
10226         int charCount = content.getCharCount();
10227         if (reset) {
10228                 start = 0;
10229                 length = charCount;
10230         }
10231         int[] formerRanges = getRanges(start, length);
10232         StyleRange[] formerStyles = getStyleRanges(start, length);
10233         int end = start + length;
10234         final boolean wasFixedLineHeight = isFixedLineHeight();
10235         if (start > end || start < 0) {
10236                 SWT.error(SWT.ERROR_INVALID_RANGE);
10237         }
10238         if (styles != null) {
10239                 if (end > charCount) {
10240                         SWT.error(SWT.ERROR_INVALID_RANGE);
10241                 }
10242                 if (ranges != null) {
10243                         if (ranges.length != styles.length << 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10244                 }
10245                 int lastOffset = 0;
10246                 for (int i = 0; i < styles.length; i ++) {
10247                         if (styles[i] == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10248                         int rangeStart, rangeLength;
10249                         if (ranges != null) {
10250                                 rangeStart = ranges[i << 1];
10251                                 rangeLength = ranges[(i << 1) + 1];
10252                         } else {
10253                                 rangeStart = styles[i].start;
10254                                 rangeLength = styles[i].length;
10255                         }
10256                         if (rangeLength < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10257                         if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10258                         if (lastOffset > rangeStart) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10259                         hasStyleWithVariableHeight |= styles[i].isVariableHeight();
10260                         lastOffset = rangeStart + rangeLength;
10261                 }
10262         }
10263         int rangeStart = start, rangeEnd = end;
10264         if (styles != null && styles.length > 0) {
10265                 if (ranges != null) {
10266                         rangeStart = ranges[0];
10267                         rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
10268                 } else {
10269                         rangeStart = styles[0].start;
10270                         rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
10271                 }
10272         }
10273
10274         // This needs to happen before new styles are applied
10275         int expectedBottom = 0;
10276         if (!isFixedLineHeight() && !reset) {
10277                 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
10278                 int partialTopIndex = getPartialTopIndex();
10279                 int partialBottomIndex = getPartialBottomIndex();
10280                 if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
10281                         expectedBottom = getLinePixel(lineEnd + 1);
10282                 }
10283         }
10284         if (reset) {
10285                 renderer.setStyleRanges(null, null);
10286         } else {
10287                 renderer.updateRanges(start, length, length);
10288         }
10289         if (styles != null && styles.length > 0) {
10290                 renderer.setStyleRanges(ranges, styles);
10291         }
10292
10293         // re-evaluate variable height with all styles (including new ones)
10294         hasStyleWithVariableHeight = false;
10295         for (StyleRange style : getStyleRanges(false)) {
10296                 hasStyleWithVariableHeight = style.isVariableHeight();
10297                 if (hasStyleWithVariableHeight) break;
10298         }
10299
10300         SortedSet<Integer> modifiedLines = computeModifiedLines(formerRanges, formerStyles, ranges, styles);
10301         resetCache(modifiedLines);
10302         if (reset) {
10303                 super.redraw();
10304         } else {
10305                 int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
10306                 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
10307                 int partialTopIndex = getPartialTopIndex();
10308                 int partialBottomIndex = getPartialBottomIndex();
10309                 if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
10310                         int top = 0;
10311                         int bottom = clientAreaHeight;
10312                         if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
10313                                 top = Math.max(0, getLinePixel(lineStart));
10314                         }
10315                         if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
10316                                 bottom = getLinePixel(lineEnd + 1);
10317                         }
10318                         if (!(wasFixedLineHeight && isFixedLineHeight()) && bottom != expectedBottom) {
10319                                 bottom = clientAreaHeight;
10320                         }
10321                         super.redraw(0, top, clientAreaWidth, bottom - top, false);
10322                 }
10323         }
10324         int oldColumnX = columnX;
10325         setCaretLocation();
10326         columnX = oldColumnX;
10327         doMouseLinkCursor();
10328 }
10329
10330 /**
10331  *
10332  * @param referenceRanges former ranges, sorted by order and without overlapping, typically returned {@link #getRanges(int, int)}
10333  * @param referenceStyles
10334  * @param newRanges former ranges, sorted by order and without overlapping
10335  * @param newStyles
10336  * @return
10337  */
10338 private SortedSet<Integer> computeModifiedLines(int[] referenceRanges, StyleRange[] referenceStyles, int[] newRanges, StyleRange[] newStyles) {
10339         if (referenceStyles == null) {
10340                 referenceStyles = new StyleRange[0];
10341         }
10342         if (referenceRanges == null) {
10343                 referenceRanges = createRanges(referenceStyles);
10344         }
10345         if (newStyles == null) {
10346                 newStyles = new StyleRange[0];
10347         }
10348         if (newRanges == null) {
10349                 newRanges = createRanges(newStyles);
10350         }
10351         if (referenceRanges.length != 2 * referenceStyles.length) {
10352                 throw new IllegalArgumentException();
10353         }
10354         if (newRanges.length != 2 * newStyles.length) {
10355                 throw new IllegalArgumentException();
10356         }
10357         SortedSet<Integer> res = new TreeSet<>();
10358         int referenceRangeIndex = 0;
10359         int newRangeIndex = 0;
10360         StyleRange defaultStyle = new StyleRange();
10361         defaultStyle.foreground = this.foreground;
10362         defaultStyle.background = this.background;
10363         defaultStyle.font = getFont();
10364         int currentOffset = referenceRanges.length > 0 ? referenceRanges[0] : Integer.MAX_VALUE;
10365         if (newRanges.length > 0) {
10366                 currentOffset = Math.min(currentOffset, newRanges[0]);
10367         }
10368         while (currentOffset < content.getCharCount() && (referenceRangeIndex < referenceStyles.length || newRangeIndex < newRanges.length)) {
10369                 int nextMilestoneOffset = Integer.MAX_VALUE; // next new range start/end after current offset
10370
10371                 while (referenceRangeIndex < referenceStyles.length && endRangeOffset(referenceRanges, referenceRangeIndex) <= currentOffset) {
10372                         referenceRangeIndex++;
10373                 }
10374                 StyleRange referenceStyleAtCurrentOffset = defaultStyle;
10375                 if (isInRange(referenceRanges, referenceRangeIndex, currentOffset)) { // has styling
10376                         referenceStyleAtCurrentOffset = referenceStyles[referenceRangeIndex];
10377                         nextMilestoneOffset =  endRangeOffset(referenceRanges, referenceRangeIndex);
10378                 } else if (referenceRangeIndex < referenceStyles.length) { // no range, default styling
10379                         nextMilestoneOffset = referenceRanges[2 * referenceRangeIndex]; // beginning of next range
10380                 }
10381
10382                 while (newRangeIndex < newStyles.length && endRangeOffset(newRanges, newRangeIndex) <= currentOffset) {
10383                         newRangeIndex++;
10384                 }
10385                 StyleRange newStyleAtCurrentOffset = defaultStyle;
10386                 if (isInRange(newRanges, newRangeIndex, currentOffset)) {
10387                         newStyleAtCurrentOffset = newStyles[newRangeIndex];
10388                         nextMilestoneOffset = Math.min(nextMilestoneOffset, endRangeOffset(newRanges, newRangeIndex));
10389                 } else if (newRangeIndex < newStyles.length) {
10390                         nextMilestoneOffset = Math.min(nextMilestoneOffset, newRanges[2 * newRangeIndex]);
10391                 }
10392
10393                 if (!referenceStyleAtCurrentOffset.similarTo(newStyleAtCurrentOffset)) {
10394                         int fromLine = getLineAtOffset(currentOffset);
10395                         int toLine = getLineAtOffset(nextMilestoneOffset - 1);
10396                         for (int line = fromLine; line <= toLine; line++) {
10397                                 res.add(line);
10398                         }
10399                         currentOffset = toLine + 1 < getLineCount() ? getOffsetAtLine(toLine + 1) : content.getCharCount();
10400                 } else {
10401                         currentOffset = nextMilestoneOffset;
10402                 }
10403         }
10404         return res;
10405 }
10406 private int[] createRanges(StyleRange[] referenceStyles) {
10407         int[] referenceRanges;
10408         referenceRanges = new int[2 * referenceStyles.length];
10409         for (int i = 0; i < referenceStyles.length; i++) {
10410                 referenceRanges[2 * i] = referenceStyles[i].start;
10411                 referenceRanges[2 * i + 1] = referenceStyles[i].length;
10412         }
10413         return referenceRanges;
10414 }
10415
10416 private boolean isInRange(int[] ranges, int styleIndex, int offset) {
10417         if (ranges == null || ranges.length == 0 || styleIndex < 0 || 2 * styleIndex + 1 > ranges.length) {
10418                 return false;
10419         }
10420         int start = ranges[2 * styleIndex];
10421         int length = ranges[2 * styleIndex + 1];
10422         return offset >= start && offset < start + length;
10423 }
10424
10425 /**
10426  * The offset on which the range ends (excluded)
10427  * @param ranges
10428  * @param styleIndex
10429  * @return
10430  */
10431 private int endRangeOffset(int[] ranges, int styleIndex) {
10432         if (styleIndex < 0 || 2 * styleIndex > ranges.length) {
10433                 throw new IllegalArgumentException();
10434         }
10435         int start = ranges[2 * styleIndex];
10436         int length = ranges[2 * styleIndex + 1];
10437         return start + length;
10438 }
10439
10440 /**
10441  * Sets styles to be used for rendering the widget content. All styles
10442  * in the widget will be replaced with the given set of styles.
10443  * <p>
10444  * Note: Because a StyleRange includes the start and length, the
10445  * same instance cannot occur multiple times in the array of styles.
10446  * If the same style attributes, such as font and color, occur in
10447  * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
10448  * can be used to share styles and reduce memory usage.
10449  * </p><p>
10450  * Should not be called if a LineStyleListener has been set since the
10451  * listener maintains the styles.
10452  * </p>
10453  *
10454  * @param ranges StyleRange objects containing the style information.
10455  * The ranges should not overlap. The style rendering is undefined if
10456  * the ranges do overlap. Must not be null. The styles need to be in order.
10457  * @exception SWTException <ul>
10458  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10459  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10460  * </ul>
10461  * @exception IllegalArgumentException <ul>
10462  *    <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li>
10463  *    <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (&gt; getCharCount())</li>
10464  * </ul>
10465  *
10466  * @see #setStyleRanges(int[], StyleRange[])
10467  */
10468 public void setStyleRanges(StyleRange[] ranges) {
10469         checkWidget();
10470         if (isListening(ST.LineGetStyle)) return;
10471         if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
10472         setStyleRanges(0, 0, null, ranges, true);
10473 }
10474 /**
10475  * Sets the tab width.
10476  *
10477  * @param tabs tab width measured in characters.
10478  * @exception SWTException <ul>
10479  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10480  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10481  * </ul>
10482  *
10483  * @see #setTabStops(int[])
10484  */
10485 public void setTabs(int tabs) {
10486         checkWidget();
10487         tabLength = tabs;
10488         renderer.setFont(null, tabs);
10489         resetCache(0, content.getLineCount());
10490         setCaretLocation();
10491         super.redraw();
10492 }
10493
10494 /**
10495  * Sets the receiver's tab list. Each value in the tab list specifies
10496  * the space in points from the origin of the document to the respective
10497  * tab stop.  The last tab stop width is repeated continuously.
10498  *
10499  * @param tabs the new tab list (or null)
10500  *
10501  * @exception SWTException <ul>
10502  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10503  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10504  * </ul>
10505  * @exception IllegalArgumentException <ul>
10506  *    <li>ERROR_INVALID_ARGUMENT - if a tab stop is negative or less than the previous stop in the list</li>
10507  * </ul>
10508  *
10509  * @see StyledText#getTabStops()
10510  *
10511  * @since 3.6
10512  */
10513 public void setTabStops(int [] tabs) {
10514         checkWidget();
10515         if (tabs != null) {
10516                 int pos = 0;
10517                 int[] newTabs = new int[tabs.length];
10518                 for (int i = 0; i < tabs.length; i++) {
10519                         if (tabs[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10520                         newTabs[i] = pos = tabs[i];
10521                 }
10522                 this.tabs = newTabs;
10523         } else {
10524                 this.tabs = null;
10525         }
10526         resetCache(0, content.getLineCount());
10527         setCaretLocation();
10528         super.redraw();
10529 }
10530
10531 /**
10532  * Sets the widget content.
10533  * If the widget has the SWT.SINGLE style and "text" contains more than
10534  * one line, only the first line is rendered but the text is stored
10535  * unchanged. A subsequent call to getText will return the same text
10536  * that was set.
10537  * <p>
10538  * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE
10539  * style is used.
10540  * </p>
10541  *
10542  * @param text new widget content. Replaces existing content. Line styles
10543  *      that were set using StyledText API are discarded.  The
10544  *      current selection is also discarded.
10545  * @exception SWTException <ul>
10546  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10547  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10548  * </ul>
10549  * @exception IllegalArgumentException <ul>
10550  *    <li>ERROR_NULL_ARGUMENT when string is null</li>
10551  * </ul>
10552  */
10553 public void setText(String text) {
10554         checkWidget();
10555         if (text == null) {
10556                 SWT.error(SWT.ERROR_NULL_ARGUMENT);
10557         }
10558         Event event = new Event();
10559         event.start = 0;
10560         event.end = getCharCount();
10561         event.text = text;
10562         event.doit = true;
10563         notifyListeners(SWT.Verify, event);
10564         if (event.doit) {
10565                 StyledTextEvent styledTextEvent = null;
10566                 if (isListening(ST.ExtendedModify)) {
10567                         styledTextEvent = new StyledTextEvent(content);
10568                         styledTextEvent.start = event.start;
10569                         styledTextEvent.end = event.start + event.text.length();
10570                         styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
10571                 }
10572                 content.setText(event.text);
10573                 notifyListeners(SWT.Modify, event);
10574                 if (styledTextEvent != null) {
10575                         notifyListeners(ST.ExtendedModify, styledTextEvent);
10576                 }
10577         }
10578 }
10579
10580 /**
10581  * Sets the base text direction (a.k.a. "paragraph direction") of the receiver,
10582  * which must be one of the constants <code>SWT.LEFT_TO_RIGHT</code> or
10583  * <code>SWT.RIGHT_TO_LEFT</code>.
10584  * <p>
10585  * <code>setOrientation</code> would override this value with the text direction
10586  * that is consistent with the new orientation.
10587  * </p>
10588  * <p>
10589  * <b>Warning</b>: This API is currently only implemented on Windows.
10590  * It doesn't set the base text direction on GTK and Cocoa.
10591  * </p>
10592  *
10593  * @param textDirection the base text direction style
10594  *
10595  * @exception SWTException <ul>
10596  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10597  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10598  * </ul>
10599  *
10600  * @see SWT#FLIP_TEXT_DIRECTION
10601  */
10602 @Override
10603 public void setTextDirection(int textDirection) {
10604         checkWidget();
10605         int oldStyle = getStyle();
10606         super.setTextDirection(textDirection);
10607         if (isAutoDirection () || oldStyle != getStyle()) {
10608                 resetBidiData();
10609         }
10610 }
10611
10612 /**
10613  * Sets the text limit to the specified number of characters.
10614  * <p>
10615  * The text limit specifies the amount of text that
10616  * the user can type into the widget.
10617  * </p>
10618  *
10619  * @param limit the new text limit.
10620  * @exception SWTException <ul>
10621  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10622  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10623  * </ul>
10624  * @exception IllegalArgumentException <ul>
10625  *   <li>ERROR_CANNOT_BE_ZERO when limit is 0</li>
10626  * </ul>
10627  */
10628 public void setTextLimit(int limit) {
10629         checkWidget();
10630         if (limit == 0) {
10631                 SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
10632         }
10633         textLimit = limit;
10634 }
10635 /**
10636  * Sets the top index. Do nothing if there is no text set.
10637  * <p>
10638  * The top index is the index of the line that is currently at the top
10639  * of the widget. The top index changes when the widget is scrolled.
10640  * Indexing starts from zero.
10641  * Note: The top index is reset to 0 when new text is set in the widget.
10642  * </p>
10643  *
10644  * @param topIndex new top index. Must be between 0 and
10645  *      getLineCount() - fully visible lines per page. If no lines are fully
10646  *      visible the maximum value is getLineCount() - 1. An out of range
10647  *      index will be adjusted accordingly.
10648  * @exception SWTException <ul>
10649  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10650  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10651  * </ul>
10652  */
10653 public void setTopIndex(int topIndex) {
10654         checkWidget();
10655         if (getCharCount() == 0) {
10656                 return;
10657         }
10658         int lineCount = content.getLineCount(), pixel;
10659         if (isFixedLineHeight()) {
10660                 int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
10661                 if (topIndex < 0) {
10662                         topIndex = 0;
10663                 } else if (topIndex > lineCount - pageSize) {
10664                         topIndex = lineCount - pageSize;
10665                 }
10666                 pixel = getLinePixel(topIndex);
10667         } else {
10668                 topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
10669                 pixel = getLinePixel(topIndex);
10670                 if (pixel > 0) {
10671                         pixel = getAvailableHeightBellow(pixel);
10672                 } else {
10673                         pixel = getAvailableHeightAbove(pixel);
10674                 }
10675         }
10676         scrollVertical(pixel, true);
10677 }
10678 /**
10679  * Sets the top margin.
10680  *
10681  * @param topMargin the top margin.
10682  * @exception SWTException <ul>
10683  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10684  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10685  * </ul>
10686  *
10687  * @since 3.5
10688  */
10689 public void setTopMargin (int topMargin) {
10690         checkWidget();
10691         setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
10692 }
10693 /**
10694  * Sets the top SWT logical point offset. Do nothing if there is no text set.
10695  * <p>
10696  * The top point offset is the vertical SWT logical point offset of the widget. The
10697  * widget is scrolled so that the given SWT logical point position is at the top.
10698  * The top index is adjusted to the corresponding top line.
10699  * Note: The top point is reset to 0 when new text is set in the widget.
10700  * </p>
10701  *
10702  * @param pixel new top point offset. Must be between 0 and
10703  *      (getLineCount() - visible lines per page) / getLineHeight()). An out
10704  *      of range offset will be adjusted accordingly.
10705  * @exception SWTException <ul>
10706  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10707  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10708  * </ul>
10709  * @since 2.0
10710  */
10711 public void setTopPixel(int pixel) {
10712         checkWidget();
10713         if (getCharCount() == 0) {
10714                 return;
10715         }
10716         if (pixel < 0) pixel = 0;
10717         int lineCount = content.getLineCount();
10718         int height = clientAreaHeight - topMargin - bottomMargin;
10719         int verticalOffset = getVerticalScrollOffset();
10720         if (isFixedLineHeight()) {
10721                 int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
10722                 if (pixel > maxTopPixel) pixel = maxTopPixel;
10723                 pixel -= verticalOffset;
10724         } else {
10725                 pixel -= verticalOffset;
10726                 if (pixel > 0) {
10727                         pixel = getAvailableHeightBellow(pixel);
10728                 }
10729         }
10730         scrollVertical(pixel, true);
10731 }
10732 /**
10733  * Sets whether the widget wraps lines.
10734  * <p>
10735  * This overrides the creation style bit SWT.WRAP.
10736  * </p>
10737  *
10738  * @param wrap true=widget wraps lines, false=widget does not wrap lines
10739  * @since 2.0
10740  */
10741 public void setWordWrap(boolean wrap) {
10742         checkWidget();
10743         if ((getStyle() & SWT.SINGLE) != 0) return;
10744         if (wordWrap == wrap) return;
10745         if (wordWrap && blockSelection) setBlockSelection(false);
10746         wordWrap = wrap;
10747         resetCache(0, content.getLineCount());
10748         horizontalScrollOffset = 0;
10749         ScrollBar horizontalBar = getHorizontalBar();
10750         if (horizontalBar != null) {
10751                 horizontalBar.setVisible(!wordWrap);
10752         }
10753         setScrollBars(true);
10754         setCaretLocation();
10755         super.redraw();
10756 }
10757 /**
10758  * Sets the wrap line indentation of the widget.
10759  * <p>
10760  * It is the amount of blank space, in points, at the beginning of each wrapped line.
10761  * When a line wraps in several lines all the lines but the first one is indented
10762  * by this amount.
10763  * </p>
10764  *
10765  * @param wrapIndent the new wrap indent
10766  *
10767  * @exception SWTException <ul>
10768  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10769  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10770  * </ul>
10771  *
10772  * @see #setLineWrapIndent(int, int, int)
10773  *
10774  * @since 3.6
10775  */
10776 public void setWrapIndent(int wrapIndent) {
10777         checkWidget();
10778         if (this.wrapIndent == wrapIndent || wrapIndent < 0) return;
10779         this.wrapIndent = wrapIndent;
10780         resetCache(0, content.getLineCount());
10781         setCaretLocation();
10782         super.redraw();
10783 }
10784 boolean showLocation(Rectangle rect, boolean scrollPage) {
10785         boolean scrolled = false;
10786         if (rect.y < topMargin) {
10787                 scrolled = scrollVertical(rect.y - topMargin, true);
10788         } else if (rect.y + rect.height > clientAreaHeight - bottomMargin) {
10789                 if (clientAreaHeight - topMargin - bottomMargin <= 0) {
10790                         scrolled = scrollVertical(rect.y - topMargin, true);
10791                 } else {
10792                         scrolled = scrollVertical(rect.y + rect.height - (clientAreaHeight - bottomMargin), true);
10793                 }
10794         }
10795         int width = clientAreaWidth - rightMargin - leftMargin;
10796         if (width > 0) {
10797                 int minScroll = scrollPage ? width / 4 : 0;
10798                 if (rect.x < leftMargin) {
10799                         int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
10800                         int maxScroll = horizontalScrollOffset;
10801                         scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
10802                 } else if (rect.x + rect.width > (clientAreaWidth - rightMargin)) {
10803                         int scrollWidth =  Math.max(rect.x + rect.width - (clientAreaWidth - rightMargin), minScroll);
10804                         int maxScroll = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
10805                         scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
10806                 }
10807         }
10808         return scrolled;
10809 }
10810 /**
10811  * Sets the caret location and scrolls the caret offset into view.
10812  */
10813 void showCaret() {
10814         Rectangle bounds = getBoundsAtOffset(caretOffset);
10815         if (!showLocation(bounds, true)) {
10816                 setCaretLocation();
10817         }
10818 }
10819 /**
10820  * Scrolls the selection into view.
10821  * <p>
10822  * The end of the selection will be scrolled into view.
10823  * Note that if a right-to-left selection exists, the end of the selection is
10824  * the visual beginning of the selection (i.e., where the caret is located).
10825  * </p>
10826  *
10827  * @exception SWTException <ul>
10828  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10829  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10830  * </ul>
10831  */
10832 public void showSelection() {
10833         checkWidget();
10834         // is selection from right-to-left?
10835         boolean rightToLeft = caretOffset == selection.x;
10836         int startOffset, endOffset;
10837         if (rightToLeft) {
10838                 startOffset = selection.y;
10839                 endOffset = selection.x;
10840         } else {
10841                 startOffset = selection.x;
10842                 endOffset = selection.y;
10843         }
10844
10845         Rectangle startBounds = getBoundsAtOffset(startOffset);
10846         Rectangle endBounds = getBoundsAtOffset(endOffset);
10847
10848         // can the selection be fully displayed within the widget's visible width?
10849         int w = clientAreaWidth - leftMargin - rightMargin;
10850         boolean selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
10851         if (selectionFits) {
10852                 // show as much of the selection as possible by first showing
10853                 // the start of the selection
10854                 if (showLocation(startBounds, false)) {
10855                         // endX value could change if showing startX caused a scroll to occur
10856                         endBounds = getBoundsAtOffset(endOffset);
10857                 }
10858                 // the character at endOffset is not part of the selection
10859                 endBounds.width = endOffset == caretOffset ? getCaretWidth() : 0;
10860                 showLocation(endBounds, false);
10861         } else {
10862                 // just show the end of the selection since the selection start
10863                 // will not be visible
10864                 showLocation(endBounds, true);
10865         }
10866 }
10867 void updateCaretVisibility() {
10868         Caret caret = getCaret();
10869         if (caret != null) {
10870                 if (blockSelection && blockXLocation != -1) {
10871                         caret.setVisible(false);
10872                 } else {
10873                         Point location = caret.getLocation();
10874                         Point size = caret.getSize();
10875                         boolean visible =
10876                                 topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin &&
10877                                 leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin;
10878                         caret.setVisible(visible);
10879                 }
10880         }
10881 }
10882 /**
10883  * Updates the selection and caret position depending on the text change.
10884  * <p>
10885  * If the selection intersects with the replaced text, the selection is
10886  * reset and the caret moved to the end of the new text.
10887  * If the selection is behind the replaced text it is moved so that the
10888  * same text remains selected.  If the selection is before the replaced text
10889  * it is left unchanged.
10890  * </p>
10891  *
10892  * @param startOffset offset of the text change
10893  * @param replacedLength length of text being replaced
10894  * @param newLength length of new text
10895  */
10896 void updateSelection(int startOffset, int replacedLength, int newLength) {
10897         if (selection.y <= startOffset) {
10898                 // selection ends before text change
10899                 if (isWordWrap()) setCaretLocation();
10900                 return;
10901         }
10902         if (selection.x < startOffset) {
10903                 // clear selection fragment before text change
10904                 internalRedrawRange(selection.x, startOffset - selection.x);
10905         }
10906         if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
10907                 // clear selection fragment after text change.
10908                 // do this only when the selection is actually affected by the
10909                 // change. Selection is only affected if it intersects the change (1GDY217).
10910                 int netNewLength = newLength - replacedLength;
10911                 int redrawStart = startOffset + newLength;
10912                 internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
10913         }
10914         if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
10915                 // selection intersects replaced text. set caret behind text change
10916                 setSelection(startOffset + newLength, 0, true, false);
10917         } else {
10918                 // move selection to keep same text selected
10919                 setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true, false);
10920         }
10921         setCaretLocation();
10922 }
10923 }