1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.custom.StyledText;
14 
15 
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.SWTError;
18 import org.eclipse.swt.SWTException;
19 import org.eclipse.swt.accessibility.ACC;
20 import org.eclipse.swt.accessibility.Accessible;
21 import org.eclipse.swt.accessibility.AccessibleAdapter;
22 import org.eclipse.swt.accessibility.AccessibleControlAdapter;
23 import org.eclipse.swt.accessibility.AccessibleControlEvent;
24 import org.eclipse.swt.accessibility.AccessibleEvent;
25 import org.eclipse.swt.accessibility.AccessibleTextAdapter;
26 import org.eclipse.swt.accessibility.AccessibleTextEvent;
27 import org.eclipse.swt.dnd.Clipboard;
28 import org.eclipse.swt.dnd.DND;
29 import org.eclipse.swt.dnd.RTFTransfer;
30 import org.eclipse.swt.dnd.TextTransfer;
31 import org.eclipse.swt.dnd.Transfer;
32 import org.eclipse.swt.events.ModifyListener;
33 import org.eclipse.swt.events.SelectionEvent;
34 import org.eclipse.swt.events.SelectionListener;
35 import org.eclipse.swt.events.VerifyListener;
36 import org.eclipse.swt.graphics.Color;
37 import org.eclipse.swt.graphics.Cursor;
38 import org.eclipse.swt.graphics.Font;
39 import org.eclipse.swt.graphics.FontData;
40 import org.eclipse.swt.graphics.FontMetrics;
41 import org.eclipse.swt.graphics.GC;
42 import org.eclipse.swt.graphics.GlyphMetrics;
43 import org.eclipse.swt.graphics.Image;
44 import org.eclipse.swt.graphics.Device;
45 import org.eclipse.swt.graphics.Point;
46 import org.eclipse.swt.graphics.Rectangle;
47 import org.eclipse.swt.graphics.Resource;
48 import org.eclipse.swt.graphics.TextLayout;
49 import org.eclipse.swt.internal.BidiUtil;
50 import org.eclipse.swt.internal.Compatibility;
51 import org.eclipse.swt.printing.Printer;
52 import org.eclipse.swt.printing.PrinterData;
53 import org.eclipse.swt.widgets.Canvas;
54 import org.eclipse.swt.widgets.Caret;
55 import org.eclipse.swt.widgets.Composite;
56 import org.eclipse.swt.widgets.Control;
57 import org.eclipse.swt.widgets.Display;
58 import org.eclipse.swt.widgets.Event;
59 import org.eclipse.swt.widgets.IME;
60 import org.eclipse.swt.widgets.Label;
61 import org.eclipse.swt.widgets.Listener;
62 import org.eclipse.swt.widgets.ScrollBar;
63 import org.eclipse.swt.widgets.TypedListener;
64 import org.eclipse.swt.custom.StyledTextContent;
65 import org.eclipse.swt.custom.TextChangeListener;
66 import org.eclipse.swt.custom.StyledTextRenderer;
67 import org.eclipse.swt.custom.StyledTextPrintOptions;
68 import org.eclipse.swt.custom.ExtendedModifyListener;
69 import org.eclipse.swt.custom.BidiSegmentListener;
70 import org.eclipse.swt.custom.LineBackgroundListener;
71 import org.eclipse.swt.custom.LineStyleListener;
72 import org.eclipse.swt.custom.PaintObjectListener;
73 import org.eclipse.swt.custom.VerifyKeyListener;
74 import org.eclipse.swt.custom.MovementListener;
75 import org.eclipse.swt.custom.Bullet;
76 import org.eclipse.swt.custom.StyledTextEvent;
77 import org.eclipse.swt.custom.StyleRange;
78 import org.eclipse.swt.custom.TextChangedEvent;
79 import org.eclipse.swt.custom.TextChangingEvent;
80 import org.eclipse.swt.custom.DefaultContent;
81 import org.eclipse.swt.custom.StyledTextDropTargetEffect;
82 import org.eclipse.swt.custom.StyledTextListener;
83 import org.eclipse.swt.custom.ST;
84 
85 import java.lang.all;
86 import java.nonstandard.UnsafeUtf;
87 
88 version(Tango){
89     static import tango.io.model.IFile;
90 } else { // Phobos
91     static import std..string;
92     static import std.ascii;
93 }
94 
95 
96 /**
97  * A StyledText is an editable user interface object that displays lines
98  * of text.  The following style attributes can be defined for the text:
99  * <ul>
100  * <li>foreground color
101  * <li>background color
102  * <li>font style (bold, italic, bold-italic, regular)
103  * <li>underline
104  * <li>strikeout
105  * </ul>
106  * <p>
107  * In addition to text style attributes, the background color of a line may
108  * be specified.
109  * </p><p>
110  * There are two ways to use this widget when specifying text style information.
111  * You may use the API that is defined for StyledText or you may define your own
112  * LineStyleListener.  If you define your own listener, you will be responsible
113  * for maintaining the text style information for the widget.  IMPORTANT: You may
114  * not define your own listener and use the StyledText API.  The following
115  * StyledText API is not supported if you have defined a LineStyleListener:
116  * <ul>
117  * <li>getStyleRangeAtOffset(int)
118  * <li>getStyleRanges()
119  * <li>replaceStyleRanges(int,int,StyleRange[])
120  * <li>setStyleRange(StyleRange)
121  * <li>setStyleRanges(StyleRange[])
122  * </ul>
123  * </p><p>
124  * There are two ways to use this widget when specifying line background colors.
125  * You may use the API that is defined for StyledText or you may define your own
126  * LineBackgroundListener.  If you define your own listener, you will be responsible
127  * for maintaining the line background color information for the widget.
128  * IMPORTANT: You may not define your own listener and use the StyledText API.
129  * The following StyledText API is not supported if you have defined a
130  * LineBackgroundListener:
131  * <ul>
132  * <li>getLineBackground(int)
133  * <li>setLineBackground(int,int,Color)
134  * </ul>
135  * </p><p>
136  * The content implementation for this widget may also be user-defined.  To do so,
137  * you must implement the StyledTextContent interface and use the StyledText API
138  * setContent(StyledTextContent) to initialize the widget.
139  * </p><p>
140  * <dl>
141  * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
142  * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey
143  * </dl>
144  * </p><p>
145  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
146  * </p>
147  *
148  * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a>
149  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a>
150  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
151  */
152 public class StyledText : Canvas {
153     alias Canvas.computeSize computeSize;
154 package:
155 
156     static const char TAB = '\t';
157     version(Tango){
158         static const String PlatformLineDelimiter = tango.io.model.IFile.FileConst.NewlineString;
159     } else { // Phobos
160         static const String PlatformLineDelimiter = std.ascii.newline;
161     }
162     static const int BIDI_CARET_WIDTH = 3;
163     static const int DEFAULT_WIDTH  = 64;
164     static const int DEFAULT_HEIGHT = 64;
165     static const int V_SCROLL_RATE = 50;
166     static const int H_SCROLL_RATE = 10;
167 
168     static const int ExtendedModify = 3000;
169     static const int LineGetBackground = 3001;
170     static const int LineGetStyle = 3002;
171     static const int TextChanging = 3003;
172     static const int TextSet = 3004;
173     static const int VerifyKey = 3005;
174     static const int TextChanged = 3006;
175     static const int LineGetSegments = 3007;
176     static const int PaintObject = 3008;
177     static const int WordNext = 3009;
178     static const int WordPrevious = 3010;
179 
180     static const int PREVIOUS_OFFSET_TRAILING = 0;
181     static const int OFFSET_LEADING = 1;
182 
183     Color selectionBackground;  // selection background color
184     Color selectionForeground;  // selection foreground color
185     StyledTextContent content;          // native content (default or user specified)
186     StyledTextRenderer renderer;
187     Listener listener;
188     TextChangeListener textChangeListener;  // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
189     int verticalScrollOffset = 0;       // pixel based
190     int horizontalScrollOffset = 0;     // pixel based
191     int topIndex = 0;                   // top visible line
192     int topIndexY;
193     int clientAreaHeight = 0;           // the client area height. Needed to calculate content width for new visible lines during Resize callback
194     int clientAreaWidth = 0;            // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated
195     int tabLength = 4;                  // number of characters in a tab
196     int leftMargin;
197     int topMargin;
198     int rightMargin;
199     int bottomMargin;
200     int columnX;                        // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
201     int/*UTF8index*/ caretOffset = 0;
202     int caretAlignment;
203     Point selection;                    // x and y are start and end caret offsets of selection
204     Point clipboardSelection;           // x and y are start and end caret offsets of previous selection
205     bool selectedTextValid = true;      // DWT: false if we just changed a text witch was selected
206     int/*UTF8index*/ selectionAnchor;   // position of selection anchor. 0 based offset from beginning of text
207     Point doubleClickSelection;         // selection after last mouse double click
208     bool editable = true;
209     bool wordWrap = false;
210     bool doubleClickEnabled = true;     // see getDoubleClickEnabled
211     bool overwrite = false;             // insert/overwrite edit mode
212     int/*UTF8index*/ textLimit = -1;           // limits the number of characters the user can type in the widget. Unlimited by default.
213     int[int] keyActionMap;
214     Color background = null;            // workaround for bug 4791
215     Color foreground = null;            //
216     Clipboard clipboard;
217     int clickCount;
218     int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left)
219     int autoScrollDistance = 0;
220     int lastTextChangeStart;            // cache data of the
221     int lastTextChangeNewLineCount;     // last text changing
222     int lastTextChangeNewCharCount;     // event for use in the
223     int lastTextChangeReplaceLineCount; // text changed handler
224     int lastTextChangeReplaceCharCount;
225     int lastLineBottom;                 // the bottom pixel of the last line been replaced
226     bool isMirrored_;
227     bool bidiColoring = false;       // apply the BIDI algorithm on text segments of the same color
228     Image leftCaretBitmap = null;
229     Image rightCaretBitmap = null;
230     int caretDirection = SWT.NULL;
231     int caretWidth = 0;
232     Caret defaultCaret = null;
233     bool updateCaretDirection = true;
234     bool fixedLineHeight;
235     bool dragDetect_ = true;
236     IME ime;
237 
238     int alignment;
239     bool justify;
240     int indent;
241     int lineSpacing;
242 
243     const static bool IS_CARBON, IS_GTK, IS_MOTIF;
244 mixin(sharedStaticThis!(`{
245         String platform = SWT.getPlatform();
246         IS_CARBON = ("carbon" == platform);
247         IS_GTK    = ("gtk"    == platform);
248         IS_MOTIF  = ("motif"  == platform);
249     }`));
250 
251     /**
252      * The Printing class : printing of a range of text.
253      * An instance of <code>Printing</code> is returned in the
254      * StyledText#print(Printer) API. The run() method may be
255      * invoked from any thread.
256      */
257     static class Printing : Runnable {
258         const static int LEFT = 0;                      // left aligned header/footer segment
259         const static int CENTER = 1;                    // centered header/footer segment
260         const static int RIGHT = 2;                     // right aligned header/footer segment
261 
262         Printer printer;
263         StyledTextRenderer printerRenderer;
264         StyledTextPrintOptions printOptions;
265         Rectangle clientArea;
266         FontData fontData;
267         Font printerFont;
268         Resource[Resource] resources;
269         int tabLength;
270         GC gc;                                          // printer GC
271         int pageWidth;                                  // width of a printer page in pixels
272         int startPage;                                  // first page to print
273         int endPage;                                    // last page to print
274         int startLine;                                  // first (wrapped) line to print
275         int endLine;                                    // last (wrapped) line to print
276         bool singleLine;                             // widget single line mode
277         Point selection = null;                 // selected text
278         bool mirrored;                       // indicates the printing gc should be mirrored
279         int lineSpacing;
280         int printMargin;
281 
282     /**
283      * Creates an instance of <code>Printing</code>.
284      * Copies the widget content and rendering data that needs
285      * to be requested from listeners.
286      * </p>
287      * @param parent StyledText widget to print.
288      * @param printer printer device to print on.
289      * @param printOptions print options
290      */
291     this(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) {
292         this.printer = printer;
293         this.printOptions = printOptions;
294         this.mirrored = (styledText.getStyle() & SWT.MIRRORED) !is 0;
295         singleLine = styledText.isSingleLine();
296         startPage = 1;
297         endPage = int.max;
298         PrinterData data = printer.getPrinterData();
299         if (data.scope_ is PrinterData.PAGE_RANGE) {
300             startPage = data.startPage;
301             endPage = data.endPage;
302             if (endPage < startPage) {
303                 int temp = endPage;
304                 endPage = startPage;
305                 startPage = temp;
306             }
307         } else if (data.scope_ is PrinterData.SELECTION) {
308             selection = styledText.getSelectionRange();
309         }
310         printerRenderer = new StyledTextRenderer(printer, null);
311         printerRenderer.setContent(copyContent(styledText.getContent()));
312         cacheLineData(styledText);
313     }
314     /**
315      * Caches all line data that needs to be requested from a listener.
316      * </p>
317      * @param printerContent <code>StyledTextContent</code> to request
318      *  line data for.
319      */
320     void cacheLineData(StyledText styledText) {
321         StyledTextRenderer renderer = styledText.renderer;
322         renderer.copyInto(printerRenderer);
323         fontData = styledText.getFont().getFontData()[0];
324         tabLength = styledText.tabLength;
325         int lineCount = printerRenderer.lineCount;
326         if (styledText.isListening(LineGetBackground) || (styledText.isBidi() && styledText.isListening(LineGetSegments)) || styledText.isListening(LineGetStyle)) {
327             StyledTextContent content = printerRenderer.content;
328             for (int i = 0; i < lineCount; i++) {
329                 String line = content.getLine(i);
330                 int lineOffset = content.getOffsetAtLine(i);
331                 StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
332                 if (event !is null && event.lineBackground !is null) {
333                     printerRenderer.setLineBackground(i, 1, event.lineBackground);
334                 }
335                 if (styledText.isBidi()) {
336                     int[] segments = styledText.getBidiSegments(lineOffset, line);
337                     printerRenderer.setLineSegments(i, 1, segments);
338                 }
339                 event = styledText.getLineStyleData(lineOffset, line);
340                 if (event !is null) {
341                     printerRenderer.setLineIndent(i, 1, event.indent);
342                     printerRenderer.setLineAlignment(i, 1, event.alignment);
343                     printerRenderer.setLineJustify(i, 1, event.justify);
344                     printerRenderer.setLineBullet(i, 1, event.bullet);
345                     StyleRange[] styles = event.styles;
346                     if (styles !is null && styles.length > 0) {
347                         printerRenderer.setStyleRanges(event.ranges, styles);
348                     }
349                 }
350             }
351         }
352         Point screenDPI = styledText.getDisplay().getDPI();
353         Point printerDPI = printer.getDPI();
354         resources = null;
355         for (int i = 0; i < lineCount; i++) {
356             Color color = printerRenderer.getLineBackground(i, null);
357             if (color !is null) {
358                 if (printOptions.printLineBackground) {
359                     Color printerColor;
360                     if ( auto p = color in resources ) {
361                         printerColor = cast(Color)*p;
362                     }
363                     else {
364                         printerColor = new Color (printer, color.getRGB());
365                         resources[color]=printerColor;
366                     }
367                     printerRenderer.setLineBackground(i, 1, printerColor);
368                 } else {
369                     printerRenderer.setLineBackground(i, 1, null);
370                 }
371             }
372             int indent = printerRenderer.getLineIndent(i, 0);
373             if (indent !is 0) {
374                 printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
375             }
376         }
377         StyleRange[] styles = printerRenderer.styles;
378         for (int i = 0; i < printerRenderer.styleCount; i++) {
379             StyleRange style = styles[i];
380             Font font = style.font;
381             if (style.font !is null) {
382                 Font printerFont;
383                 if ( auto p = font in resources ) {
384                     printerFont = cast(Font)*p;
385                 }
386                 else {
387                     printerFont = new Font (printer, font.getFontData());
388                     resources[font]= printerFont;
389                 }
390                 style.font = printerFont;
391             }
392             Color color = style.foreground;
393             if (color !is null) {
394                 if (printOptions.printTextForeground) {
395                     Color printerColor;
396                     if ( auto p = color in resources ) {
397                         printerColor = cast(Color)*p;
398                     }
399                     else {
400                         printerColor = new Color (printer, color.getRGB());
401                         resources[color]=printerColor;
402                     }
403                     style.foreground = printerColor;
404                 } else {
405                     style.foreground = null;
406                 }
407             }
408             color = style.background;
409             if (color !is null) {
410                 if (printOptions.printTextBackground) {
411                     Color printerColor;
412                     if ( auto p = color in resources ) {
413                         printerColor = cast(Color)*p;
414                     }
415                     else {
416                         printerColor = new Color (printer, color.getRGB());
417                         resources[color]=printerColor;
418                     }
419                     style.background = printerColor;
420                 } else {
421                     style.background = null;
422                 }
423             }
424             if (!printOptions.printTextFontStyle) {
425                 style.fontStyle = SWT.NORMAL;
426             }
427             style.rise = style.rise * printerDPI.y / screenDPI.y;
428             GlyphMetrics metrics = style.metrics;
429             if (metrics !is null) {
430                 metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
431                 metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
432                 metrics.width = metrics.width * printerDPI.x / screenDPI.x;
433             }
434         }
435         lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
436         if (printOptions.printLineNumbers) {
437             printMargin = 3 * printerDPI.x / screenDPI.x;
438         }
439     }
440     /**
441      * Copies the text of the specified <code>StyledTextContent</code>.
442      * </p>
443      * @param original the <code>StyledTextContent</code> to copy.
444      */
445     StyledTextContent copyContent(StyledTextContent original) {
446         StyledTextContent printerContent = new DefaultContent();
447         int insertOffset = 0;
448         for (int i = 0; i < original.getLineCount(); i++) {
449             int insertEndOffset;
450             if (i < original.getLineCount() - 1) {
451                 insertEndOffset = original.getOffsetAtLine(i + 1);
452             } else {
453                 insertEndOffset = original.getCharCount();
454             }
455             printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset));
456             insertOffset = insertEndOffset;
457         }
458         return printerContent;
459     }
460     /**
461      * Disposes of the resources and the <code>PrintRenderer</code>.
462      */
463     void dispose() {
464         if (gc !is null) {
465             gc.dispose();
466             gc = null;
467         }
468         foreach( resource; resources.values ){
469             resource.dispose();
470         }
471         resources = null;
472         if (printerFont !is null) {
473             printerFont.dispose();
474             printerFont = null;
475         }
476         if (printerRenderer !is null) {
477             printerRenderer.dispose();
478             printerRenderer = null;
479         }
480     }
481     void init_() {
482         Rectangle trim = printer.computeTrim(0, 0, 0, 0);
483         Point dpi = printer.getDPI();
484 
485         printerFont = new Font( cast(Device)printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL);
486         clientArea = printer.getClientArea();
487         pageWidth = clientArea.width;
488         // one inch margin around text
489         clientArea.x = dpi.x + trim.x;
490         clientArea.y = dpi.y + trim.y;
491         clientArea.width -= (clientArea.x + trim.width);
492         clientArea.height -= (clientArea.y + trim.height);
493 
494         int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
495         gc = new GC(printer, style);
496         gc.setFont(printerFont);
497         printerRenderer.setFont(printerFont, tabLength);
498         int lineHeight = printerRenderer.getLineHeight();
499         if (printOptions.header !is null) {
500             clientArea.y += lineHeight * 2;
501             clientArea.height -= lineHeight * 2;
502         }
503         if (printOptions.footer !is null) {
504             clientArea.height -= lineHeight * 2;
505         }
506 
507         // TODO not wrapped
508         StyledTextContent content = printerRenderer.content;
509         startLine = 0;
510         endLine = singleLine ? 0 : content.getLineCount() - 1;
511         PrinterData data = printer.getPrinterData();
512         if (data.scope_ is PrinterData.PAGE_RANGE) {
513             int pageSize = clientArea.height / lineHeight;//WRONG
514             startLine = (startPage - 1) * pageSize;
515         } else if (data.scope_ is PrinterData.SELECTION) {
516             startLine = content.getLineAtOffset(selection.x);
517             if (selection.y > 0) {
518                 // DWT: index isn't a valid UTF-8 index
519                 endLine = content.getLineAtOffset(selection.x + selection.y - 1);
520             } else {
521                 endLine = startLine - 1;
522             }
523         }
524     }
525     /**
526      * Prints the lines in the specified page range.
527      */
528     void print() {
529         Color background = gc.getBackground();
530         Color foreground = gc.getForeground();
531         int paintY = clientArea.y;
532         int paintX = clientArea.x;
533         int width = clientArea.width;
534         int page = startPage;
535         int pageBottom = clientArea.y + clientArea.height;
536         int orientation =  gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT);
537         TextLayout printLayout = null;
538         if (printOptions.printLineNumbers || printOptions.header !is null || printOptions.footer !is null) {
539             printLayout = new TextLayout(printer);
540             printLayout.setFont(printerFont);
541         }
542         if (printOptions.printLineNumbers) {
543             int numberingWidth = 0;
544             int count = endLine - startLine + 1;
545             String[] lineLabels = printOptions.lineLabels;
546             if (lineLabels !is null) {
547                 for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
548                     if (lineLabels[i] !is null) {
549                         printLayout.setText(lineLabels[i]);
550                         int lineWidth = printLayout.getBounds().width;
551                         numberingWidth = Math.max(numberingWidth, lineWidth);
552                     }
553                 }
554             } else {
555                 StringBuffer buffer = new StringBuffer("0");
556                 while ((count /= 10) > 0) buffer.append("0");
557                 printLayout.setText(buffer.toString());
558                 numberingWidth = printLayout.getBounds().width;
559             }
560             numberingWidth += printMargin;
561             if (numberingWidth > width) numberingWidth = width;
562             paintX += numberingWidth;
563             width -= numberingWidth;
564         }
565         for (int i = startLine; i <= endLine && page <= endPage; i++) {
566             if (paintY is clientArea.y) {
567                 printer.startPage();
568                 printDecoration(page, true, printLayout);
569             }
570             TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
571             Color lineBackground = printerRenderer.getLineBackground(i, background);
572             int paragraphBottom = paintY + layout.getBounds().height;
573             if (paragraphBottom <= pageBottom) {
574                 //normal case, the whole paragraph fits in the current page
575                 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
576                 paintY = paragraphBottom;
577             } else {
578                 int lineCount = layout.getLineCount();
579                 while (paragraphBottom > pageBottom && lineCount > 0) {
580                     lineCount--;
581                     paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
582                 }
583                 if (lineCount is 0) {
584                     //the whole paragraph goes to the next page
585                     printDecoration(page, false, printLayout);
586                     printer.endPage();
587                     page++;
588                     if (page <= endPage) {
589                         printer.startPage();
590                         printDecoration(page, true, printLayout);
591                         paintY = clientArea.y;
592                         printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
593                         paintY += layout.getBounds().height;
594                     }
595                 } else {
596                     //draw paragraph top in the current page and paragraph bottom in the next
597                     int height = paragraphBottom - paintY;
598                     gc.setClipping(clientArea.x, paintY, clientArea.width, height);
599                     printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
600                     gc.setClipping(cast(Rectangle)null);
601                     printDecoration(page, false, printLayout);
602                     printer.endPage();
603                     page++;
604                     if (page <= endPage) {
605                         printer.startPage();
606                         printDecoration(page, true, printLayout);
607                         paintY = clientArea.y - height;
608                         int layoutHeight = layout.getBounds().height;
609                         gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
610                         printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
611                         gc.setClipping(cast(Rectangle)null);
612                         paintY += layoutHeight;
613                     }
614                 }
615             }
616             printerRenderer.disposeTextLayout(layout);
617         }
618         if (page <= endPage && paintY > clientArea.y) {
619             // close partial page
620             printDecoration(page, false, printLayout);
621             printer.endPage();
622         }
623         if (printLayout !is null) printLayout.dispose();
624     }
625     /**
626      * Print header or footer decorations.
627      *
628      * @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
629      * @param header true = print the header, false = print the footer
630      */
631     void printDecoration(int page, bool header, TextLayout layout) {
632         String text = header ? printOptions.header : printOptions.footer;
633         if (text is null) return;
634         int lastSegmentIndex = 0;
635         for (int i = 0; i < 3; i++) {
636             auto segmentIndex = text.indexOf( StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
637             String segment;
638             if (segmentIndex is -1 ) {
639                 segment = text.substring(lastSegmentIndex);
640                 printDecorationSegment(segment, i, page, header, layout);
641                 break;
642             } else {
643                 segment = text.substring(lastSegmentIndex, segmentIndex);
644                 printDecorationSegment(segment, i, page, header, layout);
645                 lastSegmentIndex = cast(int)/*64bit*/(segmentIndex
646                     + StyledTextPrintOptions.SEPARATOR.length);
647             }
648         }
649     }
650     /**
651      * Print one segment of a header or footer decoration.
652      * Headers and footers have three different segments.
653      * One each for left aligned, centered, and right aligned text.
654      *
655      * @param segment decoration segment to print
656      * @param alignment alignment of the segment. 0=left, 1=center, 2=right
657      * @param page page number to print, if specified in the decoration segment.
658      * @param header true = print the header, false = print the footer
659      */
660     void printDecorationSegment(String segment, int alignment, int page, bool header, TextLayout layout) {
661         auto pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
662         if (pageIndex !is -1 ) {
663             int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length;
664             StringBuffer buffer = new StringBuffer(segment.substring (0, pageIndex));
665             buffer.append (page);
666             buffer.append (segment.substring(pageIndex + pageTagLength));
667             segment = buffer.toString()._idup();
668         }
669         if (segment.length > 0) {
670             layout.setText(segment);
671             int segmentWidth = layout.getBounds().width;
672             int segmentHeight = printerRenderer.getLineHeight();
673             int drawX = 0, drawY;
674             if (alignment is LEFT) {
675                 drawX = clientArea.x;
676             } else if (alignment is CENTER) {
677                 drawX = (pageWidth - segmentWidth) / 2;
678             } else if (alignment is RIGHT) {
679                 drawX = clientArea.x + clientArea.width - segmentWidth;
680             }
681             if (header) {
682                 drawY = clientArea.y - segmentHeight * 2;
683             } else {
684                 drawY = clientArea.y + clientArea.height + segmentHeight;
685             }
686             layout.draw(gc, drawX, drawY);
687         }
688     }
689     void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) {
690         if (background !is null) {
691             Rectangle rect = layout.getBounds();
692             gc.setBackground(background);
693             gc.fillRectangle(x, y, rect.width, rect.height);
694 
695 //          int lineCount = layout.getLineCount();
696 //          for (int i = 0; i < lineCount; i++) {
697 //              Rectangle rect = layout.getLineBounds(i);
698 //              rect.x += paintX;
699 //              rect.y += paintY + layout.getSpacing();
700 //              rect.width = width;//layout bounds
701 //              gc.fillRectangle(rect);
702 //          }
703         }
704         if (printOptions.printLineNumbers) {
705             FontMetrics metrics = layout.getLineMetrics(0);
706             printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
707             printLayout.setDescent(metrics.getDescent());
708             String[] lineLabels = printOptions.lineLabels;
709             if (lineLabels !is null) {
710                 if (0 <= index && index < lineLabels.length && lineLabels[index] !is null) {
711                     printLayout.setText(lineLabels[index]);
712                 } else {
713                     printLayout.setText("");
714                 }
715             } else {
716                 printLayout.setText(String_valueOf(index));
717             }
718             int paintX = x - printMargin - printLayout.getBounds().width;
719             printLayout.draw(gc, paintX, y);
720             printLayout.setAscent(-1);
721             printLayout.setDescent(-1);
722         }
723         gc.setForeground(foreground);
724         layout.draw(gc, x, y);
725     }
726     /**
727      * Starts a print job and prints the pages specified in the constructor.
728      */
729     public void run() {
730         String jobName = printOptions.jobName;
731         if (jobName is null) {
732             jobName = "Printing";
733         }
734         if (printer.startJob(jobName)) {
735             init_();
736             print();
737             dispose();
738             printer.endJob();
739         }
740     }
741     }
742     /**
743      * The <code>RTFWriter</code> class is used to write widget content as
744      * rich text. The implementation complies with the RTF specification
745      * version 1.5.
746      * <p>
747      * toString() is guaranteed to return a valid RTF string only after
748      * close() has been called.
749      * </p><p>
750      * Whole and partial lines and line breaks can be written. Lines will be
751      * formatted using the styles queried from the LineStyleListener, if
752      * set, or those set directly in the widget. All styles are applied to
753      * the RTF stream like they are rendered by the widget. In addition, the
754      * widget font name and size is used for the whole text.
755      * </p>
756      */
757     class RTFWriter : TextWriter {
758 
759         alias TextWriter.write write;
760 
761         static const int DEFAULT_FOREGROUND = 0;
762         static const int DEFAULT_BACKGROUND = 1;
763         Color[] colorTable;
764         Font[] fontTable;
765         bool WriteUnicode;
766 
767     /**
768      * Creates a RTF writer that writes content starting at offset "start"
769      * in the document.  <code>start</code> and <code>length</code>can be set to specify partial
770      * lines.
771      *
772      * @param start start offset of content to write, 0 based from
773      *  beginning of document
774      * @param length length of content to write
775      */
776     public this(int start, int length) {
777         super(start, length);
778         colorTable ~= getForeground();
779         colorTable ~= getBackground();
780         fontTable ~= getFont();
781         setUnicode();
782     }
783     /**
784      * Closes the RTF writer. Once closed no more content can be written.
785      * <b>NOTE:</b>  <code>toString()</code> does not return a valid RTF string until
786      * <code>close()</code> has been called.
787      */
788     public override void close() {
789         if (!isClosed()) {
790             writeHeader();
791             write("\n}}\0");
792             super.close();
793         }
794     }
795     /**
796      * Returns the index of the specified color in the RTF color table.
797      *
798      * @param color the color
799      * @param defaultIndex return value if color is null
800      * @return the index of the specified color in the RTF color table
801      *  or "defaultIndex" if "color" is null.
802      */
803     int getColorIndex(Color color, int defaultIndex) {
804         if (color is null) return defaultIndex;
805         int index = -1;
806         foreach( i, col; colorTable ){
807             if( col == color ){
808                 index = cast(int)/*64bit*/i;
809                 break;
810             }
811         }
812         if (index is -1) {
813             index = cast(int)/*64bit*/colorTable.length;
814             colorTable ~= color;
815         }
816         return index;
817     }
818     /**
819      * Returns the index of the specified color in the RTF color table.
820      *
821      * @param color the color
822      * @param defaultIndex return value if color is null
823      * @return the index of the specified color in the RTF color table
824      *  or "defaultIndex" if "color" is null.
825      */
826     int getFontIndex(Font font) {
827         int index = -1;
828         foreach( i, f; colorTable ){
829             if( f == font ){
830                 index = cast(int)/*64bit*/i;
831                 break;
832             }
833         }
834         if (index is -1) {
835             index = cast(int)/*64bit*/fontTable.length;
836             fontTable ~= font;
837         }
838         return index;
839     }
840     /**
841      * Determines if Unicode RTF should be written.
842      * Don't write Unicode RTF on Windows 95/98/ME or NT.
843      */
844     void setUnicode() {/*!!!*/
845 //         const String Win95 = "windows 95";
846 //         const String Win98 = "windows 98";
847 //         const String WinME = "windows me";
848 //         const String WinNT = "windows nt";
849 //         String osName = System.getProperty("os.name").toLowerCase();
850 //         String osVersion = System.getProperty("os.version");
851 //         int majorVersion = 0;
852 //
853 //         if (osName.startsWith(WinNT) && osVersion !is null) {
854 //             int majorIndex = osVersion.indexOf('.');
855 //             if (majorIndex !is -1) {
856 //                 osVersion = osVersion.substring(0, majorIndex);
857 //                 try {
858 //                     majorVersion = Integer.parseInt(osVersion);
859 //                 } catch (NumberFormatException exception) {
860 //                     // ignore exception. version number remains unknown.
861 //                     // will write without Unicode
862 //                 }
863 //             }
864 //         }
865 //         WriteUnicode =  !osName.startsWith(Win95) &&
866 //                         !osName.startsWith(Win98) &&
867 //                         !osName.startsWith(WinME) &&
868 //                         (!osName.startsWith(WinNT) || majorVersion > 4);
869         WriteUnicode = true; // we are on linux-gtk
870     }
871     /**
872      * Appends the specified segment of "string" to the RTF data.
873      * Copy from <code>start</code> up to, but excluding, <code>end</code>.
874      *
875      * @param string string to copy a segment from. Must not contain
876      *  line breaks. Line breaks should be written using writeLineDelimiter()
877      * @param start start offset of segment. 0 based.
878      * @param end end offset of segment
879      */
880     void write(String string, int start, int end) {
881         ptrdiff_t incr;
882         for (int index = start; index < end; index += incr) {
883             dchar ch = string.dcharAt(index, incr);
884             if (ch > 0xFF && WriteUnicode) {
885                 // write the sub string from the last escaped character
886                 // to the current one. Fixes bug 21698.
887                 if (index > start) {
888                     write( string[start .. index ] );
889                 }
890                 write("\\u");
891                 write( String_valueOf( cast(short)ch ));
892                 write(' ');                     // control word delimiter
893                 start = cast(int)/*64bit*/(index + incr);
894             } else if (ch is '}' || ch is '{' || ch is '\\') {
895                 // write the sub string from the last escaped character
896                 // to the current one. Fixes bug 21698.
897                 if (index > start) {
898                     write(string[start .. index]);
899                 }
900                 write('\\');
901                 write(cast(char)ch); // ok because one of {}\
902                 assert(incr == 1);
903                 start = index + 1;
904             }
905         }
906         // write from the last escaped character to the end.
907         // Fixes bug 21698.
908         if (start < end) {
909             write(string[ start .. end]);
910         }
911     }
912     /**
913      * Writes the RTF header including font table and color table.
914      */
915     void writeHeader() {
916         StringBuffer header = new StringBuffer();
917         FontData fontData = getFont().getFontData()[0];
918         header.append("{\\rtf1\\ansi");
919         // specify code page, necessary for copy to work in bidi
920         // systems that don't support Unicode RTF.
921         // PORTING_TODO: String cpg = System.getProperty("file.encoding").toLowerCase();
922         //String cpg = "UTF16";
923         /+
924         if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
925             cpg = cpg.substring(2, cpg.length());
926             header.append("\\ansicpg");
927             header.append(cpg);
928         }
929         +/
930         header.append("\\uc0\\deff0{\\fonttbl{\\f0\\fnil ");
931         header.append(fontData.getName());
932         header.append(";");
933         for (int i = 1; i < fontTable.length; i++) {
934             header.append("\\f");
935             header.append(i);
936             header.append(" ");
937             FontData fd = (cast(Font)fontTable[i]).getFontData()[0];
938             header.append(fd.getName());
939             header.append(";");
940         }
941         header.append("}}\n{\\colortbl");
942         for (int i = 0; i < colorTable.length; i++) {
943             Color color = cast(Color) colorTable[i];
944             header.append("\\red");
945             header.append(color.getRed());
946             header.append("\\green");
947             header.append(color.getGreen());
948             header.append("\\blue");
949             header.append(color.getBlue());
950             header.append(";");
951         }
952         // some RTF readers ignore the deff0 font tag. Explicitly
953         // set the font for the whole document to work around this.
954         header.append("}\n{\\f0\\fs");
955         // font size is specified in half points
956         header.append(fontData.getHeight() * 2);
957         header.append(" ");
958         write(header.toString(), 0);
959     }
960     /**
961      * Appends the specified line text to the RTF data.  Lines will be formatted
962      * using the styles queried from the LineStyleListener, if set, or those set
963      * directly in the widget.
964      *
965      * @param line line text to write as RTF. Must not contain line breaks
966      *  Line breaks should be written using writeLineDelimiter()
967      * @param lineOffset offset of the line. 0 based from the start of the
968      *  widget document. Any text occurring before the start offset or after the
969      *  end offset specified during object creation is ignored.
970      * @exception SWTException <ul>
971      *   <li>ERROR_IO when the writer is closed.</li>
972      * </ul>
973      */
974     public override void writeLine(String line, int lineOffset) {
975         if (isClosed()) {
976             SWT.error(SWT.ERROR_IO);
977         }
978         int lineIndex = content.getLineAtOffset(lineOffset);
979         int lineAlignment, lineIndent;
980         bool lineJustify;
981         int[] ranges;
982         StyleRange[] styles;
983         StyledTextEvent event = getLineStyleData(lineOffset, line);
984         if (event !is null) {
985             lineAlignment = event.alignment;
986             lineIndent = event.indent;
987             lineJustify = event.justify;
988             ranges = event.ranges;
989             styles = event.styles;
990         } else {
991             lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
992             lineIndent =  renderer.getLineIndent(lineIndex, indent);
993             lineJustify = renderer.getLineJustify(lineIndex, justify);
994             ranges = renderer.getRanges(lineOffset, cast(int)/*64bit*/line.length);
995             styles = renderer.getStyleRanges(lineOffset, cast(int)/*64bit*/line.length, false);
996         }
997         if (styles is null) styles = new StyleRange[0];
998         Color lineBackground = renderer.getLineBackground(lineIndex, null);
999         event = getLineBackgroundData(lineOffset, line);
1000         if (event !is null && event.lineBackground !is null) lineBackground = event.lineBackground;
1001         writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify);
1002     }
1003     /**
1004      * Appends the specified line delimiter to the RTF data.
1005      *
1006      * @param lineDelimiter line delimiter to write as RTF.
1007      * @exception SWTException <ul>
1008      *   <li>ERROR_IO when the writer is closed.</li>
1009      * </ul>
1010      */
1011     public override void writeLineDelimiter(String lineDelimiter) {
1012         if (isClosed()) {
1013             SWT.error(SWT.ERROR_IO);
1014         }
1015         write(lineDelimiter, 0, cast(int)/*64bit*/lineDelimiter.length);
1016         write("\\par ");
1017     }
1018     /**
1019      * Appends the specified line text to the RTF data.
1020      * <p>
1021      * Use the colors and font styles specified in "styles" and "lineBackground".
1022      * Formatting is written to reflect the text rendering by the text widget.
1023      * Style background colors take precedence over the line background color.
1024      * Background colors are written using the \highlight tag (vs. the \cb tag).
1025      * </p>
1026      *
1027      * @param line line text to write as RTF. Must not contain line breaks
1028      *  Line breaks should be written using writeLineDelimiter()
1029      * @param lineOffset offset of the line. 0 based from the start of the
1030      *  widget document. Any text occurring before the start offset or after the
1031      *  end offset specified during object creation is ignored.
1032      * @param styles styles to use for formatting. Must not be null.
1033      * @param lineBackground line background color to use for formatting.
1034      *  May be null.
1035      */
1036     void writeStyledLine(String line, int lineOffset, int[] ranges, StyleRange[] styles, Color lineBackground, int indent, int alignment, bool justify) {
1037         auto lineLength = line.length;
1038         int startOffset = getStart();
1039         int writeOffset = startOffset - lineOffset;
1040         if (writeOffset >= lineLength) return;
1041         int lineIndex = Math.max(0, writeOffset);
1042 
1043         write("\\fi");
1044         write(indent);
1045         switch (alignment) {
1046             case SWT.LEFT: write("\\ql"); break;
1047             case SWT.CENTER: write("\\qc"); break;
1048             case SWT.RIGHT: write("\\qr"); break;
1049             default:
1050         }
1051         if (justify) write("\\qj");
1052         write(" ");
1053 
1054         if (lineBackground !is null) {
1055             write("{\\highlight");
1056             write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
1057             write(" ");
1058         }
1059         int endOffset = startOffset + super.getCharCount();
1060         auto lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
1061         for (size_t i = 0; i < styles.length; i++) {
1062             StyleRange style = styles[i];
1063             int start, end;
1064             if (ranges !is null) {
1065                 start = ranges[i << 1] - lineOffset;
1066                 end = start + ranges[(i << 1) + 1];
1067             } else {
1068                 start = style.start - lineOffset;
1069                 end = start + style.length;
1070             }
1071             // skip over partial first line
1072             if (end < writeOffset) {
1073                 continue;
1074             }
1075             // style starts beyond line end or RTF write end
1076             if (start >= lineEndOffset) {
1077                 break;
1078             }
1079             // write any unstyled text
1080             if (lineIndex < start) {
1081                 // copy to start of style
1082                 // style starting beyond end of write range or end of line
1083                 // is guarded against above.
1084                 write(line, lineIndex, start);
1085                 lineIndex = start;
1086             }
1087             // write styled text
1088             write("{\\cf");
1089             write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
1090             int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
1091             if (colorIndex !is DEFAULT_BACKGROUND) {
1092                 write("\\highlight");
1093                 write(colorIndex);
1094             }
1095             Font font = style.font;
1096             if (font !is null) {
1097                 int fontIndex = getFontIndex(font);
1098                 write("\\f");
1099                 write(fontIndex);
1100                 FontData fontData = font.getFontData()[0];
1101                 write("\\fs");
1102                 write(fontData.getHeight() * 2);
1103             } else {
1104                 if ((style.fontStyle & SWT.BOLD) !is 0) {
1105                     write("\\b");
1106                 }
1107                 if ((style.fontStyle & SWT.ITALIC) !is 0) {
1108                     write("\\i");
1109                 }
1110             }
1111             if (style.underline) {
1112                 write("\\ul");
1113             }
1114             if (style.strikeout) {
1115                 write("\\strike");
1116             }
1117             write(" ");
1118             // copy to end of style or end of write range or end of line
1119             int copyEnd = cast(int)/*64bit*/Math.min(end, lineEndOffset);
1120             // guard against invalid styles and let style processing continue
1121             copyEnd = Math.max(copyEnd, lineIndex);
1122             write(line, lineIndex, copyEnd);
1123             if (font is null) {
1124                 if ((style.fontStyle & SWT.BOLD) !is 0) {
1125                     write("\\b0");
1126                 }
1127                 if ((style.fontStyle & SWT.ITALIC) !is 0) {
1128                     write("\\i0");
1129                 }
1130             }
1131             if (style.underline) {
1132                 write("\\ul0");
1133             }
1134             if (style.strikeout) {
1135                 write("\\strike0");
1136             }
1137             write("}");
1138             lineIndex = copyEnd;
1139         }
1140         // write unstyled text at the end of the line
1141         if (lineIndex < lineEndOffset) {
1142             write(line, lineIndex, cast(int)/*64bit*/lineEndOffset);
1143         }
1144         if (lineBackground !is null) write("}");
1145     }
1146     }
1147     /**
1148      * The <code>TextWriter</code> class is used to write widget content to
1149      * a string.  Whole and partial lines and line breaks can be written. To write
1150      * partial lines, specify the start and length of the desired segment
1151      * during object creation.
1152      * <p>
1153      * </b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close()
1154      * has been called.
1155      * </p>
1156      */
1157     class TextWriter {
1158         private StringBuffer buffer;
1159         private int startOffset;    // offset of first character that will be written
1160         private int endOffset;      // offset of last character that will be written.
1161                                     // 0 based from the beginning of the widget text.
1162         private bool isClosed_ = false;
1163 
1164     /**
1165      * Creates a writer that writes content starting at offset "start"
1166      * in the document.  <code>start</code> and <code>length</code> can be set to specify partial lines.
1167      *
1168      * @param start start offset of content to write, 0 based from beginning of document
1169      * @param length length of content to write
1170      */
1171     public this(int start, int length) {
1172         buffer = new StringBuffer(length);
1173         startOffset = start;
1174         endOffset = start + length;
1175     }
1176     /**
1177      * Closes the writer. Once closed no more content can be written.
1178      * <b>NOTE:</b>  <code>toString()</code> is not guaranteed to return a valid string unless
1179      * the writer is closed.
1180      */
1181     public void close() {
1182         if (!isClosed_) {
1183             isClosed_ = true;
1184         }
1185     }
1186     /**
1187      * Returns the number of characters to write.
1188      * @return the integer number of characters to write
1189      */
1190     public int getCharCount() {
1191         return endOffset - startOffset;
1192     }
1193     /**
1194      * Returns the offset where writing starts. 0 based from the start of
1195      * the widget text. Used to write partial lines.
1196      * @return the integer offset where writing starts
1197      */
1198     public int getStart() {
1199         return startOffset;
1200     }
1201     /**
1202      * Returns whether the writer is closed.
1203      * @return a bool specifying whether or not the writer is closed
1204      */
1205     public bool isClosed() {
1206         return isClosed_;
1207     }
1208     /**
1209      * Returns the string.  <code>close()</code> must be called before <code>toString()</code>
1210      * is guaranteed to return a valid string.
1211      *
1212      * @return the string
1213      */
1214     public override String toString() {
1215         return buffer.toString();
1216     }
1217     /**
1218      * Appends the given string to the data.
1219      */
1220     void write(String string) {
1221         buffer.append(string);
1222     }
1223     /**
1224      * Inserts the given string to the data at the specified offset.
1225      * <p>
1226      * Do nothing if "offset" is < 0 or > getCharCount()
1227      * </p>
1228      *
1229      * @param string text to insert
1230      * @param offset offset in the existing data to insert "string" at.
1231      */
1232     void write(String string, int offset) {
1233         if (offset < 0 || offset > buffer.length()) {
1234             return;
1235         }
1236         buffer.insert( offset, string );
1237     }
1238     /**
1239      * Appends the given int to the data.
1240      */
1241     void write(int i) {
1242         buffer.append(i);
1243     }
1244     /**
1245      * Appends the given character to the data.
1246      */
1247     void write(char i) {
1248         buffer.append(i);
1249     }
1250     /**
1251      * Appends the specified line text to the data.
1252      *
1253      * @param line line text to write. Must not contain line breaks
1254      *  Line breaks should be written using writeLineDelimiter()
1255      * @param lineOffset offset of the line. 0 based from the start of the
1256      *  widget document. Any text occurring before the start offset or after the
1257      *  end offset specified during object creation is ignored.
1258      * @exception SWTException <ul>
1259      *   <li>ERROR_IO when the writer is closed.</li>
1260      * </ul>
1261      */
1262     public void writeLine(String line, int lineOffset) {
1263         if (isClosed_) {
1264             SWT.error(SWT.ERROR_IO);
1265         }
1266         int writeOffset = startOffset - lineOffset;
1267         auto lineLength = line.length;
1268         int lineIndex;
1269         if (writeOffset >= lineLength) {
1270             return;                         // whole line is outside write range
1271         } else if (writeOffset > 0) {
1272             lineIndex = writeOffset;        // line starts before write start
1273         } else {
1274             lineIndex = 0;
1275         }
1276         int copyEnd = cast(int)/*64bit*/Math.min(lineLength, endOffset - lineOffset);
1277         if (lineIndex < copyEnd) {
1278             write(line.substring(lineIndex, copyEnd));
1279         }
1280     }
1281     /**
1282      * Appends the specified line delimiter to the data.
1283      *
1284      * @param lineDelimiter line delimiter to write
1285      * @exception SWTException <ul>
1286      *   <li>ERROR_IO when the writer is closed.</li>
1287      * </ul>
1288      */
1289     public void writeLineDelimiter(String lineDelimiter) {
1290         if (isClosed_) {
1291             SWT.error(SWT.ERROR_IO);
1292         }
1293         write(lineDelimiter);
1294     }
1295     }
1296 
1297 /**
1298  * Constructs a new instance of this class given its parent
1299  * and a style value describing its behavior and appearance.
1300  * <p>
1301  * The style value is either one of the style constants defined in
1302  * class <code>SWT</code> which is applicable to instances of this
1303  * class, or must be built by <em>bitwise OR</em>'ing together
1304  * (that is, using the <code>int</code> "|" operator) two or more
1305  * of those <code>SWT</code> style constants. The class description
1306  * lists the style constants that are applicable to the class.
1307  * Style bits are also inherited from superclasses.
1308  * </p>
1309  *
1310  * @param parent a widget which will be the parent of the new instance (cannot be null)
1311  * @param style the style of widget to construct
1312  *
1313  * @exception IllegalArgumentException <ul>
1314  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
1315  * </ul>
1316  * @exception SWTException <ul>
1317  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
1318  * </ul>
1319  *
1320  * @see SWT#FULL_SELECTION
1321  * @see SWT#MULTI
1322  * @see SWT#READ_ONLY
1323  * @see SWT#SINGLE
1324  * @see SWT#WRAP
1325  * @see #getStyle
1326  */
1327 public this(Composite parent, int style) {
1328     selection = new Point(0, 0);
1329     super(parent, checkStyle(style));
1330     // set the fg in the OS to ensure that these are the same as StyledText, necessary
1331     // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
1332     super.setForeground(getForeground());
1333     super.setDragDetect(false);
1334     Display display = getDisplay();
1335     isMirrored_ = (super.getStyle() & SWT.MIRRORED) !is 0;
1336     fixedLineHeight = true;
1337     if ((style & SWT.READ_ONLY) !is 0) {
1338         setEditable(false);
1339     }
1340     leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
1341     if ((style & SWT.SINGLE) !is 0 && (style & SWT.BORDER) !is 0) {
1342         leftMargin = topMargin = rightMargin = bottomMargin = 2;
1343     }
1344     alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
1345     if (alignment is 0) alignment = SWT.LEFT;
1346     clipboard = new Clipboard(display);
1347     installDefaultContent();
1348     renderer = new StyledTextRenderer(getDisplay(), this);
1349     renderer.setContent(content);
1350     renderer.setFont(getFont(), tabLength);
1351     ime = new IME(this, SWT.NONE);
1352     defaultCaret = new Caret(this, SWT.NONE);
1353     if ((style & SWT.WRAP) !is 0) {
1354         setWordWrap(true);
1355     }
1356     if (isBidiCaret()) {
1357         createCaretBitmaps();
1358         Runnable runnable = new class() Runnable {
1359             public void run() {
1360                 int direction = BidiUtil.getKeyboardLanguage() is BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
1361                 if (direction is caretDirection) return;
1362                 if (getCaret() !is defaultCaret) return;
1363                 Point newCaretPos = getPointAtOffset(caretOffset);
1364                 setCaretLocation(newCaretPos, direction);
1365             }
1366         };
1367         BidiUtil.addLanguageListener(this, runnable);
1368     }
1369     setCaret(defaultCaret);
1370     calculateScrollBars();
1371     createKeyBindings();
1372     setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
1373     installListeners();
1374     initializeAccessible();
1375     setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
1376 }
1377 /**
1378  * Adds an extended modify listener. An ExtendedModify event is sent by the
1379  * widget when the widget text has changed.
1380  *
1381  * @param extendedModifyListener the listener
1382  * @exception SWTException <ul>
1383  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1384  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1385  * </ul>
1386  * @exception IllegalArgumentException <ul>
1387  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1388  * </ul>
1389  */
1390 public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
1391     checkWidget();
1392     if (extendedModifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1393     StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
1394     addListener(ExtendedModify, typedListener);
1395 }
1396 /**
1397  * Adds a bidirectional segment listener.
1398  * <p>
1399  * A BidiSegmentEvent is sent
1400  * whenever a line of text is measured or rendered. The user can
1401  * specify text ranges in the line that should be treated as if they
1402  * had a different direction than the surrounding text.
1403  * This may be used when adjacent segments of right-to-left text should
1404  * not be reordered relative to each other.
1405  * E.g., Multiple Java string literals in a right-to-left language
1406  * should generally remain in logical order to each other, that is, the
1407  * way they are stored.
1408  * </p>
1409  *
1410  * @param listener the listener
1411  * @exception SWTException <ul>
1412  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1413  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1414  * </ul>
1415  * @exception IllegalArgumentException <ul>
1416  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1417  * </ul>
1418  * @see BidiSegmentEvent
1419  * @since 2.0
1420  */
1421 public void addBidiSegmentListener(BidiSegmentListener listener) {
1422     checkWidget();
1423     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1424     addListener(LineGetSegments, new StyledTextListener(listener));
1425 }
1426 /**
1427  * Adds a line background listener. A LineGetBackground event is sent by the
1428  * widget to determine the background color for a line.
1429  *
1430  * @param listener the listener
1431  * @exception SWTException <ul>
1432  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1433  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1434  * </ul>
1435  * @exception IllegalArgumentException <ul>
1436  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1437  * </ul>
1438  */
1439 public void addLineBackgroundListener(LineBackgroundListener listener) {
1440     checkWidget();
1441     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1442     if (!isListening(LineGetBackground)) {
1443         renderer.clearLineBackground(0, content.getLineCount());
1444     }
1445     addListener(LineGetBackground, new StyledTextListener(listener));
1446 }
1447 /**
1448  * Adds a line style listener. A LineGetStyle event is sent by the widget to
1449  * determine the styles for a line.
1450  *
1451  * @param listener the listener
1452  * @exception SWTException <ul>
1453  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1454  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1455  * </ul>
1456  * @exception IllegalArgumentException <ul>
1457  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1458  * </ul>
1459  */
1460 public void addLineStyleListener(LineStyleListener listener) {
1461     checkWidget();
1462     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1463     if (!isListening(LineGetStyle)) {
1464         setStyleRanges(0, 0, null, null, true);
1465         renderer.clearLineStyle(0, content.getLineCount());
1466     }
1467     addListener(LineGetStyle, new StyledTextListener(listener));
1468 }
1469 /**
1470  * Adds a modify listener. A Modify event is sent by the widget when the widget text
1471  * has changed.
1472  *
1473  * @param modifyListener the listener
1474  * @exception SWTException <ul>
1475  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1476  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1477  * </ul>
1478  * @exception IllegalArgumentException <ul>
1479  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1480  * </ul>
1481  */
1482 public void addModifyListener(ModifyListener modifyListener) {
1483     checkWidget();
1484     if (modifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1485     addListener(SWT.Modify, new TypedListener(modifyListener));
1486 }
1487 /**
1488  * Adds a paint object listener. A paint object event is sent by the widget when an object
1489  * needs to be drawn.
1490  *
1491  * @param listener the listener
1492  * @exception SWTException <ul>
1493  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1494  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1495  * </ul>
1496  * @exception IllegalArgumentException <ul>
1497  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1498  * </ul>
1499  *
1500  * @since 3.2
1501  *
1502  * @see PaintObjectListener
1503  * @see PaintObjectEvent
1504  */
1505 public void addPaintObjectListener(PaintObjectListener listener) {
1506     checkWidget();
1507     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1508     addListener(PaintObject, new StyledTextListener(listener));
1509 }
1510 /**
1511  * Adds a selection listener. A Selection event is sent by the widget when the
1512  * user changes the selection.
1513  * <p>
1514  * When <code>widgetSelected</code> is called, the event x and y fields contain
1515  * the start and end caret indices of the selection.
1516  * <code>widgetDefaultSelected</code> is not called for StyledTexts.
1517  * </p>
1518  *
1519  * @param listener the listener which should be notified when the user changes the receiver's selection
1520 
1521  * @exception IllegalArgumentException <ul>
1522  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
1523  * </ul>
1524  * @exception SWTException <ul>
1525  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1526  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1527  * </ul>
1528  *
1529  * @see SelectionListener
1530  * @see #removeSelectionListener
1531  * @see SelectionEvent
1532  */
1533 public void addSelectionListener(SelectionListener listener) {
1534     checkWidget();
1535     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1536     addListener(SWT.Selection, new TypedListener(listener));
1537 }
1538 /**
1539  * Adds a verify key listener. A VerifyKey event is sent by the widget when a key
1540  * is pressed. The widget ignores the key press if the listener sets the doit field
1541  * of the event to false.
1542  *
1543  * @param listener the listener
1544  * @exception SWTException <ul>
1545  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1546  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1547  * </ul>
1548  * @exception IllegalArgumentException <ul>
1549  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1550  * </ul>
1551  */
1552 public void addVerifyKeyListener(VerifyKeyListener listener) {
1553     checkWidget();
1554     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1555     addListener(VerifyKey, new StyledTextListener(listener));
1556 }
1557 /**
1558  * Adds a verify listener. A Verify event is sent by the widget when the widget text
1559  * is about to change. The listener can set the event text and the doit field to
1560  * change the text that is set in the widget or to force the widget to ignore the
1561  * text change.
1562  *
1563  * @param verifyListener the listener
1564  * @exception SWTException <ul>
1565  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1566  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1567  * </ul>
1568  * @exception IllegalArgumentException <ul>
1569  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1570  * </ul>
1571  */
1572 public void addVerifyListener(VerifyListener verifyListener) {
1573     checkWidget();
1574     if (verifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1575     addListener(SWT.Verify, new TypedListener(verifyListener));
1576 }
1577 /**
1578  * Adds a word movement listener. A movement event is sent when the boundary
1579  * of a word is needed. For example, this occurs during word next and word
1580  * previous actions.
1581  *
1582  * @param movementListener the listener
1583  * @exception SWTException <ul>
1584  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1585  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1586  * </ul>
1587  * @exception IllegalArgumentException <ul>
1588  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
1589  * </ul>
1590  *
1591  * @see MovementEvent
1592  * @see MovementListener
1593  * @see #removeWordMovementListener
1594  *
1595  * @since 3.3
1596  */
1597 public void addWordMovementListener(MovementListener movementListener) {
1598     checkWidget();
1599     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1600     addListener(WordNext, new StyledTextListener(movementListener));
1601     addListener(WordPrevious, new StyledTextListener(movementListener));
1602 }
1603 /**
1604  * Appends a string to the text at the end of the widget.
1605  *
1606  * @param string the string to be appended
1607  * @see #replaceTextRange(int,int,String)
1608  * @exception SWTException <ul>
1609  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1610  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1611  * </ul>
1612  */
1613 public void append(String string) {
1614     checkWidget();
1615     // SWT extension: allow null for zero length string
1616 //     if (string is null) {
1617 //         SWT.error(SWT.ERROR_NULL_ARGUMENT);
1618 //     }
1619     int lastChar = Math.max(getCharCount(), 0);
1620     replaceTextRange(lastChar, 0, string);
1621 }
1622 /**
1623  * Calculates the scroll bars
1624  */
1625 void calculateScrollBars() {
1626     ScrollBar horizontalBar = getHorizontalBar();
1627     ScrollBar verticalBar = getVerticalBar();
1628     setScrollBars(true);
1629     if (verticalBar !is null) {
1630         verticalBar.setIncrement(getVerticalIncrement());
1631     }
1632     if (horizontalBar !is null) {
1633         horizontalBar.setIncrement(getHorizontalIncrement());
1634     }
1635 }
1636 /**
1637  * Calculates the top index based on the current vertical scroll offset.
1638  * The top index is the index of the topmost fully visible line or the
1639  * topmost partially visible line if no line is fully visible.
1640  * The top index starts at 0.
1641  */
1642 void calculateTopIndex(int delta) {
1643     int oldTopIndex = topIndex;
1644     int oldTopIndexY = topIndexY;
1645     if (isFixedLineHeight()) {
1646         int verticalIncrement = getVerticalIncrement();
1647         if (verticalIncrement is 0) {
1648             return;
1649         }
1650         topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement);
1651         // Set top index to partially visible top line if no line is fully
1652         // visible but at least some of the widget client area is visible.
1653         // Fixes bug 15088.
1654         if (topIndex > 0) {
1655             if (clientAreaHeight > 0) {
1656                 int bottomPixel = getVerticalScrollOffset() + clientAreaHeight;
1657                 int fullLineTopPixel = topIndex * verticalIncrement;
1658                 int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
1659                 // set top index to partially visible line if no line fully fits in
1660                 // client area or if space is available but not used (the latter should
1661                 // never happen because we use claimBottomFreeSpace)
1662                 if (fullLineVisibleHeight < verticalIncrement) {
1663                     topIndex--;
1664                 }
1665             } else if (topIndex >= content.getLineCount()) {
1666                 topIndex = content.getLineCount() - 1;
1667             }
1668         }
1669     } else {
1670         if (delta >= 0) {
1671             delta -= topIndexY;
1672             int lineIndex = topIndex;
1673             int lineCount = content.getLineCount();
1674             while (lineIndex < lineCount) {
1675                 if (delta <= 0) break;
1676                 delta -= renderer.getLineHeight(lineIndex++);
1677             }
1678             if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1679                 topIndex = lineIndex;
1680                 topIndexY = -delta;
1681             } else {
1682                 topIndex = lineIndex - 1;
1683                 topIndexY = -renderer.getLineHeight(topIndex) - delta;
1684             }
1685         } else {
1686             delta -= topIndexY;
1687             int lineIndex = topIndex;
1688             while (lineIndex > 0) {
1689                 int lineHeight = renderer.getLineHeight(lineIndex - 1);
1690                 if (delta + lineHeight > 0) break;
1691                 delta += lineHeight;
1692                 lineIndex--;
1693             }
1694             if (lineIndex is 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1695                 topIndex = lineIndex;
1696                 topIndexY = - delta;
1697             } else {
1698                 topIndex = lineIndex - 1;
1699                 topIndexY = - renderer.getLineHeight(topIndex) - delta;
1700             }
1701         }
1702     }
1703     if (topIndex !is oldTopIndex || oldTopIndexY !is topIndexY) {
1704         renderer.calculateClientArea();
1705         setScrollBars(false);
1706     }
1707 }
1708 /**
1709  * Hides the scroll bars if widget is created in single line mode.
1710  */
1711 static int checkStyle(int style) {
1712     if ((style & SWT.SINGLE) !is 0) {
1713         style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI);
1714     } else {
1715         style |= SWT.MULTI;
1716         if ((style & SWT.WRAP) !is 0) {
1717             style &= ~SWT.H_SCROLL;
1718         }
1719     }
1720     style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND;
1721     return style;
1722 }
1723 /**
1724  * Scrolls down the text to use new space made available by a resize or by
1725  * deleted lines.
1726  */
1727 void claimBottomFreeSpace() {
1728     int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
1729     if (isFixedLineHeight()) {
1730         int lineHeight = renderer.getLineHeight();
1731         int newVerticalOffset = Math.max(0, content.getLineCount() * lineHeight - clientAreaHeight);
1732         if (newVerticalOffset < getVerticalScrollOffset()) {
1733             scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
1734         }
1735     } else {
1736         int bottomIndex = getPartialBottomIndex();
1737         int height = getLinePixel(bottomIndex + 1);
1738         if (clientAreaHeight > height) {
1739             scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
1740         }
1741     }
1742 }
1743 /**
1744  * Scrolls text to the right to use new space made available by a resize.
1745  */
1746 void claimRightFreeSpace() {
1747     int newHorizontalOffset = Math.max(0, renderer.getWidth() - (clientAreaWidth - leftMargin - rightMargin));
1748     if (newHorizontalOffset < horizontalScrollOffset) {
1749         // item is no longer drawn past the right border of the client area
1750         // align the right end of the item with the right border of the
1751         // client area (window is scrolled right).
1752         scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
1753     }
1754 }
1755 /**
1756  * Removes the widget selection.
1757  *
1758  * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
1759  */
1760 void clearSelection(bool sendEvent) {
1761     int selectionStart = selection.x;
1762     int selectionEnd = selection.y;
1763     resetSelection();
1764     // redraw old selection, if any
1765     if (selectionEnd - selectionStart > 0) {
1766         int length = content.getCharCount();
1767         // called internally to remove selection after text is removed
1768         // therefore make sure redraw range is valid.
1769         int redrawStart = Math.min(selectionStart, length);
1770         int redrawEnd = Math.min(selectionEnd, length);
1771         if (redrawEnd - redrawStart > 0 && selectedTextValid) {
1772             internalRedrawRange(redrawStart, redrawEnd - redrawStart);
1773         }
1774         if (sendEvent) {
1775             sendSelectionEvent();
1776         }
1777     }
1778     selectedTextValid = true;
1779 }
1780 public override Point computeSize (int wHint, int hHint, bool changed) {
1781     checkWidget();
1782     int lineCount = (getStyle() & SWT.SINGLE) !is 0 ? 1 : content.getLineCount();
1783     int width = 0;
1784     int height = 0;
1785     if (wHint is SWT.DEFAULT || hHint is SWT.DEFAULT) {
1786         Display display = getDisplay();
1787         int maxHeight = display.getClientArea().height;
1788         for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
1789             TextLayout layout = renderer.getTextLayout(lineIndex);
1790             int wrapWidth = layout.getWidth();
1791             if (wordWrap) layout.setWidth(wHint is 0 ? 1 : wHint);
1792             Rectangle rect = layout.getBounds();
1793             height += rect.height;
1794             width = Math.max(width, rect.width);
1795             layout.setWidth(wrapWidth);
1796             renderer.disposeTextLayout(layout);
1797             if (isFixedLineHeight() && height > maxHeight) break;
1798         }
1799         if (isFixedLineHeight()) {
1800             height = lineCount * renderer.getLineHeight();
1801         }
1802     }
1803     // Use default values if no text is defined.
1804     if (width is 0) width = DEFAULT_WIDTH;
1805     if (height is 0) height = DEFAULT_HEIGHT;
1806     if (wHint !is SWT.DEFAULT) width = wHint;
1807     if (hHint !is SWT.DEFAULT) height = hHint;
1808     int wTrim = leftMargin + rightMargin + getCaretWidth();
1809     int hTrim = topMargin + bottomMargin;
1810     Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
1811     return new Point (rect.width, rect.height);
1812 }
1813 /**
1814  * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
1815  * <p>
1816  * The text will be put on the clipboard in plain text format and RTF format.
1817  * The <code>DND.CLIPBOARD</code> clipboard is used for data that is
1818  * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
1819  * by menu action.
1820  * </p>
1821  *
1822  * @exception SWTException <ul>
1823  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1824  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1825  * </ul>
1826  */
1827 public void copy() {
1828     checkWidget();
1829     copy(DND.CLIPBOARD);
1830 }
1831 /**
1832  * Copies the selected text to the specified clipboard.  The text will be put in the
1833  * clipboard in plain text format and RTF format.
1834  * <p>
1835  * The clipboardType is  one of the clipboard constants defined in class
1836  * <code>DND</code>.  The <code>DND.CLIPBOARD</code>  clipboard is
1837  * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
1838  * or by menu action.  The <code>DND.SELECTION_CLIPBOARD</code>
1839  * clipboard is used for data that is transferred by selecting text and pasting
1840  * with the middle mouse button.
1841  * </p>
1842  *
1843  * @param clipboardType indicates the type of clipboard
1844  *
1845  * @exception SWTException <ul>
1846  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1847  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1848  * </ul>
1849  *
1850  * @since 3.1
1851  */
1852 public void copy(int clipboardType) {
1853     checkWidget();
1854     if (clipboardType !is DND.CLIPBOARD && clipboardType !is DND.SELECTION_CLIPBOARD) return;
1855     int length = selection.y - selection.x;
1856     if (length > 0) {
1857         try {
1858             setClipboardContent(selection.x, length, clipboardType);
1859         } catch (SWTError error) {
1860             // Copy to clipboard failed. This happens when another application
1861             // is accessing the clipboard while we copy. Ignore the error.
1862             // Fixes 1GDQAVN
1863             // Rethrow all other errors. Fixes bug 17578.
1864             if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
1865                 throw error;
1866             }
1867         }
1868     }
1869 }
1870 /**
1871  * Returns the alignment of the widget.
1872  *
1873  * @return the alignment
1874  *
1875  * @exception SWTException <ul>
1876  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1877  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1878  * </ul>
1879  *
1880  * @see #getLineAlignment(int)
1881  *
1882  * @since 3.2
1883  */
1884 public int getAlignment() {
1885     checkWidget();
1886     return alignment;
1887 }
1888 int getAvailableHeightAbove(int height) {
1889     int maxHeight = verticalScrollOffset;
1890     if (maxHeight is -1) {
1891         int lineIndex = topIndex - 1;
1892         maxHeight = -topIndexY;
1893         if (topIndexY > 0) {
1894             maxHeight += renderer.getLineHeight(lineIndex--);
1895         }
1896         while (height > maxHeight && lineIndex >= 0) {
1897             maxHeight += renderer.getLineHeight(lineIndex--);
1898         }
1899     }
1900     return Math.min(height, maxHeight);
1901 }
1902 int getAvailableHeightBellow(int height) {
1903     int partialBottomIndex = getPartialBottomIndex();
1904     int topY = getLinePixel(partialBottomIndex);
1905     int lineHeight = renderer.getLineHeight(partialBottomIndex);
1906     int availableHeight = 0;
1907     int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
1908     if (topY + lineHeight > clientAreaHeight) {
1909         availableHeight = lineHeight - (clientAreaHeight - topY);
1910     }
1911     int lineIndex = partialBottomIndex + 1;
1912     int lineCount = content.getLineCount();
1913     while (height > availableHeight && lineIndex < lineCount) {
1914         availableHeight += renderer.getLineHeight(lineIndex++);
1915     }
1916     return Math.min(height, availableHeight);
1917 }
1918 /**
1919  * Returns a string that uses only the line delimiter specified by the
1920  * StyledTextContent implementation.
1921  * <p>
1922  * Returns only the first line if the widget has the SWT.SINGLE style.
1923  * </p>
1924  *
1925  * @param text the text that may have line delimiters that don't
1926  *  match the model line delimiter. Possible line delimiters
1927  *  are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
1928  * @return the converted text that only uses the line delimiter
1929  *  specified by the model. Returns only the first line if the widget
1930  *  has the SWT.SINGLE style.
1931  */
1932 String getModelDelimitedText(String text) {
1933     int length = cast(int)/*64bit*/text.length;
1934     if (length is 0) {
1935         return text;
1936     }
1937     int crIndex = 0;
1938     int lfIndex = 0;
1939     int i = 0;
1940     StringBuffer convertedText = new StringBuffer(length);
1941     String delimiter = getLineDelimiter();
1942     while (i < length) {
1943         if (crIndex !is -1) {
1944             crIndex = text.indexOf (SWT.CR, i);
1945         }
1946         if (lfIndex !is -1) {
1947             lfIndex = text.indexOf (SWT.LF, i);
1948         }
1949         if (lfIndex is -1 && crIndex is -1) {   // no more line breaks?
1950             break;
1951         } else if ((crIndex < lfIndex && crIndex !is -1) || lfIndex is -1) {
1952             convertedText.append(text.substring(i, crIndex));
1953             if (lfIndex is crIndex + 1) {       // CR/LF combination?
1954                 i = lfIndex + 1;
1955             } else {
1956                 i = crIndex + 1;
1957             }
1958         } else {                                    // LF occurs before CR!
1959             convertedText.append(text.substring(i, lfIndex));
1960             i = lfIndex + 1;
1961         }
1962         if (isSingleLine()) {
1963             break;
1964         }
1965         convertedText.append(delimiter);
1966     }
1967     // copy remaining text if any and if not in single line mode or no
1968     // text copied thus far (because there only is one line)
1969     if (i < length && (!isSingleLine() || convertedText.length() is 0)) {
1970         convertedText.append(text.substring(i));
1971     }
1972     return convertedText.toString();
1973 }
1974 bool checkDragDetect(Event event) {
1975     if (!isListening(SWT.DragDetect)) return false;
1976     if (IS_MOTIF) {
1977         if (event.button !is 2) return false;
1978     } else {
1979         if (event.button !is 1) return false;
1980     }
1981     if (selection.x is selection.y) return false;
1982     int offset = getOffsetAtPoint(event.x, event.y, null, true);
1983     if (selection.x <= offset && offset < selection.y) {
1984         return dragDetect(event);
1985     }
1986     return false;
1987 }
1988 /**
1989  * Creates default key bindings.
1990  */
1991 void createKeyBindings() {
1992     int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT;
1993     int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
1994 
1995     // Navigation
1996     setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
1997     setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
1998     if (IS_CARBON) {
1999         setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START);
2000         setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END);
2001         setKeyBinding(SWT.HOME, ST.TEXT_START);
2002         setKeyBinding(SWT.END, ST.TEXT_END);
2003         setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START);
2004         setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END);
2005         setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT);
2006         setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS);
2007     } else {
2008         setKeyBinding(SWT.HOME, ST.LINE_START);
2009         setKeyBinding(SWT.END, ST.LINE_END);
2010         setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
2011         setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
2012         setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
2013         setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
2014     }
2015     setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
2016     setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
2017     setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START);
2018     setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END);
2019     setKeyBinding(nextKey, ST.COLUMN_NEXT);
2020     setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
2021 
2022     // Selection
2023     setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
2024     setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
2025     if (IS_CARBON) {
2026         setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START);
2027         setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END);
2028         setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START);
2029         setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END);
2030         setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2031         setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2032         setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT);
2033         setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS);
2034     } else  {
2035         setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
2036         setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
2037         setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2038         setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2039         setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
2040         setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
2041     }
2042     setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
2043     setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
2044     setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START);
2045     setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END);
2046     setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT);
2047     setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
2048 
2049     // Modification
2050     // Cut, Copy, Paste
2051     setKeyBinding('X' | SWT.MOD1, ST.CUT);
2052     setKeyBinding('C' | SWT.MOD1, ST.COPY);
2053     setKeyBinding('V' | SWT.MOD1, ST.PASTE);
2054     if (IS_CARBON) {
2055         setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT);
2056         setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS);
2057         setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT);
2058     } else {
2059         // Cut, Copy, Paste Wordstar style
2060         setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT);
2061         setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY);
2062         setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE);
2063     }
2064     setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS);
2065     setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
2066     setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
2067     setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS);
2068     setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT);
2069 
2070     // Miscellaneous
2071     setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
2072 }
2073 /**
2074  * Create the bitmaps to use for the caret in bidi mode.  This
2075  * method only needs to be called upon widget creation and when the
2076  * font changes (the caret bitmap height needs to match font height).
2077  */
2078 void createCaretBitmaps() {
2079     int caretWidth = BIDI_CARET_WIDTH;
2080     Display display = getDisplay();
2081     if (leftCaretBitmap !is null) {
2082         if (defaultCaret !is null && leftCaretBitmap==/*eq*/defaultCaret.getImage()) {
2083             defaultCaret.setImage(null);
2084         }
2085         leftCaretBitmap.dispose();
2086     }
2087     int lineHeight = renderer.getLineHeight();
2088     leftCaretBitmap = new Image(display, caretWidth, lineHeight);
2089     GC gc = new GC (leftCaretBitmap);
2090     gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2091     gc.fillRectangle(0, 0, caretWidth, lineHeight);
2092     gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2093     gc.drawLine(0,0,0,lineHeight);
2094     gc.drawLine(0,0,caretWidth-1,0);
2095     gc.drawLine(0,1,1,1);
2096     gc.dispose();
2097 
2098     if (rightCaretBitmap !is null) {
2099         if (defaultCaret !is null && rightCaretBitmap==/*eq*/defaultCaret.getImage()) {
2100             defaultCaret.setImage(null);
2101         }
2102         rightCaretBitmap.dispose();
2103     }
2104     rightCaretBitmap = new Image(display, caretWidth, lineHeight);
2105     gc = new GC (rightCaretBitmap);
2106     gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2107     gc.fillRectangle(0, 0, caretWidth, lineHeight);
2108     gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2109     gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight);
2110     gc.drawLine(0,0,caretWidth-1,0);
2111     gc.drawLine(caretWidth-1,1,1,1);
2112     gc.dispose();
2113 }
2114 /**
2115  * Moves the selected text to the clipboard.  The text will be put in the
2116  * clipboard in plain text format and RTF format.
2117  *
2118  * @exception SWTException <ul>
2119  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2120  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2121  * </ul>
2122  */
2123 public void cut(){
2124     checkWidget();
2125     int length = selection.y - selection.x;
2126     if (length > 0) {
2127         try {
2128             setClipboardContent(selection.x, length, DND.CLIPBOARD);
2129         } catch (SWTError error) {
2130             // Copy to clipboard failed. This happens when another application
2131             // is accessing the clipboard while we copy. Ignore the error.
2132             // Fixes 1GDQAVN
2133             // Rethrow all other errors. Fixes bug 17578.
2134             if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
2135                 throw error;
2136             }
2137             // Abort cut operation if copy to clipboard fails.
2138             // Fixes bug 21030.
2139             return;
2140         }
2141         doDelete();
2142     }
2143 }
2144 /**
2145  * A mouse move event has occurred.  See if we should start autoscrolling.  If
2146  * the move position is outside of the client area, initiate autoscrolling.
2147  * Otherwise, we've moved back into the widget so end autoscrolling.
2148  */
2149 void doAutoScroll(Event event) {
2150     if (event.y > clientAreaHeight) {
2151         doAutoScroll(SWT.DOWN, event.y - clientAreaHeight);
2152     } else if (event.y < 0) {
2153         doAutoScroll(SWT.UP, -event.y);
2154     } else if (event.x < leftMargin && !wordWrap) {
2155         doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
2156     } else if (event.x > clientAreaWidth - leftMargin - rightMargin && !wordWrap) {
2157         doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - leftMargin - rightMargin));
2158     } else {
2159         endAutoScroll();
2160     }
2161 }
2162 /**
2163  * Initiates autoscrolling.
2164  *
2165  * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
2166  */
2167 void doAutoScroll(int direction, int distance) {
2168     autoScrollDistance = distance;
2169     // If we're already autoscrolling in the given direction do nothing
2170     if (autoScrollDirection is direction) {
2171         return;
2172     }
2173 
2174     Runnable timer = null;
2175     Display disp = getDisplay();
2176     // Set a timer that will simulate the user pressing and holding
2177     // down a cursor key (i.e., arrowUp, arrowDown).
2178     if (direction is SWT.UP) {
2179         timer = new class(disp) Runnable {
2180             Display display;
2181             this( Display d ){ this.display = d; }
2182             public void run() {
2183                 if (autoScrollDirection is SWT.UP) {
2184                     doSelectionPageUp(autoScrollDistance);
2185                     display.timerExec(V_SCROLL_RATE, this);
2186                 }
2187             }
2188         };
2189         autoScrollDirection = direction;
2190         display.timerExec(V_SCROLL_RATE, timer);
2191     } else if (direction is SWT.DOWN) {
2192         timer = new class(disp) Runnable {
2193             Display display;
2194             this( Display d ){ this.display = d; }
2195             public void run() {
2196                 if (autoScrollDirection is SWT.DOWN) {
2197                     doSelectionPageDown(autoScrollDistance);
2198                     display.timerExec(V_SCROLL_RATE, this);
2199                 }
2200             }
2201         };
2202         autoScrollDirection = direction;
2203         display.timerExec(V_SCROLL_RATE, timer);
2204     } else if (direction is ST.COLUMN_NEXT) {
2205         timer = new class(disp) Runnable {
2206             Display display;
2207             this( Display d ){ this.display = d; }
2208             public void run() {
2209                 if (autoScrollDirection is ST.COLUMN_NEXT) {
2210                     doVisualNext();
2211                     setMouseWordSelectionAnchor();
2212                     doMouseSelection();
2213                     display.timerExec(H_SCROLL_RATE, this);
2214                 }
2215             }
2216         };
2217         autoScrollDirection = direction;
2218         display.timerExec(H_SCROLL_RATE, timer);
2219     } else if (direction is ST.COLUMN_PREVIOUS) {
2220         timer = new class(disp) Runnable {
2221             Display display;
2222             this( Display d ){ this.display = d; }
2223             public void run() {
2224                 if (autoScrollDirection is ST.COLUMN_PREVIOUS) {
2225                     doVisualPrevious();
2226                     setMouseWordSelectionAnchor();
2227                     doMouseSelection();
2228                     display.timerExec(H_SCROLL_RATE, this);
2229                 }
2230             }
2231         };
2232         autoScrollDirection = direction;
2233         display.timerExec(H_SCROLL_RATE, timer);
2234     }
2235 }
2236 /**
2237  * Deletes the previous character. Delete the selected text if any.
2238  * Move the caret in front of the deleted text.
2239  */
2240 void doBackspace() {
2241     Event event = new Event();
2242     event.text = "";
2243     if (selection.x !is selection.y) {
2244         event.start = selection.x;
2245         event.end = selection.y;
2246         sendKeyEvent(event);
2247     } else if (caretOffset > 0) {
2248         int lineIndex = content.getLineAtOffset(caretOffset);
2249         int lineOffset = content.getOffsetAtLine(lineIndex);
2250         if (caretOffset is lineOffset) {
2251             // DWT: on line start, delete line break
2252             lineOffset = content.getOffsetAtLine(lineIndex - 1);
2253             event.start = lineOffset
2254                 + cast(int)/*64bit*/content.getLine(lineIndex - 1).length;
2255             event.end = caretOffset;
2256         } else {
2257             TextLayout layout = renderer.getTextLayout(lineIndex);
2258             int start = layout.getPreviousOffset(caretOffset - lineOffset, SWT.MOVEMENT_CHAR);
2259             renderer.disposeTextLayout(layout);
2260             event.start = start + lineOffset;
2261             event.end = caretOffset;
2262         }
2263         sendKeyEvent(event);
2264     }
2265 }
2266 /**
2267  * Replaces the selection with the character or insert the character at the
2268  * current caret position if no selection exists.
2269  * <p>
2270  * If a carriage return was typed replace it with the line break character
2271  * used by the widget on this platform.
2272  * </p>
2273  *
2274  * @param key the character typed by the user
2275  */
2276 void doContent(dchar key) {
2277     Event event = new Event();
2278     event.start = selection.x;
2279     event.end = selection.y;
2280     // replace a CR line break with the widget line break
2281     // CR does not make sense on Windows since most (all?) applications
2282     // don't recognize CR as a line break.
2283     if (key is SWT.CR || key is SWT.LF) {
2284         if (!isSingleLine()) {
2285             event.text = getLineDelimiter();
2286         }
2287     } else if (selection.x is selection.y && overwrite && key !is TAB) {
2288         // no selection and overwrite mode is on and the typed key is not a
2289         // tab character (tabs are always inserted without overwriting)?
2290         int lineIndex = content.getLineAtOffset(event.end);
2291         int lineOffset = content.getOffsetAtLine(lineIndex);
2292         String line = content.getLine(lineIndex);
2293         // replace character at caret offset if the caret is not at the
2294         // end of the line
2295         if (event.end < lineOffset + line.length) {
2296             event.end += line.UTF8strideAt(event.end - lineOffset);
2297         }
2298         event.text = dcharToString( key );
2299     } else {
2300         event.text = dcharToString( key );
2301     }
2302     if (event.text !is null) {
2303         if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
2304             return;
2305         }
2306         sendKeyEvent(event);
2307     }
2308 }
2309 /**
2310  * Moves the caret after the last character of the widget content.
2311  */
2312 void doContentEnd() {
2313     // place caret at end of first line if receiver is in single
2314     // line mode. fixes 4820.
2315     if (isSingleLine()) {
2316         doLineEnd();
2317     } else {
2318         int length = content.getCharCount();
2319         if (caretOffset < length) {
2320             caretOffset = length;
2321             showCaret();
2322         }
2323     }
2324 }
2325 /**
2326  * Moves the caret in front of the first character of the widget content.
2327  */
2328 void doContentStart() {
2329     if (caretOffset > 0) {
2330         caretOffset = 0;
2331         showCaret();
2332     }
2333 }
2334 /**
2335  * Moves the caret to the start of the selection if a selection exists.
2336  * Otherwise, if no selection exists move the cursor according to the
2337  * cursor selection rules.
2338  *
2339  * @see #doSelectionCursorPrevious
2340  */
2341 void doCursorPrevious() {
2342     if (selection.y - selection.x > 0) {
2343         caretOffset = selection.x;
2344         caretAlignment = OFFSET_LEADING;
2345         showCaret();
2346     } else {
2347         doSelectionCursorPrevious();
2348     }
2349 }
2350 /**
2351  * Moves the caret to the end of the selection if a selection exists.
2352  * Otherwise, if no selection exists move the cursor according to the
2353  * cursor selection rules.
2354  *
2355  * @see #doSelectionCursorNext
2356  */
2357 void doCursorNext() {
2358     if (selection.y - selection.x > 0) {
2359         caretOffset = selection.y;
2360         caretAlignment = PREVIOUS_OFFSET_TRAILING;
2361         showCaret();
2362     } else {
2363         doSelectionCursorNext();
2364     }
2365 }
2366 /**
2367  * Deletes the next character. Delete the selected text if any.
2368  */
2369 void doDelete() {
2370     Event event = new Event();
2371     event.text = "";
2372     if (selection.x !is selection.y) {
2373         event.start = selection.x;
2374         event.end = selection.y;
2375         sendKeyEvent(event);
2376     } else if (caretOffset < content.getCharCount()) {
2377         int line = content.getLineAtOffset(caretOffset);
2378         int lineOffset = content.getOffsetAtLine(line);
2379         auto lineLength = content.getLine(line).length;
2380         if (caretOffset is lineOffset + lineLength) {
2381             event.start = caretOffset;
2382             event.end = content.getOffsetAtLine(line + 1);
2383         } else {
2384             event.start = caretOffset;
2385             event.end = getClusterNext(caretOffset, line);
2386         }
2387         sendKeyEvent(event);
2388     }
2389 }
2390 /**
2391  * Deletes the next word.
2392  */
2393 void doDeleteWordNext() {
2394     if (selection.x !is selection.y) {
2395         // if a selection exists, treat the as if
2396         // only the delete key was pressed
2397         doDelete();
2398     } else {
2399         Event event = new Event();
2400         event.text = "";
2401         event.start = caretOffset;
2402         event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
2403         sendKeyEvent(event);
2404     }
2405 }
2406 /**
2407  * Deletes the previous word.
2408  */
2409 void doDeleteWordPrevious() {
2410     if (selection.x !is selection.y) {
2411         // if a selection exists, treat as if
2412         // only the backspace key was pressed
2413         doBackspace();
2414     } else {
2415         Event event = new Event();
2416         event.text = "";
2417         event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
2418         event.end = caretOffset;
2419         sendKeyEvent(event);
2420     }
2421 }
2422 /**
2423  * Moves the caret one line down and to the same character offset relative
2424  * to the beginning of the line. Move the caret to the end of the new line
2425  * if the new line is shorter than the character offset.
2426  */
2427 void doLineDown(bool select) {
2428     int caretLine = getCaretLine();
2429     int lineCount = content.getLineCount();
2430     int y = 0;
2431     bool lastLine = false;
2432     if (wordWrap) {
2433         int lineOffset = content.getOffsetAtLine(caretLine);
2434         int offsetInLine = caretOffset - lineOffset;
2435         TextLayout layout = renderer.getTextLayout(caretLine);
2436         int lineIndex = getVisualLineIndex(layout, offsetInLine);
2437         int layoutLineCount = layout.getLineCount();
2438         if (lineIndex is layoutLineCount - 1) {
2439             lastLine = caretLine is lineCount - 1;
2440             caretLine++;
2441         } else {
2442             y = layout.getLineBounds(lineIndex + 1).y;
2443         }
2444         renderer.disposeTextLayout(layout);
2445     } else {
2446         lastLine = caretLine is lineCount - 1;
2447         caretLine++;
2448     }
2449     if (lastLine) {
2450         if (select) caretOffset = content.getCharCount();
2451     } else {
2452         caretOffset = getOffsetAtPoint(columnX, y, caretLine);
2453     }
2454     int oldColumnX = columnX;
2455     int oldHScrollOffset = horizontalScrollOffset;
2456     if (select) {
2457         setMouseWordSelectionAnchor();
2458         // select first and then scroll to reduce flash when key
2459         // repeat scrolls lots of lines
2460         doSelection(ST.COLUMN_NEXT);
2461     }
2462     showCaret();
2463     int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2464     columnX = oldColumnX + hScrollChange;
2465 }
2466 /**
2467  * Moves the caret to the end of the line.
2468  */
2469 void doLineEnd() {
2470     int caretLine = getCaretLine();
2471     int lineOffset = content.getOffsetAtLine(caretLine);
2472     int lineEndOffset;
2473     if (wordWrap) {
2474         TextLayout layout = renderer.getTextLayout(caretLine);
2475         int offsetInLine = caretOffset - lineOffset;
2476         int lineIndex = getVisualLineIndex(layout, offsetInLine);
2477         int[] offsets = layout.getLineOffsets();
2478         lineEndOffset = lineOffset + offsets[lineIndex + 1];
2479         renderer.disposeTextLayout(layout);
2480     } else {
2481         auto lineLength = content.getLine(caretLine).length;
2482         lineEndOffset = cast(int)/*64bit*/(lineOffset + lineLength);
2483     }
2484     if (caretOffset < lineEndOffset) {
2485         caretOffset = lineEndOffset;
2486         caretAlignment = PREVIOUS_OFFSET_TRAILING;
2487         showCaret();
2488     }
2489 }
2490 /**
2491  * Moves the caret to the beginning of the line.
2492  */
2493 void doLineStart() {
2494     int caretLine = getCaretLine();
2495     int lineOffset = content.getOffsetAtLine(caretLine);
2496     if (wordWrap) {
2497         TextLayout layout = renderer.getTextLayout(caretLine);
2498         int offsetInLine = caretOffset - lineOffset;
2499         int lineIndex = getVisualLineIndex(layout, offsetInLine);
2500         int[] offsets = layout.getLineOffsets();
2501         lineOffset += offsets[lineIndex];
2502         renderer.disposeTextLayout(layout);
2503     }
2504     if (caretOffset > lineOffset) {
2505         caretOffset = lineOffset;
2506         caretAlignment = OFFSET_LEADING;
2507         showCaret();
2508     }
2509 }
2510 /**
2511  * Moves the caret one line up and to the same character offset relative
2512  * to the beginning of the line. Move the caret to the end of the new line
2513  * if the new line is shorter than the character offset.
2514  */
2515 void doLineUp(bool select) {
2516     int caretLine = getCaretLine(), y = 0;
2517     bool firstLine = false;
2518     if (wordWrap) {
2519         int lineOffset = content.getOffsetAtLine(caretLine);
2520         int offsetInLine = caretOffset - lineOffset;
2521         TextLayout layout = renderer.getTextLayout(caretLine);
2522         int lineIndex = getVisualLineIndex(layout, offsetInLine);
2523         if (lineIndex is 0) {
2524             firstLine = caretLine is 0;
2525             if (!firstLine) {
2526                 caretLine--;
2527                 y = renderer.getLineHeight(caretLine) - 1;
2528             }
2529         } else {
2530             y = layout.getLineBounds(lineIndex - 1).y;
2531         }
2532         renderer.disposeTextLayout(layout);
2533     } else {
2534         firstLine = caretLine is 0;
2535         caretLine--;
2536     }
2537     if (firstLine) {
2538         if (select) caretOffset = 0;
2539     } else {
2540         caretOffset = getOffsetAtPoint(columnX, y, caretLine);
2541     }
2542     int oldColumnX = columnX;
2543     int oldHScrollOffset = horizontalScrollOffset;
2544     if (select) setMouseWordSelectionAnchor();
2545     showCaret();
2546     if (select) doSelection(ST.COLUMN_PREVIOUS);
2547     int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2548     columnX = oldColumnX + hScrollChange;
2549 }
2550 /**
2551  * Moves the caret to the specified location.
2552  *
2553  * @param x x location of the new caret position
2554  * @param y y location of the new caret position
2555  * @param select the location change is a selection operation.
2556  *  include the line delimiter in the selection
2557  */
2558 void doMouseLocationChange(int x, int y, bool select) {
2559     int line = getLineIndex(y);
2560 
2561     updateCaretDirection = true;
2562     // allow caret to be placed below first line only if receiver is
2563     // not in single line mode. fixes 4820.
2564     if (line < 0 || (isSingleLine() && line > 0)) {
2565         return;
2566     }
2567     int oldCaretAlignment = caretAlignment;
2568     int newCaretOffset = getOffsetAtPoint(x, y);
2569 
2570     if (doubleClickEnabled && clickCount > 1) {
2571         newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
2572     }
2573 
2574     int newCaretLine = content.getLineAtOffset(newCaretOffset);
2575 
2576     // Is the mouse within the left client area border or on
2577     // a different line? If not the autoscroll selection
2578     // could be incorrectly reset. Fixes 1GKM3XS
2579     if (0 <= y && y < clientAreaHeight &&
2580         (0 <= x && x < clientAreaWidth || wordWrap ||
2581         newCaretLine !is content.getLineAtOffset(caretOffset))) {
2582         if (newCaretOffset !is caretOffset || caretAlignment !is oldCaretAlignment) {
2583             caretOffset = newCaretOffset;
2584             if (select) doMouseSelection();
2585             showCaret();
2586         }
2587     }
2588     if (!select) {
2589         caretOffset = newCaretOffset;
2590         clearSelection(true);
2591     }
2592 }
2593 /**
2594  * Updates the selection based on the caret position
2595  */
2596 void doMouseSelection() {
2597     if (caretOffset <= selection.x ||
2598         (caretOffset > selection.x &&
2599          caretOffset < selection.y && selectionAnchor is selection.x)) {
2600         doSelection(ST.COLUMN_PREVIOUS);
2601     } else {
2602         doSelection(ST.COLUMN_NEXT);
2603     }
2604 }
2605 /**
2606  * Returns the offset of the word at the specified offset.
2607  * If the current selection extends from high index to low index
2608  * (i.e., right to left, or caret is at left border of selection on
2609  * non-bidi platforms) the start offset of the word preceding the
2610  * selection is returned. If the current selection extends from
2611  * low index to high index the end offset of the word following
2612  * the selection is returned.
2613  *
2614  * @param x mouse x location
2615  * @param newCaretOffset caret offset of the mouse cursor location
2616  * @param line line index of the mouse cursor location
2617  */
2618 int doMouseWordSelect(int x, int newCaretOffset, int line) {
2619     // flip selection anchor based on word selection direction from
2620     // base double click. Always do this here (and don't rely on doAutoScroll)
2621     // because auto scroll only does not cover all possible mouse selections
2622     // (e.g., mouse x < 0 && mouse y > caret line y)
2623     if (newCaretOffset < selectionAnchor && selectionAnchor is selection.x) {
2624         selectionAnchor = doubleClickSelection.y;
2625     } else if (newCaretOffset > selectionAnchor && selectionAnchor is selection.y) {
2626         selectionAnchor = doubleClickSelection.x;
2627     }
2628     if (0 <= x && x < clientAreaWidth) {
2629         bool wordSelect = (clickCount & 1) is 0;
2630         if (caretOffset is selection.x) {
2631             if (wordSelect) {
2632                 newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START);
2633             } else {
2634                 newCaretOffset = content.getOffsetAtLine(line);
2635             }
2636         } else {
2637             if (wordSelect) {
2638                 newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END);
2639             } else {
2640                 int lineEnd = content.getCharCount();
2641                 if (line + 1 < content.getLineCount()) {
2642                     lineEnd = content.getOffsetAtLine(line + 1);
2643                 }
2644                 newCaretOffset = lineEnd;
2645             }
2646         }
2647     }
2648     return newCaretOffset;
2649 }
2650 /**
2651  * Scrolls one page down so that the last line (truncated or whole)
2652  * of the current page becomes the fully visible top line.
2653  * <p>
2654  * The caret is scrolled the same number of lines so that its location
2655  * relative to the top line remains the same. The exception is the end
2656  * of the text where a full page scroll is not possible. In this case
2657  * the caret is moved after the last character.
2658  * </p>
2659  *
2660  * @param select whether or not to select the page
2661  */
2662 void doPageDown(bool select, int height) {
2663     if (isSingleLine()) return;
2664     int oldColumnX = columnX;
2665     int oldHScrollOffset = horizontalScrollOffset;
2666     if (isFixedLineHeight()) {
2667         int lineCount = content.getLineCount();
2668         int caretLine = getCaretLine();
2669         if (caretLine < lineCount - 1) {
2670             int lineHeight = renderer.getLineHeight();
2671             int lines = (height is -1 ? clientAreaHeight : height) / lineHeight;
2672             int scrollLines = Math.min(lineCount - caretLine - 1, lines);
2673             // ensure that scrollLines never gets negative and at least one
2674             // line is scrolled. fixes bug 5602.
2675             scrollLines = Math.max(1, scrollLines);
2676             caretOffset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines));
2677             if (select) {
2678                 doSelection(ST.COLUMN_NEXT);
2679             }
2680             // scroll one page down or to the bottom
2681             int verticalMaximum = lineCount * getVerticalIncrement();
2682             int pageSize = clientAreaHeight;
2683             int verticalScrollOffset = getVerticalScrollOffset();
2684             int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
2685             if (scrollOffset + pageSize > verticalMaximum) {
2686                 scrollOffset = verticalMaximum - pageSize;
2687             }
2688             if (scrollOffset > verticalScrollOffset) {
2689                 scrollVertical(scrollOffset - verticalScrollOffset, true);
2690             }
2691         }
2692     } else {
2693         int lineCount = content.getLineCount();
2694         int caretLine = getCaretLine();
2695         int lineIndex, lineHeight;
2696         if (height is -1) {
2697             lineIndex = getPartialBottomIndex();
2698             int topY = getLinePixel(lineIndex);
2699             lineHeight = renderer.getLineHeight(lineIndex);
2700             height = topY;
2701             if (topY + lineHeight <= clientAreaHeight) {
2702                 height += lineHeight;
2703             } else {
2704                 if (wordWrap) {
2705                     TextLayout layout = renderer.getTextLayout(lineIndex);
2706                     int y = clientAreaHeight - topY;
2707                     for (int i = 0; i < layout.getLineCount(); i++) {
2708                         Rectangle bounds = layout.getLineBounds(i);
2709                         if (bounds.contains(bounds.x, y)) {
2710                             height += bounds.y;
2711                             break;
2712                         }
2713                     }
2714                     renderer.disposeTextLayout(layout);
2715                 }
2716             }
2717         } else {
2718             lineIndex = getLineIndex(height);
2719             int topLineY = getLinePixel(lineIndex);
2720             if (wordWrap) {
2721                 TextLayout layout = renderer.getTextLayout(lineIndex);
2722                 int y = height - topLineY;
2723                 for (int i = 0; i < layout.getLineCount(); i++) {
2724                     Rectangle bounds = layout.getLineBounds(i);
2725                     if (bounds.contains(bounds.x, y)) {
2726                         height = topLineY + bounds.y + bounds.height;
2727                         break;
2728                     }
2729                 }
2730                 renderer.disposeTextLayout(layout);
2731             } else {
2732                 height = topLineY + renderer.getLineHeight(lineIndex);
2733             }
2734         }
2735         int caretHeight = height;
2736         if (wordWrap) {
2737             TextLayout layout = renderer.getTextLayout(caretLine);
2738             int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
2739             lineIndex = getVisualLineIndex(layout, offsetInLine);
2740             caretHeight += layout.getLineBounds(lineIndex).y;
2741             renderer.disposeTextLayout(layout);
2742         }
2743         lineIndex = caretLine;
2744         lineHeight = renderer.getLineHeight(lineIndex);
2745         while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
2746             caretHeight -= lineHeight;
2747             lineHeight = renderer.getLineHeight(++lineIndex);
2748         }
2749         caretOffset = getOffsetAtPoint(columnX, caretHeight, lineIndex);
2750         if (select) doSelection(ST.COLUMN_NEXT);
2751         height = getAvailableHeightBellow(height);
2752         scrollVertical(height, true);
2753         if (height is 0) setCaretLocation();
2754     }
2755     showCaret();
2756     int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2757     columnX = oldColumnX + hScrollChange;
2758 }
2759 /**
2760  * Moves the cursor to the end of the last fully visible line.
2761  */
2762 void doPageEnd() {
2763     // go to end of line if in single line mode. fixes 5673
2764     if (isSingleLine()) {
2765         doLineEnd();
2766     } else {
2767         int bottomOffset;
2768         if (wordWrap) {
2769             int lineIndex = getPartialBottomIndex();
2770             TextLayout layout = renderer.getTextLayout(lineIndex);
2771             int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);
2772             int index = layout.getLineCount() - 1;
2773             while (index >= 0) {
2774                 Rectangle bounds = layout.getLineBounds(index);
2775                 if (y >= bounds.y + bounds.height) break;
2776                 index--;
2777             }
2778             if (index is -1 && lineIndex > 0) {
2779                 bottomOffset = content.getOffsetAtLine(lineIndex - 1)
2780                     + cast(int)/*64bit*/content.getLine(lineIndex - 1).length;
2781             } else {
2782                 bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, getPreviousCharOffset(lineIndex, layout.getLineOffsets()[index + 1]));
2783             }
2784             renderer.disposeTextLayout(layout);
2785         } else {
2786             int lineIndex = getBottomIndex();
2787             bottomOffset = content.getOffsetAtLine(lineIndex)
2788                 + cast(int)/*64bit*/content.getLine(lineIndex).length;
2789         }
2790         if (caretOffset < bottomOffset) {
2791             caretOffset = bottomOffset;
2792             caretAlignment = OFFSET_LEADING;
2793             showCaret();
2794         }
2795     }
2796 }
2797 /**
2798  * Moves the cursor to the beginning of the first fully visible line.
2799  */
2800 void doPageStart() {
2801     int topOffset;
2802     if (wordWrap) {
2803         int y, lineIndex;
2804         if (topIndexY > 0) {
2805             lineIndex = topIndex - 1;
2806             y = renderer.getLineHeight(lineIndex) - topIndexY;
2807         } else {
2808             lineIndex = topIndex;
2809             y = -topIndexY;
2810         }
2811         TextLayout layout = renderer.getTextLayout(lineIndex);
2812         int index = 0;
2813         int lineCount = layout.getLineCount();
2814         while (index < lineCount) {
2815             Rectangle bounds = layout.getLineBounds(index);
2816             if (y <= bounds.y) break;
2817             index++;
2818         }
2819         if (index is lineCount) {
2820             topOffset = content.getOffsetAtLine(lineIndex + 1);
2821         } else {
2822             topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
2823         }
2824         renderer.disposeTextLayout(layout);
2825     } else {
2826         topOffset = content.getOffsetAtLine(topIndex);
2827     }
2828     if (caretOffset > topOffset) {
2829         caretOffset = topOffset;
2830         caretAlignment = OFFSET_LEADING;
2831         showCaret();
2832     }
2833 }
2834 /**
2835  * Scrolls one page up so that the first line (truncated or whole)
2836  * of the current page becomes the fully visible last line.
2837  * The caret is scrolled the same number of lines so that its location
2838  * relative to the top line remains the same. The exception is the beginning
2839  * of the text where a full page scroll is not possible. In this case the
2840  * caret is moved in front of the first character.
2841  */
2842 void doPageUp(bool select, int height) {
2843     if (isSingleLine()) return;
2844     int oldHScrollOffset = horizontalScrollOffset;
2845     int oldColumnX = columnX;
2846     if (isFixedLineHeight()) {
2847         int caretLine = getCaretLine();
2848         if (caretLine > 0) {
2849             int lineHeight = renderer.getLineHeight();
2850             int lines = (height is -1 ? clientAreaHeight : height) / lineHeight;
2851             int scrollLines = Math.max(1, Math.min(caretLine, lines));
2852             caretLine -= scrollLines;
2853             caretOffset = getOffsetAtPoint(columnX, getLinePixel(caretLine));
2854             if (select) {
2855                 doSelection(ST.COLUMN_PREVIOUS);
2856             }
2857             int verticalScrollOffset = getVerticalScrollOffset();
2858             int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
2859             if (scrollOffset < verticalScrollOffset) {
2860                 scrollVertical(scrollOffset - verticalScrollOffset, true);
2861             }
2862         }
2863     } else {
2864         int caretLine = getCaretLine();
2865         int lineHeight, lineIndex;
2866         if (height is -1) {
2867             if (topIndexY is 0) {
2868                 height = clientAreaHeight;
2869             } else {
2870                 int y;
2871                 if (topIndex > 0) {
2872                     lineIndex = topIndex - 1;
2873                     lineHeight = renderer.getLineHeight(lineIndex);
2874                     height = clientAreaHeight - topIndexY;
2875                     y = lineHeight - topIndexY;
2876                 } else {
2877                     lineIndex = topIndex;
2878                     lineHeight = renderer.getLineHeight(lineIndex);
2879                     height = clientAreaHeight - (lineHeight + topIndexY);
2880                     y = -topIndexY;
2881                 }
2882                 if (wordWrap) {
2883                     TextLayout layout = renderer.getTextLayout(lineIndex);
2884                     for (int i = 0; i < layout.getLineCount(); i++) {
2885                         Rectangle bounds = layout.getLineBounds(i);
2886                         if (bounds.contains(bounds.x, y)) {
2887                             height += lineHeight - (bounds.y + bounds.height);
2888                             break;
2889                         }
2890                     }
2891                     renderer.disposeTextLayout(layout);
2892                 }
2893             }
2894         } else {
2895             lineIndex = getLineIndex(clientAreaHeight - height);
2896             int topLineY = getLinePixel(lineIndex);
2897             if (wordWrap) {
2898                 TextLayout layout = renderer.getTextLayout(lineIndex);
2899                 int y = topLineY;
2900                 for (int i = 0; i < layout.getLineCount(); i++) {
2901                     Rectangle bounds = layout.getLineBounds(i);
2902                     if (bounds.contains(bounds.x, y)) {
2903                         height = clientAreaHeight - (topLineY + bounds.y);
2904                         break;
2905                     }
2906                 }
2907                 renderer.disposeTextLayout(layout);
2908             } else {
2909                 height = clientAreaHeight - topLineY;
2910             }
2911         }
2912         int caretHeight = height;
2913         if (wordWrap) {
2914             TextLayout layout = renderer.getTextLayout(caretLine);
2915             int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
2916             lineIndex = getVisualLineIndex(layout, offsetInLine);
2917             caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
2918             renderer.disposeTextLayout(layout);
2919         }
2920         lineIndex = caretLine;
2921         lineHeight = renderer.getLineHeight(lineIndex);
2922         while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
2923             caretHeight -= lineHeight;
2924             lineHeight = renderer.getLineHeight(--lineIndex);
2925         }
2926         lineHeight = renderer.getLineHeight(lineIndex);
2927         caretOffset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex);
2928         if (select) doSelection(ST.COLUMN_PREVIOUS);
2929         height = getAvailableHeightAbove(height);
2930         scrollVertical(-height, true);
2931         if (height is 0) setCaretLocation();
2932     }
2933     showCaret();
2934     int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2935     columnX = oldColumnX + hScrollChange;
2936 }
2937 /**
2938  * Updates the selection to extend to the current caret position.
2939  */
2940 void doSelection(int direction) {
2941     int redrawStart = -1;
2942     int redrawEnd = -1;
2943     if (selectionAnchor is -1) {
2944         selectionAnchor = selection.x;
2945     }
2946     if (direction is ST.COLUMN_PREVIOUS) {
2947         if (caretOffset < selection.x) {
2948             // grow selection
2949             redrawEnd = selection.x;
2950             redrawStart = selection.x = caretOffset;
2951             // check if selection has reversed direction
2952             if (selection.y !is selectionAnchor) {
2953                 redrawEnd = selection.y;
2954                 selection.y = selectionAnchor;
2955             }
2956         // test whether selection actually changed. Fixes 1G71EO1
2957         } else if (selectionAnchor is selection.x && caretOffset < selection.y) {
2958             // caret moved towards selection anchor (left side of selection).
2959             // shrink selection
2960             redrawEnd = selection.y;
2961             redrawStart = selection.y = caretOffset;
2962         }
2963     } else {
2964         if (caretOffset > selection.y) {
2965             // grow selection
2966             redrawStart = selection.y;
2967             redrawEnd = selection.y = caretOffset;
2968             // check if selection has reversed direction
2969             if (selection.x !is selectionAnchor) {
2970                 redrawStart = selection.x;
2971                 selection.x = selectionAnchor;
2972             }
2973         // test whether selection actually changed. Fixes 1G71EO1
2974         } else if (selectionAnchor is selection.y && caretOffset > selection.x) {
2975             // caret moved towards selection anchor (right side of selection).
2976             // shrink selection
2977             redrawStart = selection.x;
2978             redrawEnd = selection.x = caretOffset;
2979         }
2980     }
2981     if (redrawStart !is -1 && redrawEnd !is -1) {
2982         internalRedrawRange(redrawStart, redrawEnd - redrawStart);
2983         sendSelectionEvent();
2984     }
2985 }
2986 /**
2987  * Moves the caret to the next character or to the beginning of the
2988  * next line if the cursor is at the end of a line.
2989  */
2990 void doSelectionCursorNext() {
2991     int caretLine = getCaretLine();
2992     int lineOffset = content.getOffsetAtLine(caretLine);
2993     int offsetInLine = caretOffset - lineOffset;
2994     if (offsetInLine < content.getLine(caretLine).length) {
2995         TextLayout layout = renderer.getTextLayout(caretLine);
2996         offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
2997         int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
2998         renderer.disposeTextLayout(layout);
2999         caretOffset = offsetInLine + lineOffset;
3000         caretAlignment = offsetInLine is lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
3001         showCaret();
3002     } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
3003         caretLine++;
3004         caretOffset = content.getOffsetAtLine(caretLine);
3005         caretAlignment = PREVIOUS_OFFSET_TRAILING;
3006         showCaret();
3007     }
3008 }
3009 /**
3010  * Moves the caret to the previous character or to the end of the previous
3011  * line if the cursor is at the beginning of a line.
3012  */
3013 void doSelectionCursorPrevious() {
3014     int caretLine = getCaretLine();
3015     int lineOffset = content.getOffsetAtLine(caretLine);
3016     int offsetInLine = caretOffset - lineOffset;
3017     caretAlignment = OFFSET_LEADING;
3018 
3019     if (offsetInLine > 0) {
3020         caretOffset = getClusterPrevious(caretOffset, caretLine);
3021         showCaret();
3022     } else if (caretLine > 0) {
3023         caretLine--;
3024         lineOffset = content.getOffsetAtLine(caretLine);
3025         caretOffset = lineOffset + cast(int)/*64bit*/content.getLine(caretLine).length;
3026         showCaret();
3027     }
3028 }
3029 /**
3030  * Moves the caret one line down and to the same character offset relative
3031  * to the beginning of the line. Moves the caret to the end of the new line
3032  * if the new line is shorter than the character offset.
3033  * Moves the caret to the end of the text if the caret already is on the
3034  * last line.
3035  * Adjusts the selection according to the caret change. This can either add
3036  * to or subtract from the old selection, depending on the previous selection
3037  * direction.
3038  */
3039 void doSelectionLineDown() {
3040     int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3041     doLineDown(true);
3042     columnX = oldColumnX;
3043 }
3044 /**
3045  * Moves the caret one line up and to the same character offset relative
3046  * to the beginning of the line. Moves the caret to the end of the new line
3047  * if the new line is shorter than the character offset.
3048  * Moves the caret to the beginning of the document if it is already on the
3049  * first line.
3050  * Adjusts the selection according to the caret change. This can either add
3051  * to or subtract from the old selection, depending on the previous selection
3052  * direction.
3053  */
3054 void doSelectionLineUp() {
3055     int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3056     doLineUp(true);
3057     columnX = oldColumnX;
3058 }
3059 /**
3060  * Scrolls one page down so that the last line (truncated or whole)
3061  * of the current page becomes the fully visible top line.
3062  * <p>
3063  * The caret is scrolled the same number of lines so that its location
3064  * relative to the top line remains the same. The exception is the end
3065  * of the text where a full page scroll is not possible. In this case
3066  * the caret is moved after the last character.
3067  * <p></p>
3068  * Adjusts the selection according to the caret change. This can either add
3069  * to or subtract from the old selection, depending on the previous selection
3070  * direction.
3071  * </p>
3072  */
3073 void doSelectionPageDown(int pixels) {
3074     int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3075     doPageDown(true, pixels);
3076     columnX = oldColumnX;
3077 }
3078 /**
3079  * Scrolls one page up so that the first line (truncated or whole)
3080  * of the current page becomes the fully visible last line.
3081  * <p>
3082  * The caret is scrolled the same number of lines so that its location
3083  * relative to the top line remains the same. The exception is the beginning
3084  * of the text where a full page scroll is not possible. In this case the
3085  * caret is moved in front of the first character.
3086  * </p><p>
3087  * Adjusts the selection according to the caret change. This can either add
3088  * to or subtract from the old selection, depending on the previous selection
3089  * direction.
3090  * </p>
3091  */
3092 void doSelectionPageUp(int pixels) {
3093     int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3094     doPageUp(true, pixels);
3095     columnX = oldColumnX;
3096 }
3097 /**
3098  * Moves the caret to the end of the next word .
3099  */
3100 void doSelectionWordNext() {
3101     int newCaretOffset = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
3102     // Force symmetrical movement for word next and previous. Fixes 14536
3103     caretAlignment = OFFSET_LEADING;
3104     // don't change caret position if in single line mode and the cursor
3105     // would be on a different line. fixes 5673
3106     if (!isSingleLine() ||
3107         content.getLineAtOffset(caretOffset) is content.getLineAtOffset(newCaretOffset)) {
3108         caretOffset = newCaretOffset;
3109         showCaret();
3110     }
3111 }
3112 /**
3113  * Moves the caret to the start of the previous word.
3114  */
3115 void doSelectionWordPrevious() {
3116     caretAlignment = OFFSET_LEADING;
3117     caretOffset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
3118     int caretLine = content.getLineAtOffset(caretOffset);
3119     // word previous always comes from bottom line. when
3120     // wrapping lines, stay on bottom line when on line boundary
3121     if (wordWrap && caretLine < content.getLineCount() - 1 &&
3122         caretOffset is content.getOffsetAtLine(caretLine + 1)) {
3123         caretLine++;
3124     }
3125     showCaret();
3126 }
3127 /**
3128  * Moves the caret one character to the left.  Do not go to the previous line.
3129  * When in a bidi locale and at a R2L character the caret is moved to the
3130  * beginning of the R2L segment (visually right) and then one character to the
3131  * left (visually left because it's now in a L2R segment).
3132  */
3133 void doVisualPrevious() {
3134     caretOffset = getClusterPrevious(caretOffset, getCaretLine());
3135     showCaret();
3136 }
3137 /**
3138  * Moves the caret one character to the right.  Do not go to the next line.
3139  * When in a bidi locale and at a R2L character the caret is moved to the
3140  * end of the R2L segment (visually left) and then one character to the
3141  * right (visually right because it's now in a L2R segment).
3142  */
3143 void doVisualNext() {
3144     caretOffset = getClusterNext(caretOffset, getCaretLine());
3145     showCaret();
3146 }
3147 /**
3148  * Moves the caret to the end of the next word.
3149  * If a selection exists, move the caret to the end of the selection
3150  * and remove the selection.
3151  */
3152 void doWordNext() {
3153     if (selection.y - selection.x > 0) {
3154         caretOffset = selection.y;
3155         showCaret();
3156     } else {
3157         doSelectionWordNext();
3158     }
3159 }
3160 /**
3161  * Moves the caret to the start of the previous word.
3162  * If a selection exists, move the caret to the start of the selection
3163  * and remove the selection.
3164  */
3165 void doWordPrevious() {
3166     if (selection.y - selection.x > 0) {
3167         caretOffset = selection.x;
3168         showCaret();
3169     } else {
3170         doSelectionWordPrevious();
3171     }
3172 }
3173 /**
3174  * Ends the autoscroll process.
3175  */
3176 void endAutoScroll() {
3177     autoScrollDirection = SWT.NULL;
3178 }
3179 public override Color getBackground() {
3180     checkWidget();
3181     if (background is null) {
3182         return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
3183     }
3184     return background;
3185 }
3186 /**
3187  * Returns the baseline, in pixels.
3188  *
3189  * Note: this API should not be used if a StyleRange attribute causes lines to
3190  * have different heights (i.e. different fonts, rise, etc).
3191  *
3192  * @return baseline the baseline
3193  * @exception SWTException <ul>
3194  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3195  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3196  * </ul>
3197  * @since 3.0
3198  *
3199  * @see #getBaseline(int)
3200  */
3201 public int getBaseline() {
3202     checkWidget();
3203     return renderer.getBaseline();
3204 }
3205 /**
3206  * Returns the baseline at the given offset, in pixels.
3207  *
3208  * @param offset the offset
3209  *
3210  * @return baseline the baseline
3211  *
3212  * @exception SWTException <ul>
3213  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3214  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3215  * </ul>
3216  * @exception IllegalArgumentException <ul>
3217  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3218  * </ul>
3219  *
3220  * @since 3.2
3221  */
3222 public int getBaseline(int offset) {
3223     checkWidget();
3224     if (!(0 <= offset && offset <= content.getCharCount())) {
3225         SWT.error(SWT.ERROR_INVALID_RANGE);
3226     }
3227     if (isFixedLineHeight()) {
3228         return renderer.getBaseline();
3229     }
3230     int lineIndex = content.getLineAtOffset(offset);
3231     int lineOffset = content.getOffsetAtLine(lineIndex);
3232     TextLayout layout = renderer.getTextLayout(lineIndex);
3233     int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, cast(int)/*64bit*/layout.getText().length));
3234     FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
3235     renderer.disposeTextLayout(layout);
3236     return metrics.getAscent() + metrics.getLeading();
3237 }
3238 /**
3239  * Gets the BIDI coloring mode.  When true the BIDI text display
3240  * algorithm is applied to segments of text that are the same
3241  * color.
3242  *
3243  * @return the current coloring mode
3244  * @exception SWTException <ul>
3245  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3246  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3247  * </ul>
3248  *
3249  * @deprecated use BidiSegmentListener instead.
3250  */
3251 public bool getBidiColoring() {
3252     checkWidget();
3253     return bidiColoring;
3254 }
3255 /**
3256  * Returns the index of the last fully visible line.
3257  *
3258  * @return index of the last fully visible line.
3259  */
3260 int getBottomIndex() {
3261     int bottomIndex;
3262     if (isFixedLineHeight()) {
3263         int lineCount = 1;
3264         int lineHeight = renderer.getLineHeight();
3265         if (lineHeight !is 0) {
3266             // calculate the number of lines that are fully visible
3267             int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
3268             lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
3269         }
3270         bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
3271     } else {
3272         int clientAreaHeight = this.clientAreaHeight - bottomMargin;
3273         bottomIndex = getLineIndex(clientAreaHeight);
3274         if (bottomIndex > 0) {
3275             int linePixel = getLinePixel(bottomIndex);
3276             int lineHeight = renderer.getLineHeight(bottomIndex);
3277             if (linePixel + lineHeight > clientAreaHeight) {
3278                 if (getLinePixel(bottomIndex - 1) >= topMargin) {
3279                     bottomIndex--;
3280                 }
3281             }
3282         }
3283     }
3284     return bottomIndex;
3285 }
3286 Rectangle getBoundsAtOffset(int offset) {
3287     int lineIndex = content.getLineAtOffset(offset);
3288     int lineOffset = content.getOffsetAtLine(lineIndex);
3289     String line = content.getLine(lineIndex);
3290     Rectangle bounds;
3291     if (line.length !is 0) {
3292         int offsetInLine = offset - lineOffset;
3293         TextLayout layout = renderer.getTextLayout(lineIndex);
3294         bounds = layout.getBounds(offsetInLine, offsetInLine);
3295         renderer.disposeTextLayout(layout);
3296     } else {
3297         bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
3298     }
3299     if (offset is caretOffset) {
3300         auto lineEnd = lineOffset + line.length;
3301         if (offset is lineEnd && caretAlignment is PREVIOUS_OFFSET_TRAILING) {
3302             bounds.width += getCaretWidth();
3303         }
3304     }
3305     bounds.x += leftMargin - horizontalScrollOffset;
3306     bounds.y += getLinePixel(lineIndex);
3307     return bounds;
3308 }
3309 /**
3310  * Returns the caret position relative to the start of the text.
3311  *
3312  * @return the caret position relative to the start of the text.
3313  * @exception SWTException <ul>
3314  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3315  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3316  * </ul>
3317  */
3318 public int getCaretOffset() {
3319     checkWidget();
3320     return caretOffset;
3321 }
3322 /**
3323  * Returns the caret width.
3324  *
3325  * @return the caret width, 0 if caret is null.
3326  */
3327 int getCaretWidth() {
3328     Caret caret = getCaret();
3329     if (caret is null) return 0;
3330     return caret.getSize().x;
3331 }
3332 Object getClipboardContent(int clipboardType) {
3333     TextTransfer plainTextTransfer = TextTransfer.getInstance();
3334     return clipboard.getContents(plainTextTransfer, clipboardType);
3335 }
3336 int getClusterNext(int offset, int lineIndex) {
3337     int lineOffset = content.getOffsetAtLine(lineIndex);
3338     TextLayout layout = renderer.getTextLayout(lineIndex);
3339     offset -= lineOffset;
3340     offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
3341     offset += lineOffset;
3342     renderer.disposeTextLayout(layout);
3343     return offset;
3344 }
3345 int getClusterPrevious(int offset, int lineIndex) {
3346     int lineOffset = content.getOffsetAtLine(lineIndex);
3347     TextLayout layout = renderer.getTextLayout(lineIndex);
3348     offset -= lineOffset;
3349     offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
3350     offset += lineOffset;
3351     renderer.disposeTextLayout(layout);
3352     return offset;
3353 }
3354 /**
3355  * Returns the content implementation that is used for text storage.
3356  *
3357  * @return content the user defined content implementation that is used for
3358  * text storage or the default content implementation if no user defined
3359  * content implementation has been set.
3360  * @exception SWTException <ul>
3361  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3362  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3363  * </ul>
3364  */
3365 public StyledTextContent getContent() {
3366     checkWidget();
3367     return content;
3368 }
3369 public override bool getDragDetect () {
3370     checkWidget ();
3371     return dragDetect_;
3372 }
3373 /**
3374  * Returns whether the widget implements double click mouse behavior.
3375  *
3376  * @return true if double clicking a word selects the word, false if double clicks
3377  * have the same effect as regular mouse clicks
3378  * @exception SWTException <ul>
3379  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3380  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3381  * </ul>
3382  */
3383 public bool getDoubleClickEnabled() {
3384     checkWidget();
3385     return doubleClickEnabled;
3386 }
3387 /**
3388  * Returns whether the widget content can be edited.
3389  *
3390  * @return true if content can be edited, false otherwise
3391  * @exception SWTException <ul>
3392  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3393  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3394  * </ul>
3395  */
3396 public bool getEditable() {
3397     checkWidget();
3398     return editable;
3399 }
3400 public override Color getForeground() {
3401     checkWidget();
3402     if (foreground is null) {
3403         return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
3404     }
3405     return foreground;
3406 }
3407 /**
3408  * Returns the horizontal scroll increment.
3409  *
3410  * @return horizontal scroll increment.
3411  */
3412 int getHorizontalIncrement() {
3413     return renderer.averageCharWidth;
3414 }
3415 /**
3416  * Returns the horizontal scroll offset relative to the start of the line.
3417  *
3418  * @return horizontal scroll offset relative to the start of the line,
3419  * measured in character increments starting at 0, if > 0 the content is scrolled
3420  * @exception SWTException <ul>
3421  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3422  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3423  * </ul>
3424  */
3425 public int getHorizontalIndex() {
3426     checkWidget();
3427     return horizontalScrollOffset / getHorizontalIncrement();
3428 }
3429 /**
3430  * Returns the horizontal scroll offset relative to the start of the line.
3431  *
3432  * @return the horizontal scroll offset relative to the start of the line,
3433  * measured in pixel starting at 0, if > 0 the content is scrolled.
3434  * @exception SWTException <ul>
3435  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3436  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3437  * </ul>
3438  */
3439 public int getHorizontalPixel() {
3440     checkWidget();
3441     return horizontalScrollOffset;
3442 }
3443 /**
3444  * Returns the line indentation of the widget.
3445  *
3446  * @return the line indentation
3447  *
3448  * @exception SWTException <ul>
3449  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3450  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3451  * </ul>
3452  *
3453  * @see #getLineIndent(int)
3454  *
3455  * @since 3.2
3456  */
3457 public int getIndent() {
3458     checkWidget();
3459     return indent;
3460 }
3461 /**
3462  * Returns whether the widget justifies lines.
3463  *
3464  * @return whether lines are justified
3465  *
3466  * @exception SWTException <ul>
3467  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3468  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3469  * </ul>
3470  *
3471  * @see #getLineJustify(int)
3472  *
3473  * @since 3.2
3474  */
3475 public bool getJustify() {
3476     checkWidget();
3477     return justify;
3478 }
3479 /**
3480  * Returns the action assigned to the key.
3481  * Returns SWT.NULL if there is no action associated with the key.
3482  *
3483  * @param key a key code defined in SWT.java or a character.
3484  *  Optionally ORd with a state mask.  Preferred state masks are one or more of
3485  *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
3486  *  differences.  However, there may be cases where using the specific state masks
3487  *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
3488  * @return one of the predefined actions defined in ST.java or SWT.NULL
3489  *  if there is no action associated with the key.
3490  * @exception SWTException <ul>
3491  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3492  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3493  * </ul>
3494  */
3495 public int getKeyBinding(int key) {
3496     checkWidget();
3497     if( auto p = key in keyActionMap ){
3498         return *p;
3499     }
3500     return SWT.NULL;
3501 }
3502 /**
3503  * Gets the number of characters.
3504  *
3505  * @return number of characters in the widget
3506  * @exception SWTException <ul>
3507  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3508  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3509  * </ul>
3510  */
3511 public int getCharCount() {
3512     checkWidget();
3513     return content.getCharCount();
3514 }
3515 /**
3516  * Returns the line at the given line index without delimiters.
3517  * Index 0 is the first line of the content. When there are not
3518  * any lines, getLine(0) is a valid call that answers an empty string.
3519  * <p>
3520  *
3521  * @param lineIndex index of the line to return.
3522  * @return the line text without delimiters
3523  *
3524  * @exception SWTException <ul>
3525  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3526  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3527  * </ul>
3528  * @exception IllegalArgumentException <ul>
3529  *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
3530  * </ul>
3531  * @since 3.4
3532  */
3533 public String getLine(int lineIndex) {
3534     checkWidget();
3535     if (lineIndex < 0 ||
3536         (lineIndex > 0 && lineIndex >= content.getLineCount())) {
3537         SWT.error(SWT.ERROR_INVALID_RANGE);
3538     }
3539     return content.getLine(lineIndex);
3540 }
3541 /**
3542  * Returns the alignment of the line at the given index.
3543  *
3544  * @param index the index of the line
3545  *
3546  * @return the line alignment
3547  *
3548  * @exception SWTException <ul>
3549  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3550  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3551  * </ul>
3552  * @exception IllegalArgumentException <ul>
3553  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3554  * </ul>
3555  *
3556  * @see #getAlignment()
3557  *
3558  * @since 3.2
3559  */
3560 public int getLineAlignment(int index) {
3561     checkWidget();
3562     if (index < 0 || index > content.getLineCount()) {
3563         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3564     }
3565     return renderer.getLineAlignment(index, alignment);
3566 }
3567 /**
3568  * Returns the line at the specified offset in the text
3569  * where 0 &lt; offset &lt; getCharCount() so that getLineAtOffset(getCharCount())
3570  * returns the line of the insert location.
3571  *
3572  * @param offset offset relative to the start of the content.
3573  *  0 <= offset <= getCharCount()
3574  * @return line at the specified offset in the text
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  * @exception IllegalArgumentException <ul>
3580  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3581  * </ul>
3582  */
3583 public int getLineAtOffset(int offset) {
3584     checkWidget();
3585     if (offset < 0 || offset > getCharCount()) {
3586         SWT.error(SWT.ERROR_INVALID_RANGE);
3587     }
3588     return content.getLineAtOffset(offset);
3589 }
3590 /**
3591  * Returns the background color of the line at the given index.
3592  * Returns null if a LineBackgroundListener has been set or if no background
3593  * color has been specified for the line. Should not be called if a
3594  * LineBackgroundListener has been set since the listener maintains the
3595  * line background colors.
3596  *
3597  * @param index the index of the line
3598  * @return the background color of the line at the given index.
3599  *
3600  * @exception SWTException <ul>
3601  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3602  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3603  * </ul>
3604  * @exception IllegalArgumentException <ul>
3605  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3606  * </ul>
3607  */
3608 public Color getLineBackground(int index) {
3609     checkWidget();
3610     if (index < 0 || index > content.getLineCount()) {
3611         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3612     }
3613     return isListening(LineGetBackground) ? null : renderer.getLineBackground(index, null);
3614 }
3615 /**
3616  * Returns the bullet of the line at the given index.
3617  *
3618  * @param index the index of the line
3619  *
3620  * @return the line bullet
3621  *
3622  * @exception SWTException <ul>
3623  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3624  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3625  * </ul>
3626  * @exception IllegalArgumentException <ul>
3627  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3628  * </ul>
3629  *
3630  * @since 3.2
3631  */
3632 public Bullet getLineBullet(int index) {
3633     checkWidget();
3634     if (index < 0 || index > content.getLineCount()) {
3635         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3636     }
3637     return isListening(LineGetStyle) ? null : renderer.getLineBullet(index, null);
3638 }
3639 /**
3640  * Returns the line background data for the given line or null if
3641  * there is none.
3642  *
3643  * @param lineOffset offset of the line start relative to the start
3644  *  of the content.
3645  * @param line line to get line background data for
3646  * @return line background data for the given line.
3647  */
3648 StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
3649     return sendLineEvent(LineGetBackground, lineOffset, line);
3650 }
3651 /**
3652  * Gets the number of text lines.
3653  *
3654  * @return the number of lines in the widget
3655  * @exception SWTException <ul>
3656  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3657  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3658  * </ul>
3659  */
3660 public int getLineCount() {
3661     checkWidget();
3662     return content.getLineCount();
3663 }
3664 /**
3665  * Returns the number of lines that can be completely displayed in the
3666  * widget client area.
3667  *
3668  * @return number of lines that can be completely displayed in the widget
3669  *  client area.
3670  */
3671 int getLineCountWhole() {
3672     if (isFixedLineHeight()) {
3673         int lineHeight = renderer.getLineHeight();
3674         return lineHeight !is 0 ? clientAreaHeight / lineHeight : 1;
3675     }
3676     return getBottomIndex() - topIndex + 1;
3677 }
3678 /**
3679  * Returns the line delimiter used for entering new lines by key down
3680  * or paste operation.
3681  *
3682  * @return line delimiter used for entering new lines by key down
3683  * or paste operation.
3684  * @exception SWTException <ul>
3685  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3686  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3687  * </ul>
3688  */
3689 public String getLineDelimiter() {
3690     checkWidget();
3691     return content.getLineDelimiter();
3692 }
3693 /**
3694  * Returns the line height.
3695  * <p>
3696  * Note: this API should not be used if a StyleRange attribute causes lines to
3697  * have different heights (i.e. different fonts, rise, etc).
3698  * </p>
3699  *
3700  * @return line height in pixel.
3701  * @exception SWTException <ul>
3702  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3703  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3704  * </ul>
3705  * @see #getLineHeight(int)
3706  */
3707 public int getLineHeight() {
3708     checkWidget();
3709     return renderer.getLineHeight();
3710 }
3711 /**
3712  * Returns the line height at the given offset.
3713  *
3714  * @param offset the offset
3715  *
3716  * @return line height in pixels
3717  *
3718  * @exception SWTException <ul>
3719  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3720  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3721  * </ul>
3722  * @exception IllegalArgumentException <ul>
3723  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3724  * </ul>
3725  *
3726  * @since 3.2
3727  */
3728 public int getLineHeight(int offset) {
3729     checkWidget();
3730     if (!(0 <= offset && offset <= content.getCharCount())) {
3731         SWT.error(SWT.ERROR_INVALID_RANGE);
3732     }
3733     if (isFixedLineHeight()) {
3734         return renderer.getLineHeight();
3735     }
3736     int lineIndex = content.getLineAtOffset(offset);
3737     int lineOffset = content.getOffsetAtLine(lineIndex);
3738     TextLayout layout = renderer.getTextLayout(lineIndex);
3739     int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, cast(int)/*64bit*/layout.getText().length));
3740     int height = layout.getLineBounds(lineInParagraph).height;
3741     renderer.disposeTextLayout(layout);
3742     return height;
3743 }
3744 /**
3745  * Returns the indentation of the line at the given index.
3746  *
3747  * @param index the index of the line
3748  *
3749  * @return the line indentation
3750  *
3751  * @exception SWTException <ul>
3752  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3753  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3754  * </ul>
3755  * @exception IllegalArgumentException <ul>
3756  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3757  * </ul>
3758  *
3759  * @see #getIndent()
3760  *
3761  * @since 3.2
3762  */
3763 public int getLineIndent(int index) {
3764     checkWidget();
3765     if (index < 0 || index > content.getLineCount()) {
3766         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3767     }
3768     return isListening(LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
3769 }
3770 /**
3771  * Returns whether the line at the given index is justified.
3772  *
3773  * @param index the index of the line
3774  *
3775  * @return whether the line is justified
3776  *
3777  * @exception SWTException <ul>
3778  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3779  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3780  * </ul>
3781  * @exception IllegalArgumentException <ul>
3782  *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3783  * </ul>
3784  *
3785  * @see #getJustify()
3786  *
3787  * @since 3.2
3788  */
3789 public bool getLineJustify(int index) {
3790     checkWidget();
3791     if (index < 0 || index > content.getLineCount()) {
3792         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3793     }
3794     return isListening(LineGetStyle) ? false : renderer.getLineJustify(index, justify);
3795 }
3796 /**
3797  * Returns the line spacing of the widget.
3798  *
3799  * @return the line spacing
3800  *
3801  * @exception SWTException <ul>
3802  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3803  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3804  * </ul>
3805  *
3806  * @since 3.2
3807  */
3808 public int getLineSpacing() {
3809     checkWidget();
3810     return lineSpacing;
3811 }
3812 /**
3813  * Returns the line style data for the given line or null if there is
3814  * none.
3815  * <p>
3816  * If there is a LineStyleListener but it does not set any styles,
3817  * the StyledTextEvent.styles field will be initialized to an empty
3818  * array.
3819  * </p>
3820  *
3821  * @param lineOffset offset of the line start relative to the start of
3822  *  the content.
3823  * @param line line to get line styles for
3824  * @return line style data for the given line. Styles may start before
3825  *  line start and end after line end
3826  */
3827 StyledTextEvent getLineStyleData(int lineOffset, String line) {
3828     return sendLineEvent(LineGetStyle, lineOffset, line);
3829 }
3830 /**
3831  * Returns the top pixel, relative to the client area, of a given line.
3832  * Clamps out of ranges index.
3833  *
3834  * @param lineIndex the line index, the max value is lineCount. If
3835  * lineIndex is lineCount it returns the bottom pixel of the last line.
3836  * It means this function can be used to retrieve the bottom pixel of any line.
3837  *
3838  * @return the top pixel of a given line index
3839  *
3840  * @since 3.2
3841  */
3842 public int getLinePixel(int lineIndex) {
3843     checkWidget();
3844     int lineCount = content.getLineCount();
3845     lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
3846     if (isFixedLineHeight()) {
3847         int lineHeight = renderer.getLineHeight();
3848         return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
3849     }
3850     if (lineIndex is topIndex) return topIndexY + topMargin;
3851     int height = topIndexY;
3852     if (lineIndex > topIndex) {
3853         for (int i = topIndex; i < lineIndex; i++) {
3854             height += renderer.getLineHeight(i);
3855         }
3856     } else {
3857         for (int i = topIndex - 1; i >= lineIndex; i--) {
3858             height -= renderer.getLineHeight(i);
3859         }
3860     }
3861     return height + topMargin;
3862 }
3863 /**
3864  * Returns the line index for a y, relative to the client area.
3865  * The line index returned is always in the range 0..lineCount - 1.
3866  *
3867  * @param y the y-coordinate pixel
3868  *
3869  * @return the line index for a given y-coordinate pixel
3870  *
3871  * @since 3.2
3872  */
3873 public int getLineIndex(int y) {
3874     checkWidget();
3875     y -= topMargin;
3876     if (isFixedLineHeight()) {
3877         int lineHeight = renderer.getLineHeight();
3878         int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
3879         int lineCount = content.getLineCount();
3880         lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
3881         return lineIndex;
3882     }
3883     if (y is topIndexY) return topIndex;
3884     int line = topIndex;
3885     if (y < topIndexY) {
3886         while (y < topIndexY && line > 0) {
3887             y += renderer.getLineHeight(--line);
3888         }
3889     } else {
3890         int lineCount = content.getLineCount();
3891         int lineHeight = renderer.getLineHeight(line);
3892         while (y - lineHeight >= topIndexY && line < lineCount - 1) {
3893             y -= lineHeight;
3894             lineHeight = renderer.getLineHeight(++line);
3895         }
3896     }
3897     return line;
3898 }
3899 /**
3900  * Returns the x, y location of the upper left corner of the character
3901  * bounding box at the specified offset in the text. The point is
3902  * relative to the upper left corner of the widget client area.
3903  *
3904  * @param offset offset relative to the start of the content.
3905  *  0 <= offset <= getCharCount()
3906  * @return x, y location of the upper left corner of the character
3907  *  bounding box at the specified offset in the text.
3908  * @exception SWTException <ul>
3909  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3910  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3911  * </ul>
3912  * @exception IllegalArgumentException <ul>
3913  *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3914  * </ul>
3915  */
3916 public Point getLocationAtOffset(int offset) {
3917     checkWidget();
3918     if (offset < 0 || offset > getCharCount()) {
3919         SWT.error(SWT.ERROR_INVALID_RANGE);
3920     }
3921     return getPointAtOffset(offset);
3922 }
3923 /**
3924  * Returns the character offset of the first character of the given line.
3925  *
3926  * @param lineIndex index of the line, 0 based relative to the first
3927  *  line in the content. 0 <= lineIndex < getLineCount(), except
3928  *  lineIndex may always be 0
3929  * @return offset offset of the first character of the line, relative to
3930  *  the beginning of the document. The first character of the document is
3931  *  at offset 0.
3932  *  When there are not any lines, getOffsetAtLine(0) is a valid call that
3933  *  answers 0.
3934  * @exception SWTException <ul>
3935  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3936  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3937  * </ul>
3938  * @exception IllegalArgumentException <ul>
3939  *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
3940  * </ul>
3941  * @since 2.0
3942  */
3943 public int getOffsetAtLine(int lineIndex) {
3944     checkWidget();
3945     if (lineIndex < 0 ||
3946         (lineIndex > 0 && lineIndex >= content.getLineCount())) {
3947         SWT.error(SWT.ERROR_INVALID_RANGE);
3948     }
3949     return content.getOffsetAtLine(lineIndex);
3950 }
3951 /**
3952  * Returns the offset of the character at the given location relative
3953  * to the first character in the document.
3954  * <p>
3955  * The return value reflects the character offset that the caret will
3956  * be placed at if a mouse click occurred at the specified location.
3957  * If the x coordinate of the location is beyond the center of a character
3958  * the returned offset will be behind the character.
3959  * </p>
3960  *
3961  * @param point the origin of character bounding box relative to
3962  *  the origin of the widget client area.
3963  * @return offset of the character at the given location relative
3964  *  to the first character in the document.
3965  * @exception SWTException <ul>
3966  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3967  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3968  * </ul>
3969  * @exception IllegalArgumentException <ul>
3970  *   <li>ERROR_NULL_ARGUMENT when point is null</li>
3971  *   <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
3972  * </ul>
3973  */
3974 public int getOffsetAtLocation(Point point) {
3975     checkWidget();
3976     if (point is null) {
3977         SWT.error(SWT.ERROR_NULL_ARGUMENT);
3978     }
3979     int[1] trailing;
3980     int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
3981     if (offset is -1) {
3982         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3983     }
3984     return offset + trailing[0];
3985 }
3986 int getOffsetAtPoint(int x, int y) {
3987     int lineIndex = getLineIndex(y);
3988     y -= getLinePixel(lineIndex);
3989     return getOffsetAtPoint(x, y, lineIndex);
3990 }
3991 /**
3992  * Returns the offset at the specified x location in the specified line.
3993  *
3994  * @param x x location of the mouse location
3995  * @param line  line the mouse location is in
3996  * @return the offset at the specified x location in the specified line,
3997  *  relative to the beginning of the document
3998  */
3999 int getOffsetAtPoint(int x, int y, int lineIndex) {
4000     TextLayout layout = renderer.getTextLayout(lineIndex);
4001     x += horizontalScrollOffset - leftMargin;
4002     int[1] trailing;
4003     int offsetInLine = layout.getOffset(x, y, trailing);
4004     caretAlignment = OFFSET_LEADING;
4005     if (trailing[0] !is 0) {
4006         int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
4007         int lineStart = layout.getLineOffsets()[lineInParagraph];
4008         if (offsetInLine + trailing[0] is lineStart) {
4009             offsetInLine += trailing[0];
4010             caretAlignment = PREVIOUS_OFFSET_TRAILING;
4011         } else {
4012             String line = content.getLine(lineIndex);
4013             int level;
4014             int offset = offsetInLine;
4015             while (offset > 0 && Character.isDigit(line.dcharAt(offset)))
4016                 offset = cast(int)/*64bit*/line.offsetBefore(offset);
4017             if (offset is 0 && Character.isDigit(line.dcharAt(offset))) {
4018                 level = isMirrored() ? 1 : 0;
4019             } else {
4020                 level = layout.getLevel(offset) & 0x1;
4021             }
4022             offsetInLine += trailing[0];
4023             int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
4024             if ((level ^ trailingLevel) !is 0) {
4025                 caretAlignment = PREVIOUS_OFFSET_TRAILING;
4026             } else {
4027                 caretAlignment = OFFSET_LEADING;
4028             }
4029         }
4030     }
4031     renderer.disposeTextLayout(layout);
4032     return offsetInLine + content.getOffsetAtLine(lineIndex);
4033 }
4034 int getOffsetAtPoint(int x, int y, int[] trailing, bool inTextOnly) {
4035     if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
4036         return -1;
4037     }
4038     int bottomIndex = getPartialBottomIndex();
4039     int height = getLinePixel(bottomIndex + 1);
4040     if (inTextOnly && y > height) {
4041         return -1;
4042     }
4043     int lineIndex = getLineIndex(y);
4044     int lineOffset = content.getOffsetAtLine(lineIndex);
4045     TextLayout layout = renderer.getTextLayout(lineIndex);
4046     x += horizontalScrollOffset - leftMargin ;
4047     y -= getLinePixel(lineIndex);
4048     int offset = layout.getOffset(x, y, trailing);
4049     Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
4050     renderer.disposeTextLayout(layout);
4051     if (inTextOnly && !(rect.x  <= x && x <=  rect.x + rect.width)) {
4052         return -1;
4053     }
4054     return offset + lineOffset;
4055 }
4056 /**
4057  * Returns the orientation of the receiver.
4058  *
4059  * @return the orientation style
4060  *
4061  * @exception SWTException <ul>
4062  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4063  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4064  * </ul>
4065  *
4066  * @since 2.1.2
4067  */
4068 public int getOrientation () {
4069     checkWidget();
4070     return isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
4071 }
4072 /**
4073  * Returns the index of the last partially visible line.
4074  *
4075  * @return index of the last partially visible line.
4076  */
4077 int getPartialBottomIndex() {
4078     if (isFixedLineHeight()) {
4079         int lineHeight = renderer.getLineHeight();
4080         int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
4081         return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
4082     }
4083     return getLineIndex(clientAreaHeight - bottomMargin);
4084 }
4085 /**
4086  * Returns the index of the first partially visible line.
4087  *
4088  * @return index of the first partially visible line.
4089  */
4090 int getPartialTopIndex() {
4091     if (isFixedLineHeight()) {
4092         int lineHeight = renderer.getLineHeight();
4093         return getVerticalScrollOffset() / lineHeight;
4094     }
4095     return topIndexY <= 0 ? topIndex : topIndex - 1;
4096 }
4097 /**
4098  * Returns the content in the specified range using the platform line
4099  * delimiter to separate lines.
4100  *
4101  * @param writer the TextWriter to write line text into
4102  * @return the content in the specified range using the platform line
4103  *  delimiter to separate lines as written by the specified TextWriter.
4104  */
4105 String getPlatformDelimitedText(TextWriter writer) {
4106     int end = writer.getStart() + writer.getCharCount();
4107     int startLine = content.getLineAtOffset(writer.getStart());
4108     int endLine = content.getLineAtOffset(end);
4109     String endLineText = content.getLine(endLine);
4110     int endLineOffset = content.getOffsetAtLine(endLine);
4111 
4112     for (int i = startLine; i <= endLine; i++) {
4113         writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
4114         if (i < endLine) {
4115             writer.writeLineDelimiter(PlatformLineDelimiter);
4116         }
4117     }
4118     if (end > endLineOffset + endLineText.length) {
4119         writer.writeLineDelimiter(PlatformLineDelimiter);
4120     }
4121     writer.close();
4122     return writer.toString();
4123 }
4124 /**
4125  * Returns all the ranges of text that have an associated StyleRange.
4126  * Returns an empty array if a LineStyleListener has been set.
4127  * Should not be called if a LineStyleListener has been set since the
4128  * listener maintains the styles.
4129  * <p>
4130  * The ranges array contains start and length pairs.  Each pair refers to
4131  * the corresponding style in the styles array.  For example, the pair
4132  * that starts at ranges[n] with length ranges[n+1] uses the style
4133  * at styles[n/2] returned by <code>getStyleRanges(int, int, bool)</code>.
4134  * </p>
4135  *
4136  * @return the ranges or an empty array if a LineStyleListener has been set.
4137  *
4138  * @exception SWTException <ul>
4139  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4140  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4141  * </ul>
4142  *
4143  * @since 3.2
4144  *
4145  * @see #getStyleRanges(bool)
4146  */
4147 public int[] getRanges() {
4148     checkWidget();
4149     if (!isListening(LineGetStyle)) {
4150         int[] ranges = renderer.getRanges(0, content.getCharCount());
4151         if (ranges !is null) return ranges;
4152     }
4153     return new int[0];
4154 }
4155 /**
4156  * Returns the ranges of text that have an associated StyleRange.
4157  * Returns an empty array if a LineStyleListener has been set.
4158  * Should not be called if a LineStyleListener has been set since the
4159  * listener maintains the styles.
4160  * <p>
4161  * The ranges array contains start and length pairs.  Each pair refers to
4162  * the corresponding style in the styles array.  For example, the pair
4163  * that starts at ranges[n] with length ranges[n+1] uses the style
4164  * at styles[n/2] returned by <code>getStyleRanges(int, int, bool)</code>.
4165  * </p>
4166  *
4167  * @param start the start offset of the style ranges to return
4168  * @param length the number of style ranges to return
4169  *
4170  * @return the ranges or an empty array if a LineStyleListener has been set.
4171  *
4172  * @exception SWTException <ul>
4173  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4174  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4175  * </ul>
4176  * @exception IllegalArgumentException <ul>
4177  *   <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
4178  * </ul>
4179  *
4180  * @since 3.2
4181  *
4182  * @see #getStyleRanges(int, int, bool)
4183  */
4184 public int[] getRanges(int start, int length) {
4185     checkWidget();
4186     int contentLength = getCharCount();
4187     int end = start + length;
4188     if (start > end || start < 0 || end > contentLength) {
4189         SWT.error(SWT.ERROR_INVALID_RANGE);
4190     }
4191     if (!isListening(LineGetStyle)) {
4192         int[] ranges = renderer.getRanges(start, length);
4193         if (ranges !is null) return ranges;
4194     }
4195     return new int[0];
4196 }
4197 /**
4198  * Returns the selection.
4199  * <p>
4200  * Text selections are specified in terms of caret positions.  In a text
4201  * widget that contains N characters, there are N+1 caret positions,
4202  * ranging from 0..N
4203  * </p>
4204  *
4205  * @return start and end of the selection, x is the offset of the first
4206  *  selected character, y is the offset after the last selected character.
4207  *  The selection values returned are visual (i.e., x will always always be
4208  *  <= y).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
4209  *  (LtoR), compare the caretOffset to the start and end of the selection
4210  *  (e.g., caretOffset is start of selection implies that the selection is RtoL).
4211  * @see #getSelectionRange
4212  * @exception SWTException <ul>
4213  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4214  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4215  * </ul>
4216  */
4217 public Point getSelection() {
4218     checkWidget();
4219     return new Point(selection.x, selection.y);
4220 }
4221 /**
4222  * Returns the selection.
4223  *
4224  * @return start and length of the selection, x is the offset of the
4225  *  first selected character, relative to the first character of the
4226  *  widget content. y is the length of the selection.
4227  *  The selection values returned are visual (i.e., length will always always be
4228  *  positive).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
4229  *  (LtoR), compare the caretOffset to the start and end of the selection
4230  *  (e.g., caretOffset is start of selection implies that the selection is RtoL).
4231  * @exception SWTException <ul>
4232  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4233  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4234  * </ul>
4235  */
4236 public Point getSelectionRange() {
4237     checkWidget();
4238     return new Point(selection.x, selection.y - selection.x);
4239 }
4240 /**
4241  * Returns the receiver's selection background color.
4242  *
4243  * @return the selection background color
4244  *
4245  * @exception SWTException <ul>
4246  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4247  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4248  * </ul>
4249  * @since 2.1
4250  */
4251 public Color getSelectionBackground() {
4252     checkWidget();
4253     if (selectionBackground is null) {
4254         return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
4255     }
4256     return selectionBackground;
4257 }
4258 /**
4259  * Gets the number of selected characters.
4260  *
4261  * @return the number of selected characters.
4262  * @exception SWTException <ul>
4263  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4264  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4265  * </ul>
4266  */
4267 public int getSelectionCount() {
4268     checkWidget();
4269     return getSelectionRange().y;
4270 }
4271 /**
4272  * Returns the receiver's selection foreground color.
4273  *
4274  * @return the selection foreground color
4275  *
4276  * @exception SWTException <ul>
4277  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4278  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4279  * </ul>
4280  * @since 2.1
4281  */
4282 public Color getSelectionForeground() {
4283     checkWidget();
4284     if (selectionForeground is null) {
4285         return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
4286     }
4287     return selectionForeground;
4288 }
4289 /**
4290  * Returns the selected text.
4291  *
4292  * @return selected text, or an empty String if there is no selection.
4293  * @exception SWTException <ul>
4294  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4295  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4296  * </ul>
4297  */
4298 public String getSelectionText() {
4299     checkWidget();
4300     return content.getTextRange(selection.x, selection.y - selection.x);
4301 }
4302 public override int getStyle() {
4303     int style = super.getStyle();
4304     style &= ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.MIRRORED);
4305     if (isMirrored()) {
4306         style |= SWT.RIGHT_TO_LEFT | SWT.MIRRORED;
4307     } else {
4308         style |= SWT.LEFT_TO_RIGHT;
4309     }
4310     return style;
4311 }
4312 
4313 /**
4314  * Returns the text segments that should be treated as if they
4315  * had a different direction than the surrounding text.
4316  *
4317  * @param lineOffset offset of the first character in the line.
4318  *  0 based from the beginning of the document.
4319  * @param line text of the line to specify bidi segments for
4320  * @return text segments that should be treated as if they had a
4321  *  different direction than the surrounding text. Only the start
4322  *  index of a segment is specified, relative to the start of the
4323  *  line. Always starts with 0 and ends with the line length.
4324  * @exception IllegalArgumentException <ul>
4325  *    <li>ERROR_INVALID_ARGUMENT - if the segment indices returned
4326  *      by the listener do not start with 0, are not in ascending order,
4327  *      exceed the line length or have duplicates</li>
4328  * </ul>
4329  */
4330 int [] getBidiSegments(int lineOffset, String line) {
4331     if (!isBidi()) return null;
4332     if (!isListening(LineGetSegments)) {
4333         return getBidiSegmentsCompatibility(line, lineOffset);
4334     }
4335     StyledTextEvent event = sendLineEvent(LineGetSegments, lineOffset, line);
4336     int lineLength = cast(int)/*64bit*/line.length;
4337     int[] segments;
4338     if (event is null || event.segments is null || event.segments.length is 0) {
4339         segments = [0, lineLength];
4340     } else {
4341         auto segmentCount = event.segments.length;
4342 
4343         // test segment index consistency
4344         if (event.segments[0] !is 0) {
4345             SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4346         }
4347         for (int i = 1; i < segmentCount; i++) {
4348             if (event.segments[i] <= event.segments[i - 1] || event.segments[i] > lineLength) {
4349                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4350             }
4351         }
4352         // ensure that last segment index is line end offset
4353         if (event.segments[segmentCount - 1] !is lineLength) {
4354             segments = new int[segmentCount + 1];
4355             System.arraycopy(event.segments, 0, segments, 0, segmentCount);
4356             segments[segmentCount] = lineLength;
4357         } else {
4358             segments = event.segments;
4359         }
4360     }
4361     return segments;
4362 }
4363 /**
4364  * @see #getBidiSegments
4365  * Supports deprecated setBidiColoring API. Remove when API is removed.
4366  */
4367 int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
4368     int lineLength = cast(int)/*64bit*/line.length;
4369     if (!bidiColoring) {
4370         return [0, lineLength];
4371     }
4372     StyleRange [] styles = null;
4373     StyledTextEvent event = getLineStyleData(lineOffset, line);
4374     if (event !is null) {
4375         styles = event.styles;
4376     } else {
4377         styles = renderer.getStyleRanges(lineOffset, lineLength, true);
4378     }
4379     if (styles is null || styles.length is 0) {
4380         return [0, lineLength];
4381     }
4382     int k=0, count = 1;
4383     while (k < styles.length && styles[k].start is 0 && styles[k].length is lineLength) {
4384         k++;
4385     }
4386     int[] offsets = new int[(styles.length - k) * 2 + 2];
4387     for (int i = k; i < styles.length; i++) {
4388         StyleRange style = styles[i];
4389         int styleLineStart = Math.max(style.start - lineOffset, 0);
4390         int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
4391         styleLineEnd = cast(int)/*64bit*/Math.min (styleLineEnd, line.length );
4392         if (i > 0 && count > 1 &&
4393             ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
4394              (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
4395              style.similarTo(styles[i-1])) {
4396             offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
4397             offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
4398         } else {
4399             if (styleLineStart > offsets[count - 1]) {
4400                 offsets[count] = styleLineStart;
4401                 count++;
4402             }
4403             offsets[count] = styleLineEnd;
4404             count++;
4405         }
4406     }
4407     // add offset for last non-colored segment in line, if any
4408     if (lineLength > offsets[count-1]) {
4409         offsets [count] = lineLength;
4410         count++;
4411     }
4412     if (count is offsets.length) {
4413         return offsets;
4414     }
4415     int [] result = new int [count];
4416     System.arraycopy (offsets, 0, result, 0, count);
4417     return result;
4418 }
4419 /**
4420  * Returns the style range at the given offset.
4421  * <p>
4422  * Returns null if a LineStyleListener has been set or if a style is not set
4423  * for the offset.
4424  * Should not be called if a LineStyleListener has been set since the
4425  * listener maintains the styles.
4426  * </p>
4427  *
4428  * @param offset the offset to return the style for.
4429  *  0 <= offset < getCharCount() must be true.
4430  * @return a StyleRange with start is offset and length is 1, indicating
4431  *  the style at the given offset. null if a LineStyleListener has been set
4432  *  or if a style is not set for the given offset.
4433  * @exception SWTException <ul>
4434  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4435  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4436  * </ul>
4437  * @exception IllegalArgumentException <ul>
4438  *   <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
4439  * </ul>
4440  */
4441 public StyleRange getStyleRangeAtOffset(int offset) {
4442     checkWidget();
4443     if (offset < 0 || offset >= getCharCount()) {
4444         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4445     }
4446     if (!isListening(LineGetStyle)) {
4447         StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
4448         if (ranges !is null) return ranges[0];
4449     }
4450     return null;
4451 }
4452 /**
4453  * Returns the styles.
4454  * <p>
4455  * Returns an empty array if a LineStyleListener has been set.
4456  * Should not be called if a LineStyleListener has been set since the
4457  * listener maintains the styles.
4458  * <p></p>
4459  * Note: Because a StyleRange includes the start and length, the
4460  * same instance cannot occur multiple times in the array of styles.
4461  * If the same style attributes, such as font and color, occur in
4462  * multiple StyleRanges, <code>getStyleRanges(bool)</code>
4463  * can be used to get the styles without the ranges.
4464  * </p>
4465  *
4466  * @return the styles or an empty array if a LineStyleListener has been set.
4467  *
4468  * @exception SWTException <ul>
4469  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4470  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4471  * </ul>
4472  *
4473  * @see #getStyleRanges(bool)
4474  */
4475 public StyleRange[] getStyleRanges() {
4476     checkWidget();
4477     return getStyleRanges(0, content.getCharCount(), true);
4478 }
4479 /**
4480  * Returns the styles.
4481  * <p>
4482  * Returns an empty array if a LineStyleListener has been set.
4483  * Should not be called if a LineStyleListener has been set since the
4484  * listener maintains the styles.
4485  * </p><p>
4486  * Note: When <code>includeRanges</code> is true, the start and length
4487  * fields of each StyleRange will be valid, however the StyleRange
4488  * objects may need to be cloned. When <code>includeRanges</code> is
4489  * false, <code>getRanges(int, int)</code> can be used to get the
4490  * associated ranges.
4491  * </p>
4492  *
4493  * @param includeRanges whether the start and length field of the StyleRanges should be set.
4494  *
4495  * @return the styles or an empty array if a LineStyleListener has been set.
4496  *
4497  * @exception SWTException <ul>
4498  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4499  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4500  * </ul>
4501  *
4502  * @since 3.2
4503  *
4504  * @see #getRanges(int, int)
4505  * @see #setStyleRanges(int[], StyleRange[])
4506  */
4507 public StyleRange[] getStyleRanges(bool includeRanges) {
4508     checkWidget();
4509     return getStyleRanges(0, content.getCharCount(), includeRanges);
4510 }
4511 /**
4512  * Returns the styles for the given text range.
4513  * <p>
4514  * Returns an empty array if a LineStyleListener has been set.
4515  * Should not be called if a LineStyleListener has been set since the
4516  * listener maintains the styles.
4517  * </p><p>
4518  * Note: Because the StyleRange includes the start and length, the
4519  * same instance cannot occur multiple times in the array of styles.
4520  * If the same style attributes, such as font and color, occur in
4521  * multiple StyleRanges, <code>getStyleRanges(int, int, bool)</code>
4522  * can be used to get the styles without the ranges.
4523  * </p>
4524  * @param start the start offset of the style ranges to return
4525  * @param length the number of style ranges to return
4526  *
4527  * @return the styles or an empty array if a LineStyleListener has
4528  *  been set.  The returned styles will reflect the given range.  The first
4529  *  returned <code>StyleRange</code> will have a starting offset >= start
4530  *  and the last returned <code>StyleRange</code> will have an ending
4531  *  offset <= start + length - 1
4532  *
4533  * @exception SWTException <ul>
4534  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4535  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4536  * </ul>
4537  * @exception IllegalArgumentException <ul>
4538  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4539  * </ul>
4540  *
4541  * @see #getStyleRanges(int, int, bool)
4542  *
4543  * @since 3.0
4544  */
4545 public StyleRange[] getStyleRanges(int start, int length) {
4546     checkWidget();
4547     return getStyleRanges(start, length, true);
4548 }
4549 /**
4550  * Returns the styles for the given text range.
4551  * <p>
4552  * Returns an empty array if a LineStyleListener has been set.
4553  * Should not be called if a LineStyleListener has been set since the
4554  * listener maintains the styles.
4555  * </p><p>
4556  * Note: When <code>includeRanges</code> is true, the start and length
4557  * fields of each StyleRange will be valid, however the StyleRange
4558  * objects may need to be cloned. When <code>includeRanges</code> is
4559  * false, <code>getRanges(int, int)</code> can be used to get the
4560  * associated ranges.
4561  * </p>
4562  *
4563  * @param start the start offset of the style ranges to return
4564  * @param length the number of style ranges to return
4565  * @param includeRanges whether the start and length field of the StyleRanges should be set.
4566  *
4567  * @return the styles or an empty array if a LineStyleListener has
4568  *  been set.  The returned styles will reflect the given range.  The first
4569  *  returned <code>StyleRange</code> will have a starting offset >= start
4570  *  and the last returned <code>StyleRange</code> will have an ending
4571  *  offset <= start + length - 1
4572  *
4573  * @exception SWTException <ul>
4574  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4575  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4576  * </ul>
4577  * @exception IllegalArgumentException <ul>
4578  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4579  * </ul>
4580  *
4581  * @since 3.2
4582  *
4583  * @see #getRanges(int, int)
4584  * @see #setStyleRanges(int[], StyleRange[])
4585  */
4586 public StyleRange[] getStyleRanges(int start, int length, bool includeRanges) {
4587     checkWidget();
4588     int contentLength = getCharCount();
4589     int end = start + length;
4590     if (start > end || start < 0 || end > contentLength) {
4591         SWT.error(SWT.ERROR_INVALID_RANGE);
4592     }
4593     if (!isListening(LineGetStyle)) {
4594         StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
4595         if (ranges !is null) return ranges;
4596     }
4597     return new StyleRange[0];
4598 }
4599 /**
4600  * Returns the tab width measured in characters.
4601  *
4602  * @return tab width measured in characters
4603  * @exception SWTException <ul>
4604  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4605  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4606  * </ul>
4607  */
4608 public int getTabs() {
4609     checkWidget();
4610     return tabLength;
4611 }
4612 /**
4613  * Returns a copy of the widget content.
4614  *
4615  * @return copy of the widget content
4616  * @exception SWTException <ul>
4617  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4618  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4619  * </ul>
4620  */
4621 public String getText() {
4622     checkWidget();
4623     return content.getTextRange(0, getCharCount());
4624 }
4625 /**
4626  * Returns the widget content between the two offsets.
4627  *
4628  * @param start offset of the first character in the returned String
4629  * @param end offset of the last character in the returned String
4630  * @return widget content starting at start and ending at end
4631  * @see #getTextRange(int,int)
4632  * @exception SWTException <ul>
4633  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4634  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4635  * </ul>
4636  * @exception IllegalArgumentException <ul>
4637  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4638  * </ul>
4639  */
4640 public String getText(int start, int end) {
4641     checkWidget();
4642     int contentLength = getCharCount();
4643     if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
4644         SWT.error(SWT.ERROR_INVALID_RANGE);
4645     }
4646     auto res = content.getTextRange(start, content.getCharCount() - start);
4647     return res[0 .. res.offsetAfter(end - start)];
4648 }
4649 /**
4650  * Returns the smallest bounding rectangle that includes the characters between two offsets.
4651  *
4652  * @param start offset of the first character included in the bounding box
4653  * @param end offset of the last character included in the bounding box
4654  * @return bounding box of the text between start and end
4655  * @exception SWTException <ul>
4656  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4657  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4658  * </ul>
4659  * @exception IllegalArgumentException <ul>
4660  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4661  * </ul>
4662  * @since 3.1
4663  */
4664 public Rectangle getTextBounds(int start, int end) {
4665     checkWidget();
4666     int contentLength = getCharCount();
4667     if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
4668         SWT.error(SWT.ERROR_INVALID_RANGE);
4669     }
4670     int lineStart = content.getLineAtOffset(start);
4671     int lineEnd = content.getLineAtOffset(end);
4672     Rectangle rect;
4673     int y = getLinePixel(lineStart);
4674     int height = 0;
4675     int left = 0x7fffffff, right = 0;
4676     for (int i = lineStart; i <= lineEnd; i++) {
4677         int lineOffset = content.getOffsetAtLine(i);
4678         TextLayout layout = renderer.getTextLayout(i);
4679         int length = cast(int)/*64bit*/layout.getText().length;
4680         if (length > 0) {
4681             if (i is lineStart) {
4682                 if (i is lineEnd) {
4683                     rect = layout.getBounds(start - lineOffset, end - lineOffset);
4684                 } else {
4685                     rect = layout.getBounds(start - lineOffset, length);
4686                 }
4687                 y += rect.y;
4688             } else if (i is lineEnd) {
4689                 rect = layout.getBounds(0, end - lineOffset);
4690             } else {
4691                 rect = layout.getBounds();
4692             }
4693             left = Math.min(left, rect.x);
4694             right = Math.max(right, rect.x + rect.width);
4695             height += rect.height;
4696         } else {
4697             height += renderer.getLineHeight();
4698         }
4699         renderer.disposeTextLayout(layout);
4700     }
4701     rect = new Rectangle (left, y, right-left, height);
4702     rect.x += leftMargin - horizontalScrollOffset;
4703     return rect;
4704 }
4705 /**
4706  * Returns the widget content starting at start for length characters.
4707  *
4708  * @param start offset of the first character in the returned String
4709  * @param length number of characters to return
4710  * @return widget content starting at start and extending length characters.
4711  * @exception SWTException <ul>
4712  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4713  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4714  * </ul>
4715  * @exception IllegalArgumentException <ul>
4716  *   <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
4717  * </ul>
4718  */
4719 public String getTextRange(int start, int length) {
4720     checkWidget();
4721     int contentLength = getCharCount();
4722     int end = start + length;
4723     if (start > end || start < 0 || end > contentLength) {
4724         SWT.error(SWT.ERROR_INVALID_RANGE);
4725     }
4726     return content.getTextRange(start, length);
4727 }
4728 /**
4729  * Returns the maximum number of characters that the receiver is capable of holding.
4730  *
4731  * @return the text limit
4732  *
4733  * @exception SWTException <ul>
4734  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4735  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4736  * </ul>
4737  */
4738 public int getTextLimit() {
4739     checkWidget();
4740     return textLimit;
4741 }
4742 /**
4743  * Gets the top index.
4744  * <p>
4745  * The top index is the index of the fully visible line that is currently
4746  * at the top of the widget or the topmost partially visible line if no line is fully visible.
4747  * The top index changes when the widget is scrolled. Indexing is zero based.
4748  * </p>
4749  *
4750  * @return the index of the top line
4751  * @exception SWTException <ul>
4752  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4753  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4754  * </ul>
4755  */
4756 public int getTopIndex() {
4757     checkWidget();
4758     return topIndex;
4759 }
4760 /**
4761  * Gets the top pixel.
4762  * <p>
4763  * The top pixel is the pixel position of the line that is
4764  * currently at the top of the widget. The text widget can be scrolled by pixels
4765  * by dragging the scroll thumb so that a partial line may be displayed at the top
4766  * the widget.  The top pixel changes when the widget is scrolled.  The top pixel
4767  * does not include the widget trimming.
4768  * </p>
4769  *
4770  * @return pixel position of the top line
4771  * @exception SWTException <ul>
4772  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4773  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4774  * </ul>
4775  */
4776 public int getTopPixel() {
4777     checkWidget();
4778     return getVerticalScrollOffset();
4779 }
4780 /**
4781  * Returns the vertical scroll increment.
4782  *
4783  * @return vertical scroll increment.
4784  */
4785 int getVerticalIncrement() {
4786     return renderer.getLineHeight();
4787 }
4788 int getVerticalScrollOffset() {
4789     if (verticalScrollOffset is -1) {
4790         renderer.calculate(0, topIndex);
4791         int height = 0;
4792         for (int i = 0; i < topIndex; i++) {
4793             height += renderer.getLineHeight(i);
4794         }
4795         height -= topIndexY;
4796         verticalScrollOffset = height;
4797     }
4798     return verticalScrollOffset;
4799 }
4800 int getVisualLineIndex(TextLayout layout, int offsetInLine) {
4801     int lineIndex = layout.getLineIndex(offsetInLine);
4802     int[] offsets = layout.getLineOffsets();
4803     if (lineIndex !is 0 && offsetInLine is offsets[lineIndex]) {
4804         int lineY = layout.getLineBounds(lineIndex).y;
4805         int caretY = getCaret().getLocation().y - topMargin - getLinePixel(getCaretLine());
4806         if (lineY > caretY) lineIndex--;
4807     }
4808     return lineIndex;
4809 }
4810 int getCaretDirection() {
4811     if (!isBidiCaret()) return SWT.DEFAULT;
4812     if (ime.getCompositionOffset() !is -1) return SWT.DEFAULT;
4813     if (!updateCaretDirection && caretDirection !is SWT.NULL) return caretDirection;
4814     updateCaretDirection = false;
4815     int caretLine = getCaretLine();
4816     int lineOffset = content.getOffsetAtLine(caretLine);
4817     String line = content.getLine(caretLine);
4818     int offset = caretOffset - lineOffset;
4819     int lineLength = cast(int)/*64bit*/line.length;
4820     if (lineLength is 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT;
4821     if (caretAlignment is PREVIOUS_OFFSET_TRAILING && offset > 0)
4822         offset = cast(int)/*64bit*/line.offsetBefore(offset);
4823     if (offset is lineLength && offset > 0)
4824         offset = cast(int)/*64bit*/line.offsetBefore(offset);
4825     while (offset > 0 && Character.isDigit(line.dcharAt(offset)))
4826         offset = cast(int)/*64bit*/line.offsetBefore(offset);
4827     if (offset is 0 && Character.isDigit(line.dcharAt(offset))) {
4828         return isMirrored() ? SWT.RIGHT : SWT.LEFT;
4829     }
4830     TextLayout layout = renderer.getTextLayout(caretLine);
4831     int level = layout.getLevel(offset);
4832     renderer.disposeTextLayout(layout);
4833     return ((level & 1) !is 0) ? SWT.RIGHT : SWT.LEFT;
4834 }
4835 /*
4836  * Returns the index of the line the caret is on.
4837  */
4838 int getCaretLine() {
4839     return content.getLineAtOffset(caretOffset);
4840 }
4841 int getWrapWidth () {
4842     if (wordWrap && !isSingleLine()) {
4843         int width = clientAreaWidth - leftMargin - rightMargin - getCaretWidth();
4844         return width > 0 ? width : 1;
4845     }
4846     return -1;
4847 }
4848 int getWordNext (int offset, int movement) {
4849     int newOffset, lineOffset;
4850     String lineText;
4851     if (offset >= getCharCount()) {
4852         newOffset = offset;
4853         int lineIndex = content.getLineCount() - 1;
4854         lineOffset = content.getOffsetAtLine(lineIndex);
4855         lineText = content.getLine(lineIndex);
4856     } else {
4857         int lineIndex = content.getLineAtOffset(offset);
4858         lineOffset = content.getOffsetAtLine(lineIndex);
4859         lineText = content.getLine(lineIndex);
4860         int lineLength = cast(int)/*64bit*/lineText.length;
4861         if (offset is lineOffset + lineLength) {
4862             newOffset = content.getOffsetAtLine(lineIndex + 1);
4863         } else {
4864             TextLayout layout = renderer.getTextLayout(lineIndex);
4865             newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
4866             renderer.disposeTextLayout(layout);
4867         }
4868     }
4869     return sendWordBoundaryEvent(WordNext, movement, offset, newOffset, lineText, lineOffset);
4870 }
4871 int getWordPrevious(int offset, int movement) {
4872     int newOffset, lineOffset;
4873     String lineText;
4874     if (offset <= 0) {
4875         newOffset = 0;
4876         int lineIndex = content.getLineAtOffset(newOffset);
4877         lineOffset = content.getOffsetAtLine(lineIndex);
4878         lineText = content.getLine(lineIndex);
4879     } else {
4880         int lineIndex = content.getLineAtOffset(offset);
4881         lineOffset = content.getOffsetAtLine(lineIndex);
4882         lineText = content.getLine(lineIndex);
4883         if (offset is lineOffset) {
4884             String nextLineText = content.getLine(lineIndex - 1);
4885             int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
4886             newOffset = cast(int)/*64bit*/(nextLineOffset + nextLineText.length);
4887         } else {
4888             TextLayout layout = renderer.getTextLayout(lineIndex);
4889             newOffset = lineOffset + layout.getPreviousOffset(offset - lineOffset, movement);
4890             renderer.disposeTextLayout(layout);
4891         }
4892     }
4893     return sendWordBoundaryEvent(WordPrevious, movement, offset, newOffset, lineText, lineOffset);
4894 }
4895 /**
4896  * Returns whether the widget wraps lines.
4897  *
4898  * @return true if widget wraps lines, false otherwise
4899  * @since 2.0
4900  */
4901 public bool getWordWrap() {
4902     checkWidget();
4903     return wordWrap;
4904 }
4905 /**
4906  * Returns the location of the given offset.
4907  * <p>
4908  * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
4909  * </p>
4910  *
4911  * @return location of the character at the given offset in the line.
4912  */
4913 Point getPointAtOffset(int offset) {
4914     int lineIndex = content.getLineAtOffset(offset);
4915     String line = content.getLine(lineIndex);
4916     int lineOffset = content.getOffsetAtLine(lineIndex);
4917     int offsetInLine = offset - lineOffset;
4918     int lineLength = cast(int)/*64bit*/line.length;
4919     if (lineIndex < content.getLineCount() - 1) {
4920         int afterEndLineOffset = content.getOffsetAtLine(lineIndex + 1);
4921         if (lineLength < offsetInLine && offsetInLine < afterEndLineOffset) {
4922             offsetInLine = lineLength;
4923         }
4924     }
4925     Point point;
4926     TextLayout layout = renderer.getTextLayout(lineIndex);
4927     if (lineLength !is 0  && offsetInLine <= lineLength) {
4928         if (offsetInLine is lineLength) {
4929             point = layout.getLocation(getPreviousCharOffset(lineIndex, offsetInLine), true);
4930         } else {
4931             switch (caretAlignment) {
4932                 case OFFSET_LEADING:
4933                     point = layout.getLocation(offsetInLine, false);
4934                     break;
4935                 case PREVIOUS_OFFSET_TRAILING:
4936                 default:
4937                     if (offsetInLine is 0) {
4938                         point = layout.getLocation(offsetInLine, false);
4939                     } else {
4940                         point = layout.getLocation(getPreviousCharOffset(lineIndex, offsetInLine), true);
4941                     }
4942                     break;
4943             }
4944         }
4945     } else {
4946         point = new Point(layout.getIndent(), 0);
4947     }
4948     renderer.disposeTextLayout(layout);
4949     point.x += leftMargin - horizontalScrollOffset;
4950     point.y += getLinePixel(lineIndex);
4951     return point;
4952 }
4953 /**
4954  * Inserts a string.  The old selection is replaced with the new text.
4955  *
4956  * @param string the string
4957  * @see #replaceTextRange(int,int,String)
4958  * @exception SWTException <ul>
4959  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4960  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4961  * </ul>
4962  */
4963 public void insert(String string) {
4964     checkWidget();
4965     // SWT extension: allow null for zero length string
4966 //     if (string is null) {
4967 //         SWT.error(SWT.ERROR_NULL_ARGUMENT);
4968 //     }
4969     Point sel = getSelectionRange();
4970     replaceTextRange(sel.x, sel.y, string);
4971 }
4972 /**
4973  * Creates content change listeners and set the default content model.
4974  */
4975 void installDefaultContent() {
4976     textChangeListener = new class() TextChangeListener {
4977         public void textChanging(TextChangingEvent event) {
4978             handleTextChanging(event);
4979         }
4980         public void textChanged(TextChangedEvent event) {
4981             handleTextChanged(event);
4982         }
4983         public void textSet(TextChangedEvent event) {
4984             handleTextSet(event);
4985         }
4986     };
4987     content = new DefaultContent();
4988     content.addTextChangeListener(textChangeListener);
4989 }
4990 /**
4991  * Adds event listeners
4992  */
4993 void installListeners() {
4994     ScrollBar verticalBar = getVerticalBar();
4995     ScrollBar horizontalBar = getHorizontalBar();
4996 
4997     listener = new class() Listener {
4998         public void handleEvent(Event event) {
4999             switch (event.type) {
5000                 case SWT.Dispose: handleDispose(event); break;
5001                 case SWT.KeyDown: handleKeyDown(event); break;
5002                 case SWT.KeyUp: handleKeyUp(event); break;
5003                 case SWT.MouseDown: handleMouseDown(event); break;
5004                 case SWT.MouseUp: handleMouseUp(event); break;
5005                 case SWT.MouseMove: handleMouseMove(event); break;
5006                 case SWT.Paint: handlePaint(event); break;
5007                 case SWT.Resize: handleResize(event); break;
5008                 case SWT.Traverse: handleTraverse(event); break;
5009                 default:
5010             }
5011         }
5012     };
5013     addListener(SWT.Dispose, listener);
5014     addListener(SWT.KeyDown, listener);
5015     addListener(SWT.KeyUp, listener);
5016     addListener(SWT.MouseDown, listener);
5017     addListener(SWT.MouseUp, listener);
5018     addListener(SWT.MouseMove, listener);
5019     addListener(SWT.Paint, listener);
5020     addListener(SWT.Resize, listener);
5021     addListener(SWT.Traverse, listener);
5022     ime.addListener(SWT.ImeComposition, new class() Listener {
5023         public void handleEvent(Event event) {
5024             switch (event.detail) {
5025                 case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break;
5026                 case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break;
5027                 case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break;
5028                 default:
5029             }
5030         }
5031     });
5032     if (verticalBar !is null) {
5033         verticalBar.addListener(SWT.Selection, new class() Listener {
5034             public void handleEvent(Event event) {
5035                 handleVerticalScroll(event);
5036             }
5037         });
5038     }
5039     if (horizontalBar !is null) {
5040         horizontalBar.addListener(SWT.Selection, new class() Listener {
5041             public void handleEvent(Event event) {
5042                 handleHorizontalScroll(event);
5043             }
5044         });
5045     }
5046 }
5047 void internalRedrawRange(int start, int length) {
5048     if (length <= 0) return;
5049     int end = start + length;
5050     int startLine = content.getLineAtOffset(start);
5051     int endLine = content.getLineAtOffset(end);
5052     int partialBottomIndex = getPartialBottomIndex();
5053     int partialTopIndex = getPartialTopIndex();
5054     if (startLine > partialBottomIndex || endLine < partialTopIndex) {
5055         return;
5056     }
5057     if (partialTopIndex > startLine) {
5058         startLine = partialTopIndex;
5059         start = 0;
5060     } else {
5061         start -= content.getOffsetAtLine(startLine);
5062     }
5063     if (partialBottomIndex < endLine) {
5064         endLine = partialBottomIndex + 1;
5065         end = 0;
5066     } else {
5067         end -= content.getOffsetAtLine(endLine);
5068     }
5069 
5070     TextLayout layout = renderer.getTextLayout(startLine);
5071     int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
5072     int[] offsets = layout.getLineOffsets();
5073     auto startIndex = layout.getLineIndex
5074         (cast(int)/*64bit*/Math.min(start, layout.getText().length));
5075 
5076     /* Redraw end of line before start line if wrapped and start offset is first char */
5077     if (wordWrap && startIndex > 0 && offsets[startIndex] is start) {
5078         Rectangle rect = layout.getLineBounds(startIndex - 1);
5079         rect.x = rect.width;
5080         rect.width = clientAreaWidth - rightMargin - rect.x;
5081         rect.x += lineX;
5082         rect.y += startLineY;
5083         super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5084     }
5085 
5086     if (startLine is endLine) {
5087         int endIndex = layout.getLineIndex
5088             (cast(int)/*64bit*/Math.min(end, layout.getText().length));
5089         if (startIndex is endIndex) {
5090             /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
5091             Rectangle rect = layout.getBounds( start, getPreviousCharOffset(startLine, end) );
5092             rect.x += lineX;
5093             rect.y += startLineY;
5094             super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5095             renderer.disposeTextLayout(layout);
5096             return;
5097         }
5098     }
5099 
5100     /* Redraw start line from the start offset to the end of client area */
5101     Rectangle startRect = layout.getBounds( start, getPreviousCharOffset(startLine, offsets[startIndex + 1]) );
5102     if (startRect.height is 0) {
5103         Rectangle bounds = layout.getLineBounds(startIndex);
5104         startRect.x = bounds.width;
5105         startRect.y = bounds.y;
5106         startRect.height = bounds.height;
5107     }
5108     startRect.x += lineX;
5109     startRect.y += startLineY;
5110     startRect.width = clientAreaWidth - rightMargin - startRect.x;
5111     super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);
5112 
5113     /* Redraw end line from the beginning of the line to the end offset */
5114     if (startLine !is endLine) {
5115         renderer.disposeTextLayout(layout);
5116         layout = renderer.getTextLayout(endLine);
5117         offsets = layout.getLineOffsets();
5118     }
5119     int endIndex = layout.getLineIndex
5120         (cast(int)/*64bit*/Math.min(end, layout.getText().length));
5121     Rectangle endRect = layout.getBounds(offsets[endIndex], getPreviousCharOffset(endLine, end));
5122     if (endRect.height is 0) {
5123         Rectangle bounds = layout.getLineBounds(endIndex);
5124         endRect.y = bounds.y;
5125         endRect.height = bounds.height;
5126     }
5127     endRect.x += lineX;
5128     endRect.y += getLinePixel(endLine);
5129     super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
5130     renderer.disposeTextLayout(layout);
5131 
5132     /* Redraw all lines in between start and end line */
5133     int y = startRect.y + startRect.height;
5134     if (endRect.y > y) {
5135         super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
5136     }
5137 }
5138 void handleCompositionOffset (Event event) {
5139     int[1] trailing;
5140     event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
5141     event.count = trailing[0];
5142 }
5143 void handleCompositionSelection (Event event) {
5144     event.start = selection.x;
5145     event.end = selection.y;
5146     event.text = getSelectionText();
5147 }
5148 void handleCompositionChanged(Event event) {
5149     String text = event.text;
5150     int start = event.start;
5151     int end = event.end;
5152     int length = cast(int)/*64bit*/text.length;
5153     if (length is ime.getCommitCount()) {
5154         content.replaceTextRange(start, end - start, "");
5155         caretOffset = ime.getCompositionOffset();
5156         caretWidth = 0;
5157         caretDirection = SWT.NULL;
5158     } else {
5159         content.replaceTextRange(start, end - start, text);
5160         caretOffset = ime.getCaretOffset();
5161         if (ime.getWideCaret()) {
5162             start = ime.getCompositionOffset();
5163             int lineIndex = getCaretLine();
5164             int lineOffset = content.getOffsetAtLine(lineIndex);
5165             TextLayout layout = renderer.getTextLayout(lineIndex);
5166             caretWidth = layout.getBounds(start - lineOffset, getPreviousCharOffset(lineIndex, start + length - lineOffset)).width;
5167             renderer.disposeTextLayout(layout);
5168         }
5169     }
5170     showCaret();
5171 }
5172 /**
5173  * Frees resources.
5174  */
5175 void handleDispose(Event event) {
5176     removeListener(SWT.Dispose, listener);
5177     notifyListeners(SWT.Dispose, event);
5178     event.type = SWT.None;
5179 
5180     clipboard.dispose();
5181     if (renderer !is null) {
5182         renderer.dispose();
5183         renderer = null;
5184     }
5185     if (content !is null) {
5186         content.removeTextChangeListener(textChangeListener);
5187         content = null;
5188     }
5189     if (defaultCaret !is null) {
5190         defaultCaret.dispose();
5191         defaultCaret = null;
5192     }
5193     if (leftCaretBitmap !is null) {
5194         leftCaretBitmap.dispose();
5195         leftCaretBitmap = null;
5196     }
5197     if (rightCaretBitmap !is null) {
5198         rightCaretBitmap.dispose();
5199         rightCaretBitmap = null;
5200     }
5201     if (isBidiCaret()) {
5202         BidiUtil.removeLanguageListener(this);
5203     }
5204     selectionBackground = null;
5205     selectionForeground = null;
5206     textChangeListener = null;
5207     selection = null;
5208     doubleClickSelection = null;
5209     keyActionMap = null;
5210     background = null;
5211     foreground = null;
5212     clipboard = null;
5213 }
5214 /**
5215  * Scrolls the widget horizontally.
5216  */
5217 void handleHorizontalScroll(Event event) {
5218     int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
5219     scrollHorizontal(scrollPixel, false);
5220 }
5221 /**
5222  * If an action has been registered for the key stroke execute the action.
5223  * Otherwise, if a character has been entered treat it as new content.
5224  *
5225  * @param event keyboard event
5226  */
5227 void handleKey(Event event) {
5228     int action;
5229     caretAlignment = PREVIOUS_OFFSET_TRAILING;
5230     if (event.keyCode !is 0) {
5231         // special key pressed (e.g., F1)
5232         action = getKeyBinding(event.keyCode | event.stateMask);
5233     } else {
5234         // character key pressed
5235         action = getKeyBinding(event.character | event.stateMask);
5236         if (action is SWT.NULL) {
5237             // see if we have a control character
5238             if ((event.stateMask & SWT.CTRL) !is 0 && (event.character >= 0) && event.character <= 31) {
5239                 // get the character from the CTRL+char sequence, the control
5240                 // key subtracts 64 from the value of the key that it modifies
5241                 int c = event.character + 64;
5242                 action = getKeyBinding(c | event.stateMask);
5243             }
5244         }
5245     }
5246     if (action is SWT.NULL) {
5247         bool ignore = false;
5248 
5249         if (IS_CARBON) {
5250             // Ignore accelerator key combinations (we do not want to
5251             // insert a character in the text in this instance). Do not
5252             // ignore COMMAND+ALT combinations since that key sequence
5253             // produces characters on the mac.
5254             ignore = (event.stateMask ^ SWT.COMMAND) is 0 ||
5255                     (event.stateMask ^ (SWT.COMMAND | SWT.SHIFT)) is 0;
5256         } else if (IS_MOTIF) {
5257             // Ignore accelerator key combinations (we do not want to
5258             // insert a character in the text in this instance). Do not
5259             // ignore ALT combinations since this key sequence
5260             // produces characters on motif.
5261             ignore = (event.stateMask ^ SWT.CTRL) is 0 ||
5262                     (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) is 0;
5263         } else {
5264             // Ignore accelerator key combinations (we do not want to
5265             // insert a character in the text in this instance). Don't
5266             // ignore CTRL+ALT combinations since that is the Alt Gr
5267             // key on some keyboards.  See bug 20953.
5268             ignore = (event.stateMask ^ SWT.ALT) is 0 ||
5269                     (event.stateMask ^ SWT.CTRL) is 0 ||
5270                     (event.stateMask ^ (SWT.ALT | SWT.SHIFT)) is 0 ||
5271                     (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) is 0;
5272         }
5273         // -ignore anything below SPACE except for line delimiter keys and tab.
5274         // -ignore DEL
5275         if (!ignore && event.character > 31 && event.character !is SWT.DEL ||
5276             event.character is SWT.CR || event.character is SWT.LF ||
5277             event.character is TAB) {
5278             doContent(event.character);
5279             update();
5280         }
5281     } else {
5282         invokeAction(action);
5283     }
5284 }
5285 /**
5286  * If a VerifyKey listener exists, verify that the key that was entered
5287  * should be processed.
5288  *
5289  * @param event keyboard event
5290  */
5291 void handleKeyDown(Event event) {
5292     if (clipboardSelection is null) {
5293         clipboardSelection = new Point(selection.x, selection.y);
5294     }
5295 
5296     Event verifyEvent = new Event();
5297     verifyEvent.character = event.character;
5298     verifyEvent.keyCode = event.keyCode;
5299     verifyEvent.stateMask = event.stateMask;
5300     verifyEvent.doit = true;
5301     notifyListeners(VerifyKey, verifyEvent);
5302     if (verifyEvent.doit) {
5303         handleKey(event);
5304     }
5305 }
5306 /**
5307  * Update the Selection Clipboard.
5308  *
5309  * @param event keyboard event
5310  */
5311 void handleKeyUp(Event event) {
5312     if (clipboardSelection !is null) {
5313         if (clipboardSelection.x !is selection.x || clipboardSelection.y !is selection.y) {
5314             try {
5315                 if (selection.y - selection.x > 0) {
5316                     setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
5317                 }
5318             } catch (SWTError error) {
5319                 // Copy to clipboard failed. This happens when another application
5320                 // is accessing the clipboard while we copy. Ignore the error.
5321                 // Fixes 1GDQAVN
5322                 // Rethrow all other errors. Fixes bug 17578.
5323                 if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
5324                     throw error;
5325                 }
5326             }
5327         }
5328     }
5329     clipboardSelection = null;
5330 }
5331 /**
5332  * Updates the caret location and selection if mouse button 1 has been
5333  * pressed.
5334  */
5335 void handleMouseDown(Event event) {
5336     //force focus (object support)
5337     forceFocus();
5338 
5339     //drag detect
5340     if (dragDetect_ && checkDragDetect(event)) return;
5341 
5342     //paste clipboard selection
5343     if (event.button is 2) {
5344         String text = stringcast(getClipboardContent(DND.SELECTION_CLIPBOARD));
5345         if (text !is null && text.length > 0) {
5346             // position cursor
5347             doMouseLocationChange(event.x, event.y, false);
5348             // insert text
5349             Event e = new Event();
5350             e.start = selection.x;
5351             e.end = selection.y;
5352             e.text = getModelDelimitedText(text);
5353             sendKeyEvent(e);
5354         }
5355     }
5356 
5357     //set selection
5358     if ((event.button !is 1) || (IS_CARBON && (event.stateMask & SWT.MOD4) !is 0)) {
5359         return;
5360     }
5361     clickCount = event.count;
5362     if (clickCount is 1) {
5363         bool select = (event.stateMask & SWT.MOD2) !is 0;
5364         doMouseLocationChange(event.x, event.y, select);
5365     } else {
5366         if (doubleClickEnabled) {
5367             clearSelection(false);
5368             int offset = getOffsetAtPoint(event.x, event.y);
5369             int lineIndex = content.getLineAtOffset(offset);
5370             int lineOffset = content.getOffsetAtLine(lineIndex);
5371             int lineEnd = content.getCharCount();
5372             if (lineIndex + 1 < content.getLineCount()) {
5373                 lineEnd = content.getOffsetAtLine(lineIndex + 1);
5374             }
5375             int start, end;
5376             if ((clickCount & 1) is 0) {
5377                 start = Math.max(0, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
5378                 end = Math.min(content.getCharCount(), getWordNext(start, SWT.MOVEMENT_WORD_END));
5379             } else {
5380                 start = lineOffset;
5381                 end = lineEnd;
5382             }
5383             caretOffset = start;
5384             resetSelection();
5385             caretOffset = end;
5386             showCaret();
5387             doMouseSelection();
5388             doubleClickSelection = new Point(selection.x, selection.y);
5389         }
5390     }
5391 }
5392 /**
5393  * Updates the caret location and selection if mouse button 1 is pressed
5394  * during the mouse move.
5395  */
5396 void handleMouseMove(Event event) {
5397     if (clickCount is 0) return;
5398     doMouseLocationChange(event.x, event.y, true);
5399     update();
5400     doAutoScroll(event);
5401 }
5402 /**
5403  * Autoscrolling ends when the mouse button is released.
5404  */
5405 void handleMouseUp(Event event) {
5406     clickCount = 0;
5407     endAutoScroll();
5408     if (event.button is 1) {
5409         try {
5410             if (selection.y - selection.x > 0) {
5411                 setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
5412             }
5413         } catch (SWTError error) {
5414             // Copy to clipboard failed. This happens when another application
5415             // is accessing the clipboard while we copy. Ignore the error.
5416             // Fixes 1GDQAVN
5417             // Rethrow all other errors. Fixes bug 17578.
5418             if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
5419                 throw error;
5420             }
5421         }
5422     }
5423 }
5424 /**
5425  * Renders the invalidated area specified in the paint event.
5426  *
5427  * @param event paint event
5428  */
5429 void handlePaint(Event event) {
5430     if (event.width is 0 || event.height is 0) return;
5431     if (clientAreaWidth is 0 || clientAreaHeight is 0) return;
5432 
5433     int startLine = getLineIndex(event.y);
5434     int y = getLinePixel(startLine);
5435     int endY = event.y + event.height;
5436     GC gc = event.gc;
5437     Color background = getBackground();
5438     Color foreground = getForeground();
5439     if (endY > 0) {
5440         int lineCount = isSingleLine() ? 1 : content.getLineCount();
5441         int x = leftMargin - horizontalScrollOffset;
5442         for (int i = startLine; y < endY && i < lineCount; i++) {
5443             y += renderer.drawLine(i, x, y, gc, background, foreground);
5444         }
5445         if (y < endY) {
5446             gc.setBackground(background);
5447             drawBackground(gc, 0, y, clientAreaWidth, endY - y);
5448         }
5449     }
5450     // fill the margin background
5451     gc.setBackground(background);
5452     if (topMargin > 0) {
5453         drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
5454     }
5455     if (bottomMargin > 0) {
5456         drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
5457     }
5458     if (leftMargin > 0) {
5459         drawBackground(gc, 0, 0, leftMargin, clientAreaHeight);
5460     }
5461     if (rightMargin > 0) {
5462         drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
5463     }
5464 }
5465 /**
5466  * Recalculates the scroll bars. Rewraps all lines when in word
5467  * wrap mode.
5468  *
5469  * @param event resize event
5470  */
5471 void handleResize(Event event) {
5472     int oldHeight = clientAreaHeight;
5473     int oldWidth = clientAreaWidth;
5474     Rectangle clientArea = getClientArea();
5475     clientAreaHeight = clientArea.height;
5476     clientAreaWidth = clientArea.width;
5477     /* Redraw the old or new right/bottom margin if needed */
5478     if (oldWidth !is clientAreaWidth) {
5479         if (rightMargin > 0) {
5480             int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
5481             super.redraw(x, 0, rightMargin, oldHeight, false);
5482         }
5483     }
5484     if (oldHeight !is clientAreaHeight) {
5485         if (bottomMargin > 0) {
5486             int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
5487             super.redraw(0, y, oldWidth, bottomMargin, false);
5488         }
5489     }
5490     if (wordWrap) {
5491         if (oldWidth !is clientAreaWidth) {
5492             renderer.reset(0, content.getLineCount());
5493             verticalScrollOffset = -1;
5494             renderer.calculateIdle();
5495             super.redraw();
5496         }
5497         if (oldHeight !is clientAreaHeight) {
5498             if (oldHeight is 0) topIndexY = 0;
5499             setScrollBars(true);
5500         }
5501         setCaretLocation();
5502     } else  {
5503         renderer.calculateClientArea();
5504         setScrollBars(true);
5505         claimRightFreeSpace();
5506         // StyledText allows any value for horizontalScrollOffset when clientArea is zero
5507         // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
5508         if (clientAreaWidth !is 0) {
5509             ScrollBar horizontalBar = getHorizontalBar();
5510             if (horizontalBar !is null && horizontalBar.getVisible()) {
5511                 if (horizontalScrollOffset !is horizontalBar.getSelection()) {
5512                     horizontalBar.setSelection(horizontalScrollOffset);
5513                     horizontalScrollOffset = horizontalBar.getSelection();
5514                 }
5515             }
5516         }
5517     }
5518     claimBottomFreeSpace();
5519     //TODO FIX TOP INDEX DURING RESIZE
5520 //  if (oldHeight !is clientAreaHeight || wordWrap) {
5521 //      calculateTopIndex(0);
5522 //  }
5523 }
5524 /**
5525  * Updates the caret position and selection and the scroll bars to reflect
5526  * the content change.
5527  */
5528 void handleTextChanged(TextChangedEvent event) {
5529     int offset = ime.getCompositionOffset();
5530     if (offset !is -1 && lastTextChangeStart < offset) {
5531         ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount);
5532     }
5533     int firstLine = content.getLineAtOffset(lastTextChangeStart);
5534     resetCache(firstLine, 0);
5535     if (!isFixedLineHeight() && topIndex > firstLine) {
5536         topIndex = firstLine;
5537         topIndexY = 0;
5538         super.redraw();
5539     } else {
5540         int lastLine = firstLine + lastTextChangeNewLineCount;
5541         int firstLineTop = getLinePixel(firstLine);
5542         int newLastLineBottom = getLinePixel(lastLine + 1);
5543         if (lastLineBottom !is newLastLineBottom) {
5544             super.redraw();
5545         } else {
5546             super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
5547             redrawLinesBullet(renderer.redrawLines);
5548         }
5549     }
5550     renderer.redrawLines = null;
5551     // update selection/caret location after styles have been changed.
5552     // otherwise any text measuring could be incorrect
5553     //
5554     // also, this needs to be done after all scrolling. Otherwise,
5555     // selection redraw would be flushed during scroll which is wrong.
5556     // in some cases new text would be drawn in scroll source area even
5557     // though the intent is to scroll it.
5558     updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
5559     if (lastTextChangeReplaceLineCount > 0 || wordWrap) {
5560         claimBottomFreeSpace();
5561     }
5562     if (lastTextChangeReplaceCharCount > 0) {
5563         claimRightFreeSpace();
5564     }
5565 }
5566 /**
5567  * Updates the screen to reflect a pending content change.
5568  *
5569  * @param event .start the start offset of the change
5570  * @param event .newText text that is going to be inserted or empty String
5571  *  if no text will be inserted
5572  * @param event .replaceCharCount length of text that is going to be replaced
5573  * @param event .newCharCount length of text that is going to be inserted
5574  * @param event .replaceLineCount number of lines that are going to be replaced
5575  * @param event .newLineCount number of new lines that are going to be inserted
5576  */
5577 void handleTextChanging(TextChangingEvent event) {
5578     if (event.replaceCharCount < 0) {
5579         event.start += event.replaceCharCount;
5580         event.replaceCharCount *= -1;
5581     }
5582     lastTextChangeStart = event.start;
5583     lastTextChangeNewLineCount = event.newLineCount;
5584     lastTextChangeNewCharCount = event.newCharCount;
5585     lastTextChangeReplaceLineCount = event.replaceLineCount;
5586     lastTextChangeReplaceCharCount = event.replaceCharCount;
5587     int lineIndex = content.getLineAtOffset(event.start);
5588     int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
5589     int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
5590     lastLineBottom = destY;
5591     if (srcY < 0 && destY < 0) {
5592         lastLineBottom += srcY - destY;
5593         verticalScrollOffset += destY - srcY;
5594         calculateTopIndex(destY - srcY);
5595         setScrollBars(true);
5596     } else {
5597         scrollText(srcY, destY);
5598     }
5599 
5600     renderer.textChanging(event);
5601 
5602     // Update the caret offset if it is greater than the length of the content.
5603     // This is necessary since style range API may be called between the
5604     // handleTextChanging and handleTextChanged events and this API sets the
5605     // caretOffset.
5606     int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
5607     if (caretOffset > newEndOfText) caretOffset = newEndOfText;
5608 }
5609 /**
5610  * Called when the widget content is set programmatically, overwriting
5611  * the old content. Resets the caret position, selection and scroll offsets.
5612  * Recalculates the content width and scroll bars. Redraws the widget.
5613  *
5614  * @param event text change event.
5615  */
5616 void handleTextSet(TextChangedEvent event) {
5617     reset();
5618 }
5619 /**
5620  * Called when a traversal key is pressed.
5621  * Allow tab next traversal to occur when the widget is in single
5622  * line mode or in multi line and non-editable mode .
5623  * When in editable multi line mode we want to prevent the tab
5624  * traversal and receive the tab key event instead.
5625  *
5626  * @param event the event
5627  */
5628 void handleTraverse(Event event) {
5629     switch (event.detail) {
5630         case SWT.TRAVERSE_ESCAPE:
5631         case SWT.TRAVERSE_PAGE_NEXT:
5632         case SWT.TRAVERSE_PAGE_PREVIOUS:
5633             event.doit = true;
5634             break;
5635         case SWT.TRAVERSE_RETURN:
5636         case SWT.TRAVERSE_TAB_NEXT:
5637         case SWT.TRAVERSE_TAB_PREVIOUS:
5638             if ((getStyle() & SWT.SINGLE) !is 0) {
5639                 event.doit = true;
5640             } else {
5641                 if (!editable || (event.stateMask & SWT.MODIFIER_MASK) !is 0) {
5642                     event.doit = true;
5643                 }
5644             }
5645             break;
5646         default:
5647     }
5648 }
5649 /**
5650  * Scrolls the widget vertically.
5651  */
5652 void handleVerticalScroll(Event event) {
5653     int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
5654     scrollVertical(scrollPixel, false);
5655 }
5656 /**
5657  * Add accessibility support for the widget.
5658  */
5659 void initializeAccessible() {
5660     Accessible accessible = getAccessible();
5661     accessible.addAccessibleListener(new class() AccessibleAdapter {
5662         override
5663         public void getName (AccessibleEvent e) {
5664             String name = null;
5665             Label label = getAssociatedLabel ();
5666             if (label !is null) {
5667                 name = stripMnemonic (label.getText());
5668             }
5669             e.result = name;
5670         }
5671         override
5672         public void getHelp(AccessibleEvent e) {
5673             e.result = getToolTipText();
5674         }
5675         override
5676         public void getKeyboardShortcut(AccessibleEvent e) {
5677             String shortcut = null;
5678             Label label = getAssociatedLabel ();
5679             if (label !is null) {
5680                 String text = label.getText ();
5681                 if (text !is null) {
5682                     dchar mnemonic = _findMnemonic (text);
5683                     if (mnemonic !is '\0') {
5684                         shortcut = "Alt+"~dcharToString(mnemonic); //$NON-NLS-1$
5685                     }
5686                 }
5687             }
5688             e.result = shortcut;
5689         }
5690     });
5691     accessible.addAccessibleTextListener(new class() AccessibleTextAdapter {
5692         override
5693         public void getCaretOffset(AccessibleTextEvent e) {
5694             e.offset = this.outer.getCaretOffset();
5695         }
5696         override
5697         public void getSelectionRange(AccessibleTextEvent e) {
5698             Point selection = this.outer.getSelectionRange();
5699             e.offset = selection.x;
5700             e.length = selection.y;
5701         }
5702     });
5703     accessible.addAccessibleControlListener(new class() AccessibleControlAdapter {
5704         override
5705         public void getRole(AccessibleControlEvent e) {
5706             e.detail = ACC.ROLE_TEXT;
5707         }
5708         override
5709         public void getState(AccessibleControlEvent e) {
5710             int state = 0;
5711             if (isEnabled()) state |= ACC.STATE_FOCUSABLE;
5712             if (isFocusControl()) state |= ACC.STATE_FOCUSED;
5713             if (!isVisible()) state |= ACC.STATE_INVISIBLE;
5714             if (!getEditable()) state |= ACC.STATE_READONLY;
5715             e.detail = state;
5716         }
5717         override
5718         public void getValue(AccessibleControlEvent e) {
5719             e.result = this.outer.getText();
5720         }
5721     });
5722     addListener(SWT.FocusIn, new class(accessible) Listener {
5723         Accessible acc;
5724         this( Accessible acc ){ this.acc = acc; }
5725         public void handleEvent(Event event) {
5726             acc.setFocus(ACC.CHILDID_SELF);
5727         }
5728     });
5729 }
5730 /*
5731  * Return the Label immediately preceding the receiver in the z-order,
5732  * or null if none.
5733  */
5734 Label getAssociatedLabel () {
5735     Control[] siblings = getParent ().getChildren ();
5736     for (int i = 0; i < siblings.length; i++) {
5737         if (siblings [i] is this) {
5738             if (i > 0 && ( null !is cast(Label)siblings [i-1])) {
5739                 return cast(Label) siblings [i-1];
5740             }
5741         }
5742     }
5743     return null;
5744 }
5745 String stripMnemonic (String string) {
5746     int index = 0;
5747     int length_ = cast(int)/*64bit*/string.length;
5748     do {
5749         while ((index < length_) && (string[index] !is '&')) index++;
5750         if (++index >= length_) return string;
5751         if (string[index] !is '&') {
5752             return string.substring(0, index-1) ~ string.substring(index, length_);
5753         }
5754         index++;
5755     } while (index < length_);
5756     return string;
5757 }
5758 /*
5759  * Return the lowercase of the first non-'&' character following
5760  * an '&' character in the given string. If there are no '&'
5761  * characters in the given string, return '\0'.
5762  */
5763 dchar _findMnemonic (String string) {
5764     if (string is null) return '\0';
5765     int index = 0;
5766     int length_ = cast(int)/*64bit*/string.length;
5767     do {
5768         while (index < length_ && string[index] !is '&') index++;
5769         if (++index >= length_) return '\0';
5770         if (string[index] !is '&') return Character.toLowerCase (string.dcharAt (index));
5771         index++;
5772     } while (index < length_);
5773     return '\0';
5774 }
5775 /**
5776  * Executes the action.
5777  *
5778  * @param action one of the actions defined in ST.java
5779  */
5780 public void invokeAction(int action) {
5781     checkWidget();
5782     updateCaretDirection = true;
5783     switch (action) {
5784         // Navigation
5785         case ST.LINE_UP:
5786             doLineUp(false);
5787             clearSelection(true);
5788             break;
5789         case ST.LINE_DOWN:
5790             doLineDown(false);
5791             clearSelection(true);
5792             break;
5793         case ST.LINE_START:
5794             doLineStart();
5795             clearSelection(true);
5796             break;
5797         case ST.LINE_END:
5798             doLineEnd();
5799             clearSelection(true);
5800             break;
5801         case ST.COLUMN_PREVIOUS:
5802             doCursorPrevious();
5803             clearSelection(true);
5804             break;
5805         case ST.COLUMN_NEXT:
5806             doCursorNext();
5807             clearSelection(true);
5808             break;
5809         case ST.PAGE_UP:
5810             doPageUp(false, -1);
5811             clearSelection(true);
5812             break;
5813         case ST.PAGE_DOWN:
5814             doPageDown(false, -1);
5815             clearSelection(true);
5816             break;
5817         case ST.WORD_PREVIOUS:
5818             doWordPrevious();
5819             clearSelection(true);
5820             break;
5821         case ST.WORD_NEXT:
5822             doWordNext();
5823             clearSelection(true);
5824             break;
5825         case ST.TEXT_START:
5826             doContentStart();
5827             clearSelection(true);
5828             break;
5829         case ST.TEXT_END:
5830             doContentEnd();
5831             clearSelection(true);
5832             break;
5833         case ST.WINDOW_START:
5834             doPageStart();
5835             clearSelection(true);
5836             break;
5837         case ST.WINDOW_END:
5838             doPageEnd();
5839             clearSelection(true);
5840             break;
5841         // Selection
5842         case ST.SELECT_LINE_UP:
5843             doSelectionLineUp();
5844             break;
5845         case ST.SELECT_ALL:
5846             selectAll();
5847             break;
5848         case ST.SELECT_LINE_DOWN:
5849             doSelectionLineDown();
5850             break;
5851         case ST.SELECT_LINE_START:
5852             doLineStart();
5853             doSelection(ST.COLUMN_PREVIOUS);
5854             break;
5855         case ST.SELECT_LINE_END:
5856             doLineEnd();
5857             doSelection(ST.COLUMN_NEXT);
5858             break;
5859         case ST.SELECT_COLUMN_PREVIOUS:
5860             doSelectionCursorPrevious();
5861             doSelection(ST.COLUMN_PREVIOUS);
5862             break;
5863         case ST.SELECT_COLUMN_NEXT:
5864             doSelectionCursorNext();
5865             doSelection(ST.COLUMN_NEXT);
5866             break;
5867         case ST.SELECT_PAGE_UP:
5868             doSelectionPageUp(-1);
5869             break;
5870         case ST.SELECT_PAGE_DOWN:
5871             doSelectionPageDown(-1);
5872             break;
5873         case ST.SELECT_WORD_PREVIOUS:
5874             doSelectionWordPrevious();
5875             doSelection(ST.COLUMN_PREVIOUS);
5876             break;
5877         case ST.SELECT_WORD_NEXT:
5878             doSelectionWordNext();
5879             doSelection(ST.COLUMN_NEXT);
5880             break;
5881         case ST.SELECT_TEXT_START:
5882             doContentStart();
5883             doSelection(ST.COLUMN_PREVIOUS);
5884             break;
5885         case ST.SELECT_TEXT_END:
5886             doContentEnd();
5887             doSelection(ST.COLUMN_NEXT);
5888             break;
5889         case ST.SELECT_WINDOW_START:
5890             doPageStart();
5891             doSelection(ST.COLUMN_PREVIOUS);
5892             break;
5893         case ST.SELECT_WINDOW_END:
5894             doPageEnd();
5895             doSelection(ST.COLUMN_NEXT);
5896             break;
5897         // Modification
5898         case ST.CUT:
5899             cut();
5900             break;
5901         case ST.COPY:
5902             copy();
5903             break;
5904         case ST.PASTE:
5905             paste();
5906             break;
5907         case ST.DELETE_PREVIOUS:
5908             doBackspace();
5909             break;
5910         case ST.DELETE_NEXT:
5911             doDelete();
5912             break;
5913         case ST.DELETE_WORD_PREVIOUS:
5914             doDeleteWordPrevious();
5915             break;
5916         case ST.DELETE_WORD_NEXT:
5917             doDeleteWordNext();
5918             break;
5919         // Miscellaneous
5920         case ST.TOGGLE_OVERWRITE:
5921             overwrite = !overwrite;     // toggle insert/overwrite mode
5922             break;
5923         default:
5924     }
5925 }
5926 /**
5927  * Temporary until SWT provides this
5928  */
5929 bool isBidi() {
5930     return IS_GTK || IS_CARBON || BidiUtil.isBidiPlatform() || isMirrored_;
5931 }
5932 bool isBidiCaret() {
5933     return BidiUtil.isBidiPlatform();
5934 }
5935 bool isFixedLineHeight() {
5936     return fixedLineHeight;
5937 }
5938 /**
5939  * Returns whether the given offset is inside a multi byte line delimiter.
5940  * Example:
5941  * "Line1\r\n" isLineDelimiter(5) is false but isLineDelimiter(6) is true
5942  *
5943  * @return true if the given offset is inside a multi byte line delimiter.
5944  * false if the given offset is before or after a line delimiter.
5945  */
5946 bool isLineDelimiter(int offset) {
5947     int line = content.getLineAtOffset(offset);
5948     int lineOffset = content.getOffsetAtLine(line);
5949     int offsetInLine = offset - lineOffset;
5950     // offsetInLine will be greater than line length if the line
5951     // delimiter is longer than one character and the offset is set
5952     // in between parts of the line delimiter.
5953     return offsetInLine > content.getLine(line).length;
5954 }
5955 /**
5956  * Returns whether the widget is mirrored (right oriented/right to left
5957  * writing order).
5958  *
5959  * @return isMirrored true=the widget is right oriented, false=the widget
5960  *  is left oriented
5961  */
5962 bool isMirrored() {
5963     return isMirrored_;
5964 }
5965 /**
5966  * Returns whether the widget can have only one line.
5967  *
5968  * @return true if widget can have only one line, false if widget can have
5969  *  multiple lines
5970  */
5971 bool isSingleLine() {
5972     return (getStyle() & SWT.SINGLE) !is 0;
5973 }
5974 /**
5975  * Sends the specified verify event, replace/insert text as defined by
5976  * the event and send a modify event.
5977  *
5978  * @param event the text change event.
5979  *  <ul>
5980  *  <li>event.start - the replace start offset</li>
5981  *  <li>event.end - the replace end offset</li>
5982  *  <li>event.text - the new text</li>
5983  *  </ul>
5984  * @param updateCaret whether or not he caret should be set behind
5985  *  the new text
5986  */
5987 void modifyContent(Event event, bool updateCaret) {
5988     event.doit = true;
5989     notifyListeners(SWT.Verify, event);
5990     if (event.doit) {
5991         StyledTextEvent styledTextEvent = null;
5992         int replacedLength = event.end - event.start;
5993         if (isListening(ExtendedModify)) {
5994             styledTextEvent = new StyledTextEvent(content);
5995             styledTextEvent.start = event.start;
5996             styledTextEvent.end = event.start + cast(int)/*64bit*/event.text.length;
5997             styledTextEvent.text = content.getTextRange(event.start, replacedLength);
5998         }
5999         if (updateCaret) {
6000             //Fix advancing flag for delete/backspace key on direction boundary
6001             if (event.text.length is 0) {
6002                 int lineIndex = content.getLineAtOffset(event.start);
6003                 int lineOffset = content.getOffsetAtLine(lineIndex);
6004                 TextLayout layout = renderer.getTextLayout(lineIndex);
6005                 int levelStart = layout.getLevel(event.start - lineOffset);
6006                 int lineIndexEnd = content.getLineAtOffset(event.end);
6007                 if (lineIndex !is lineIndexEnd) {
6008                     renderer.disposeTextLayout(layout);
6009                     lineOffset = content.getOffsetAtLine(lineIndexEnd);
6010                     layout = renderer.getTextLayout(lineIndexEnd);
6011                 }
6012                 int levelEnd = layout.getLevel(event.end - lineOffset);
6013                 renderer.disposeTextLayout(layout);
6014                 if (levelStart !is levelEnd) {
6015                     caretAlignment = PREVIOUS_OFFSET_TRAILING;
6016                 } else {
6017                     caretAlignment = OFFSET_LEADING;
6018                 }
6019             }
6020         }
6021         content.replaceTextRange(event.start, replacedLength, event.text);
6022         // set the caret position prior to sending the modify event.
6023         // fixes 1GBB8NJ
6024         if (updateCaret) {
6025             // always update the caret location. fixes 1G8FODP
6026             setSelection(cast(int)/*64bit*/(event.start + event.text.length), 0, true);
6027             showCaret();
6028         }
6029         sendModifyEvent(event);
6030         if (isListening(ExtendedModify)) {
6031             notifyListeners(ExtendedModify, styledTextEvent);
6032         }
6033     }
6034 }
6035 void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) {
6036     if (isListening(PaintObject)) {
6037         StyledTextEvent event = new StyledTextEvent (content) ;
6038         event.gc = gc;
6039         event.x = x;
6040         event.y = y;
6041         event.ascent = ascent;
6042         event.descent = descent;
6043         event.style = style;
6044         event.bullet = bullet;
6045         event.bulletIndex = bulletIndex;
6046         notifyListeners(PaintObject, event);
6047     }
6048 }
6049 /**
6050  * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
6051  * clipboard  or, if there is no selection,  inserts the text at the current
6052  * caret offset.   If the widget has the SWT.SINGLE style and the
6053  * clipboard text contains more than one line, only the first line without
6054  * line delimiters is  inserted in the widget.
6055  *
6056  * @exception SWTException <ul>
6057  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6058  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6059  * </ul>
6060  */
6061 public void paste(){
6062     checkWidget();
6063     String text = stringcast( getClipboardContent(DND.CLIPBOARD));
6064     if (text !is null && text.length > 0) {
6065         Event event = new Event();
6066         event.start = selection.x;
6067         event.end = selection.y;
6068         event.text = getModelDelimitedText(text);
6069         sendKeyEvent(event);
6070     }
6071 }
6072 /**
6073  * Prints the widget's text to the default printer.
6074  *
6075  * @exception SWTException <ul>
6076  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6077  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6078  * </ul>
6079  */
6080 public void print() {
6081     checkWidget();
6082     Printer printer = new Printer();
6083     StyledTextPrintOptions options = new StyledTextPrintOptions();
6084     options.printTextForeground = true;
6085     options.printTextBackground = true;
6086     options.printTextFontStyle = true;
6087     options.printLineBackground = true;
6088     (new Printing(this, printer, options)).run();
6089     printer.dispose();
6090 }
6091 /**
6092  * Returns a runnable that will print the widget's text
6093  * to the specified printer.
6094  * <p>
6095  * The runnable may be run in a non-UI thread.
6096  * </p>
6097  *
6098  * @param printer the printer to print to
6099  *
6100  * @return a <code>Runnable</code> for printing the receiver's text
6101  *
6102  * @exception SWTException <ul>
6103  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6104  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6105  * </ul>
6106  * @exception IllegalArgumentException <ul>
6107  *    <li>ERROR_NULL_ARGUMENT when printer is null</li>
6108  * </ul>
6109  */
6110 public Runnable print(Printer printer) {
6111     checkWidget();
6112     if (printer is null) {
6113         SWT.error(SWT.ERROR_NULL_ARGUMENT);
6114     }
6115     StyledTextPrintOptions options = new StyledTextPrintOptions();
6116     options.printTextForeground = true;
6117     options.printTextBackground = true;
6118     options.printTextFontStyle = true;
6119     options.printLineBackground = true;
6120     return print(printer, options);
6121 }
6122 /**
6123  * Returns a runnable that will print the widget's text
6124  * to the specified printer.
6125  * <p>
6126  * The runnable may be run in a non-UI thread.
6127  * </p>
6128  *
6129  * @param printer the printer to print to
6130  * @param options print options to use during printing
6131  *
6132  * @return a <code>Runnable</code> for printing the receiver's text
6133  *
6134  * @exception SWTException <ul>
6135  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6136  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6137  * </ul>
6138  * @exception IllegalArgumentException <ul>
6139  *    <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
6140  * </ul>
6141  * @since 2.1
6142  */
6143 public Runnable print(Printer printer, StyledTextPrintOptions options) {
6144     checkWidget();
6145     if (printer is null || options is null) {
6146         SWT.error(SWT.ERROR_NULL_ARGUMENT);
6147     }
6148     return new Printing(this, printer, options);
6149 }
6150 /**
6151  * Causes the entire bounds of the receiver to be marked
6152  * as needing to be redrawn. The next time a paint request
6153  * is processed, the control will be completely painted.
6154  * <p>
6155  * Recalculates the content width for all lines in the bounds.
6156  * When a <code>LineStyleListener</code> is used a redraw call
6157  * is the only notification to the widget that styles have changed
6158  * and that the content width may have changed.
6159  * </p>
6160  *
6161  * @exception SWTException <ul>
6162  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6163  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6164  * </ul>
6165  *
6166  * @see Control#update()
6167  */
6168 public override void redraw() {
6169     super.redraw();
6170     int itemCount = getPartialBottomIndex() - topIndex + 1;
6171     renderer.reset(topIndex, itemCount);
6172     renderer.calculate(topIndex, itemCount);
6173     setScrollBars(false);
6174 }
6175 /**
6176  * Causes the rectangular area of the receiver specified by
6177  * the arguments to be marked as needing to be redrawn.
6178  * The next time a paint request is processed, that area of
6179  * the receiver will be painted. If the <code>all</code> flag
6180  * is <code>true</code>, any children of the receiver which
6181  * intersect with the specified area will also paint their
6182  * intersecting areas. If the <code>all</code> flag is
6183  * <code>false</code>, the children will not be painted.
6184  * <p>
6185  * Marks the content width of all lines in the specified rectangle
6186  * as unknown. Recalculates the content width of all visible lines.
6187  * When a <code>LineStyleListener</code> is used a redraw call
6188  * is the only notification to the widget that styles have changed
6189  * and that the content width may have changed.
6190  * </p>
6191  *
6192  * @param x the x coordinate of the area to draw
6193  * @param y the y coordinate of the area to draw
6194  * @param width the width of the area to draw
6195  * @param height the height of the area to draw
6196  * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
6197  *
6198  * @exception SWTException <ul>
6199  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6200  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6201  * </ul>
6202  *
6203  * @see Control#update()
6204  */
6205 public override void redraw(int x, int y, int width, int height, bool all) {
6206     super.redraw(x, y, width, height, all);
6207     if (height > 0) {
6208         int firstLine = getLineIndex(y);
6209         int lastLine = getLineIndex(y + height);
6210         resetCache(firstLine, lastLine - firstLine + 1);
6211     }
6212 }
6213 void redrawLines(int startLine, int lineCount) {
6214     // do nothing if redraw range is completely invisible
6215     int partialBottomIndex = getPartialBottomIndex();
6216     if (startLine > partialBottomIndex || startLine + lineCount - 1 < topIndex) {
6217         return;
6218     }
6219     // only redraw visible lines
6220     if (startLine < topIndex) {
6221         lineCount -= topIndex - startLine;
6222         startLine = topIndex;
6223     }
6224     if (startLine + lineCount - 1 > partialBottomIndex) {
6225         lineCount = partialBottomIndex - startLine + 1;
6226     }
6227     startLine -= topIndex;
6228     int redrawTop = getLinePixel(startLine);
6229     int redrawBottom = getLinePixel(startLine + lineCount);
6230     int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
6231     super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
6232 }
6233 void redrawLinesBullet (int[] redrawLines) {
6234     if (redrawLines is null) return;
6235     int topIndex = getPartialTopIndex();
6236     int bottomIndex = getPartialBottomIndex();
6237     for (int i = 0; i < redrawLines.length; i++) {
6238         int lineIndex = redrawLines[i];
6239         if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue;
6240         int width = -1;
6241         Bullet bullet = renderer.getLineBullet(lineIndex, null);
6242         if (bullet !is null) {
6243             StyleRange style = bullet.style;
6244             GlyphMetrics metrics = style.metrics;
6245             width = metrics.width;
6246         }
6247         if (width is -1) width = getClientArea().width;
6248         int height = renderer.getLineHeight(lineIndex);
6249         int y = getLinePixel(lineIndex);
6250         super.redraw(0, y, width, height, false);
6251     }
6252 }
6253 /**
6254  * Redraws the specified text range.
6255  *
6256  * @param start offset of the first character to redraw
6257  * @param length number of characters to redraw
6258  * @param clearBackground true if the background should be cleared as
6259  *  part of the redraw operation.  If true, the entire redraw range will
6260  *  be cleared before anything is redrawn.  If the redraw range includes
6261  *  the last character of a line (i.e., the entire line is redrawn) the
6262  *  line is cleared all the way to the right border of the widget.
6263  *  The redraw operation will be faster and smoother if clearBackground
6264  *  is set to false.  Whether or not the flag can be set to false depends
6265  *  on the type of change that has taken place.  If font styles or
6266  *  background colors for the redraw range have changed, clearBackground
6267  *  should be set to true.  If only foreground colors have changed for
6268  *  the redraw range, clearBackground can be set to false.
6269  * @exception SWTException <ul>
6270  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6271  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6272  * </ul>
6273  * @exception IllegalArgumentException <ul>
6274  *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
6275  * </ul>
6276  */
6277 public void redrawRange(int start, int length, bool clearBackground) {
6278     checkWidget();
6279     int end = start + length;
6280     int contentLength = content.getCharCount();
6281     if (start > end || start < 0 || end > contentLength) {
6282         SWT.error(SWT.ERROR_INVALID_RANGE);
6283     }
6284     int firstLine = content.getLineAtOffset(start);
6285     int lastLine = content.getLineAtOffset(end);
6286     resetCache(firstLine, lastLine - firstLine + 1);
6287     internalRedrawRange(start, length);
6288 }
6289 /**
6290  * Removes the specified bidirectional segment listener.
6291  *
6292  * @param listener the listener which should no longer be notified
6293  *
6294  * @exception SWTException <ul>
6295  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6296  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6297  * </ul>
6298  * @exception IllegalArgumentException <ul>
6299  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6300  * </ul>
6301  *
6302  * @since 2.0
6303  */
6304 public void removeBidiSegmentListener(BidiSegmentListener listener) {
6305     checkWidget();
6306     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6307     removeListener(LineGetSegments, listener);
6308 }
6309 /**
6310  * Removes the specified extended modify listener.
6311  *
6312  * @param extendedModifyListener the listener which should no longer be notified
6313  *
6314  * @exception SWTException <ul>
6315  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6316  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6317  * </ul>
6318  * @exception IllegalArgumentException <ul>
6319  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6320  * </ul>
6321  */
6322 public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
6323     checkWidget();
6324     if (extendedModifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6325     removeListener(ExtendedModify, extendedModifyListener);
6326 }
6327 /**
6328  * Removes the specified line background listener.
6329  *
6330  * @param listener the listener which should no longer be notified
6331  *
6332  * @exception SWTException <ul>
6333  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6334  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6335  * </ul>
6336  * @exception IllegalArgumentException <ul>
6337  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6338  * </ul>
6339  */
6340 public void removeLineBackgroundListener(LineBackgroundListener listener) {
6341     checkWidget();
6342     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6343     removeListener(LineGetBackground, listener);
6344 }
6345 /**
6346  * Removes the specified line style listener.
6347  *
6348  * @param listener the listener which should no longer be notified
6349  *
6350  * @exception SWTException <ul>
6351  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6352  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6353  * </ul>
6354  * @exception IllegalArgumentException <ul>
6355  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6356  * </ul>
6357  */
6358 public void removeLineStyleListener(LineStyleListener listener) {
6359     checkWidget();
6360     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6361     removeListener(LineGetStyle, listener);
6362 }
6363 /**
6364  * Removes the specified modify listener.
6365  *
6366  * @param modifyListener the listener which should no longer be notified
6367  *
6368  * @exception SWTException <ul>
6369  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6370  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6371  * </ul>
6372  * @exception IllegalArgumentException <ul>
6373  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6374  * </ul>
6375  */
6376 public void removeModifyListener(ModifyListener modifyListener) {
6377     checkWidget();
6378     if (modifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6379     removeListener(SWT.Modify, modifyListener);
6380 }
6381 /**
6382  * Removes the specified listener.
6383  *
6384  * @param listener the listener which should no longer be notified
6385  *
6386  * @exception SWTException <ul>
6387  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6388  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6389  * </ul>
6390  * @exception IllegalArgumentException <ul>
6391  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6392  * </ul>
6393  * @since 3.2
6394  */
6395 public void removePaintObjectListener(PaintObjectListener listener) {
6396     checkWidget();
6397     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6398     removeListener(PaintObject, listener);
6399 }
6400 /**
6401  * Removes the listener from the collection of listeners who will
6402  * be notified when the user changes the receiver's selection.
6403  *
6404  * @param listener the listener which should no longer be notified
6405  *
6406  * @exception IllegalArgumentException <ul>
6407  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
6408  * </ul>
6409  * @exception SWTException <ul>
6410  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6411  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6412  * </ul>
6413  *
6414  * @see SelectionListener
6415  * @see #addSelectionListener
6416  */
6417 public void removeSelectionListener(SelectionListener listener) {
6418     checkWidget();
6419     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6420     removeListener(SWT.Selection, listener);
6421 }
6422 /**
6423  * Removes the specified verify listener.
6424  *
6425  * @param verifyListener the listener which should no longer be notified
6426  *
6427  * @exception SWTException <ul>
6428  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6429  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6430  * </ul>
6431  * @exception IllegalArgumentException <ul>
6432  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6433  * </ul>
6434  */
6435 public void removeVerifyListener(VerifyListener verifyListener) {
6436     checkWidget();
6437     if (verifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6438     removeListener(SWT.Verify, verifyListener);
6439 }
6440 /**
6441  * Removes the specified key verify listener.
6442  *
6443  * @param listener the listener which should no longer be notified
6444  *
6445  * @exception SWTException <ul>
6446  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6447  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6448  * </ul>
6449  * @exception IllegalArgumentException <ul>
6450  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6451  * </ul>
6452  */
6453 public void removeVerifyKeyListener(VerifyKeyListener listener) {
6454     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6455     removeListener(VerifyKey, listener);
6456 }
6457 /**
6458  * Removes the specified word movement listener.
6459  *
6460  * @param listener the listener which should no longer be notified
6461  *
6462  * @exception SWTException <ul>
6463  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6464  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6465  * </ul>
6466  * @exception IllegalArgumentException <ul>
6467  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
6468  * </ul>
6469  *
6470  * @see MovementEvent
6471  * @see MovementListener
6472  * @see #addWordMovementListener
6473  *
6474  * @since 3.3
6475  */
6476 
6477 public void removeWordMovementListener(MovementListener listener) {
6478     checkWidget();
6479     if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6480     removeListener(WordNext, listener);
6481     removeListener(WordPrevious, listener);
6482 }
6483 /**
6484  * Replaces the styles in the given range with new styles.  This method
6485  * effectively deletes the styles in the given range and then adds the
6486  * the new styles.
6487  * <p>
6488  * Note: Because a StyleRange includes the start and length, the
6489  * same instance cannot occur multiple times in the array of styles.
6490  * If the same style attributes, such as font and color, occur in
6491  * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
6492  * can be used to share styles and reduce memory usage.
6493  * </p><p>
6494  * Should not be called if a LineStyleListener has been set since the
6495  * listener maintains the styles.
6496  * </p>
6497  *
6498  * @param start offset of first character where styles will be deleted
6499  * @param length length of the range to delete styles in
6500  * @param ranges StyleRange objects containing the new style information.
6501  * The ranges should not overlap and should be within the specified start
6502  * and length. The style rendering is undefined if the ranges do overlap
6503  * or are ill-defined. Must not be null.
6504  * @exception SWTException <ul>
6505  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6506  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6507  * </ul>
6508  * @exception IllegalArgumentException <ul>
6509  *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
6510  * </ul>
6511  *
6512  * @since 2.0
6513  *
6514  * @see #setStyleRanges(int, int, int[], StyleRange[])
6515  */
6516 public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
6517     checkWidget();
6518     if (isListening(LineGetStyle)) return;
6519     // SWT extension: allow null for zero length string
6520     //if (ranges is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
6521     setStyleRanges(start, length, null, ranges, false);
6522 }
6523 /**
6524  * Replaces the given text range with new text.
6525  * If the widget has the SWT.SINGLE style and "text" contains more than
6526  * one line, only the first line is rendered but the text is stored
6527  * unchanged. A subsequent call to getText will return the same text
6528  * that was set. Note that only a single line of text should be set when
6529  * the SWT.SINGLE style is used.
6530  * <p>
6531  * <b>NOTE:</b> During the replace operation the current selection is
6532  * changed as follows:
6533  * <ul>
6534  * <li>selection before replaced text: selection unchanged
6535  * <li>selection after replaced text: adjust the selection so that same text
6536  * remains selected
6537  * <li>selection intersects replaced text: selection is cleared and caret
6538  * is placed after inserted text
6539  * </ul>
6540  * </p>
6541  *
6542  * @param start offset of first character to replace
6543  * @param length number of characters to replace. Use 0 to insert text
6544  * @param text new text. May be empty to delete text.
6545  * @exception SWTException <ul>
6546  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6547  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6548  * </ul>
6549  * @exception IllegalArgumentException <ul>
6550  *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
6551  *   <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
6552  *      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>
6553  * </ul>
6554  */
6555 public void replaceTextRange(int start, int length, String text) {
6556     checkWidget();
6557     // SWT extension: allow null for zero length string
6558 //     if (text is null) {
6559 //         SWT.error(SWT.ERROR_NULL_ARGUMENT);
6560 //     }
6561     int contentLength = getCharCount();
6562     int end = start + length;
6563     if (start > end || start < 0 || end > contentLength) {
6564         SWT.error(SWT.ERROR_INVALID_RANGE);
6565     }
6566     Event event = new Event();
6567     event.start = start;
6568     event.end = end;
6569     event.text = text;
6570     modifyContent(event, false);
6571 }
6572 /**
6573  * Resets the caret position, selection and scroll offsets. Recalculate
6574  * the content width and scroll bars. Redraw the widget.
6575  */
6576 void reset() {
6577     ScrollBar verticalBar = getVerticalBar();
6578     ScrollBar horizontalBar = getHorizontalBar();
6579     caretOffset = 0;
6580     topIndex = 0;
6581     topIndexY = 0;
6582     verticalScrollOffset = 0;
6583     horizontalScrollOffset = 0;
6584     resetSelection();
6585     renderer.setContent(content);
6586     if (verticalBar !is null) {
6587         verticalBar.setSelection(0);
6588     }
6589     if (horizontalBar !is null) {
6590         horizontalBar.setSelection(0);
6591     }
6592     resetCache(0, 0);
6593     setCaretLocation();
6594     super.redraw();
6595 }
6596 void resetCache(int firstLine, int count) {
6597     int maxLineIndex = renderer.maxWidthLineIndex;
6598     renderer.reset(firstLine, count);
6599     renderer.calculateClientArea();
6600     if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
6601         renderer.calculate(maxLineIndex, 1);
6602     }
6603     setScrollBars(true);
6604     if (!isFixedLineHeight()) {
6605         if (topIndex > firstLine) {
6606             verticalScrollOffset = -1;
6607         }
6608         renderer.calculateIdle();
6609     }
6610 }
6611 /**
6612  * Resets the selection.
6613  */
6614 void resetSelection() {
6615     selection.x = selection.y = caretOffset;
6616     selectionAnchor = -1;
6617 }
6618 
6619 public override void scroll(int destX, int destY, int x, int y, int width, int height, bool all) {
6620     super.scroll(destX, destY, x, y, width, height, false);
6621     if (all) {
6622         int deltaX = destX - x, deltaY = destY - y;
6623         Control[] children = getChildren();
6624         for (int i=0; i<children.length; i++) {
6625             Control child = children[i];
6626             Rectangle rect = child.getBounds();
6627             child.setLocation(rect.x + deltaX, rect.y + deltaY);
6628         }
6629     }
6630 }
6631 
6632 /**
6633  * Scrolls the widget horizontally.
6634  *
6635  * @param pixels number of pixels to scroll, > 0 = scroll left,
6636  *  < 0 scroll right
6637  * @param adjustScrollBar
6638  *  true= the scroll thumb will be moved to reflect the new scroll offset.
6639  *  false = the scroll thumb will not be moved
6640  * @return
6641  *  true=the widget was scrolled
6642  *  false=the widget was not scrolled, the given offset is not valid.
6643  */
6644 bool scrollHorizontal(int pixels, bool adjustScrollBar) {
6645     if (pixels is 0) {
6646         return false;
6647     }
6648     ScrollBar horizontalBar = getHorizontalBar();
6649     if (horizontalBar !is null && adjustScrollBar) {
6650         horizontalBar.setSelection(horizontalScrollOffset + pixels);
6651     }
6652     int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
6653     if (pixels > 0) {
6654         int sourceX = leftMargin + pixels;
6655         int scrollWidth = clientAreaWidth - sourceX - rightMargin;
6656         if (scrollWidth > 0) {
6657             scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
6658         }
6659         if (sourceX > scrollWidth) {
6660             super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
6661         }
6662     } else {
6663         int destinationX = leftMargin - pixels;
6664         int scrollWidth = clientAreaWidth - destinationX - rightMargin;
6665         if (scrollWidth > 0) {
6666             scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
6667         }
6668         if (destinationX > scrollWidth) {
6669             super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
6670         }
6671     }
6672     horizontalScrollOffset += pixels;
6673     setCaretLocation();
6674     return true;
6675 }
6676 /**
6677  * Scrolls the widget vertically.
6678  *
6679  * @param pixel the new vertical scroll offset
6680  * @param adjustScrollBar
6681  *  true= the scroll thumb will be moved to reflect the new scroll offset.
6682  *  false = the scroll thumb will not be moved
6683  * @return
6684  *  true=the widget was scrolled
6685  *  false=the widget was not scrolled
6686  */
6687 bool scrollVertical(int pixels, bool adjustScrollBar) {
6688     if (pixels is 0) {
6689         return false;
6690     }
6691     if (verticalScrollOffset !is -1) {
6692         ScrollBar verticalBar = getVerticalBar();
6693         if (verticalBar !is null && adjustScrollBar) {
6694             verticalBar.setSelection(verticalScrollOffset + pixels);
6695         }
6696         int scrollWidth = clientAreaWidth - leftMargin - rightMargin;
6697         if (pixels > 0) {
6698             int sourceY = topMargin + pixels;
6699             int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
6700             if (scrollHeight > 0) {
6701                 scroll(leftMargin, topMargin, leftMargin, sourceY, scrollWidth, scrollHeight, true);
6702             }
6703             if (sourceY > scrollHeight) {
6704                 int redrawY = Math.max(0, topMargin + scrollHeight);
6705                 int redrawHeight = Math.min(clientAreaHeight, pixels - scrollHeight);
6706                 super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true);
6707             }
6708         } else {
6709             int destinationY = topMargin - pixels;
6710             int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
6711             if (scrollHeight > 0) {
6712                 scroll(leftMargin, destinationY, leftMargin, topMargin, scrollWidth, scrollHeight, true);
6713             }
6714             if (destinationY > scrollHeight) {
6715                 int redrawY = Math.max(0, topMargin + scrollHeight);
6716                 int redrawHeight = Math.min(clientAreaHeight, -pixels - scrollHeight);
6717                 super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true);
6718             }
6719         }
6720         verticalScrollOffset += pixels;
6721         calculateTopIndex(pixels);
6722     } else {
6723         calculateTopIndex(pixels);
6724         super.redraw();
6725     }
6726     setCaretLocation();
6727     return true;
6728 }
6729 void scrollText(int srcY, int destY) {
6730     if (srcY is destY) return;
6731     int deltaY = destY - srcY;
6732     int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
6733     if (deltaY > 0) {
6734         scrollHeight = clientAreaHeight - srcY - bottomMargin;
6735     } else {
6736         scrollHeight = clientAreaHeight - destY - bottomMargin;
6737     }
6738     scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
6739     if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
6740         super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
6741     }
6742     if ((0 < destY + scrollHeight) && (topMargin > destY)) {
6743         super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
6744     }
6745     if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
6746         super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
6747     }
6748     if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
6749         super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
6750     }
6751 }
6752 /**
6753  * Selects all the text.
6754  *
6755  * @exception SWTException <ul>
6756  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6757  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6758  * </ul>
6759  */
6760 public void selectAll() {
6761     checkWidget();
6762     setSelection(0, Math.max(getCharCount(),0));
6763 }
6764 /**
6765  * Replaces/inserts text as defined by the event.
6766  *
6767  * @param event the text change event.
6768  *  <ul>
6769  *  <li>event.start - the replace start offset</li>
6770  *  <li>event.end - the replace end offset</li>
6771  *  <li>event.text - the new text</li>
6772  *  </ul>
6773  */
6774 void sendKeyEvent(Event event) {
6775     if (editable) {
6776         modifyContent(event, true);
6777     }
6778 }
6779 /**
6780  * Returns a StyledTextEvent that can be used to request data such
6781  * as styles and background color for a line.
6782  * <p>
6783  * The specified line may be a visual (wrapped) line if in word
6784  * wrap mode. The returned object will always be for a logical
6785  * (unwrapped) line.
6786  * </p>
6787  *
6788  * @param lineOffset offset of the line. This may be the offset of
6789  *  a visual line if the widget is in word wrap mode.
6790  * @param line line text. This may be the text of a visual line if
6791  *  the widget is in word wrap mode.
6792  * @return StyledTextEvent that can be used to request line data
6793  *  for the given line.
6794  */
6795 StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
6796     StyledTextEvent event = null;
6797     if (isListening(eventType)) {
6798         event = new StyledTextEvent(content);
6799         event.detail = lineOffset;
6800         event.text = line;
6801         event.alignment = alignment;
6802         event.indent = indent;
6803         event.justify = justify;
6804         notifyListeners(eventType, event);
6805     }
6806     return event;
6807 }
6808 void sendModifyEvent(Event event) {
6809     Accessible accessible = getAccessible();
6810     if (event.text.length is 0) {
6811         accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
6812     } else {
6813         if (event.start is event.end) {
6814             accessible.textChanged
6815                 (ACC.TEXT_INSERT, event.start, cast(int)/*64bit*/event.text.length);
6816         } else {
6817             accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
6818             accessible.textChanged
6819                 (ACC.TEXT_INSERT, event.start, cast(int)/*64bit*/event.text.length);
6820         }
6821     }
6822     notifyListeners(SWT.Modify, event);
6823 }
6824 /**
6825  * Sends the specified selection event.
6826  */
6827 void sendSelectionEvent() {
6828     getAccessible().textSelectionChanged();
6829     Event event = new Event();
6830     event.x = selection.x;
6831     event.y = selection.y;
6832     notifyListeners(SWT.Selection, event);
6833 }
6834 int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) {
6835     if (isListening(eventType)) {
6836         StyledTextEvent event = new StyledTextEvent(content);
6837         event.detail = lineOffset;
6838         event.text = lineText;
6839         event.count = movement;
6840         event.start = offset;
6841         event.end = newOffset;
6842         notifyListeners(eventType, event);
6843         offset = event.end;
6844         if (offset !is newOffset) {
6845             int length = getCharCount();
6846             if (offset < 0) {
6847                 offset = 0;
6848             } else if (offset > length) {
6849                 offset = length;
6850             } else {
6851                 if (isLineDelimiter(offset)) {
6852                     SWT.error(SWT.ERROR_INVALID_ARGUMENT);
6853                 }
6854             }
6855         }
6856         return offset;
6857     }
6858     return newOffset;
6859 }
6860 /**
6861  * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>,
6862  * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines.
6863  * </p><p>
6864  * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
6865  * in order to stabilize the right edge before setting alignment.
6866  * </p>
6867  *
6868  * @param alignment the new alignment
6869  *
6870  * @exception SWTException <ul>
6871  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6872  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6873  * </ul>
6874  *
6875  * @see #setLineAlignment(int, int, int)
6876  *
6877  * @since 3.2
6878  */
6879 public void setAlignment(int alignment) {
6880     checkWidget();
6881     alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
6882     if (alignment is 0 || this.alignment is alignment) return;
6883     this.alignment = alignment;
6884     resetCache(0, content.getLineCount());
6885     setCaretLocation();
6886     super.redraw();
6887 }
6888 /**
6889  * @see Control#setBackground(Color)
6890  */
6891 public override void setBackground(Color color) {
6892     checkWidget();
6893     background = color;
6894     super.setBackground(color);
6895     super.redraw();
6896 }
6897 /**
6898  * Sets the receiver's caret.  Set the caret's height and location.
6899  *
6900  * </p>
6901  * @param caret the new caret for the receiver
6902  *
6903  * @exception SWTException <ul>
6904  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6905  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6906  * </ul>
6907  */
6908 public override void setCaret(Caret caret) {
6909     checkWidget ();
6910     super.setCaret(caret);
6911     caretDirection = SWT.NULL;
6912     if (caret !is null) {
6913         setCaretLocation();
6914     }
6915 }
6916 /**
6917  * Sets the BIDI coloring mode.  When true the BIDI text display
6918  * algorithm is applied to segments of text that are the same
6919  * color.
6920  *
6921  * @param mode the new coloring mode
6922  * @exception SWTException <ul>
6923  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6924  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6925  * </ul>
6926  *
6927  * @deprecated use BidiSegmentListener instead.
6928  */
6929 public void setBidiColoring(bool mode) {
6930     checkWidget();
6931     bidiColoring = mode;
6932 }
6933 /**
6934  * Moves the Caret to the current caret offset.
6935  */
6936 void setCaretLocation() {
6937     Point newCaretPos = getPointAtOffset(caretOffset);
6938     setCaretLocation(newCaretPos, getCaretDirection());
6939 }
6940 void setCaretLocation(Point location, int direction) {
6941     Caret caret = getCaret();
6942     if (caret !is null) {
6943         bool isDefaultCaret = caret is defaultCaret;
6944         int lineHeight = renderer.getLineHeight();
6945         int caretHeight = lineHeight;
6946         if (!isFixedLineHeight() && isDefaultCaret) {
6947             caretHeight = getBoundsAtOffset(caretOffset).height;
6948             if (caretHeight !is lineHeight) {
6949                 direction = SWT.DEFAULT;
6950             }
6951         }
6952         int imageDirection = direction;
6953         if (isMirrored()) {
6954             if (imageDirection is SWT.LEFT) {
6955                 imageDirection = SWT.RIGHT;
6956             } else if (imageDirection is SWT.RIGHT) {
6957                 imageDirection = SWT.LEFT;
6958             }
6959         }
6960         if (isDefaultCaret && imageDirection is SWT.RIGHT) {
6961             location.x -= (caret.getSize().x - 1);
6962         }
6963         if (isDefaultCaret) {
6964             caret.setBounds(location.x, location.y, caretWidth, caretHeight);
6965         } else {
6966             caret.setLocation(location);
6967         }
6968         getAccessible().textCaretMoved(getCaretOffset());
6969         if (direction !is caretDirection) {
6970             caretDirection = direction;
6971             if (isDefaultCaret) {
6972                 if (imageDirection is SWT.DEFAULT) {
6973                     defaultCaret.setImage(null);
6974                 } else if (imageDirection is SWT.LEFT) {
6975                     defaultCaret.setImage(leftCaretBitmap);
6976                 } else if (imageDirection is SWT.RIGHT) {
6977                     defaultCaret.setImage(rightCaretBitmap);
6978                 }
6979             }
6980             if (caretDirection is SWT.LEFT) {
6981                 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
6982             } else if (caretDirection is SWT.RIGHT) {
6983                 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
6984             }
6985         }
6986     }
6987     columnX = location.x;
6988 }
6989 /**
6990  * Sets the caret offset.
6991  *
6992  * @param offset caret offset, relative to the first character in the text.
6993  * @exception SWTException <ul>
6994  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6995  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6996  * </ul>
6997  * @exception IllegalArgumentException <ul>
6998  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
6999  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7000  * </ul>
7001  */
7002 public void setCaretOffset(int offset) {
7003     checkWidget();
7004     int length = getCharCount();
7005     if (length > 0 && offset !is caretOffset) {
7006         if (offset < 0) {
7007             caretOffset = 0;
7008         } else if (offset > length) {
7009             caretOffset = length;
7010         } else {
7011             if (isLineDelimiter(offset)) {
7012                 // offset is inside a multi byte line delimiter. This is an
7013                 // illegal operation and an exception is thrown. Fixes 1GDKK3R
7014                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7015             }
7016             caretOffset = offset;
7017         }
7018         caretAlignment = PREVIOUS_OFFSET_TRAILING;
7019         // clear the selection if the caret is moved.
7020         // don't notify listeners about the selection change.
7021         clearSelection(false);
7022     }
7023     setCaretLocation();
7024 }
7025 /**
7026  * Copies the specified text range to the clipboard.  The text will be placed
7027  * in the clipboard in plain text format and RTF format.
7028  *
7029  * @param start start index of the text
7030  * @param length length of text to place in clipboard
7031  *
7032  * @exception SWTError, see Clipboard.setContents
7033  * @see org.eclipse.swt.dnd.Clipboard#setContents
7034  */
7035 void setClipboardContent(int start, int length, int clipboardType) {
7036     if (clipboardType is DND.SELECTION_CLIPBOARD && !(IS_MOTIF || IS_GTK)) return;
7037     TextTransfer plainTextTransfer = TextTransfer.getInstance();
7038     TextWriter plainTextWriter = new TextWriter(start, length);
7039     String plainText = getPlatformDelimitedText(plainTextWriter);
7040     Object[] data;
7041     Transfer[] types;
7042     if (clipboardType is DND.SELECTION_CLIPBOARD) {
7043         data = [ cast(Object) new ArrayWrapperString(plainText) ];
7044         types = [plainTextTransfer];
7045     } else {
7046         RTFTransfer rtfTransfer = RTFTransfer.getInstance();
7047         RTFWriter rtfWriter = new RTFWriter(start, length);
7048         String rtfText = getPlatformDelimitedText(rtfWriter);
7049         data = [ cast(Object) new ArrayWrapperString(rtfText), new ArrayWrapperString(plainText) ];
7050         types = [ cast(Transfer)rtfTransfer, plainTextTransfer];
7051     }
7052     clipboard.setContents(data, types, clipboardType);
7053 }
7054 /**
7055  * Sets the content implementation to use for text storage.
7056  *
7057  * @param newContent StyledTextContent implementation to use for text storage.
7058  * @exception SWTException <ul>
7059  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7060  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7061  * </ul>
7062  * @exception IllegalArgumentException <ul>
7063  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
7064  * </ul>
7065  */
7066 public void setContent(StyledTextContent newContent) {
7067     checkWidget();
7068     if (newContent is null) {
7069         SWT.error(SWT.ERROR_NULL_ARGUMENT);
7070     }
7071     if (content !is null) {
7072         content.removeTextChangeListener(textChangeListener);
7073     }
7074     content = newContent;
7075     content.addTextChangeListener(textChangeListener);
7076     reset();
7077 }
7078 /**
7079  * Sets the receiver's cursor to the cursor specified by the
7080  * argument.  Overridden to handle the null case since the
7081  * StyledText widget uses an ibeam as its default cursor.
7082  *
7083  * @see Control#setCursor(Cursor)
7084  */
7085 public override void setCursor (Cursor cursor) {
7086     if (cursor is null) {
7087         Display display = getDisplay();
7088         super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
7089     } else {
7090         super.setCursor(cursor);
7091     }
7092 }
7093 /**
7094  * Sets whether the widget : double click mouse behavior.
7095  * </p>
7096  *
7097  * @param enable if true double clicking a word selects the word, if false
7098  *  double clicks have the same effect as regular mouse clicks.
7099  * @exception SWTException <ul>
7100  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7101  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7102  * </ul>
7103  */
7104 public void setDoubleClickEnabled(bool enable) {
7105     checkWidget();
7106     doubleClickEnabled = enable;
7107 }
7108 public override void setDragDetect (bool dragDetect_) {
7109     checkWidget ();
7110     this.dragDetect_ = dragDetect_;
7111 }
7112 /**
7113  * Sets whether the widget content can be edited.
7114  * </p>
7115  *
7116  * @param editable if true content can be edited, if false content can not be
7117  *  edited
7118  * @exception SWTException <ul>
7119  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7120  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7121  * </ul>
7122  */
7123 public void setEditable(bool editable) {
7124     checkWidget();
7125     this.editable = editable;
7126 }
7127 /**
7128  * Sets a new font to render text with.
7129  * <p>
7130  * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
7131  * and the same baseline as regular fonts.
7132  * </p>
7133  *
7134  * @param font new font
7135  * @exception SWTException <ul>
7136  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7137  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7138  * </ul>
7139  */
7140 public override void setFont(Font font) {
7141     checkWidget();
7142     int oldLineHeight = renderer.getLineHeight();
7143     super.setFont(font);
7144     renderer.setFont(getFont(), tabLength);
7145     // keep the same top line visible. fixes 5815
7146     if (isFixedLineHeight()) {
7147         int lineHeight = renderer.getLineHeight();
7148         if (lineHeight !is oldLineHeight) {
7149             int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
7150             scrollVertical(vscroll, true);
7151         }
7152     }
7153     resetCache(0, content.getLineCount());
7154     claimBottomFreeSpace();
7155     calculateScrollBars();
7156     if (isBidiCaret()) createCaretBitmaps();
7157     caretDirection = SWT.NULL;
7158     setCaretLocation();
7159     super.redraw();
7160 }
7161 public override void setForeground(Color color) {
7162     checkWidget();
7163     foreground = color;
7164     super.setForeground(getForeground());
7165     super.redraw();
7166 }
7167 /**
7168  * Sets the horizontal scroll offset relative to the start of the line.
7169  * Do nothing if there is no text set.
7170  * <p>
7171  * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
7172  * widget.
7173  * </p>
7174  *
7175  * @param offset horizontal scroll offset relative to the start
7176  *  of the line, measured in character increments starting at 0, if
7177  *  equal to 0 the content is not scrolled, if > 0 = the content is scrolled.
7178  * @exception SWTException <ul>
7179  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7180  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7181  * </ul>
7182  */
7183 public void setHorizontalIndex(int offset) {
7184     checkWidget();
7185     if (getCharCount() is 0) {
7186         return;
7187     }
7188     if (offset < 0) {
7189         offset = 0;
7190     }
7191     offset *= getHorizontalIncrement();
7192     // allow any value if client area width is unknown or 0.
7193     // offset will be checked in resize handler.
7194     // don't use isVisible since width is known even if widget
7195     // is temporarily invisible
7196     if (clientAreaWidth > 0) {
7197         int width = renderer.getWidth();
7198         // prevent scrolling if the content fits in the client area.
7199         // align end of longest line with right border of client area
7200         // if offset is out of range.
7201         if (offset > width - clientAreaWidth) {
7202             offset = Math.max(0, width - clientAreaWidth);
7203         }
7204     }
7205     scrollHorizontal(offset - horizontalScrollOffset, true);
7206 }
7207 /**
7208  * Sets the horizontal pixel offset relative to the start of the line.
7209  * Do nothing if there is no text set.
7210  * <p>
7211  * <b>NOTE:</b> The horizontal pixel offset is reset to 0 when new text
7212  * is set in the widget.
7213  * </p>
7214  *
7215  * @param pixel horizontal pixel offset relative to the start
7216  *  of the line.
7217  * @exception SWTException <ul>
7218  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7219  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7220  * </ul>
7221  * @since 2.0
7222  */
7223 public void setHorizontalPixel(int pixel) {
7224     checkWidget();
7225     if (getCharCount() is 0) {
7226         return;
7227     }
7228     if (pixel < 0) {
7229         pixel = 0;
7230     }
7231     // allow any value if client area width is unknown or 0.
7232     // offset will be checked in resize handler.
7233     // don't use isVisible since width is known even if widget
7234     // is temporarily invisible
7235     if (clientAreaWidth > 0) {
7236         int width = renderer.getWidth();
7237         // prevent scrolling if the content fits in the client area.
7238         // align end of longest line with right border of client area
7239         // if offset is out of range.
7240         if (pixel > width - clientAreaWidth) {
7241             pixel = Math.max(0, width - clientAreaWidth);
7242         }
7243     }
7244     scrollHorizontal(pixel - horizontalScrollOffset, true);
7245 }
7246 /**
7247  * Sets the line indentation of the widget.
7248  * <p>
7249  * It is the amount of blank space, in pixels, at the beginning of each line.
7250  * When a line wraps in several lines only the first one is indented.
7251  * </p>
7252  *
7253  * @param indent the new indent
7254  *
7255  * @exception SWTException <ul>
7256  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7257  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7258  * </ul>
7259  *
7260  * @see #setLineIndent(int, int, int)
7261  *
7262  * @since 3.2
7263  */
7264 public void setIndent(int indent) {
7265     checkWidget();
7266     if (this.indent is indent || indent < 0) return;
7267     this.indent = indent;
7268     resetCache(0, content.getLineCount());
7269     setCaretLocation();
7270     super.redraw();
7271 }
7272 /**
7273  * Sets whether the widget should justify lines.
7274  *
7275  * @param justify whether lines should be justified
7276  *
7277  * @exception SWTException <ul>
7278  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7279  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7280  * </ul>
7281  *
7282  * @see #setLineJustify(int, int, bool)
7283  *
7284  * @since 3.2
7285  */
7286 public void setJustify(bool justify) {
7287     checkWidget();
7288     if (this.justify is justify) return;
7289     this.justify = justify;
7290     resetCache(0, content.getLineCount());
7291     setCaretLocation();
7292     super.redraw();
7293 }
7294 /**
7295  * Maps a key to an action.
7296  * <p>
7297  * One action can be associated with N keys. However, each key can only
7298  * have one action (key:action is N:1 relation).
7299  * </p>
7300  *
7301  * @param key a key code defined in SWT.java or a character.
7302  *  Optionally ORd with a state mask.  Preferred state masks are one or more of
7303  *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
7304  *  differences.  However, there may be cases where using the specific state masks
7305  *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
7306  * @param action one of the predefined actions defined in ST.java.
7307  *  Use SWT.NULL to remove a key binding.
7308  * @exception SWTException <ul>
7309  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7310  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7311  * </ul>
7312  */
7313 public void setKeyBinding(int key, int action) {
7314     checkWidget();
7315     int modifierValue = key & SWT.MODIFIER_MASK;
7316     char keyChar = cast(char)(key & SWT.KEY_MASK);
7317     if (Compatibility.isLetter(keyChar)) {
7318         // make the keybinding case insensitive by adding it
7319         // in its upper and lower case form
7320         char ch = cast(char) CharacterToUpper(keyChar);
7321         int newKey = ch | modifierValue;
7322         if (action is SWT.NULL) {
7323             keyActionMap.remove(newKey);
7324         } else {
7325             keyActionMap[newKey] = action;
7326         }
7327         ch = cast(char) CharacterToLower(keyChar);
7328         newKey = ch | modifierValue;
7329         if (action is SWT.NULL) {
7330             keyActionMap.remove(newKey);
7331         } else {
7332             keyActionMap[newKey] = action;
7333         }
7334     } else {
7335         if (action is SWT.NULL) {
7336             keyActionMap.remove(key);
7337         } else {
7338             keyActionMap[key]=action;
7339         }
7340     }
7341 }
7342 /**
7343  * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>,
7344  * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>.
7345  * <p><p>
7346  * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
7347  * in order to stabilize the right edge before setting alignment.
7348  * </p>
7349  * Should not be called if a LineStyleListener has been set since the listener
7350  * maintains the line attributes.
7351  * </p><p>
7352  * All line attributes are maintained relative to the line text, not the
7353  * line index that is specified in this method call.
7354  * During text changes, when entire lines are inserted or removed, the line
7355  * attributes that are associated with the lines after the change
7356  * will "move" with their respective text. An entire line is defined as
7357  * extending from the first character on a line to the last and including the
7358  * line delimiter.
7359  * </p><p>
7360  * When two lines are joined by deleting a line delimiter, the top line
7361  * attributes take precedence and the attributes of the bottom line are deleted.
7362  * For all other text changes line attributes will remain unchanged.
7363  *
7364  * @param startLine first line the alignment is applied to, 0 based
7365  * @param lineCount number of lines the alignment applies to.
7366  * @param alignment line alignment
7367  *
7368  * @exception SWTException <ul>
7369  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7370  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7371  * </ul>
7372  * @exception IllegalArgumentException <ul>
7373  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7374  * </ul>
7375  * @see #setAlignment(int)
7376  * @since 3.2
7377  */
7378 public void setLineAlignment(int startLine, int lineCount, int alignment) {
7379     checkWidget();
7380     if (isListening(LineGetStyle)) return;
7381     if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7382         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7383     }
7384 
7385     renderer.setLineAlignment(startLine, lineCount, alignment);
7386     resetCache(startLine, lineCount);
7387     redrawLines(startLine, lineCount);
7388     int caretLine = getCaretLine();
7389     if (startLine <= caretLine && caretLine < startLine + lineCount) {
7390         setCaretLocation();
7391     }
7392 }
7393 /**
7394  * Sets the background color of the specified lines.
7395  * <p>
7396  * The background color is drawn for the width of the widget. All
7397  * line background colors are discarded when setText is called.
7398  * The text background color if defined in a StyleRange overlays the
7399  * line background color.
7400  * </p><p>
7401  * Should not be called if a LineBackgroundListener has been set since the
7402  * listener maintains the line backgrounds.
7403  * </p><p>
7404  * All line attributes are maintained relative to the line text, not the
7405  * line index that is specified in this method call.
7406  * During text changes, when entire lines are inserted or removed, the line
7407  * attributes that are associated with the lines after the change
7408  * will "move" with their respective text. An entire line is defined as
7409  * extending from the first character on a line to the last and including the
7410  * line delimiter.
7411  * </p><p>
7412  * When two lines are joined by deleting a line delimiter, the top line
7413  * attributes take precedence and the attributes of the bottom line are deleted.
7414  * For all other text changes line attributes will remain unchanged.
7415  * </p>
7416  *
7417  * @param startLine first line the color is applied to, 0 based
7418  * @param lineCount number of lines the color applies to.
7419  * @param background line background color
7420  * @exception SWTException <ul>
7421  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7422  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7423  * </ul>
7424  * @exception IllegalArgumentException <ul>
7425  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7426  * </ul>
7427  */
7428 public void setLineBackground(int startLine, int lineCount, Color background) {
7429     checkWidget();
7430     if (isListening(LineGetBackground)) return;
7431     if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7432         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7433     }
7434     if (background !is null) {
7435         renderer.setLineBackground(startLine, lineCount, background);
7436     } else {
7437         renderer.clearLineBackground(startLine, lineCount);
7438     }
7439     redrawLines(startLine, lineCount);
7440 }
7441 /**
7442  * Sets the bullet of the specified lines.
7443  * <p>
7444  * Should not be called if a LineStyleListener has been set since the listener
7445  * maintains the line attributes.
7446  * </p><p>
7447  * All line attributes are maintained relative to the line text, not the
7448  * line index that is specified in this method call.
7449  * During text changes, when entire lines are inserted or removed, the line
7450  * attributes that are associated with the lines after the change
7451  * will "move" with their respective text. An entire line is defined as
7452  * extending from the first character on a line to the last and including the
7453  * line delimiter.
7454  * </p><p>
7455  * When two lines are joined by deleting a line delimiter, the top line
7456  * attributes take precedence and the attributes of the bottom line are deleted.
7457  * For all other text changes line attributes will remain unchanged.
7458  * </p>
7459  *
7460  * @param startLine first line the bullet is applied to, 0 based
7461  * @param lineCount number of lines the bullet applies to.
7462  * @param bullet line bullet
7463  *
7464  * @exception SWTException <ul>
7465  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7466  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7467  * </ul>
7468  * @exception IllegalArgumentException <ul>
7469  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7470  * </ul>
7471  * @since 3.2
7472  */
7473 public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
7474     checkWidget();
7475     if (isListening(LineGetStyle)) return;
7476     if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7477         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7478     }
7479 
7480     renderer.setLineBullet(startLine, lineCount, bullet);
7481     resetCache(startLine, lineCount);
7482     redrawLines(startLine, lineCount);
7483     int caretLine = getCaretLine();
7484     if (startLine <= caretLine && caretLine < startLine + lineCount) {
7485         setCaretLocation();
7486     }
7487 }
7488 void setVariableLineHeight () {
7489     if (!fixedLineHeight) return;
7490     fixedLineHeight = false;
7491     renderer.calculateIdle();
7492 }
7493 /**
7494  * Sets the indent of the specified lines.
7495  * <p>
7496  * Should not be called if a LineStyleListener has been set since the listener
7497  * maintains the line attributes.
7498  * </p><p>
7499  * All line attributes are maintained relative to the line text, not the
7500  * line index that is specified in this method call.
7501  * During text changes, when entire lines are inserted or removed, the line
7502  * attributes that are associated with the lines after the change
7503  * will "move" with their respective text. An entire line is defined as
7504  * extending from the first character on a line to the last and including the
7505  * line delimiter.
7506  * </p><p>
7507  * When two lines are joined by deleting a line delimiter, the top line
7508  * attributes take precedence and the attributes of the bottom line are deleted.
7509  * For all other text changes line attributes will remain unchanged.
7510  * </p>
7511  *
7512  * @param startLine first line the indent is applied to, 0 based
7513  * @param lineCount number of lines the indent applies to.
7514  * @param indent line indent
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  * @exception IllegalArgumentException <ul>
7521  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7522  * </ul>
7523  * @see #setIndent(int)
7524  * @since 3.2
7525  */
7526 public void setLineIndent(int startLine, int lineCount, int indent) {
7527     checkWidget();
7528     if (isListening(LineGetStyle)) return;
7529     if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7530         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7531     }
7532 
7533     renderer.setLineIndent(startLine, lineCount, indent);
7534     resetCache(startLine, lineCount);
7535     redrawLines(startLine, lineCount);
7536     int caretLine = getCaretLine();
7537     if (startLine <= caretLine && caretLine < startLine + lineCount) {
7538         setCaretLocation();
7539     }
7540 }
7541 /**
7542  * Sets the justify of the specified lines.
7543  * <p>
7544  * Should not be called if a LineStyleListener has been set since the listener
7545  * maintains the line attributes.
7546  * </p><p>
7547  * All line attributes are maintained relative to the line text, not the
7548  * line index that is specified in this method call.
7549  * During text changes, when entire lines are inserted or removed, the line
7550  * attributes that are associated with the lines after the change
7551  * will "move" with their respective text. An entire line is defined as
7552  * extending from the first character on a line to the last and including the
7553  * line delimiter.
7554  * </p><p>
7555  * When two lines are joined by deleting a line delimiter, the top line
7556  * attributes take precedence and the attributes of the bottom line are deleted.
7557  * For all other text changes line attributes will remain unchanged.
7558  * </p>
7559  *
7560  * @param startLine first line the justify is applied to, 0 based
7561  * @param lineCount number of lines the justify applies to.
7562  * @param justify true if lines should be justified
7563  *
7564  * @exception SWTException <ul>
7565  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7566  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7567  * </ul>
7568  * @exception IllegalArgumentException <ul>
7569  *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7570  * </ul>
7571  * @see #setJustify(bool)
7572  * @since 3.2
7573  */
7574 public void setLineJustify(int startLine, int lineCount, bool justify) {
7575     checkWidget();
7576     if (isListening(LineGetStyle)) return;
7577     if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7578         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7579     }
7580 
7581     renderer.setLineJustify(startLine, lineCount, justify);
7582     resetCache(startLine, lineCount);
7583     redrawLines(startLine, lineCount);
7584     int caretLine = getCaretLine();
7585     if (startLine <= caretLine && caretLine < startLine + lineCount) {
7586         setCaretLocation();
7587     }
7588 }
7589 /**
7590  * Sets the line spacing of the widget. The line spacing applies for all lines.
7591  *
7592  * @param lineSpacing the line spacing
7593  * @exception SWTException <ul>
7594  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7595  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7596  * </ul>
7597  * @since 3.2
7598  */
7599 public void setLineSpacing(int lineSpacing) {
7600     checkWidget();
7601     if (this.lineSpacing is lineSpacing || lineSpacing < 0) return;
7602     this.lineSpacing = lineSpacing;
7603     setVariableLineHeight();
7604     resetCache(0, content.getLineCount());
7605     setCaretLocation();
7606     super.redraw();
7607 }
7608 void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
7609     checkWidget();
7610     this.leftMargin = leftMargin;
7611     this.topMargin = topMargin;
7612     this.rightMargin = rightMargin;
7613     this.bottomMargin = bottomMargin;
7614     setCaretLocation();
7615 }
7616 /**
7617  * Flips selection anchor based on word selection direction.
7618  */
7619 void setMouseWordSelectionAnchor() {
7620     if (clickCount > 1) {
7621         if (caretOffset < doubleClickSelection.x) {
7622             selectionAnchor = doubleClickSelection.y;
7623         } else if (caretOffset > doubleClickSelection.y) {
7624             selectionAnchor = doubleClickSelection.x;
7625         }
7626     }
7627 }
7628 /**
7629  * Sets the orientation of the receiver, which must be one
7630  * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
7631  *
7632  * @param orientation new orientation style
7633  *
7634  * @exception SWTException <ul>
7635  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7636  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7637  * </ul>
7638  *
7639  * @since 2.1.2
7640  */
7641 public void setOrientation(int orientation) {
7642     if ((orientation & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT)) is 0) {
7643         return;
7644     }
7645     if ((orientation & SWT.RIGHT_TO_LEFT) !is 0 && (orientation & SWT.LEFT_TO_RIGHT) !is 0) {
7646         return;
7647     }
7648     if ((orientation & SWT.RIGHT_TO_LEFT) !is 0 && isMirrored()) {
7649         return;
7650     }
7651     if ((orientation & SWT.LEFT_TO_RIGHT) !is 0 && !isMirrored()) {
7652         return;
7653     }
7654     if (!BidiUtil.setOrientation(this, orientation)) {
7655         return;
7656     }
7657     isMirrored_ = (orientation & SWT.RIGHT_TO_LEFT) !is 0;
7658     caretDirection = SWT.NULL;
7659     resetCache(0, content.getLineCount());
7660     setCaretLocation();
7661     keyActionMap = null;
7662     createKeyBindings();
7663     super.redraw();
7664 }
7665 /**
7666  * Adjusts the maximum and the page size of the scroll bars to
7667  * reflect content width/length changes.
7668  *
7669  * @param vertical indicates if the vertical scrollbar also needs to be set
7670  */
7671 void setScrollBars(bool vertical) {
7672     int inactive = 1;
7673     if (vertical || !isFixedLineHeight()) {
7674         ScrollBar verticalBar = getVerticalBar();
7675         if (verticalBar !is null) {
7676             int maximum = renderer.getHeight();
7677             // only set the real values if the scroll bar can be used
7678             // (ie. because the thumb size is less than the scroll maximum)
7679             // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
7680             if (clientAreaHeight < maximum) {
7681                 verticalBar.setMaximum(maximum);
7682                 verticalBar.setThumb(clientAreaHeight);
7683                 verticalBar.setPageIncrement(clientAreaHeight);
7684             } else if (verticalBar.getThumb() !is inactive || verticalBar.getMaximum() !is inactive) {
7685                 verticalBar.setValues(
7686                     verticalBar.getSelection(),
7687                     verticalBar.getMinimum(),
7688                     inactive,
7689                     inactive,
7690                     verticalBar.getIncrement(),
7691                     inactive);
7692             }
7693         }
7694     }
7695     ScrollBar horizontalBar = getHorizontalBar();
7696     if (horizontalBar !is null && horizontalBar.getVisible()) {
7697         int maximum = renderer.getWidth();
7698         // only set the real values if the scroll bar can be used
7699         // (ie. because the thumb size is less than the scroll maximum)
7700         // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
7701         if (clientAreaWidth < maximum) {
7702             horizontalBar.setMaximum(maximum);
7703             horizontalBar.setThumb(clientAreaWidth - leftMargin - rightMargin);
7704             horizontalBar.setPageIncrement(clientAreaWidth - leftMargin - rightMargin);
7705         } else if (horizontalBar.getThumb() !is inactive || horizontalBar.getMaximum() !is inactive) {
7706             horizontalBar.setValues(
7707                 horizontalBar.getSelection(),
7708                 horizontalBar.getMinimum(),
7709                 inactive,
7710                 inactive,
7711                 horizontalBar.getIncrement(),
7712                 inactive);
7713         }
7714     }
7715 }
7716 /**
7717  * Sets the selection to the given position and scrolls it into view.  Equivalent to setSelection(start,start).
7718  *
7719  * @param start new caret position
7720  * @see #setSelection(int,int)
7721  * @exception SWTException <ul>
7722  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7723  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7724  * </ul>
7725  * @exception IllegalArgumentException <ul>
7726  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7727  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7728  * </ul>
7729  */
7730 public void setSelection(int start) {
7731     // checkWidget test done in setSelectionRange
7732     setSelection(start, start);
7733 }
7734 /**
7735  * Sets the selection and scrolls it into view.
7736  * <p>
7737  * Indexing is zero based.  Text selections are specified in terms of
7738  * caret positions.  In a text widget that contains N characters, there are
7739  * N+1 caret positions, ranging from 0..N
7740  * </p>
7741  *
7742  * @param point x=selection start offset, y=selection end offset
7743  *  The caret will be placed at the selection start when x > y.
7744  * @see #setSelection(int,int)
7745  * @exception SWTException <ul>
7746  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7747  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7748  * </ul>
7749  * @exception IllegalArgumentException <ul>
7750  *   <li>ERROR_NULL_ARGUMENT when point is null</li>
7751  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7752  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7753  * </ul>
7754  */
7755 public void setSelection(Point point) {
7756     checkWidget();
7757     if (point is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
7758     setSelection(point.x, point.y);
7759 }
7760 /**
7761  * Sets the receiver's selection background color to the color specified
7762  * by the argument, or to the default system color for the control
7763  * if the argument is null.
7764  *
7765  * @param color the new color (or null)
7766  *
7767  * @exception IllegalArgumentException <ul>
7768  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
7769  * </ul>
7770  * @exception SWTException <ul>
7771  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7772  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7773  * </ul>
7774  * @since 2.1
7775  */
7776 public void setSelectionBackground (Color color) {
7777     checkWidget ();
7778     if (color !is null) {
7779         if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7780     }
7781     selectionBackground = color;
7782     super.redraw();
7783 }
7784 /**
7785  * Sets the receiver's selection foreground color to the color specified
7786  * by the argument, or to the default system color for the control
7787  * if the argument is null.
7788  * <p>
7789  * Note that this is a <em>HINT</em>. Some platforms do not allow the application
7790  * to change the selection foreground color.
7791  * </p>
7792  * @param color the new color (or null)
7793  *
7794  * @exception IllegalArgumentException <ul>
7795  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
7796  * </ul>
7797  * @exception SWTException <ul>
7798  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7799  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7800  * </ul>
7801  * @since 2.1
7802  */
7803 public void setSelectionForeground (Color color) {
7804     checkWidget ();
7805     if (color !is null) {
7806         if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7807     }
7808     selectionForeground = color;
7809     super.redraw();
7810 }
7811 /**
7812  * Sets the selection and scrolls it into view.
7813  * <p>
7814  * Indexing is zero based.  Text selections are specified in terms of
7815  * caret positions.  In a text widget that contains N characters, there are
7816  * N+1 caret positions, ranging from 0..N
7817  * </p>
7818  *
7819  * @param start selection start offset. The caret will be placed at the
7820  *  selection start when start > end.
7821  * @param end selection end offset
7822  * @see #setSelectionRange(int,int)
7823  * @exception SWTException <ul>
7824  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7825  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7826  * </ul>
7827  * @exception IllegalArgumentException <ul>
7828  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7829  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7830  * </ul>
7831  */
7832 public void setSelection(int start, int end) {
7833     setSelectionRange(start, end - start);
7834     showSelection();
7835 }
7836 /**
7837  * Sets the selection.
7838  * <p>
7839  * The new selection may not be visible. Call showSelection to scroll
7840  * the selection into view.
7841  * </p>
7842  *
7843  * @param start offset of the first selected character, start >= 0 must be true.
7844  * @param length number of characters to select, 0 <= start + length
7845  *  <= getCharCount() must be true.
7846  *  A negative length places the caret at the selection start.
7847  * @param sendEvent a Selection event is sent when set to true and when
7848  *  the selection is reset.
7849  */
7850 void setSelection(int start, int length, bool sendEvent) {
7851     int end = start + length;
7852     if (start > end) {
7853         int temp = end;
7854         end = start;
7855         start = temp;
7856     }
7857     // is the selection range different or is the selection direction
7858     // different?
7859     if (selection.x !is start || selection.y !is end ||
7860         (length > 0 && selectionAnchor !is selection.x) ||
7861         (length < 0 && selectionAnchor !is selection.y)) {
7862         clearSelection(sendEvent);
7863         if (length < 0) {
7864             selectionAnchor = selection.y = end;
7865             caretOffset = selection.x = start;
7866         } else {
7867             selectionAnchor = selection.x = start;
7868             caretOffset = selection.y = end;
7869         }
7870         caretAlignment = PREVIOUS_OFFSET_TRAILING;
7871         internalRedrawRange(selection.x, selection.y - selection.x);
7872     }
7873 }
7874 /**
7875  * Sets the selection.
7876  * <p>
7877  * The new selection may not be visible. Call showSelection to scroll the selection
7878  * into view. A negative length places the caret at the visual start of the selection.
7879  * </p>
7880  *
7881  * @param start offset of the first selected character
7882  * @param length number of characters to select
7883  *
7884  * @exception SWTException <ul>
7885  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7886  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7887  * </ul>
7888  * @exception IllegalArgumentException <ul>
7889  *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7890  * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7891  * </ul>
7892  */
7893 public void setSelectionRange(int start, int length) {
7894     checkWidget();
7895     int contentLength = getCharCount();
7896     start = Math.max(0, Math.min (start, contentLength));
7897     int end = start + length;
7898     if (end < 0) {
7899         length = -start;
7900     } else {
7901         if (end > contentLength) length = contentLength - start;
7902     }
7903     if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
7904         // the start offset or end offset of the selection range is inside a
7905         // multi byte line delimiter. This is an illegal operation and an exception
7906         // is thrown. Fixes 1GDKK3R
7907         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
7908     }
7909     setSelection(start, length, false);
7910     setCaretLocation();
7911 }
7912 /**
7913  * Adds the specified style.
7914  * <p>
7915  * The new style overwrites existing styles for the specified range.
7916  * Existing style ranges are adjusted if they partially overlap with
7917  * the new style. To clear an individual style, call setStyleRange
7918  * with a StyleRange that has null attributes.
7919  * </p><p>
7920  * Should not be called if a LineStyleListener has been set since the
7921  * listener maintains the styles.
7922  * </p>
7923  *
7924  * @param range StyleRange object containing the style information.
7925  * Overwrites the old style in the given range. May be null to delete
7926  * all styles.
7927  * @exception SWTException <ul>
7928  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7929  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7930  * </ul>
7931  * @exception IllegalArgumentException <ul>
7932  *   <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li>
7933  * </ul>
7934  */
7935 public void setStyleRange(StyleRange range) {
7936     checkWidget();
7937     if (isListening(LineGetStyle)) return;
7938     if (range !is null) {
7939         if (range.isUnstyled()) {
7940             setStyleRanges(range.start, range.length, null, null, false);
7941         } else {
7942             setStyleRanges(range.start, 0, null, [range], false);
7943         }
7944     } else {
7945         setStyleRanges(0, 0, null, null, true);
7946     }
7947 }
7948 /**
7949  * Clears the styles in the range specified by <code>start</code> and
7950  * <code>length</code> and adds the new styles.
7951  * <p>
7952  * The ranges array contains start and length pairs.  Each pair refers to
7953  * the corresponding style in the styles array.  For example, the pair
7954  * that starts at ranges[n] with length ranges[n+1] uses the style
7955  * at styles[n/2].  The range fields within each StyleRange are ignored.
7956  * If ranges or styles is null, the specified range is cleared.
7957  * </p><p>
7958  * Note: It is expected that the same instance of a StyleRange will occur
7959  * multiple times within the styles array, reducing memory usage.
7960  * </p><p>
7961  * Should not be called if a LineStyleListener has been set since the
7962  * listener maintains the styles.
7963  * </p>
7964  *
7965  * @param start offset of first character where styles will be deleted
7966  * @param length length of the range to delete styles in
7967  * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
7968  * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
7969  *
7970  * @exception SWTException <ul>
7971  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7972  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7973  * </ul>
7974  * @exception IllegalArgumentException <ul>
7975  *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
7976  *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 is styles.length)</li>
7977  *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
7978  *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
7979  * </ul>
7980  *
7981  * @since 3.2
7982  */
7983 public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
7984     checkWidget();
7985     if (isListening(LineGetStyle)) return;
7986     if (ranges is null || styles is null) {
7987         setStyleRanges(start, length, null, null, false);
7988     } else {
7989         setStyleRanges(start, length, ranges, styles, false);
7990     }
7991 }
7992 /**
7993  * Sets styles to be used for rendering the widget content.
7994  * <p>
7995  * All styles in the widget will be replaced with the given set of ranges and styles.
7996  * The ranges array contains start and length pairs.  Each pair refers to
7997  * the corresponding style in the styles array.  For example, the pair
7998  * that starts at ranges[n] with length ranges[n+1] uses the style
7999  * at styles[n/2].  The range fields within each StyleRange are ignored.
8000  * If either argument is null, the styles are cleared.
8001  * </p><p>
8002  * Note: It is expected that the same instance of a StyleRange will occur
8003  * multiple times within the styles array, reducing memory usage.
8004  * </p><p>
8005  * Should not be called if a LineStyleListener has been set since the
8006  * listener maintains the styles.
8007  * </p>
8008  *
8009  * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
8010  * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
8011  *
8012  * @exception SWTException <ul>
8013  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8014  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8015  * </ul>
8016  * @exception IllegalArgumentException <ul>
8017  *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
8018  *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 is styles.length)</li>
8019  *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
8020  *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
8021  * </ul>
8022  *
8023  * @since 3.2
8024  */
8025 public void setStyleRanges(int[] ranges, StyleRange[] styles) {
8026     checkWidget();
8027     if (isListening(LineGetStyle)) return;
8028     if (ranges is null || styles is null) {
8029         setStyleRanges(0, 0, null, null, true);
8030     } else {
8031         setStyleRanges(0, 0, ranges, styles, true);
8032     }
8033 }
8034 void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, bool reset) {
8035     int charCount = content.getCharCount();
8036     int end = start + length;
8037     if (start > end || start < 0) {
8038         SWT.error(SWT.ERROR_INVALID_RANGE);
8039     }
8040     if (styles !is null) {
8041         if (end > charCount) {
8042             SWT.error(SWT.ERROR_INVALID_RANGE);
8043         }
8044         if (ranges !is null) {
8045             if (ranges.length !is styles.length << 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8046         }
8047         int lastOffset = 0;
8048         bool variableHeight = false;
8049         for (int i = 0; i < styles.length; i ++) {
8050             if (styles[i] is null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8051             int rangeStart, rangeLength;
8052             if (ranges !is null) {
8053                 rangeStart = ranges[i << 1];
8054                 rangeLength = ranges[(i << 1) + 1];
8055             } else {
8056                 rangeStart = styles[i].start;
8057                 rangeLength = styles[i].length;
8058             }
8059             if (rangeLength < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8060             if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8061             if (lastOffset > rangeStart) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8062             variableHeight |= styles[i].isVariableHeight();
8063             lastOffset = rangeStart + rangeLength;
8064         }
8065         if (variableHeight) setVariableLineHeight();
8066     }
8067     int rangeStart = start, rangeEnd = end;
8068     if (styles !is null && styles.length > 0) {
8069         if (ranges !is null) {
8070             rangeStart = ranges[0];
8071             rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
8072         } else {
8073             rangeStart = styles[0].start;
8074             rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
8075         }
8076     }
8077     int lastLineBottom = 0;
8078     if (!isFixedLineHeight() && !reset) {
8079         int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
8080         int partialTopIndex = getPartialTopIndex();
8081         int partialBottomIndex = getPartialBottomIndex();
8082         if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
8083             lastLineBottom = getLinePixel(lineEnd + 1);
8084         }
8085     }
8086     if (reset) {
8087         renderer.setStyleRanges(null, null);
8088     } else {
8089         renderer.updateRanges(start, length, length);
8090     }
8091     if (styles !is null && styles.length > 0) {
8092         renderer.setStyleRanges(ranges, styles);
8093     }
8094     if (reset) {
8095         resetCache(0, content.getLineCount());
8096         super.redraw();
8097     } else {
8098         int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
8099         int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
8100         resetCache(lineStart, lineEnd - lineStart + 1);
8101         int partialTopIndex = getPartialTopIndex();
8102         int partialBottomIndex = getPartialBottomIndex();
8103         if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
8104             int y = 0;
8105             int height = clientAreaHeight;
8106             if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
8107                 int lineTop = Math.max(y, getLinePixel(lineStart));
8108                 y = lineTop;
8109                 height -= lineTop;
8110             }
8111             if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
8112                 int newLastLineBottom = getLinePixel(lineEnd + 1);
8113                 if (!isFixedLineHeight()) {
8114                     scrollText(lastLineBottom, newLastLineBottom);
8115                 }
8116                 height = newLastLineBottom - y;
8117             }
8118             super.redraw(0, y, clientAreaWidth, height, false);
8119         }
8120     }
8121     setCaretLocation();
8122 }
8123 /**
8124  * Sets styles to be used for rendering the widget content. All styles
8125  * in the widget will be replaced with the given set of styles.
8126  * <p>
8127  * Note: Because a StyleRange includes the start and length, the
8128  * same instance cannot occur multiple times in the array of styles.
8129  * If the same style attributes, such as font and color, occur in
8130  * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
8131  * can be used to share styles and reduce memory usage.
8132  * </p><p>
8133  * Should not be called if a LineStyleListener has been set since the
8134  * listener maintains the styles.
8135  * </p>
8136  *
8137  * @param ranges StyleRange objects containing the style information.
8138  * The ranges should not overlap. The style rendering is undefined if
8139  * the ranges do overlap. Must not be null. The styles need to be in order.
8140  * @exception SWTException <ul>
8141  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8142  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8143  * </ul>
8144  * @exception IllegalArgumentException <ul>
8145  *    <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
8146  * </ul>
8147  *
8148  * @see #setStyleRanges(int[], StyleRange[])
8149  */
8150 public void setStyleRanges(StyleRange[] ranges) {
8151     checkWidget();
8152     if (isListening(LineGetStyle)) return;
8153     // SWT extension: allow null for zero length string
8154     //if (ranges is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
8155     setStyleRanges(0, 0, null, ranges, true);
8156 }
8157 /**
8158  * Sets the tab width.
8159  *
8160  * @param tabs tab width measured in characters.
8161  * @exception SWTException <ul>
8162  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8163  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8164  * </ul>
8165  */
8166 public void setTabs(int tabs) {
8167     checkWidget();
8168     tabLength = tabs;
8169     renderer.setFont(null, tabs);
8170     resetCache(0, content.getLineCount());
8171     setCaretLocation();
8172     super.redraw();
8173 }
8174 /**
8175  * Sets the widget content.
8176  * If the widget has the SWT.SINGLE style and "text" contains more than
8177  * one line, only the first line is rendered but the text is stored
8178  * unchanged. A subsequent call to getText will return the same text
8179  * that was set.
8180  * <p>
8181  * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE
8182  * style is used.
8183  * </p>
8184  *
8185  * @param text new widget content. Replaces existing content. Line styles
8186  *  that were set using StyledText API are discarded.  The
8187  *  current selection is also discarded.
8188  * @exception SWTException <ul>
8189  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8190  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8191  * </ul>
8192  */
8193 public void setText(String text) {
8194     checkWidget();
8195     // SWT extension: allow null for zero length string
8196 //     if (text is null) {
8197 //         SWT.error(SWT.ERROR_NULL_ARGUMENT);
8198 //     }
8199     Event event = new Event();
8200     event.start = 0;
8201     event.end = getCharCount();
8202     event.text = text;
8203     event.doit = true;
8204     notifyListeners(SWT.Verify, event);
8205     if (event.doit) {
8206         StyledTextEvent styledTextEvent = null;
8207         if (isListening(ExtendedModify)) {
8208             styledTextEvent = new StyledTextEvent(content);
8209             styledTextEvent.start = event.start;
8210             styledTextEvent.end = cast(int)/*64bit*/(event.start + event.text.length);
8211             styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
8212         }
8213         content.setText(event.text);
8214         sendModifyEvent(event);
8215         if (styledTextEvent !is null) {
8216             notifyListeners(ExtendedModify, styledTextEvent);
8217         }
8218     }
8219 }
8220 /**
8221  * Sets the text limit to the specified number of characters.
8222  * <p>
8223  * The text limit specifies the amount of text that
8224  * the user can type into the widget.
8225  * </p>
8226  *
8227  * @param limit the new text limit.
8228  * @exception SWTException <ul>
8229  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8230  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8231  * </ul>
8232  * @exception IllegalArgumentException <ul>
8233  *   <li>ERROR_CANNOT_BE_ZERO when limit is 0</li>
8234  * </ul>
8235  */
8236 public void setTextLimit(int limit) {
8237     checkWidget();
8238     if (limit is 0) {
8239         SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
8240     }
8241     textLimit = limit;
8242 }
8243 /**
8244  * Sets the top index. Do nothing if there is no text set.
8245  * <p>
8246  * The top index is the index of the line that is currently at the top
8247  * of the widget. The top index changes when the widget is scrolled.
8248  * Indexing starts from zero.
8249  * Note: The top index is reset to 0 when new text is set in the widget.
8250  * </p>
8251  *
8252  * @param topIndex new top index. Must be between 0 and
8253  *  getLineCount() - fully visible lines per page. If no lines are fully
8254  *  visible the maximum value is getLineCount() - 1. An out of range
8255  *  index will be adjusted accordingly.
8256  * @exception SWTException <ul>
8257  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8258  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8259  * </ul>
8260  */
8261 public void setTopIndex(int topIndex) {
8262     checkWidget();
8263     if (getCharCount() is 0) {
8264         return;
8265     }
8266     int lineCount = content.getLineCount(), pixel;
8267     if (isFixedLineHeight()) {
8268         int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
8269         if (topIndex < 0) {
8270             topIndex = 0;
8271         } else if (topIndex > lineCount - pageSize) {
8272             topIndex = lineCount - pageSize;
8273         }
8274         pixel = getLinePixel(topIndex);
8275     } else {
8276         topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
8277         pixel = getLinePixel(topIndex);
8278         if (pixel > 0) {
8279             pixel = getAvailableHeightBellow(pixel);
8280         } else {
8281             pixel = getAvailableHeightAbove(pixel);
8282         }
8283     }
8284     scrollVertical(pixel, true);
8285 }
8286 /**
8287  * Sets the top pixel offset. Do nothing if there is no text set.
8288  * <p>
8289  * The top pixel offset is the vertical pixel offset of the widget. The
8290  * widget is scrolled so that the given pixel position is at the top.
8291  * The top index is adjusted to the corresponding top line.
8292  * Note: The top pixel is reset to 0 when new text is set in the widget.
8293  * </p>
8294  *
8295  * @param pixel new top pixel offset. Must be between 0 and
8296  *  (getLineCount() - visible lines per page) / getLineHeight()). An out
8297  *  of range offset will be adjusted accordingly.
8298  * @exception SWTException <ul>
8299  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8300  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8301  * </ul>
8302  * @since 2.0
8303  */
8304 public void setTopPixel(int pixel) {
8305     checkWidget();
8306     if (getCharCount() is 0) {
8307         return;
8308     }
8309     if (pixel < 0) pixel = 0;
8310     int lineCount = content.getLineCount();
8311     int height = clientAreaHeight - topMargin - bottomMargin;
8312     int verticalOffset = getVerticalScrollOffset();
8313     if (isFixedLineHeight()) {
8314         int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
8315         if (pixel > maxTopPixel) pixel = maxTopPixel;
8316         pixel -= verticalOffset;
8317     } else {
8318         pixel -= verticalOffset;
8319         if (pixel > 0) {
8320             pixel = getAvailableHeightBellow(pixel);
8321         }
8322     }
8323     scrollVertical(pixel, true);
8324 }
8325 /**
8326  * Sets whether the widget wraps lines.
8327  * <p>
8328  * This overrides the creation style bit SWT.WRAP.
8329  * </p>
8330  *
8331  * @param wrap true=widget wraps lines, false=widget does not wrap lines
8332  * @since 2.0
8333  */
8334 public void setWordWrap(bool wrap) {
8335     checkWidget();
8336     if ((getStyle() & SWT.SINGLE) !is 0) return;
8337     if (wordWrap is wrap) return;
8338     wordWrap = wrap;
8339     setVariableLineHeight();
8340     resetCache(0, content.getLineCount());
8341     horizontalScrollOffset = 0;
8342     ScrollBar horizontalBar = getHorizontalBar();
8343     if (horizontalBar !is null) {
8344         horizontalBar.setVisible(!wordWrap);
8345     }
8346     setScrollBars(true);
8347     setCaretLocation();
8348     super.redraw();
8349 }
8350 // DWT: If necessary, scroll to show the location
8351 bool showLocation(Rectangle rect, bool scrollPage) {
8352     int clientAreaWidth = this.clientAreaWidth - leftMargin - rightMargin;
8353     int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
8354     bool scrolled = false;
8355     if (rect.y <= topMargin) {
8356         scrolled = scrollVertical(rect.y - topMargin, true);
8357     } else if (rect.y + rect.height > clientAreaHeight) {
8358         if (clientAreaHeight is 0) {
8359             scrolled = scrollVertical(rect.y, true);
8360         } else {
8361             scrolled = scrollVertical(rect.y + rect.height - clientAreaHeight, true);
8362         }
8363     }
8364     if (clientAreaWidth > 0) {
8365         int minScroll = scrollPage ? clientAreaWidth / 4 : 0;
8366         if (rect.x < leftMargin) {
8367             int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
8368             int maxScroll = horizontalScrollOffset;
8369             scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
8370         } else if (rect.x + rect.width > clientAreaWidth) {
8371             int scrollWidth =  Math.max(rect.x + rect.width - clientAreaWidth, minScroll);
8372             int maxScroll = renderer.getWidth() - horizontalScrollOffset - this.clientAreaWidth;
8373             scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
8374         }
8375     }
8376     return scrolled;
8377 }
8378 /**
8379  * Sets the caret location and scrolls the caret offset into view.
8380  */
8381 void showCaret() {
8382     Rectangle bounds = getBoundsAtOffset(caretOffset);
8383     if (!showLocation(bounds, true)) {
8384         setCaretLocation();
8385     }
8386 }
8387 /**
8388  * Scrolls the selection into view.
8389  * <p>
8390  * The end of the selection will be scrolled into view.
8391  * Note that if a right-to-left selection exists, the end of the selection is
8392  * the visual beginning of the selection (i.e., where the caret is located).
8393  * </p>
8394  *
8395  * @exception SWTException <ul>
8396  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8397  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8398  * </ul>
8399  */
8400 public void showSelection() {
8401     checkWidget();
8402     // is selection from right-to-left?
8403     bool rightToLeft = caretOffset is selection.x;
8404     int startOffset, endOffset;
8405     if (rightToLeft) {
8406         startOffset = selection.y;
8407         endOffset = selection.x;
8408     } else {
8409         startOffset = selection.x;
8410         endOffset = selection.y;
8411     }
8412 
8413     Rectangle startBounds = getBoundsAtOffset(startOffset);
8414     Rectangle endBounds = getBoundsAtOffset(endOffset);
8415 
8416     // can the selection be fully displayed within the widget's visible width?
8417     int w = clientAreaWidth - leftMargin - rightMargin;
8418     bool selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
8419     if (selectionFits) {
8420         // show as much of the selection as possible by first showing
8421         // the start of the selection
8422         if (showLocation(startBounds, false)) {
8423             // endX value could change if showing startX caused a scroll to occur
8424             endBounds = getBoundsAtOffset(endOffset);
8425         }
8426         // the character at endOffset is not part of the selection
8427         endBounds.width = 0;
8428         showLocation(endBounds, false);
8429     } else {
8430         // just show the end of the selection since the selection start
8431         // will not be visible
8432         showLocation(endBounds, true);
8433     }
8434 }
8435 /**
8436  * Updates the selection and caret position depending on the text change.
8437  * <p>
8438  * If the selection intersects with the replaced text, the selection is
8439  * reset and the caret moved to the end of the new text.
8440  * If the selection is behind the replaced text it is moved so that the
8441  * same text remains selected.  If the selection is before the replaced text
8442  * it is left unchanged.
8443  * </p>
8444  *
8445  * @param startOffset offset of the text change
8446  * @param replacedLength length of text being replaced
8447  * @param newLength length of new text
8448  */
8449 void updateSelection(int startOffset, int replacedLength, int newLength) {
8450     if (selection.y <= startOffset) {
8451         // selection ends before text change
8452         if (wordWrap) setCaretLocation();
8453         return;
8454     }
8455     if (selection.x < startOffset) {
8456         // clear selection fragment before text change
8457         internalRedrawRange(selection.x, startOffset - selection.x);
8458     }
8459     if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
8460         // clear selection fragment after text change.
8461         // do this only when the selection is actually affected by the
8462         // change. Selection is only affected if it intersects the change (1GDY217).
8463         int netNewLength = newLength - replacedLength;
8464         int redrawStart = startOffset + newLength;
8465         internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
8466     }
8467     selectedTextValid = false;
8468     if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
8469         // selection intersects replaced text. set caret behind text change
8470         setSelection(startOffset + newLength, 0, true);
8471     } else {
8472         // move selection to keep same text selected
8473         setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true);
8474     }
8475     setCaretLocation();
8476 }
8477 
8478 // DWT: to use instead of "offsetInLine - 1"
8479 int getPreviousCharOffset(String F = __FILE__, uint L = __LINE__)(int lineIndex, int offsetInLine) {
8480     String line = content.getLine(lineIndex);
8481     if(offsetInLine < 0 || offsetInLine > line.length) {
8482         getDwtLogger().warn(F, L, Format("Clamped UTF-8 offset:\noffsetInLine = {}, line.length = {}, line = {}", offsetInLine, line.length, line));
8483         return offsetInLine - 1;
8484     }
8485     return cast(int)/*64bit*/line.offsetBefore(offsetInLine);
8486 }
8487 }