diff org.eclipse.swt.gtk.linux.x86/src/org/eclipse/swt/custom/StyledText.d @ 25:f713da8bc051

Added SWT Linux GTK
author Frank Benoit <benoit@tionex.de>
date Fri, 20 Mar 2009 23:03:58 +0100
parents
children ddbfe84d86df
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.swt.gtk.linux.x86/src/org/eclipse/swt/custom/StyledText.d	Fri Mar 20 23:03:58 2009 +0100
@@ -0,0 +1,8453 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module org.eclipse.swt.custom.StyledText;
+
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.accessibility.ACC;
+import org.eclipse.swt.accessibility.Accessible;
+import org.eclipse.swt.accessibility.AccessibleAdapter;
+import org.eclipse.swt.accessibility.AccessibleControlAdapter;
+import org.eclipse.swt.accessibility.AccessibleControlEvent;
+import org.eclipse.swt.accessibility.AccessibleEvent;
+import org.eclipse.swt.accessibility.AccessibleTextAdapter;
+import org.eclipse.swt.accessibility.AccessibleTextEvent;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.RTFTransfer;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.GlyphMetrics;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.Resource;
+import org.eclipse.swt.graphics.TextLayout;
+import org.eclipse.swt.internal.BidiUtil;
+import org.eclipse.swt.internal.Compatibility;
+import org.eclipse.swt.printing.Printer;
+import org.eclipse.swt.printing.PrinterData;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Caret;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.IME;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.TypedListener;
+import org.eclipse.swt.custom.StyledTextContent;
+import org.eclipse.swt.custom.TextChangeListener;
+import org.eclipse.swt.custom.StyledTextRenderer;
+import org.eclipse.swt.custom.StyledTextPrintOptions;
+import org.eclipse.swt.custom.ExtendedModifyListener;
+import org.eclipse.swt.custom.BidiSegmentListener;
+import org.eclipse.swt.custom.LineBackgroundListener;
+import org.eclipse.swt.custom.LineStyleListener;
+import org.eclipse.swt.custom.PaintObjectListener;
+import org.eclipse.swt.custom.VerifyKeyListener;
+import org.eclipse.swt.custom.MovementListener;
+import org.eclipse.swt.custom.Bullet;
+import org.eclipse.swt.custom.StyledTextEvent;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.custom.TextChangedEvent;
+import org.eclipse.swt.custom.TextChangingEvent;
+import org.eclipse.swt.custom.DefaultContent;
+import org.eclipse.swt.custom.StyledTextDropTargetEffect;
+import org.eclipse.swt.custom.StyledTextListener;
+import org.eclipse.swt.custom.ST;
+
+static import tango.text.Util;
+static import tango.io.model.IFile;
+static import tango.text.convert.Utf;
+import tango.util.Convert;
+import java.lang.all;
+
+
+/**
+ * A StyledText is an editable user interface object that displays lines
+ * of text.  The following style attributes can be defined for the text:
+ * <ul>
+ * <li>foreground color
+ * <li>background color
+ * <li>font style (bold, italic, bold-italic, regular)
+ * <li>underline
+ * <li>strikeout
+ * </ul>
+ * <p>
+ * In addition to text style attributes, the background color of a line may
+ * be specified.
+ * </p><p>
+ * There are two ways to use this widget when specifying text style information.
+ * You may use the API that is defined for StyledText or you may define your own
+ * LineStyleListener.  If you define your own listener, you will be responsible
+ * for maintaining the text style information for the widget.  IMPORTANT: You may
+ * not define your own listener and use the StyledText API.  The following
+ * StyledText API is not supported if you have defined a LineStyleListener:
+ * <ul>
+ * <li>getStyleRangeAtOffset(int)
+ * <li>getStyleRanges()
+ * <li>replaceStyleRanges(int,int,StyleRange[])
+ * <li>setStyleRange(StyleRange)
+ * <li>setStyleRanges(StyleRange[])
+ * </ul>
+ * </p><p>
+ * There are two ways to use this widget when specifying line background colors.
+ * You may use the API that is defined for StyledText or you may define your own
+ * LineBackgroundListener.  If you define your own listener, you will be responsible
+ * for maintaining the line background color information for the widget.
+ * IMPORTANT: You may not define your own listener and use the StyledText API.
+ * The following StyledText API is not supported if you have defined a
+ * LineBackgroundListener:
+ * <ul>
+ * <li>getLineBackground(int)
+ * <li>setLineBackground(int,int,Color)
+ * </ul>
+ * </p><p>
+ * The content implementation for this widget may also be user-defined.  To do so,
+ * you must implement the StyledTextContent interface and use the StyledText API
+ * setContent(StyledTextContent) to initialize the widget.
+ * </p><p>
+ * <dl>
+ * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
+ * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey
+ * </dl>
+ * </p><p>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </p>
+ *
+ * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a>
+ * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a>
+ * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
+ */
+public class StyledText : Canvas {
+    alias Canvas.computeSize computeSize;
+
+    static const char TAB = '\t';
+    static const String PlatformLineDelimiter = tango.io.model.IFile.FileConst.NewlineString;
+    static const int BIDI_CARET_WIDTH = 3;
+    static const int DEFAULT_WIDTH  = 64;
+    static const int DEFAULT_HEIGHT = 64;
+    static const int V_SCROLL_RATE = 50;
+    static const int H_SCROLL_RATE = 10;
+
+    static const int ExtendedModify = 3000;
+    static const int LineGetBackground = 3001;
+    static const int LineGetStyle = 3002;
+    static const int TextChanging = 3003;
+    static const int TextSet = 3004;
+    static const int VerifyKey = 3005;
+    static const int TextChanged = 3006;
+    static const int LineGetSegments = 3007;
+    static const int PaintObject = 3008;
+    static const int WordNext = 3009;
+    static const int WordPrevious = 3010;
+
+    static const int PREVIOUS_OFFSET_TRAILING = 0;
+    static const int OFFSET_LEADING = 1;
+
+    Color selectionBackground;  // selection background color
+    Color selectionForeground;  // selection foreground color
+    StyledTextContent content;          // native content (default or user specified)
+    StyledTextRenderer renderer;
+    Listener listener;
+    TextChangeListener textChangeListener;  // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
+    int verticalScrollOffset = 0;       // pixel based
+    int horizontalScrollOffset = 0;     // pixel based
+    int topIndex = 0;                   // top visible line
+    int topIndexY;
+    int clientAreaHeight = 0;           // the client area height. Needed to calculate content width for new visible lines during Resize callback
+    int clientAreaWidth = 0;            // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated
+    int tabLength = 4;                  // number of characters in a tab
+    int leftMargin;
+    int topMargin;
+    int rightMargin;
+    int bottomMargin;
+    int columnX;                        // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
+    int caretOffset = 0;
+    int caretAlignment;
+    Point selection;                    // x and y are start and end caret offsets of selection
+    Point clipboardSelection;           // x and y are start and end caret offsets of previous selection
+    int selectionAnchor;                // position of selection anchor. 0 based offset from beginning of text
+    Point doubleClickSelection;         // selection after last mouse double click
+    bool editable = true;
+    bool wordWrap = false;
+    bool doubleClickEnabled = true;  // see getDoubleClickEnabled
+    bool overwrite = false;          // insert/overwrite edit mode
+    int textLimit = -1;                 // limits the number of characters the user can type in the widget. Unlimited by default.
+    int[int] keyActionMap;
+    Color background = null;            // workaround for bug 4791
+    Color foreground = null;            //
+    Clipboard clipboard;
+    int clickCount;
+    int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left)
+    int autoScrollDistance = 0;
+    int lastTextChangeStart;            // cache data of the
+    int lastTextChangeNewLineCount;     // last text changing
+    int lastTextChangeNewCharCount;     // event for use in the
+    int lastTextChangeReplaceLineCount; // text changed handler
+    int lastTextChangeReplaceCharCount;
+    int lastLineBottom;                 // the bottom pixel of the last line been replaced
+    bool isMirrored_;
+    bool bidiColoring = false;       // apply the BIDI algorithm on text segments of the same color
+    Image leftCaretBitmap = null;
+    Image rightCaretBitmap = null;
+    int caretDirection = SWT.NULL;
+    int caretWidth = 0;
+    Caret defaultCaret = null;
+    bool updateCaretDirection = true;
+    bool fixedLineHeight;
+    bool dragDetect_ = true;
+    IME ime;
+
+    int alignment;
+    bool justify;
+    int indent;
+    int lineSpacing;
+
+    const static bool IS_CARBON, IS_GTK, IS_MOTIF;
+    static this(){
+        String platform = SWT.getPlatform();
+        IS_CARBON = ("carbon" == platform);
+        IS_GTK    = ("gtk"    == platform);
+        IS_MOTIF  = ("motif"  == platform);
+    }
+
+    /**
+     * The Printing class : printing of a range of text.
+     * An instance of <code>Printing</code> is returned in the
+     * StyledText#print(Printer) API. The run() method may be
+     * invoked from any thread.
+     */
+    static class Printing : Runnable {
+        const static int LEFT = 0;                      // left aligned header/footer segment
+        const static int CENTER = 1;                    // centered header/footer segment
+        const static int RIGHT = 2;                     // right aligned header/footer segment
+
+        Printer printer;
+        StyledTextRenderer printerRenderer;
+        StyledTextPrintOptions printOptions;
+        Rectangle clientArea;
+        FontData fontData;
+        Font printerFont;
+        Resource[Resource] resources;
+        int tabLength;
+        GC gc;                                          // printer GC
+        int pageWidth;                                  // width of a printer page in pixels
+        int startPage;                                  // first page to print
+        int endPage;                                    // last page to print
+        int startLine;                                  // first (wrapped) line to print
+        int endLine;                                    // last (wrapped) line to print
+        bool singleLine;                             // widget single line mode
+        Point selection = null;                 // selected text
+        bool mirrored;                       // indicates the printing gc should be mirrored
+        int lineSpacing;
+        int printMargin;
+
+    /**
+     * Creates an instance of <code>Printing</code>.
+     * Copies the widget content and rendering data that needs
+     * to be requested from listeners.
+     * </p>
+     * @param parent StyledText widget to print.
+     * @param printer printer device to print on.
+     * @param printOptions print options
+     */
+    this(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) {
+        this.printer = printer;
+        this.printOptions = printOptions;
+        this.mirrored = (styledText.getStyle() & SWT.MIRRORED) !is 0;
+        singleLine = styledText.isSingleLine();
+        startPage = 1;
+        endPage = int.max;
+        PrinterData data = printer.getPrinterData();
+        if (data.scope_ is PrinterData.PAGE_RANGE) {
+            startPage = data.startPage;
+            endPage = data.endPage;
+            if (endPage < startPage) {
+                int temp = endPage;
+                endPage = startPage;
+                startPage = temp;
+            }
+        } else if (data.scope_ is PrinterData.SELECTION) {
+            selection = styledText.getSelectionRange();
+        }
+        printerRenderer = new StyledTextRenderer(printer, null);
+        printerRenderer.setContent(copyContent(styledText.getContent()));
+        cacheLineData(styledText);
+    }
+    /**
+     * Caches all line data that needs to be requested from a listener.
+     * </p>
+     * @param printerContent <code>StyledTextContent</code> to request
+     *  line data for.
+     */
+    void cacheLineData(StyledText styledText) {
+        StyledTextRenderer renderer = styledText.renderer;
+        renderer.copyInto(printerRenderer);
+        fontData = styledText.getFont().getFontData()[0];
+        tabLength = styledText.tabLength;
+        int lineCount = printerRenderer.lineCount;
+        if (styledText.isListening(LineGetBackground) || (styledText.isBidi() && styledText.isListening(LineGetSegments)) || styledText.isListening(LineGetStyle)) {
+            StyledTextContent content = printerRenderer.content;
+            for (int i = 0; i < lineCount; i++) {
+                String line = content.getLine(i);
+                int lineOffset = content.getOffsetAtLine(i);
+                StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
+                if (event !is null && event.lineBackground !is null) {
+                    printerRenderer.setLineBackground(i, 1, event.lineBackground);
+                }
+                if (styledText.isBidi()) {
+                    int[] segments = styledText.getBidiSegments(lineOffset, line);
+                    printerRenderer.setLineSegments(i, 1, segments);
+                }
+                event = styledText.getLineStyleData(lineOffset, line);
+                if (event !is null) {
+                    printerRenderer.setLineIndent(i, 1, event.indent);
+                    printerRenderer.setLineAlignment(i, 1, event.alignment);
+                    printerRenderer.setLineJustify(i, 1, event.justify);
+                    printerRenderer.setLineBullet(i, 1, event.bullet);
+                    StyleRange[] styles = event.styles;
+                    if (styles !is null && styles.length > 0) {
+                        printerRenderer.setStyleRanges(event.ranges, styles);
+                    }
+                }
+            }
+        }
+        Point screenDPI = styledText.getDisplay().getDPI();
+        Point printerDPI = printer.getDPI();
+        resources = null;
+        for (int i = 0; i < lineCount; i++) {
+            Color color = printerRenderer.getLineBackground(i, null);
+            if (color !is null) {
+                if (printOptions.printLineBackground) {
+                    Color printerColor;
+                    if ( auto p = color in resources ) {
+                        printerColor = cast(Color)*p;
+                    }
+                    else {
+                        printerColor = new Color (printer, color.getRGB());
+                        resources[color]=printerColor;
+                    }
+                    printerRenderer.setLineBackground(i, 1, printerColor);
+                } else {
+                    printerRenderer.setLineBackground(i, 1, null);
+                }
+            }
+            int indent = printerRenderer.getLineIndent(i, 0);
+            if (indent !is 0) {
+                printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
+            }
+        }
+        StyleRange[] styles = printerRenderer.styles;
+        for (int i = 0; i < printerRenderer.styleCount; i++) {
+            StyleRange style = styles[i];
+            Font font = style.font;
+            if (style.font !is null) {
+                Font printerFont;
+                if ( auto p = font in resources ) {
+                    printerFont = cast(Font)*p;
+                }
+                else {
+                    printerFont = new Font (printer, font.getFontData());
+                    resources[font]= printerFont;
+                }
+                style.font = printerFont;
+            }
+            Color color = style.foreground;
+            if (color !is null) {
+                if (printOptions.printTextForeground) {
+                    Color printerColor;
+                    if ( auto p = color in resources ) {
+                        printerColor = cast(Color)*p;
+                    }
+                    else {
+                        printerColor = new Color (printer, color.getRGB());
+                        resources[color]=printerColor;
+                    }
+                    style.foreground = printerColor;
+                } else {
+                    style.foreground = null;
+                }
+            }
+            color = style.background;
+            if (color !is null) {
+                if (printOptions.printTextBackground) {
+                    Color printerColor;
+                    if ( auto p = color in resources ) {
+                        printerColor = cast(Color)*p;
+                    }
+                    else {
+                        printerColor = new Color (printer, color.getRGB());
+                        resources[color]=printerColor;
+                    }
+                    style.background = printerColor;
+                } else {
+                    style.background = null;
+                }
+            }
+            if (!printOptions.printTextFontStyle) {
+                style.fontStyle = SWT.NORMAL;
+            }
+            style.rise = style.rise * printerDPI.y / screenDPI.y;
+            GlyphMetrics metrics = style.metrics;
+            if (metrics !is null) {
+                metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
+                metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
+                metrics.width = metrics.width * printerDPI.x / screenDPI.x;
+            }
+        }
+        lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
+        if (printOptions.printLineNumbers) {
+            printMargin = 3 * printerDPI.x / screenDPI.x;
+        }
+    }
+    /**
+     * Copies the text of the specified <code>StyledTextContent</code>.
+     * </p>
+     * @param original the <code>StyledTextContent</code> to copy.
+     */
+    StyledTextContent copyContent(StyledTextContent original) {
+        StyledTextContent printerContent = new DefaultContent();
+        int insertOffset = 0;
+        for (int i = 0; i < original.getLineCount(); i++) {
+            int insertEndOffset;
+            if (i < original.getLineCount() - 1) {
+                insertEndOffset = original.getOffsetAtLine(i + 1);
+            } else {
+                insertEndOffset = original.getCharCount();
+            }
+            printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset));
+            insertOffset = insertEndOffset;
+        }
+        return printerContent;
+    }
+    /**
+     * Disposes of the resources and the <code>PrintRenderer</code>.
+     */
+    void dispose() {
+        if (gc !is null) {
+            gc.dispose();
+            gc = null;
+        }
+        foreach( resource; resources.values ){
+            resource.dispose();
+        }
+        resources = null;
+        if (printerFont !is null) {
+            printerFont.dispose();
+            printerFont = null;
+        }
+        if (printerRenderer !is null) {
+            printerRenderer.dispose();
+            printerRenderer = null;
+        }
+    }
+    void init_() {
+        Rectangle trim = printer.computeTrim(0, 0, 0, 0);
+        Point dpi = printer.getDPI();
+
+        printerFont = new Font( cast(Device)printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL);
+        clientArea = printer.getClientArea();
+        pageWidth = clientArea.width;
+        // one inch margin around text
+        clientArea.x = dpi.x + trim.x;
+        clientArea.y = dpi.y + trim.y;
+        clientArea.width -= (clientArea.x + trim.width);
+        clientArea.height -= (clientArea.y + trim.height);
+
+        int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
+        gc = new GC(printer, style);
+        gc.setFont(printerFont);
+        printerRenderer.setFont(printerFont, tabLength);
+        int lineHeight = printerRenderer.getLineHeight();
+        if (printOptions.header !is null) {
+            clientArea.y += lineHeight * 2;
+            clientArea.height -= lineHeight * 2;
+        }
+        if (printOptions.footer !is null) {
+            clientArea.height -= lineHeight * 2;
+        }
+
+        // TODO not wrapped
+        StyledTextContent content = printerRenderer.content;
+        startLine = 0;
+        endLine = singleLine ? 0 : content.getLineCount() - 1;
+        PrinterData data = printer.getPrinterData();
+        if (data.scope_ is PrinterData.PAGE_RANGE) {
+            int pageSize = clientArea.height / lineHeight;//WRONG
+            startLine = (startPage - 1) * pageSize;
+        } else if (data.scope_ is PrinterData.SELECTION) {
+            startLine = content.getLineAtOffset(selection.x);
+            if (selection.y > 0) {
+                endLine = content.getLineAtOffset(selection.x + selection.y - 1);
+            } else {
+                endLine = startLine - 1;
+            }
+        }
+    }
+    /**
+     * Prints the lines in the specified page range.
+     */
+    void print() {
+        Color background = gc.getBackground();
+        Color foreground = gc.getForeground();
+        int paintY = clientArea.y;
+        int paintX = clientArea.x;
+        int width = clientArea.width;
+        int page = startPage;
+        int pageBottom = clientArea.y + clientArea.height;
+        int orientation =  gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT);
+        TextLayout printLayout = null;
+        if (printOptions.printLineNumbers || printOptions.header !is null || printOptions.footer !is null) {
+            printLayout = new TextLayout(printer);
+            printLayout.setFont(printerFont);
+        }
+        if (printOptions.printLineNumbers) {
+            int numberingWidth = 0;
+            int count = endLine - startLine + 1;
+            String[] lineLabels = printOptions.lineLabels;
+            if (lineLabels !is null) {
+                for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
+                    if (lineLabels[i] !is null) {
+                        printLayout.setText(lineLabels[i]);
+                        int lineWidth = printLayout.getBounds().width;
+                        numberingWidth = Math.max(numberingWidth, lineWidth);
+                    }
+                }
+            } else {
+                StringBuffer buffer = new StringBuffer("0");
+                while ((count /= 10) > 0) buffer.append("0");
+                printLayout.setText(buffer.toString());
+                numberingWidth = printLayout.getBounds().width;
+            }
+            numberingWidth += printMargin;
+            if (numberingWidth > width) numberingWidth = width;
+            paintX += numberingWidth;
+            width -= numberingWidth;
+        }
+        for (int i = startLine; i <= endLine && page <= endPage; i++) {
+            if (paintY is clientArea.y) {
+                printer.startPage();
+                printDecoration(page, true, printLayout);
+            }
+            TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
+            Color lineBackground = printerRenderer.getLineBackground(i, background);
+            int paragraphBottom = paintY + layout.getBounds().height;
+            if (paragraphBottom <= pageBottom) {
+                //normal case, the whole paragraph fits in the current page
+                printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
+                paintY = paragraphBottom;
+            } else {
+                int lineCount = layout.getLineCount();
+                while (paragraphBottom > pageBottom && lineCount > 0) {
+                    lineCount--;
+                    paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
+                }
+                if (lineCount is 0) {
+                    //the whole paragraph goes to the next page
+                    printDecoration(page, false, printLayout);
+                    printer.endPage();
+                    page++;
+                    if (page <= endPage) {
+                        printer.startPage();
+                        printDecoration(page, true, printLayout);
+                        paintY = clientArea.y;
+                        printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
+                        paintY += layout.getBounds().height;
+                    }
+                } else {
+                    //draw paragraph top in the current page and paragraph bottom in the next
+                    int height = paragraphBottom - paintY;
+                    gc.setClipping(clientArea.x, paintY, clientArea.width, height);
+                    printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
+                    gc.setClipping(cast(Rectangle)null);
+                    printDecoration(page, false, printLayout);
+                    printer.endPage();
+                    page++;
+                    if (page <= endPage) {
+                        printer.startPage();
+                        printDecoration(page, true, printLayout);
+                        paintY = clientArea.y - height;
+                        int layoutHeight = layout.getBounds().height;
+                        gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
+                        printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
+                        gc.setClipping(cast(Rectangle)null);
+                        paintY += layoutHeight;
+                    }
+                }
+            }
+            printerRenderer.disposeTextLayout(layout);
+        }
+        if (page <= endPage && paintY > clientArea.y) {
+            // close partial page
+            printDecoration(page, false, printLayout);
+            printer.endPage();
+        }
+        if (printLayout !is null) printLayout.dispose();
+    }
+    /**
+     * Print header or footer decorations.
+     *
+     * @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
+     * @param header true = print the header, false = print the footer
+     */
+    void printDecoration(int page, bool header, TextLayout layout) {
+        String text = header ? printOptions.header : printOptions.footer;
+        if (text is null) return;
+        int lastSegmentIndex = 0;
+        for (int i = 0; i < 3; i++) {
+            int segmentIndex = text.indexOf( StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
+            String segment;
+            if (segmentIndex is -1 ) {
+                segment = text.substring(lastSegmentIndex);
+                printDecorationSegment(segment, i, page, header, layout);
+                break;
+            } else {
+                segment = text.substring(lastSegmentIndex, segmentIndex);
+                printDecorationSegment(segment, i, page, header, layout);
+                lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length;
+            }
+        }
+    }
+    /**
+     * Print one segment of a header or footer decoration.
+     * Headers and footers have three different segments.
+     * One each for left aligned, centered, and right aligned text.
+     *
+     * @param segment decoration segment to print
+     * @param alignment alignment of the segment. 0=left, 1=center, 2=right
+     * @param page page number to print, if specified in the decoration segment.
+     * @param header true = print the header, false = print the footer
+     */
+    void printDecorationSegment(String segment, int alignment, int page, bool header, TextLayout layout) {
+        int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
+        if (pageIndex !is -1 ) {
+            int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length;
+            StringBuffer buffer = new StringBuffer(segment.substring (0, pageIndex));
+            buffer.append (page);
+            buffer.append (segment.substring(pageIndex + pageTagLength));
+            segment = buffer.toString().dup;
+        }
+        if (segment.length > 0) {
+            layout.setText(segment);
+            int segmentWidth = layout.getBounds().width;
+            int segmentHeight = printerRenderer.getLineHeight();
+            int drawX = 0, drawY;
+            if (alignment is LEFT) {
+                drawX = clientArea.x;
+            } else if (alignment is CENTER) {
+                drawX = (pageWidth - segmentWidth) / 2;
+            } else if (alignment is RIGHT) {
+                drawX = clientArea.x + clientArea.width - segmentWidth;
+            }
+            if (header) {
+                drawY = clientArea.y - segmentHeight * 2;
+            } else {
+                drawY = clientArea.y + clientArea.height + segmentHeight;
+            }
+            layout.draw(gc, drawX, drawY);
+        }
+    }
+    void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) {
+        if (background !is null) {
+            Rectangle rect = layout.getBounds();
+            gc.setBackground(background);
+            gc.fillRectangle(x, y, rect.width, rect.height);
+
+//          int lineCount = layout.getLineCount();
+//          for (int i = 0; i < lineCount; i++) {
+//              Rectangle rect = layout.getLineBounds(i);
+//              rect.x += paintX;
+//              rect.y += paintY + layout.getSpacing();
+//              rect.width = width;//layout bounds
+//              gc.fillRectangle(rect);
+//          }
+        }
+        if (printOptions.printLineNumbers) {
+            FontMetrics metrics = layout.getLineMetrics(0);
+            printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
+            printLayout.setDescent(metrics.getDescent());
+            String[] lineLabels = printOptions.lineLabels;
+            if (lineLabels !is null) {
+                if (0 <= index && index < lineLabels.length && lineLabels[index] !is null) {
+                    printLayout.setText(lineLabels[index]);
+                } else {
+                    printLayout.setText("");
+                }
+            } else {
+                printLayout.setText(to!(String)(index));
+            }
+            int paintX = x - printMargin - printLayout.getBounds().width;
+            printLayout.draw(gc, paintX, y);
+            printLayout.setAscent(-1);
+            printLayout.setDescent(-1);
+        }
+        gc.setForeground(foreground);
+        layout.draw(gc, x, y);
+    }
+    /**
+     * Starts a print job and prints the pages specified in the constructor.
+     */
+    public void run() {
+        String jobName = printOptions.jobName;
+        if (jobName is null) {
+            jobName = "Printing";
+        }
+        if (printer.startJob(jobName)) {
+            init_();
+            print();
+            dispose();
+            printer.endJob();
+        }
+    }
+    }
+    /**
+     * The <code>RTFWriter</code> class is used to write widget content as
+     * rich text. The implementation complies with the RTF specification
+     * version 1.5.
+     * <p>
+     * toString() is guaranteed to return a valid RTF string only after
+     * close() has been called.
+     * </p><p>
+     * Whole and partial lines and line breaks can be written. Lines will be
+     * formatted using the styles queried from the LineStyleListener, if
+     * set, or those set directly in the widget. All styles are applied to
+     * the RTF stream like they are rendered by the widget. In addition, the
+     * widget font name and size is used for the whole text.
+     * </p>
+     */
+    class RTFWriter : TextWriter {
+
+        alias TextWriter.write write;
+
+        static const int DEFAULT_FOREGROUND = 0;
+        static const int DEFAULT_BACKGROUND = 1;
+        Color[] colorTable;
+        Font[] fontTable;
+        bool WriteUnicode;
+
+    /**
+     * Creates a RTF writer that writes content starting at offset "start"
+     * in the document.  <code>start</code> and <code>length</code>can be set to specify partial
+     * lines.
+     *
+     * @param start start offset of content to write, 0 based from
+     *  beginning of document
+     * @param length length of content to write
+     */
+    public this(int start, int length) {
+        super(start, length);
+        colorTable ~= getForeground();
+        colorTable ~= getBackground();
+        fontTable ~= getFont();
+        setUnicode();
+    }
+    /**
+     * Closes the RTF writer. Once closed no more content can be written.
+     * <b>NOTE:</b>  <code>toString()</code> does not return a valid RTF string until
+     * <code>close()</code> has been called.
+     */
+    public override void close() {
+        if (!isClosed()) {
+            writeHeader();
+            write("\n}}\0");
+            super.close();
+        }
+    }
+    /**
+     * Returns the index of the specified color in the RTF color table.
+     *
+     * @param color the color
+     * @param defaultIndex return value if color is null
+     * @return the index of the specified color in the RTF color table
+     *  or "defaultIndex" if "color" is null.
+     */
+    int getColorIndex(Color color, int defaultIndex) {
+        if (color is null) return defaultIndex;
+        int index = -1;
+        foreach( i, col; colorTable ){
+            if( col == color ){
+                index = i;
+                break;
+            }
+        }
+        if (index is -1) {
+            index = colorTable.length;
+            colorTable ~= color;
+        }
+        return index;
+    }
+    /**
+     * Returns the index of the specified color in the RTF color table.
+     *
+     * @param color the color
+     * @param defaultIndex return value if color is null
+     * @return the index of the specified color in the RTF color table
+     *  or "defaultIndex" if "color" is null.
+     */
+    int getFontIndex(Font font) {
+        int index = -1;
+        foreach( i, f; colorTable ){
+            if( f == font ){
+                index = i;
+                break;
+            }
+        }
+        if (index is -1) {
+            index = fontTable.length;
+            fontTable ~= font;
+        }
+        return index;
+    }
+    /**
+     * Determines if Unicode RTF should be written.
+     * Don't write Unicode RTF on Windows 95/98/ME or NT.
+     */
+    void setUnicode() {
+//         const String Win95 = "windows 95";
+//         const String Win98 = "windows 98";
+//         const String WinME = "windows me";
+//         const String WinNT = "windows nt";
+//         String osName = System.getProperty("os.name").toLowerCase();
+//         String osVersion = System.getProperty("os.version");
+//         int majorVersion = 0;
+//
+//         if (osName.startsWith(WinNT) && osVersion !is null) {
+//             int majorIndex = osVersion.indexOf('.');
+//             if (majorIndex !is -1) {
+//                 osVersion = osVersion.substring(0, majorIndex);
+//                 try {
+//                     majorVersion = Integer.parseInt(osVersion);
+//                 } catch (NumberFormatException exception) {
+//                     // ignore exception. version number remains unknown.
+//                     // will write without Unicode
+//                 }
+//             }
+//         }
+//         WriteUnicode =  !osName.startsWith(Win95) &&
+//                         !osName.startsWith(Win98) &&
+//                         !osName.startsWith(WinME) &&
+//                         (!osName.startsWith(WinNT) || majorVersion > 4);
+        WriteUnicode = true; // we are on linux-gtk
+    }
+    /**
+     * Appends the specified segment of "string" to the RTF data.
+     * Copy from <code>start</code> up to, but excluding, <code>end</code>.
+     *
+     * @param string string to copy a segment from. Must not contain
+     *  line breaks. Line breaks should be written using writeLineDelimiter()
+     * @param start start offset of segment. 0 based.
+     * @param end end offset of segment
+     */
+    void write(String string, int start, int end) {
+        start = 0;
+        end = string.length;
+        int incr = 1;
+        for (int index = start; index < end; index+=incr) {
+            dchar ch = firstCodePoint( string[index .. $], incr );
+            if (ch > 0xFF && WriteUnicode) {
+                // write the sub string from the last escaped character
+                // to the current one. Fixes bug 21698.
+                if (index > start) {
+                    write( string[start .. index ] );
+                }
+                write("\\u");
+                write( to!(String)( cast(short)ch ));
+                write(' ');                     // control word delimiter
+                start = index + incr;
+            } else if (ch is '}' || ch is '{' || ch is '\\') {
+                // write the sub string from the last escaped character
+                // to the current one. Fixes bug 21698.
+                if (index > start) {
+                    write(string[start .. index]);
+                }
+                write('\\');
+                write(cast(char)ch); // ok because one of {}\
+                start = index + 1;
+            }
+        }
+        // write from the last escaped character to the end.
+        // Fixes bug 21698.
+        if (start < end) {
+            write(string[ start .. end]);
+        }
+    }
+    /**
+     * Writes the RTF header including font table and color table.
+     */
+    void writeHeader() {
+        StringBuffer header = new StringBuffer();
+        FontData fontData = getFont().getFontData()[0];
+        header.append("{\\rtf1\\ansi");
+        // specify code page, necessary for copy to work in bidi
+        // systems that don't support Unicode RTF.
+        // PORTING_TODO: String cpg = System.getProperty("file.encoding").toLowerCase();
+        String cpg = "UTF16";
+        /+
+        if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
+            cpg = cpg.substring(2, cpg.length());
+            header.append("\\ansicpg");
+            header.append(cpg);
+        }
+        +/
+        header.append("\\uc0\\deff0{\\fonttbl{\\f0\\fnil ");
+        header.append(fontData.getName());
+        header.append(";");
+        for (int i = 1; i < fontTable.length; i++) {
+            header.append("\\f");
+            header.append(i);
+            header.append(" ");
+            FontData fd = (cast(Font)fontTable[i]).getFontData()[0];
+            header.append(fd.getName());
+            header.append(";");
+        }
+        header.append("}}\n{\\colortbl");
+        for (int i = 0; i < colorTable.length; i++) {
+            Color color = cast(Color) colorTable[i];
+            header.append("\\red");
+            header.append(color.getRed());
+            header.append("\\green");
+            header.append(color.getGreen());
+            header.append("\\blue");
+            header.append(color.getBlue());
+            header.append(";");
+        }
+        // some RTF readers ignore the deff0 font tag. Explicitly
+        // set the font for the whole document to work around this.
+        header.append("}\n{\\f0\\fs");
+        // font size is specified in half points
+        header.append(fontData.getHeight() * 2);
+        header.append(" ");
+        write(header.toString(), 0);
+    }
+    /**
+     * Appends the specified line text to the RTF data.  Lines will be formatted
+     * using the styles queried from the LineStyleListener, if set, or those set
+     * directly in the widget.
+     *
+     * @param line line text to write as RTF. Must not contain line breaks
+     *  Line breaks should be written using writeLineDelimiter()
+     * @param lineOffset offset of the line. 0 based from the start of the
+     *  widget document. Any text occurring before the start offset or after the
+     *  end offset specified during object creation is ignored.
+     * @exception SWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public override void writeLine(String line, int lineOffset) {
+        if (isClosed()) {
+            SWT.error(SWT.ERROR_IO);
+        }
+        int lineIndex = content.getLineAtOffset(lineOffset);
+        int lineAlignment, lineIndent;
+        bool lineJustify;
+        int[] ranges;
+        StyleRange[] styles;
+        StyledTextEvent event = getLineStyleData(lineOffset, line);
+        if (event !is null) {
+            lineAlignment = event.alignment;
+            lineIndent = event.indent;
+            lineJustify = event.justify;
+            ranges = event.ranges;
+            styles = event.styles;
+        } else {
+            lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
+            lineIndent =  renderer.getLineIndent(lineIndex, indent);
+            lineJustify = renderer.getLineJustify(lineIndex, justify);
+            ranges = renderer.getRanges(lineOffset, line.length);
+            styles = renderer.getStyleRanges(lineOffset, line.length, false);
+        }
+        if (styles is null) styles = new StyleRange[0];
+        Color lineBackground = renderer.getLineBackground(lineIndex, null);
+        event = getLineBackgroundData(lineOffset, line);
+        if (event !is null && event.lineBackground !is null) lineBackground = event.lineBackground;
+        writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify);
+    }
+    /**
+     * Appends the specified line delimiter to the RTF data.
+     *
+     * @param lineDelimiter line delimiter to write as RTF.
+     * @exception SWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public override void writeLineDelimiter(String lineDelimiter) {
+        if (isClosed()) {
+            SWT.error(SWT.ERROR_IO);
+        }
+        write(lineDelimiter, 0, lineDelimiter.length);
+        write("\\par ");
+    }
+    /**
+     * Appends the specified line text to the RTF data.
+     * <p>
+     * Use the colors and font styles specified in "styles" and "lineBackground".
+     * Formatting is written to reflect the text rendering by the text widget.
+     * Style background colors take precedence over the line background color.
+     * Background colors are written using the \highlight tag (vs. the \cb tag).
+     * </p>
+     *
+     * @param line line text to write as RTF. Must not contain line breaks
+     *  Line breaks should be written using writeLineDelimiter()
+     * @param lineOffset offset of the line. 0 based from the start of the
+     *  widget document. Any text occurring before the start offset or after the
+     *  end offset specified during object creation is ignored.
+     * @param styles styles to use for formatting. Must not be null.
+     * @param lineBackground line background color to use for formatting.
+     *  May be null.
+     */
+    void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, bool justify) {
+        int lineLength = line.length;
+        int startOffset = getStart();
+        int writeOffset = startOffset - lineOffset;
+        if (writeOffset >= lineLength) return;
+        int lineIndex = Math.max(0, writeOffset);
+
+        write("\\fi");
+        write(indent);
+        switch (alignment) {
+            case SWT.LEFT: write("\\ql"); break;
+            case SWT.CENTER: write("\\qc"); break;
+            case SWT.RIGHT: write("\\qr"); break;
+            default:
+        }
+        if (justify) write("\\qj");
+        write(" ");
+
+        if (lineBackground !is null) {
+            write("{\\highlight");
+            write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
+            write(" ");
+        }
+        int endOffset = startOffset + super.getCharCount();
+        int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
+        for (int i = 0; i < styles.length; i++) {
+            StyleRange style = styles[i];
+            int start, end;
+            if (ranges !is null) {
+                start = ranges[i << 1] - lineOffset;
+                end = start + ranges[(i << 1) + 1];
+            } else {
+                start = style.start - lineOffset;
+                end = start + style.length;
+            }
+            // skip over partial first line
+            if (end < writeOffset) {
+                continue;
+            }
+            // style starts beyond line end or RTF write end
+            if (start >= lineEndOffset) {
+                break;
+            }
+            // write any unstyled text
+            if (lineIndex < start) {
+                // copy to start of style
+                // style starting beyond end of write range or end of line
+                // is guarded against above.
+                write(line, lineIndex, start);
+                lineIndex = start;
+            }
+            // write styled text
+            write("{\\cf");
+            write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
+            int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
+            if (colorIndex !is DEFAULT_BACKGROUND) {
+                write("\\highlight");
+                write(colorIndex);
+            }
+            Font font = style.font;
+            if (font !is null) {
+                int fontIndex = getFontIndex(font);
+                write("\\f");
+                write(fontIndex);
+                FontData fontData = font.getFontData()[0];
+                write("\\fs");
+                write(fontData.getHeight() * 2);
+            } else {
+                if ((style.fontStyle & SWT.BOLD) !is 0) {
+                    write("\\b");
+                }
+                if ((style.fontStyle & SWT.ITALIC) !is 0) {
+                    write("\\i");
+                }
+            }
+            if (style.underline) {
+                write("\\ul");
+            }
+            if (style.strikeout) {
+                write("\\strike");
+            }
+            write(" ");
+            // copy to end of style or end of write range or end of line
+            int copyEnd = Math.min(end, lineEndOffset);
+            // guard against invalid styles and let style processing continue
+            copyEnd = Math.max(copyEnd, lineIndex);
+            write(line, lineIndex, copyEnd);
+            if (font is null) {
+                if ((style.fontStyle & SWT.BOLD) !is 0) {
+                    write("\\b0");
+                }
+                if ((style.fontStyle & SWT.ITALIC) !is 0) {
+                    write("\\i0");
+                }
+            }
+            if (style.underline) {
+                write("\\ul0");
+            }
+            if (style.strikeout) {
+                write("\\strike0");
+            }
+            write("}");
+            lineIndex = copyEnd;
+        }
+        // write unstyled text at the end of the line
+        if (lineIndex < lineEndOffset) {
+            write(line, lineIndex, lineEndOffset);
+        }
+        if (lineBackground !is null) write("}");
+    }
+    }
+    /**
+     * The <code>TextWriter</code> class is used to write widget content to
+     * a string.  Whole and partial lines and line breaks can be written. To write
+     * partial lines, specify the start and length of the desired segment
+     * during object creation.
+     * <p>
+     * </b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close()
+     * has been called.
+     * </p>
+     */
+    class TextWriter {
+        private StringBuffer buffer;
+        private int startOffset;    // offset of first character that will be written
+        private int endOffset;      // offset of last character that will be written.
+                                    // 0 based from the beginning of the widget text.
+        private bool isClosed_ = false;
+
+    /**
+     * Creates a writer that writes content starting at offset "start"
+     * in the document.  <code>start</code> and <code>length</code> can be set to specify partial lines.
+     *
+     * @param start start offset of content to write, 0 based from beginning of document
+     * @param length length of content to write
+     */
+    public this(int start, int length) {
+        buffer = new StringBuffer(length);
+        startOffset = start;
+        endOffset = start + length;
+    }
+    /**
+     * Closes the writer. Once closed no more content can be written.
+     * <b>NOTE:</b>  <code>toString()</code> is not guaranteed to return a valid string unless
+     * the writer is closed.
+     */
+    public void close() {
+        if (!isClosed_) {
+            isClosed_ = true;
+        }
+    }
+    /**
+     * Returns the number of characters to write.
+     * @return the integer number of characters to write
+     */
+    public int getCharCount() {
+        return endOffset - startOffset;
+    }
+    /**
+     * Returns the offset where writing starts. 0 based from the start of
+     * the widget text. Used to write partial lines.
+     * @return the integer offset where writing starts
+     */
+    public int getStart() {
+        return startOffset;
+    }
+    /**
+     * Returns whether the writer is closed.
+     * @return a bool specifying whether or not the writer is closed
+     */
+    public bool isClosed() {
+        return isClosed_;
+    }
+    /**
+     * Returns the string.  <code>close()</code> must be called before <code>toString()</code>
+     * is guaranteed to return a valid string.
+     *
+     * @return the string
+     */
+    public override String toString() {
+        return buffer.toString();
+    }
+    /**
+     * Appends the given string to the data.
+     */
+    void write(String string) {
+        buffer.append(string);
+    }
+    /**
+     * Inserts the given string to the data at the specified offset.
+     * <p>
+     * Do nothing if "offset" is < 0 or > getCharCount()
+     * </p>
+     *
+     * @param string text to insert
+     * @param offset offset in the existing data to insert "string" at.
+     */
+    void write(String string, int offset) {
+        if (offset < 0 || offset > buffer.length()) {
+            return;
+        }
+        buffer.insert( offset, string );
+    }
+    /**
+     * Appends the given int to the data.
+     */
+    void write(int i) {
+        buffer.append(i);
+    }
+    /**
+     * Appends the given character to the data.
+     */
+    void write(char i) {
+        buffer.append(i);
+    }
+    /**
+     * Appends the specified line text to the data.
+     *
+     * @param line line text to write. Must not contain line breaks
+     *  Line breaks should be written using writeLineDelimiter()
+     * @param lineOffset offset of the line. 0 based from the start of the
+     *  widget document. Any text occurring before the start offset or after the
+     *  end offset specified during object creation is ignored.
+     * @exception SWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public void writeLine(String line, int lineOffset) {
+        if (isClosed_) {
+            SWT.error(SWT.ERROR_IO);
+        }
+        int writeOffset = startOffset - lineOffset;
+        int lineLength = line.length;
+        int lineIndex;
+        if (writeOffset >= lineLength) {
+            return;                         // whole line is outside write range
+        } else if (writeOffset > 0) {
+            lineIndex = writeOffset;        // line starts before write start
+        } else {
+            lineIndex = 0;
+        }
+        int copyEnd = Math.min(lineLength, endOffset - lineOffset);
+        if (lineIndex < copyEnd) {
+            write(line.substring(lineIndex, copyEnd));
+        }
+    }
+    /**
+     * Appends the specified line delimiter to the data.
+     *
+     * @param lineDelimiter line delimiter to write
+     * @exception SWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public void writeLineDelimiter(String lineDelimiter) {
+        if (isClosed_) {
+            SWT.error(SWT.ERROR_IO);
+        }
+        write(lineDelimiter);
+    }
+    }
+
+/**
+ * Constructs a new instance of this class given its parent
+ * and a style value describing its behavior and appearance.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>SWT</code> which is applicable to instances of this
+ * class, or must be built by <em>bitwise OR</em>'ing together
+ * (that is, using the <code>int</code> "|" operator) two or more
+ * of those <code>SWT</code> style constants. The class description
+ * lists the style constants that are applicable to the class.
+ * Style bits are also inherited from superclasses.
+ * </p>
+ *
+ * @param parent a widget which will be the parent of the new instance (cannot be null)
+ * @param style the style of widget to construct
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see SWT#FULL_SELECTION
+ * @see SWT#MULTI
+ * @see SWT#READ_ONLY
+ * @see SWT#SINGLE
+ * @see SWT#WRAP
+ * @see #getStyle
+ */
+public this(Composite parent, int style) {
+    selection = new Point(0, 0);
+    super(parent, checkStyle(style));
+    // set the fg in the OS to ensure that these are the same as StyledText, necessary
+    // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
+    super.setForeground(getForeground());
+    super.setDragDetect(false);
+    Display display = getDisplay();
+    isMirrored_ = (super.getStyle() & SWT.MIRRORED) !is 0;
+    fixedLineHeight = true;
+    if ((style & SWT.READ_ONLY) !is 0) {
+        setEditable(false);
+    }
+    leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
+    if ((style & SWT.SINGLE) !is 0 && (style & SWT.BORDER) !is 0) {
+        leftMargin = topMargin = rightMargin = bottomMargin = 2;
+    }
+    alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
+    if (alignment is 0) alignment = SWT.LEFT;
+    clipboard = new Clipboard(display);
+    installDefaultContent();
+    renderer = new StyledTextRenderer(getDisplay(), this);
+    renderer.setContent(content);
+    renderer.setFont(getFont(), tabLength);
+    ime = new IME(this, SWT.NONE);
+    defaultCaret = new Caret(this, SWT.NONE);
+    if ((style & SWT.WRAP) !is 0) {
+        setWordWrap(true);
+    }
+    if (isBidiCaret()) {
+        createCaretBitmaps();
+        Runnable runnable = new class() Runnable {
+            public void run() {
+                int direction = BidiUtil.getKeyboardLanguage() is BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
+                if (direction is caretDirection) return;
+                if (getCaret() !is defaultCaret) return;
+                Point newCaretPos = getPointAtOffset(caretOffset);
+                setCaretLocation(newCaretPos, direction);
+            }
+        };
+        BidiUtil.addLanguageListener(this, runnable);
+    }
+    setCaret(defaultCaret);
+    calculateScrollBars();
+    createKeyBindings();
+    setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
+    installListeners();
+    initializeAccessible();
+    setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
+}
+/**
+ * Adds an extended modify listener. An ExtendedModify event is sent by the
+ * widget when the widget text has changed.
+ *
+ * @param extendedModifyListener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
+    checkWidget();
+    if (extendedModifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
+    addListener(ExtendedModify, typedListener);
+}
+/**
+ * Adds a bidirectional segment listener.
+ * <p>
+ * A BidiSegmentEvent is sent
+ * whenever a line of text is measured or rendered. The user can
+ * specify text ranges in the line that should be treated as if they
+ * had a different direction than the surrounding text.
+ * This may be used when adjacent segments of right-to-left text should
+ * not be reordered relative to each other.
+ * E.g., Multiple Java string literals in a right-to-left language
+ * should generally remain in logical order to each other, that is, the
+ * way they are stored.
+ * </p>
+ *
+ * @param listener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ * @see BidiSegmentEvent
+ * @since 2.0
+ */
+public void addBidiSegmentListener(BidiSegmentListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    addListener(LineGetSegments, new StyledTextListener(listener));
+}
+/**
+ * Adds a line background listener. A LineGetBackground event is sent by the
+ * widget to determine the background color for a line.
+ *
+ * @param listener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addLineBackgroundListener(LineBackgroundListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    if (!isListening(LineGetBackground)) {
+        renderer.clearLineBackground(0, content.getLineCount());
+    }
+    addListener(LineGetBackground, new StyledTextListener(listener));
+}
+/**
+ * Adds a line style listener. A LineGetStyle event is sent by the widget to
+ * determine the styles for a line.
+ *
+ * @param listener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addLineStyleListener(LineStyleListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    if (!isListening(LineGetStyle)) {
+        setStyleRanges(0, 0, null, null, true);
+        renderer.clearLineStyle(0, content.getLineCount());
+    }
+    addListener(LineGetStyle, new StyledTextListener(listener));
+}
+/**
+ * Adds a modify listener. A Modify event is sent by the widget when the widget text
+ * has changed.
+ *
+ * @param modifyListener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addModifyListener(ModifyListener modifyListener) {
+    checkWidget();
+    if (modifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    addListener(SWT.Modify, new TypedListener(modifyListener));
+}
+/**
+ * Adds a paint object listener. A paint object event is sent by the widget when an object
+ * needs to be drawn.
+ *
+ * @param listener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see PaintObjectListener
+ * @see PaintObjectEvent
+ */
+public void addPaintObjectListener(PaintObjectListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    addListener(PaintObject, new StyledTextListener(listener));
+}
+/**
+ * Adds a selection listener. A Selection event is sent by the widget when the
+ * user changes the selection.
+ * <p>
+ * When <code>widgetSelected</code> is called, the event x and y fields contain
+ * the start and end caret indices of the selection.
+ * <code>widgetDefaultSelected</code> is not called for StyledTexts.
+ * </p>
+ *
+ * @param listener the listener which should be notified when the user changes the receiver's selection
+
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SelectionListener
+ * @see #removeSelectionListener
+ * @see SelectionEvent
+ */
+public void addSelectionListener(SelectionListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    addListener(SWT.Selection, new TypedListener(listener));
+}
+/**
+ * Adds a verify key listener. A VerifyKey event is sent by the widget when a key
+ * is pressed. The widget ignores the key press if the listener sets the doit field
+ * of the event to false.
+ *
+ * @param listener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addVerifyKeyListener(VerifyKeyListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    addListener(VerifyKey, new StyledTextListener(listener));
+}
+/**
+ * Adds a verify listener. A Verify event is sent by the widget when the widget text
+ * is about to change. The listener can set the event text and the doit field to
+ * change the text that is set in the widget or to force the widget to ignore the
+ * text change.
+ *
+ * @param verifyListener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addVerifyListener(VerifyListener verifyListener) {
+    checkWidget();
+    if (verifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    addListener(SWT.Verify, new TypedListener(verifyListener));
+}
+/**
+ * Adds a word movement listener. A movement event is sent when the boundary
+ * of a word is needed. For example, this occurs during word next and word
+ * previous actions.
+ *
+ * @param movementListener the listener
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ *
+ * @see MovementEvent
+ * @see MovementListener
+ * @see #removeWordMovementListener
+ *
+ * @since 3.3
+ */
+public void addWordMovementListener(MovementListener movementListener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    addListener(WordNext, new StyledTextListener(movementListener));
+    addListener(WordPrevious, new StyledTextListener(movementListener));
+}
+/**
+ * Appends a string to the text at the end of the widget.
+ *
+ * @param string the string to be appended
+ * @see #replaceTextRange(int,int,String)
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void append(String string) {
+    checkWidget();
+    // SWT extension: allow null for zero length string
+//     if (string is null) {
+//         SWT.error(SWT.ERROR_NULL_ARGUMENT);
+//     }
+    int lastChar = Math.max(getCharCount(), 0);
+    replaceTextRange(lastChar, 0, string);
+}
+/**
+ * Calculates the scroll bars
+ */
+void calculateScrollBars() {
+    ScrollBar horizontalBar = getHorizontalBar();
+    ScrollBar verticalBar = getVerticalBar();
+    setScrollBars(true);
+    if (verticalBar !is null) {
+        verticalBar.setIncrement(getVerticalIncrement());
+    }
+    if (horizontalBar !is null) {
+        horizontalBar.setIncrement(getHorizontalIncrement());
+    }
+}
+/**
+ * Calculates the top index based on the current vertical scroll offset.
+ * The top index is the index of the topmost fully visible line or the
+ * topmost partially visible line if no line is fully visible.
+ * The top index starts at 0.
+ */
+void calculateTopIndex(int delta) {
+    int oldTopIndex = topIndex;
+    int oldTopIndexY = topIndexY;
+    if (isFixedLineHeight()) {
+        int verticalIncrement = getVerticalIncrement();
+        if (verticalIncrement is 0) {
+            return;
+        }
+        topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement);
+        // Set top index to partially visible top line if no line is fully
+        // visible but at least some of the widget client area is visible.
+        // Fixes bug 15088.
+        if (topIndex > 0) {
+            if (clientAreaHeight > 0) {
+                int bottomPixel = getVerticalScrollOffset() + clientAreaHeight;
+                int fullLineTopPixel = topIndex * verticalIncrement;
+                int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
+                // set top index to partially visible line if no line fully fits in
+                // client area or if space is available but not used (the latter should
+                // never happen because we use claimBottomFreeSpace)
+                if (fullLineVisibleHeight < verticalIncrement) {
+                    topIndex--;
+                }
+            } else if (topIndex >= content.getLineCount()) {
+                topIndex = content.getLineCount() - 1;
+            }
+        }
+    } else {
+        if (delta >= 0) {
+            delta -= topIndexY;
+            int lineIndex = topIndex;
+            int lineCount = content.getLineCount();
+            while (lineIndex < lineCount) {
+                if (delta <= 0) break;
+                delta -= renderer.getLineHeight(lineIndex++);
+            }
+            if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
+                topIndex = lineIndex;
+                topIndexY = -delta;
+            } else {
+                topIndex = lineIndex - 1;
+                topIndexY = -renderer.getLineHeight(topIndex) - delta;
+            }
+        } else {
+            delta -= topIndexY;
+            int lineIndex = topIndex;
+            while (lineIndex > 0) {
+                int lineHeight = renderer.getLineHeight(lineIndex - 1);
+                if (delta + lineHeight > 0) break;
+                delta += lineHeight;
+                lineIndex--;
+            }
+            if (lineIndex is 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
+                topIndex = lineIndex;
+                topIndexY = - delta;
+            } else {
+                topIndex = lineIndex - 1;
+                topIndexY = - renderer.getLineHeight(topIndex) - delta;
+            }
+        }
+    }
+    if (topIndex !is oldTopIndex || oldTopIndexY !is topIndexY) {
+        renderer.calculateClientArea();
+        setScrollBars(false);
+    }
+}
+/**
+ * Hides the scroll bars if widget is created in single line mode.
+ */
+static int checkStyle(int style) {
+    if ((style & SWT.SINGLE) !is 0) {
+        style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI);
+    } else {
+        style |= SWT.MULTI;
+        if ((style & SWT.WRAP) !is 0) {
+            style &= ~SWT.H_SCROLL;
+        }
+    }
+    style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND;
+    return style;
+}
+/**
+ * Scrolls down the text to use new space made available by a resize or by
+ * deleted lines.
+ */
+void claimBottomFreeSpace() {
+    int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
+    if (isFixedLineHeight()) {
+        int lineHeight = renderer.getLineHeight();
+        int newVerticalOffset = Math.max(0, content.getLineCount() * lineHeight - clientAreaHeight);
+        if (newVerticalOffset < getVerticalScrollOffset()) {
+            scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
+        }
+    } else {
+        int bottomIndex = getPartialBottomIndex();
+        int height = getLinePixel(bottomIndex + 1);
+        if (clientAreaHeight > height) {
+            scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
+        }
+    }
+}
+/**
+ * Scrolls text to the right to use new space made available by a resize.
+ */
+void claimRightFreeSpace() {
+    int newHorizontalOffset = Math.max(0, renderer.getWidth() - (clientAreaWidth - leftMargin - rightMargin));
+    if (newHorizontalOffset < horizontalScrollOffset) {
+        // item is no longer drawn past the right border of the client area
+        // align the right end of the item with the right border of the
+        // client area (window is scrolled right).
+        scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
+    }
+}
+/**
+ * Removes the widget selection.
+ *
+ * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
+ */
+void clearSelection(bool sendEvent) {
+    int selectionStart = selection.x;
+    int selectionEnd = selection.y;
+    resetSelection();
+    // redraw old selection, if any
+    if (selectionEnd - selectionStart > 0) {
+        int length = content.getCharCount();
+        // called internally to remove selection after text is removed
+        // therefore make sure redraw range is valid.
+        int redrawStart = Math.min(selectionStart, length);
+        int redrawEnd = Math.min(selectionEnd, length);
+        if (redrawEnd - redrawStart > 0) {
+            internalRedrawRange(redrawStart, redrawEnd - redrawStart);
+        }
+        if (sendEvent) {
+            sendSelectionEvent();
+        }
+    }
+}
+public override Point computeSize (int wHint, int hHint, bool changed) {
+    checkWidget();
+    int lineCount = (getStyle() & SWT.SINGLE) !is 0 ? 1 : content.getLineCount();
+    int width = 0;
+    int height = 0;
+    if (wHint is SWT.DEFAULT || hHint is SWT.DEFAULT) {
+        Display display = getDisplay();
+        int maxHeight = display.getClientArea().height;
+        for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
+            TextLayout layout = renderer.getTextLayout(lineIndex);
+            int wrapWidth = layout.getWidth();
+            if (wordWrap) layout.setWidth(wHint is 0 ? 1 : wHint);
+            Rectangle rect = layout.getBounds();
+            height += rect.height;
+            width = Math.max(width, rect.width);
+            layout.setWidth(wrapWidth);
+            renderer.disposeTextLayout(layout);
+            if (isFixedLineHeight() && height > maxHeight) break;
+        }
+        if (isFixedLineHeight()) {
+            height = lineCount * renderer.getLineHeight();
+        }
+    }
+    // Use default values if no text is defined.
+    if (width is 0) width = DEFAULT_WIDTH;
+    if (height is 0) height = DEFAULT_HEIGHT;
+    if (wHint !is SWT.DEFAULT) width = wHint;
+    if (hHint !is SWT.DEFAULT) height = hHint;
+    int wTrim = leftMargin + rightMargin + getCaretWidth();
+    int hTrim = topMargin + bottomMargin;
+    Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
+    return new Point (rect.width, rect.height);
+}
+/**
+ * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
+ * <p>
+ * The text will be put on the clipboard in plain text format and RTF format.
+ * The <code>DND.CLIPBOARD</code> clipboard is used for data that is
+ * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
+ * by menu action.
+ * </p>
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void copy() {
+    checkWidget();
+    copy(DND.CLIPBOARD);
+}
+/**
+ * Copies the selected text to the specified clipboard.  The text will be put in the
+ * clipboard in plain text format and RTF format.
+ * <p>
+ * The clipboardType is  one of the clipboard constants defined in class
+ * <code>DND</code>.  The <code>DND.CLIPBOARD</code>  clipboard is
+ * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
+ * or by menu action.  The <code>DND.SELECTION_CLIPBOARD</code>
+ * clipboard is used for data that is transferred by selecting text and pasting
+ * with the middle mouse button.
+ * </p>
+ *
+ * @param clipboardType indicates the type of clipboard
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.1
+ */
+public void copy(int clipboardType) {
+    checkWidget();
+    if (clipboardType !is DND.CLIPBOARD && clipboardType !is DND.SELECTION_CLIPBOARD) return;
+    int length = selection.y - selection.x;
+    if (length > 0) {
+        try {
+            setClipboardContent(selection.x, length, clipboardType);
+        } catch (SWTError error) {
+            // Copy to clipboard failed. This happens when another application
+            // is accessing the clipboard while we copy. Ignore the error.
+            // Fixes 1GDQAVN
+            // Rethrow all other errors. Fixes bug 17578.
+            if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
+                throw error;
+            }
+        }
+    }
+}
+/**
+ * Returns the alignment of the widget.
+ *
+ * @return the alignment
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #getLineAlignment(int)
+ *
+ * @since 3.2
+ */
+public int getAlignment() {
+    checkWidget();
+    return alignment;
+}
+int getAvailableHeightAbove(int height) {
+    int maxHeight = verticalScrollOffset;
+    if (maxHeight is -1) {
+        int lineIndex = topIndex - 1;
+        maxHeight = -topIndexY;
+        if (topIndexY > 0) {
+            maxHeight += renderer.getLineHeight(lineIndex--);
+        }
+        while (height > maxHeight && lineIndex >= 0) {
+            maxHeight += renderer.getLineHeight(lineIndex--);
+        }
+    }
+    return Math.min(height, maxHeight);
+}
+int getAvailableHeightBellow(int height) {
+    int partialBottomIndex = getPartialBottomIndex();
+    int topY = getLinePixel(partialBottomIndex);
+    int lineHeight = renderer.getLineHeight(partialBottomIndex);
+    int availableHeight = 0;
+    int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
+    if (topY + lineHeight > clientAreaHeight) {
+        availableHeight = lineHeight - (clientAreaHeight - topY);
+    }
+    int lineIndex = partialBottomIndex + 1;
+    int lineCount = content.getLineCount();
+    while (height > availableHeight && lineIndex < lineCount) {
+        availableHeight += renderer.getLineHeight(lineIndex++);
+    }
+    return Math.min(height, availableHeight);
+}
+/**
+ * Returns a string that uses only the line delimiter specified by the
+ * StyledTextContent implementation.
+ * <p>
+ * Returns only the first line if the widget has the SWT.SINGLE style.
+ * </p>
+ *
+ * @param text the text that may have line delimiters that don't
+ *  match the model line delimiter. Possible line delimiters
+ *  are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
+ * @return the converted text that only uses the line delimiter
+ *  specified by the model. Returns only the first line if the widget
+ *  has the SWT.SINGLE style.
+ */
+String getModelDelimitedText(String text) {
+    int length = text.length;
+    if (length is 0) {
+        return text;
+    }
+    int crIndex = 0;
+    int lfIndex = 0;
+    int i = 0;
+    StringBuffer convertedText = new StringBuffer(length);
+    String delimiter = getLineDelimiter();
+    while (i < length) {
+        if (crIndex !is -1) {
+            crIndex = text.indexOf (SWT.CR, i);
+        }
+        if (lfIndex !is -1) {
+            lfIndex = text.indexOf (SWT.LF, i);
+        }
+        if (lfIndex is -1 && crIndex is -1) {   // no more line breaks?
+            break;
+        } else if ((crIndex < lfIndex && crIndex !is -1) || lfIndex is -1) {
+            convertedText.append(text.substring(i, crIndex));
+            if (lfIndex is crIndex + 1) {       // CR/LF combination?
+                i = lfIndex + 1;
+            } else {
+                i = crIndex + 1;
+            }
+        } else {                                    // LF occurs before CR!
+            convertedText.append(text.substring(i, lfIndex));
+            i = lfIndex + 1;
+        }
+        if (isSingleLine()) {
+            break;
+        }
+        convertedText.append(delimiter);
+    }
+    // copy remaining text if any and if not in single line mode or no
+    // text copied thus far (because there only is one line)
+    if (i < length && (!isSingleLine() || convertedText.length() is 0)) {
+        convertedText.append(text.substring(i));
+    }
+    return convertedText.toString();
+}
+bool checkDragDetect(Event event) {
+    if (!isListening(SWT.DragDetect)) return false;
+    if (IS_MOTIF) {
+        if (event.button !is 2) return false;
+    } else {
+        if (event.button !is 1) return false;
+    }
+    if (selection.x is selection.y) return false;
+    int offset = getOffsetAtPoint(event.x, event.y, null, true);
+    if (selection.x <= offset && offset < selection.y) {
+        return dragDetect(event);
+    }
+    return false;
+}
+/**
+ * Creates default key bindings.
+ */
+void createKeyBindings() {
+    int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT;
+    int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
+
+    // Navigation
+    setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
+    setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
+    if (IS_CARBON) {
+        setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START);
+        setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END);
+        setKeyBinding(SWT.HOME, ST.TEXT_START);
+        setKeyBinding(SWT.END, ST.TEXT_END);
+        setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START);
+        setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END);
+        setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT);
+        setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS);
+    } else {
+        setKeyBinding(SWT.HOME, ST.LINE_START);
+        setKeyBinding(SWT.END, ST.LINE_END);
+        setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
+        setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
+        setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
+        setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
+    }
+    setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
+    setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
+    setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START);
+    setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END);
+    setKeyBinding(nextKey, ST.COLUMN_NEXT);
+    setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
+
+    // Selection
+    setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
+    setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
+    if (IS_CARBON) {
+        setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START);
+        setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END);
+        setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START);
+        setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END);
+        setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
+        setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
+        setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT);
+        setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS);
+    } else  {
+        setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
+        setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
+        setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
+        setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
+        setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
+        setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
+    }
+    setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
+    setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
+    setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START);
+    setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END);
+    setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT);
+    setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
+
+    // Modification
+    // Cut, Copy, Paste
+    setKeyBinding('X' | SWT.MOD1, ST.CUT);
+    setKeyBinding('C' | SWT.MOD1, ST.COPY);
+    setKeyBinding('V' | SWT.MOD1, ST.PASTE);
+    if (IS_CARBON) {
+        setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT);
+        setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS);
+        setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT);
+    } else {
+        // Cut, Copy, Paste Wordstar style
+        setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT);
+        setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY);
+        setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE);
+    }
+    setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS);
+    setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
+    setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
+    setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS);
+    setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT);
+
+    // Miscellaneous
+    setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
+}
+/**
+ * Create the bitmaps to use for the caret in bidi mode.  This
+ * method only needs to be called upon widget creation and when the
+ * font changes (the caret bitmap height needs to match font height).
+ */
+void createCaretBitmaps() {
+    int caretWidth = BIDI_CARET_WIDTH;
+    Display display = getDisplay();
+    if (leftCaretBitmap !is null) {
+        if (defaultCaret !is null && leftCaretBitmap==/*eq*/defaultCaret.getImage()) {
+            defaultCaret.setImage(null);
+        }
+        leftCaretBitmap.dispose();
+    }
+    int lineHeight = renderer.getLineHeight();
+    leftCaretBitmap = new Image(display, caretWidth, lineHeight);
+    GC gc = new GC (leftCaretBitmap);
+    gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
+    gc.fillRectangle(0, 0, caretWidth, lineHeight);
+    gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
+    gc.drawLine(0,0,0,lineHeight);
+    gc.drawLine(0,0,caretWidth-1,0);
+    gc.drawLine(0,1,1,1);
+    gc.dispose();
+
+    if (rightCaretBitmap !is null) {
+        if (defaultCaret !is null && rightCaretBitmap==/*eq*/defaultCaret.getImage()) {
+            defaultCaret.setImage(null);
+        }
+        rightCaretBitmap.dispose();
+    }
+    rightCaretBitmap = new Image(display, caretWidth, lineHeight);
+    gc = new GC (rightCaretBitmap);
+    gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
+    gc.fillRectangle(0, 0, caretWidth, lineHeight);
+    gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
+    gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight);
+    gc.drawLine(0,0,caretWidth-1,0);
+    gc.drawLine(caretWidth-1,1,1,1);
+    gc.dispose();
+}
+/**
+ * Moves the selected text to the clipboard.  The text will be put in the
+ * clipboard in plain text format and RTF format.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void cut(){
+    checkWidget();
+    int length = selection.y - selection.x;
+    if (length > 0) {
+        try {
+            setClipboardContent(selection.x, length, DND.CLIPBOARD);
+        } catch (SWTError error) {
+            // Copy to clipboard failed. This happens when another application
+            // is accessing the clipboard while we copy. Ignore the error.
+            // Fixes 1GDQAVN
+            // Rethrow all other errors. Fixes bug 17578.
+            if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
+                throw error;
+            }
+            // Abort cut operation if copy to clipboard fails.
+            // Fixes bug 21030.
+            return;
+        }
+        doDelete();
+    }
+}
+/**
+ * A mouse move event has occurred.  See if we should start autoscrolling.  If
+ * the move position is outside of the client area, initiate autoscrolling.
+ * Otherwise, we've moved back into the widget so end autoscrolling.
+ */
+void doAutoScroll(Event event) {
+    if (event.y > clientAreaHeight) {
+        doAutoScroll(SWT.DOWN, event.y - clientAreaHeight);
+    } else if (event.y < 0) {
+        doAutoScroll(SWT.UP, -event.y);
+    } else if (event.x < leftMargin && !wordWrap) {
+        doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
+    } else if (event.x > clientAreaWidth - leftMargin - rightMargin && !wordWrap) {
+        doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - leftMargin - rightMargin));
+    } else {
+        endAutoScroll();
+    }
+}
+/**
+ * Initiates autoscrolling.
+ *
+ * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
+ */
+void doAutoScroll(int direction, int distance) {
+    autoScrollDistance = distance;
+    // If we're already autoscrolling in the given direction do nothing
+    if (autoScrollDirection is direction) {
+        return;
+    }
+
+    Runnable timer = null;
+    final Display disp = getDisplay();
+    // Set a timer that will simulate the user pressing and holding
+    // down a cursor key (i.e., arrowUp, arrowDown).
+    if (direction is SWT.UP) {
+        timer = new class(disp) Runnable {
+            Display display;
+            this( Display d ){ this.display = d; }
+            public void run() {
+                if (autoScrollDirection is SWT.UP) {
+                    doSelectionPageUp(autoScrollDistance);
+                    display.timerExec(V_SCROLL_RATE, this);
+                }
+            }
+        };
+        autoScrollDirection = direction;
+        display.timerExec(V_SCROLL_RATE, timer);
+    } else if (direction is SWT.DOWN) {
+        timer = new class(disp) Runnable {
+            Display display;
+            this( Display d ){ this.display = d; }
+            public void run() {
+                if (autoScrollDirection is SWT.DOWN) {
+                    doSelectionPageDown(autoScrollDistance);
+                    display.timerExec(V_SCROLL_RATE, this);
+                }
+            }
+        };
+        autoScrollDirection = direction;
+        display.timerExec(V_SCROLL_RATE, timer);
+    } else if (direction is ST.COLUMN_NEXT) {
+        timer = new class(disp) Runnable {
+            Display display;
+            this( Display d ){ this.display = d; }
+            public void run() {
+                if (autoScrollDirection is ST.COLUMN_NEXT) {
+                    doVisualNext();
+                    setMouseWordSelectionAnchor();
+                    doMouseSelection();
+                    display.timerExec(H_SCROLL_RATE, this);
+                }
+            }
+        };
+        autoScrollDirection = direction;
+        display.timerExec(H_SCROLL_RATE, timer);
+    } else if (direction is ST.COLUMN_PREVIOUS) {
+        timer = new class(disp) Runnable {
+            Display display;
+            this( Display d ){ this.display = d; }
+            public void run() {
+                if (autoScrollDirection is ST.COLUMN_PREVIOUS) {
+                    doVisualPrevious();
+                    setMouseWordSelectionAnchor();
+                    doMouseSelection();
+                    display.timerExec(H_SCROLL_RATE, this);
+                }
+            }
+        };
+        autoScrollDirection = direction;
+        display.timerExec(H_SCROLL_RATE, timer);
+    }
+}
+/**
+ * Deletes the previous character. Delete the selected text if any.
+ * Move the caret in front of the deleted text.
+ */
+void doBackspace() {
+    Event event = new Event();
+    event.text = "";
+    if (selection.x !is selection.y) {
+        event.start = selection.x;
+        event.end = selection.y;
+        sendKeyEvent(event);
+    } else if (caretOffset > 0) {
+        int lineIndex = content.getLineAtOffset(caretOffset);
+        int lineOffset = content.getOffsetAtLine(lineIndex);
+        if (caretOffset is lineOffset) {
+            // SWT: on line start, delete line break
+            lineOffset = content.getOffsetAtLine(lineIndex - 1);
+            event.start = lineOffset + content.getLine(lineIndex - 1).length;
+            event.end = caretOffset;
+        } else {
+            TextLayout layout = renderer.getTextLayout(lineIndex);
+            int start = layout.getPreviousOffset(caretOffset - lineOffset, SWT.MOVEMENT_CLUSTER);
+            renderer.disposeTextLayout(layout);
+            event.start = start + lineOffset;
+            event.end = caretOffset;
+        }
+        sendKeyEvent(event);
+    }
+}
+/**
+ * Replaces the selection with the character or insert the character at the
+ * current caret position if no selection exists.
+ * <p>
+ * If a carriage return was typed replace it with the line break character
+ * used by the widget on this platform.
+ * </p>
+ *
+ * @param key the character typed by the user
+ */
+void doContent(dchar key) {
+    Event event = new Event();
+    event.start = selection.x;
+    event.end = selection.y;
+    // replace a CR line break with the widget line break
+    // CR does not make sense on Windows since most (all?) applications
+    // don't recognize CR as a line break.
+    if (key is SWT.CR || key is SWT.LF) {
+        if (!isSingleLine()) {
+            event.text = getLineDelimiter();
+        }
+    } else if (selection.x is selection.y && overwrite && key !is TAB) {
+        // no selection and overwrite mode is on and the typed key is not a
+        // tab character (tabs are always inserted without overwriting)?
+        int lineIndex = content.getLineAtOffset(event.end);
+        int lineOffset = content.getOffsetAtLine(lineIndex);
+        String line = content.getLine(lineIndex);
+        // replace character at caret offset if the caret is not at the
+        // end of the line
+        if (event.end < lineOffset + line.length) {
+            event.end+=dcharToString( key ).length;
+        }
+        event.text = dcharToString( key );
+    } else {
+        event.text = dcharToString( key );
+    }
+    if (event.text !is null) {
+        if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
+            return;
+        }
+        sendKeyEvent(event);
+    }
+}
+/**
+ * Moves the caret after the last character of the widget content.
+ */
+void doContentEnd() {
+    // place caret at end of first line if receiver is in single
+    // line mode. fixes 4820.
+    if (isSingleLine()) {
+        doLineEnd();
+    } else {
+        int length = content.getCharCount();
+        if (caretOffset < length) {
+            caretOffset = length;
+            showCaret();
+        }
+    }
+}
+/**
+ * Moves the caret in front of the first character of the widget content.
+ */
+void doContentStart() {
+    if (caretOffset > 0) {
+        caretOffset = 0;
+        showCaret();
+    }
+}
+/**
+ * Moves the caret to the start of the selection if a selection exists.
+ * Otherwise, if no selection exists move the cursor according to the
+ * cursor selection rules.
+ *
+ * @see #doSelectionCursorPrevious
+ */
+void doCursorPrevious() {
+    if (selection.y - selection.x > 0) {
+        caretOffset = selection.x;
+        caretAlignment = OFFSET_LEADING;
+        showCaret();
+    } else {
+        doSelectionCursorPrevious();
+    }
+}
+/**
+ * Moves the caret to the end of the selection if a selection exists.
+ * Otherwise, if no selection exists move the cursor according to the
+ * cursor selection rules.
+ *
+ * @see #doSelectionCursorNext
+ */
+void doCursorNext() {
+    if (selection.y - selection.x > 0) {
+        caretOffset = selection.y;
+        caretAlignment = PREVIOUS_OFFSET_TRAILING;
+        showCaret();
+    } else {
+        doSelectionCursorNext();
+    }
+}
+/**
+ * Deletes the next character. Delete the selected text if any.
+ */
+void doDelete() {
+    Event event = new Event();
+    event.text = "";
+    if (selection.x !is selection.y) {
+        event.start = selection.x;
+        event.end = selection.y;
+        sendKeyEvent(event);
+    } else if (caretOffset < content.getCharCount()) {
+        int line = content.getLineAtOffset(caretOffset);
+        int lineOffset = content.getOffsetAtLine(line);
+        int lineLength = content.getLine(line).length;
+        if (caretOffset is lineOffset + lineLength) {
+            event.start = caretOffset;
+            event.end = content.getOffsetAtLine(line + 1);
+        } else {
+            event.start = caretOffset;
+            event.end = getClusterNext(caretOffset, line);
+        }
+        sendKeyEvent(event);
+    }
+}
+/**
+ * Deletes the next word.
+ */
+void doDeleteWordNext() {
+    if (selection.x !is selection.y) {
+        // if a selection exists, treat the as if
+        // only the delete key was pressed
+        doDelete();
+    } else {
+        Event event = new Event();
+        event.text = "";
+        event.start = caretOffset;
+        event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
+        sendKeyEvent(event);
+    }
+}
+/**
+ * Deletes the previous word.
+ */
+void doDeleteWordPrevious() {
+    if (selection.x !is selection.y) {
+        // if a selection exists, treat as if
+        // only the backspace key was pressed
+        doBackspace();
+    } else {
+        Event event = new Event();
+        event.text = "";
+        event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
+        event.end = caretOffset;
+        sendKeyEvent(event);
+    }
+}
+/**
+ * Moves the caret one line down and to the same character offset relative
+ * to the beginning of the line. Move the caret to the end of the new line
+ * if the new line is shorter than the character offset.
+ */
+void doLineDown(bool select) {
+    int caretLine = getCaretLine();
+    int lineCount = content.getLineCount();
+    int y = 0;
+    bool lastLine = false;
+    if (wordWrap) {
+        int lineOffset = content.getOffsetAtLine(caretLine);
+        int offsetInLine = caretOffset - lineOffset;
+        TextLayout layout = renderer.getTextLayout(caretLine);
+        int lineIndex = getVisualLineIndex(layout, offsetInLine);
+        int layoutLineCount = layout.getLineCount();
+        if (lineIndex is layoutLineCount - 1) {
+            lastLine = caretLine is lineCount - 1;
+            caretLine++;
+        } else {
+            y = layout.getLineBounds(lineIndex + 1).y;
+        }
+        renderer.disposeTextLayout(layout);
+    } else {
+        lastLine = caretLine is lineCount - 1;
+        caretLine++;
+    }
+    if (lastLine) {
+        if (select) caretOffset = content.getCharCount();
+    } else {
+        caretOffset = getOffsetAtPoint(columnX, y, caretLine);
+    }
+    int oldColumnX = columnX;
+    int oldHScrollOffset = horizontalScrollOffset;
+    if (select) {
+        setMouseWordSelectionAnchor();
+        // select first and then scroll to reduce flash when key
+        // repeat scrolls lots of lines
+        doSelection(ST.COLUMN_NEXT);
+    }
+    showCaret();
+    int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+    columnX = oldColumnX + hScrollChange;
+}
+/**
+ * Moves the caret to the end of the line.
+ */
+void doLineEnd() {
+    int caretLine = getCaretLine();
+    int lineOffset = content.getOffsetAtLine(caretLine);
+    int lineEndOffset;
+    if (wordWrap) {
+        TextLayout layout = renderer.getTextLayout(caretLine);
+        int offsetInLine = caretOffset - lineOffset;
+        int lineIndex = getVisualLineIndex(layout, offsetInLine);
+        int[] offsets = layout.getLineOffsets();
+        lineEndOffset = lineOffset + offsets[lineIndex + 1];
+        renderer.disposeTextLayout(layout);
+    } else {
+        int lineLength = content.getLine(caretLine).length;
+        lineEndOffset = lineOffset + lineLength;
+    }
+    if (caretOffset < lineEndOffset) {
+        caretOffset = lineEndOffset;
+        caretAlignment = PREVIOUS_OFFSET_TRAILING;
+        showCaret();
+    }
+}
+/**
+ * Moves the caret to the beginning of the line.
+ */
+void doLineStart() {
+    int caretLine = getCaretLine();
+    int lineOffset = content.getOffsetAtLine(caretLine);
+    if (wordWrap) {
+        TextLayout layout = renderer.getTextLayout(caretLine);
+        int offsetInLine = caretOffset - lineOffset;
+        int lineIndex = getVisualLineIndex(layout, offsetInLine);
+        int[] offsets = layout.getLineOffsets();
+        lineOffset += offsets[lineIndex];
+        renderer.disposeTextLayout(layout);
+    }
+    if (caretOffset > lineOffset) {
+        caretOffset = lineOffset;
+        caretAlignment = OFFSET_LEADING;
+        showCaret();
+    }
+}
+/**
+ * Moves the caret one line up and to the same character offset relative
+ * to the beginning of the line. Move the caret to the end of the new line
+ * if the new line is shorter than the character offset.
+ */
+void doLineUp(bool select) {
+    int caretLine = getCaretLine(), y = 0;
+    bool firstLine = false;
+    if (wordWrap) {
+        int lineOffset = content.getOffsetAtLine(caretLine);
+        int offsetInLine = caretOffset - lineOffset;
+        TextLayout layout = renderer.getTextLayout(caretLine);
+        int lineIndex = getVisualLineIndex(layout, offsetInLine);
+        if (lineIndex is 0) {
+            firstLine = caretLine is 0;
+            if (!firstLine) {
+                caretLine--;
+                y = renderer.getLineHeight(caretLine) - 1;
+            }
+        } else {
+            y = layout.getLineBounds(lineIndex - 1).y;
+        }
+        renderer.disposeTextLayout(layout);
+    } else {
+        firstLine = caretLine is 0;
+        caretLine--;
+    }
+    if (firstLine) {
+        if (select) caretOffset = 0;
+    } else {
+        caretOffset = getOffsetAtPoint(columnX, y, caretLine);
+    }
+    int oldColumnX = columnX;
+    int oldHScrollOffset = horizontalScrollOffset;
+    if (select) setMouseWordSelectionAnchor();
+    showCaret();
+    if (select) doSelection(ST.COLUMN_PREVIOUS);
+    int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+    columnX = oldColumnX + hScrollChange;
+}
+/**
+ * Moves the caret to the specified location.
+ *
+ * @param x x location of the new caret position
+ * @param y y location of the new caret position
+ * @param select the location change is a selection operation.
+ *  include the line delimiter in the selection
+ */
+void doMouseLocationChange(int x, int y, bool select) {
+    int line = getLineIndex(y);
+
+    updateCaretDirection = true;
+    // allow caret to be placed below first line only if receiver is
+    // not in single line mode. fixes 4820.
+    if (line < 0 || (isSingleLine() && line > 0)) {
+        return;
+    }
+    int oldCaretAlignment = caretAlignment;
+    int newCaretOffset = getOffsetAtPoint(x, y);
+
+    if (doubleClickEnabled && clickCount > 1) {
+        newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
+    }
+
+    int newCaretLine = content.getLineAtOffset(newCaretOffset);
+
+    // Is the mouse within the left client area border or on
+    // a different line? If not the autoscroll selection
+    // could be incorrectly reset. Fixes 1GKM3XS
+    if (0 <= y && y < clientAreaHeight &&
+        (0 <= x && x < clientAreaWidth || wordWrap ||
+        newCaretLine !is content.getLineAtOffset(caretOffset))) {
+        if (newCaretOffset !is caretOffset || caretAlignment !is oldCaretAlignment) {
+            caretOffset = newCaretOffset;
+            if (select) doMouseSelection();
+            showCaret();
+        }
+    }
+    if (!select) {
+        caretOffset = newCaretOffset;
+        clearSelection(true);
+    }
+}
+/**
+ * Updates the selection based on the caret position
+ */
+void doMouseSelection() {
+    if (caretOffset <= selection.x ||
+        (caretOffset > selection.x &&
+         caretOffset < selection.y && selectionAnchor is selection.x)) {
+        doSelection(ST.COLUMN_PREVIOUS);
+    } else {
+        doSelection(ST.COLUMN_NEXT);
+    }
+}
+/**
+ * Returns the offset of the word at the specified offset.
+ * If the current selection extends from high index to low index
+ * (i.e., right to left, or caret is at left border of selection on
+ * non-bidi platforms) the start offset of the word preceding the
+ * selection is returned. If the current selection extends from
+ * low index to high index the end offset of the word following
+ * the selection is returned.
+ *
+ * @param x mouse x location
+ * @param newCaretOffset caret offset of the mouse cursor location
+ * @param line line index of the mouse cursor location
+ */
+int doMouseWordSelect(int x, int newCaretOffset, int line) {
+    // flip selection anchor based on word selection direction from
+    // base double click. Always do this here (and don't rely on doAutoScroll)
+    // because auto scroll only does not cover all possible mouse selections
+    // (e.g., mouse x < 0 && mouse y > caret line y)
+    if (newCaretOffset < selectionAnchor && selectionAnchor is selection.x) {
+        selectionAnchor = doubleClickSelection.y;
+    } else if (newCaretOffset > selectionAnchor && selectionAnchor is selection.y) {
+        selectionAnchor = doubleClickSelection.x;
+    }
+    if (0 <= x && x < clientAreaWidth) {
+        bool wordSelect = (clickCount & 1) is 0;
+        if (caretOffset is selection.x) {
+            if (wordSelect) {
+                newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START);
+            } else {
+                newCaretOffset = content.getOffsetAtLine(line);
+            }
+        } else {
+            if (wordSelect) {
+                newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END);
+            } else {
+                int lineEnd = content.getCharCount();
+                if (line + 1 < content.getLineCount()) {
+                    lineEnd = content.getOffsetAtLine(line + 1);
+                }
+                newCaretOffset = lineEnd;
+            }
+        }
+    }
+    return newCaretOffset;
+}
+/**
+ * Scrolls one page down so that the last line (truncated or whole)
+ * of the current page becomes the fully visible top line.
+ * <p>
+ * The caret is scrolled the same number of lines so that its location
+ * relative to the top line remains the same. The exception is the end
+ * of the text where a full page scroll is not possible. In this case
+ * the caret is moved after the last character.
+ * </p>
+ *
+ * @param select whether or not to select the page
+ */
+void doPageDown(bool select, int height) {
+    if (isSingleLine()) return;
+    int oldColumnX = columnX;
+    int oldHScrollOffset = horizontalScrollOffset;
+    if (isFixedLineHeight()) {
+        int lineCount = content.getLineCount();
+        int caretLine = getCaretLine();
+        if (caretLine < lineCount - 1) {
+            int lineHeight = renderer.getLineHeight();
+            int lines = (height is -1 ? clientAreaHeight : height) / lineHeight;
+            int scrollLines = Math.min(lineCount - caretLine - 1, lines);
+            // ensure that scrollLines never gets negative and at least one
+            // line is scrolled. fixes bug 5602.
+            scrollLines = Math.max(1, scrollLines);
+            caretOffset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines));
+            if (select) {
+                doSelection(ST.COLUMN_NEXT);
+            }
+            // scroll one page down or to the bottom
+            int verticalMaximum = lineCount * getVerticalIncrement();
+            int pageSize = clientAreaHeight;
+            int verticalScrollOffset = getVerticalScrollOffset();
+            int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
+            if (scrollOffset + pageSize > verticalMaximum) {
+                scrollOffset = verticalMaximum - pageSize;
+            }
+            if (scrollOffset > verticalScrollOffset) {
+                scrollVertical(scrollOffset - verticalScrollOffset, true);
+            }
+        }
+    } else {
+        int lineCount = content.getLineCount();
+        int caretLine = getCaretLine();
+        int lineIndex, lineHeight;
+        if (height is -1) {
+            lineIndex = getPartialBottomIndex();
+            int topY = getLinePixel(lineIndex);
+            lineHeight = renderer.getLineHeight(lineIndex);
+            height = topY;
+            if (topY + lineHeight <= clientAreaHeight) {
+                height += lineHeight;
+            } else {
+                if (wordWrap) {
+                    TextLayout layout = renderer.getTextLayout(lineIndex);
+                    int y = clientAreaHeight - topY;
+                    for (int i = 0; i < layout.getLineCount(); i++) {
+                        Rectangle bounds = layout.getLineBounds(i);
+                        if (bounds.contains(bounds.x, y)) {
+                            height += bounds.y;
+                            break;
+                        }
+                    }
+                    renderer.disposeTextLayout(layout);
+                }
+            }
+        } else {
+            lineIndex = getLineIndex(height);
+            int topLineY = getLinePixel(lineIndex);
+            if (wordWrap) {
+                TextLayout layout = renderer.getTextLayout(lineIndex);
+                int y = height - topLineY;
+                for (int i = 0; i < layout.getLineCount(); i++) {
+                    Rectangle bounds = layout.getLineBounds(i);
+                    if (bounds.contains(bounds.x, y)) {
+                        height = topLineY + bounds.y + bounds.height;
+                        break;
+                    }
+                }
+                renderer.disposeTextLayout(layout);
+            } else {
+                height = topLineY + renderer.getLineHeight(lineIndex);
+            }
+        }
+        int caretHeight = height;
+        if (wordWrap) {
+            TextLayout layout = renderer.getTextLayout(caretLine);
+            int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
+            lineIndex = getVisualLineIndex(layout, offsetInLine);
+            caretHeight += layout.getLineBounds(lineIndex).y;
+            renderer.disposeTextLayout(layout);
+        }
+        lineIndex = caretLine;
+        lineHeight = renderer.getLineHeight(lineIndex);
+        while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
+            caretHeight -= lineHeight;
+            lineHeight = renderer.getLineHeight(++lineIndex);
+        }
+        caretOffset = getOffsetAtPoint(columnX, caretHeight, lineIndex);
+        if (select) doSelection(ST.COLUMN_NEXT);
+        height = getAvailableHeightBellow(height);
+        scrollVertical(height, true);
+        if (height is 0) setCaretLocation();
+    }
+    showCaret();
+    int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+    columnX = oldColumnX + hScrollChange;
+}
+/**
+ * Moves the cursor to the end of the last fully visible line.
+ */
+void doPageEnd() {
+    // go to end of line if in single line mode. fixes 5673
+    if (isSingleLine()) {
+        doLineEnd();
+    } else {
+        int bottomOffset;
+        if (wordWrap) {
+            int lineIndex = getPartialBottomIndex();
+            TextLayout layout = renderer.getTextLayout(lineIndex);
+            int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);
+            int index = layout.getLineCount() - 1;
+            while (index >= 0) {
+                Rectangle bounds = layout.getLineBounds(index);
+                if (y >= bounds.y + bounds.height) break;
+                index--;
+            }
+            if (index is -1 && lineIndex > 0) {
+                bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length;
+            } else {
+                bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1);
+            }
+            renderer.disposeTextLayout(layout);
+        } else {
+            int lineIndex = getBottomIndex();
+            bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length;
+        }
+        if (caretOffset < bottomOffset) {
+            caretOffset = bottomOffset;
+            caretAlignment = OFFSET_LEADING;
+            showCaret();
+        }
+    }
+}
+/**
+ * Moves the cursor to the beginning of the first fully visible line.
+ */
+void doPageStart() {
+    int topOffset;
+    if (wordWrap) {
+        int y, lineIndex;
+        if (topIndexY > 0) {
+            lineIndex = topIndex - 1;
+            y = renderer.getLineHeight(lineIndex) - topIndexY;
+        } else {
+            lineIndex = topIndex;
+            y = -topIndexY;
+        }
+        TextLayout layout = renderer.getTextLayout(lineIndex);
+        int index = 0;
+        int lineCount = layout.getLineCount();
+        while (index < lineCount) {
+            Rectangle bounds = layout.getLineBounds(index);
+            if (y <= bounds.y) break;
+            index++;
+        }
+        if (index is lineCount) {
+            topOffset = content.getOffsetAtLine(lineIndex + 1);
+        } else {
+            topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
+        }
+        renderer.disposeTextLayout(layout);
+    } else {
+        topOffset = content.getOffsetAtLine(topIndex);
+    }
+    if (caretOffset > topOffset) {
+        caretOffset = topOffset;
+        caretAlignment = OFFSET_LEADING;
+        showCaret();
+    }
+}
+/**
+ * Scrolls one page up so that the first line (truncated or whole)
+ * of the current page becomes the fully visible last line.
+ * The caret is scrolled the same number of lines so that its location
+ * relative to the top line remains the same. The exception is the beginning
+ * of the text where a full page scroll is not possible. In this case the
+ * caret is moved in front of the first character.
+ */
+void doPageUp(bool select, int height) {
+    if (isSingleLine()) return;
+    int oldHScrollOffset = horizontalScrollOffset;
+    int oldColumnX = columnX;
+    if (isFixedLineHeight()) {
+        int caretLine = getCaretLine();
+        if (caretLine > 0) {
+            int lineHeight = renderer.getLineHeight();
+            int lines = (height is -1 ? clientAreaHeight : height) / lineHeight;
+            int scrollLines = Math.max(1, Math.min(caretLine, lines));
+            caretLine -= scrollLines;
+            caretOffset = getOffsetAtPoint(columnX, getLinePixel(caretLine));
+            if (select) {
+                doSelection(ST.COLUMN_PREVIOUS);
+            }
+            int verticalScrollOffset = getVerticalScrollOffset();
+            int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
+            if (scrollOffset < verticalScrollOffset) {
+                scrollVertical(scrollOffset - verticalScrollOffset, true);
+            }
+        }
+    } else {
+        int caretLine = getCaretLine();
+        int lineHeight, lineIndex;
+        if (height is -1) {
+            if (topIndexY is 0) {
+                height = clientAreaHeight;
+            } else {
+                int y;
+                if (topIndex > 0) {
+                    lineIndex = topIndex - 1;
+                    lineHeight = renderer.getLineHeight(lineIndex);
+                    height = clientAreaHeight - topIndexY;
+                    y = lineHeight - topIndexY;
+                } else {
+                    lineIndex = topIndex;
+                    lineHeight = renderer.getLineHeight(lineIndex);
+                    height = clientAreaHeight - (lineHeight + topIndexY);
+                    y = -topIndexY;
+                }
+                if (wordWrap) {
+                    TextLayout layout = renderer.getTextLayout(lineIndex);
+                    for (int i = 0; i < layout.getLineCount(); i++) {
+                        Rectangle bounds = layout.getLineBounds(i);
+                        if (bounds.contains(bounds.x, y)) {
+                            height += lineHeight - (bounds.y + bounds.height);
+                            break;
+                        }
+                    }
+                    renderer.disposeTextLayout(layout);
+                }
+            }
+        } else {
+            lineIndex = getLineIndex(clientAreaHeight - height);
+            int topLineY = getLinePixel(lineIndex);
+            if (wordWrap) {
+                TextLayout layout = renderer.getTextLayout(lineIndex);
+                int y = topLineY;
+                for (int i = 0; i < layout.getLineCount(); i++) {
+                    Rectangle bounds = layout.getLineBounds(i);
+                    if (bounds.contains(bounds.x, y)) {
+                        height = clientAreaHeight - (topLineY + bounds.y);
+                        break;
+                    }
+                }
+                renderer.disposeTextLayout(layout);
+            } else {
+                height = clientAreaHeight - topLineY;
+            }
+        }
+        int caretHeight = height;
+        if (wordWrap) {
+            TextLayout layout = renderer.getTextLayout(caretLine);
+            int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
+            lineIndex = getVisualLineIndex(layout, offsetInLine);
+            caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
+            renderer.disposeTextLayout(layout);
+        }
+        lineIndex = caretLine;
+        lineHeight = renderer.getLineHeight(lineIndex);
+        while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
+            caretHeight -= lineHeight;
+            lineHeight = renderer.getLineHeight(--lineIndex);
+        }
+        lineHeight = renderer.getLineHeight(lineIndex);
+        caretOffset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex);
+        if (select) doSelection(ST.COLUMN_PREVIOUS);
+        height = getAvailableHeightAbove(height);
+        scrollVertical(-height, true);
+        if (height is 0) setCaretLocation();
+    }
+    showCaret();
+    int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
+    columnX = oldColumnX + hScrollChange;
+}
+/**
+ * Updates the selection to extend to the current caret position.
+ */
+void doSelection(int direction) {
+    int redrawStart = -1;
+    int redrawEnd = -1;
+    if (selectionAnchor is -1) {
+        selectionAnchor = selection.x;
+    }
+    if (direction is ST.COLUMN_PREVIOUS) {
+        if (caretOffset < selection.x) {
+            // grow selection
+            redrawEnd = selection.x;
+            redrawStart = selection.x = caretOffset;
+            // check if selection has reversed direction
+            if (selection.y !is selectionAnchor) {
+                redrawEnd = selection.y;
+                selection.y = selectionAnchor;
+            }
+        // test whether selection actually changed. Fixes 1G71EO1
+        } else if (selectionAnchor is selection.x && caretOffset < selection.y) {
+            // caret moved towards selection anchor (left side of selection).
+            // shrink selection
+            redrawEnd = selection.y;
+            redrawStart = selection.y = caretOffset;
+        }
+    } else {
+        if (caretOffset > selection.y) {
+            // grow selection
+            redrawStart = selection.y;
+            redrawEnd = selection.y = caretOffset;
+            // check if selection has reversed direction
+            if (selection.x !is selectionAnchor) {
+                redrawStart = selection.x;
+                selection.x = selectionAnchor;
+            }
+        // test whether selection actually changed. Fixes 1G71EO1
+        } else if (selectionAnchor is selection.y && caretOffset > selection.x) {
+            // caret moved towards selection anchor (right side of selection).
+            // shrink selection
+            redrawStart = selection.x;
+            redrawEnd = selection.x = caretOffset;
+        }
+    }
+    if (redrawStart !is -1 && redrawEnd !is -1) {
+        internalRedrawRange(redrawStart, redrawEnd - redrawStart);
+        sendSelectionEvent();
+    }
+}
+/**
+ * Moves the caret to the next character or to the beginning of the
+ * next line if the cursor is at the end of a line.
+ */
+void doSelectionCursorNext() {
+    int caretLine = getCaretLine();
+    int lineOffset = content.getOffsetAtLine(caretLine);
+    int offsetInLine = caretOffset - lineOffset;
+    if (offsetInLine < content.getLine(caretLine).length) {
+        TextLayout layout = renderer.getTextLayout(caretLine);
+        offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
+        int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
+        renderer.disposeTextLayout(layout);
+        caretOffset = offsetInLine + lineOffset;
+        caretAlignment = offsetInLine is lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
+        showCaret();
+    } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
+        caretLine++;
+        caretOffset = content.getOffsetAtLine(caretLine);
+        caretAlignment = PREVIOUS_OFFSET_TRAILING;
+        showCaret();
+    }
+}
+/**
+ * Moves the caret to the previous character or to the end of the previous
+ * line if the cursor is at the beginning of a line.
+ */
+void doSelectionCursorPrevious() {
+    int caretLine = getCaretLine();
+    int lineOffset = content.getOffsetAtLine(caretLine);
+    int offsetInLine = caretOffset - lineOffset;
+    caretAlignment = OFFSET_LEADING;
+
+    if (offsetInLine > 0) {
+        caretOffset = getClusterPrevious(caretOffset, caretLine);
+        showCaret();
+    } else if (caretLine > 0) {
+        caretLine--;
+        lineOffset = content.getOffsetAtLine(caretLine);
+        caretOffset = lineOffset + content.getLine(caretLine).length;
+        showCaret();
+    }
+}
+/**
+ * Moves the caret one line down and to the same character offset relative
+ * to the beginning of the line. Moves the caret to the end of the new line
+ * if the new line is shorter than the character offset.
+ * Moves the caret to the end of the text if the caret already is on the
+ * last line.
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ */
+void doSelectionLineDown() {
+    int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+    doLineDown(true);
+    columnX = oldColumnX;
+}
+/**
+ * Moves the caret one line up and to the same character offset relative
+ * to the beginning of the line. Moves the caret to the end of the new line
+ * if the new line is shorter than the character offset.
+ * Moves the caret to the beginning of the document if it is already on the
+ * first line.
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ */
+void doSelectionLineUp() {
+    int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+    doLineUp(true);
+    columnX = oldColumnX;
+}
+/**
+ * Scrolls one page down so that the last line (truncated or whole)
+ * of the current page becomes the fully visible top line.
+ * <p>
+ * The caret is scrolled the same number of lines so that its location
+ * relative to the top line remains the same. The exception is the end
+ * of the text where a full page scroll is not possible. In this case
+ * the caret is moved after the last character.
+ * <p></p>
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ * </p>
+ */
+void doSelectionPageDown(int pixels) {
+    int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+    doPageDown(true, pixels);
+    columnX = oldColumnX;
+}
+/**
+ * Scrolls one page up so that the first line (truncated or whole)
+ * of the current page becomes the fully visible last line.
+ * <p>
+ * The caret is scrolled the same number of lines so that its location
+ * relative to the top line remains the same. The exception is the beginning
+ * of the text where a full page scroll is not possible. In this case the
+ * caret is moved in front of the first character.
+ * </p><p>
+ * Adjusts the selection according to the caret change. This can either add
+ * to or subtract from the old selection, depending on the previous selection
+ * direction.
+ * </p>
+ */
+void doSelectionPageUp(int pixels) {
+    int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
+    doPageUp(true, pixels);
+    columnX = oldColumnX;
+}
+/**
+ * Moves the caret to the end of the next word .
+ */
+void doSelectionWordNext() {
+    int newCaretOffset = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
+    // Force symmetrical movement for word next and previous. Fixes 14536
+    caretAlignment = OFFSET_LEADING;
+    // don't change caret position if in single line mode and the cursor
+    // would be on a different line. fixes 5673
+    if (!isSingleLine() ||
+        content.getLineAtOffset(caretOffset) is content.getLineAtOffset(newCaretOffset)) {
+        caretOffset = newCaretOffset;
+        showCaret();
+    }
+}
+/**
+ * Moves the caret to the start of the previous word.
+ */
+void doSelectionWordPrevious() {
+    caretAlignment = OFFSET_LEADING;
+    caretOffset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
+    int caretLine = content.getLineAtOffset(caretOffset);
+    // word previous always comes from bottom line. when
+    // wrapping lines, stay on bottom line when on line boundary
+    if (wordWrap && caretLine < content.getLineCount() - 1 &&
+        caretOffset is content.getOffsetAtLine(caretLine + 1)) {
+        caretLine++;
+    }
+    showCaret();
+}
+/**
+ * Moves the caret one character to the left.  Do not go to the previous line.
+ * When in a bidi locale and at a R2L character the caret is moved to the
+ * beginning of the R2L segment (visually right) and then one character to the
+ * left (visually left because it's now in a L2R segment).
+ */
+void doVisualPrevious() {
+    caretOffset = getClusterPrevious(caretOffset, getCaretLine());
+    showCaret();
+}
+/**
+ * Moves the caret one character to the right.  Do not go to the next line.
+ * When in a bidi locale and at a R2L character the caret is moved to the
+ * end of the R2L segment (visually left) and then one character to the
+ * right (visually right because it's now in a L2R segment).
+ */
+void doVisualNext() {
+    caretOffset = getClusterNext(caretOffset, getCaretLine());
+    showCaret();
+}
+/**
+ * Moves the caret to the end of the next word.
+ * If a selection exists, move the caret to the end of the selection
+ * and remove the selection.
+ */
+void doWordNext() {
+    if (selection.y - selection.x > 0) {
+        caretOffset = selection.y;
+        showCaret();
+    } else {
+        doSelectionWordNext();
+    }
+}
+/**
+ * Moves the caret to the start of the previous word.
+ * If a selection exists, move the caret to the start of the selection
+ * and remove the selection.
+ */
+void doWordPrevious() {
+    if (selection.y - selection.x > 0) {
+        caretOffset = selection.x;
+        showCaret();
+    } else {
+        doSelectionWordPrevious();
+    }
+}
+/**
+ * Ends the autoscroll process.
+ */
+void endAutoScroll() {
+    autoScrollDirection = SWT.NULL;
+}
+public override Color getBackground() {
+    checkWidget();
+    if (background is null) {
+        return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+    }
+    return background;
+}
+/**
+ * Returns the baseline, in pixels.
+ *
+ * Note: this API should not be used if a StyleRange attribute causes lines to
+ * have different heights (i.e. different fonts, rise, etc).
+ *
+ * @return baseline the baseline
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 3.0
+ *
+ * @see #getBaseline(int)
+ */
+public int getBaseline() {
+    checkWidget();
+    return renderer.getBaseline();
+}
+/**
+ * Returns the baseline at the given offset, in pixels.
+ *
+ * @param offset the offset
+ *
+ * @return baseline the baseline
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public int getBaseline(int offset) {
+    checkWidget();
+    if (!(0 <= offset && offset <= content.getCharCount())) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    if (isFixedLineHeight()) {
+        return renderer.getBaseline();
+    }
+    int lineIndex = content.getLineAtOffset(offset);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length));
+    FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
+    renderer.disposeTextLayout(layout);
+    return metrics.getAscent() + metrics.getLeading();
+}
+/**
+ * Gets the BIDI coloring mode.  When true the BIDI text display
+ * algorithm is applied to segments of text that are the same
+ * color.
+ *
+ * @return the current coloring mode
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @deprecated use BidiSegmentListener instead.
+ */
+public bool getBidiColoring() {
+    checkWidget();
+    return bidiColoring;
+}
+/**
+ * Returns the index of the last fully visible line.
+ *
+ * @return index of the last fully visible line.
+ */
+int getBottomIndex() {
+    int bottomIndex;
+    if (isFixedLineHeight()) {
+        int lineCount = 1;
+        int lineHeight = renderer.getLineHeight();
+        if (lineHeight !is 0) {
+            // calculate the number of lines that are fully visible
+            int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
+            lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
+        }
+        bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
+    } else {
+        int clientAreaHeight = this.clientAreaHeight - bottomMargin;
+        bottomIndex = getLineIndex(clientAreaHeight);
+        if (bottomIndex > 0) {
+            int linePixel = getLinePixel(bottomIndex);
+            int lineHeight = renderer.getLineHeight(bottomIndex);
+            if (linePixel + lineHeight > clientAreaHeight) {
+                if (getLinePixel(bottomIndex - 1) >= topMargin) {
+                    bottomIndex--;
+                }
+            }
+        }
+    }
+    return bottomIndex;
+}
+Rectangle getBoundsAtOffset(int offset) {
+    int lineIndex = content.getLineAtOffset(offset);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    String line = content.getLine(lineIndex);
+    Rectangle bounds;
+    if (line.length !is 0) {
+        int offsetInLine = offset - lineOffset;
+        TextLayout layout = renderer.getTextLayout(lineIndex);
+        bounds = layout.getBounds(offsetInLine, offsetInLine);
+        renderer.disposeTextLayout(layout);
+    } else {
+        bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
+    }
+    if (offset is caretOffset) {
+        int lineEnd = lineOffset + line.length;
+        if (offset is lineEnd && caretAlignment is PREVIOUS_OFFSET_TRAILING) {
+            bounds.width += getCaretWidth();
+        }
+    }
+    bounds.x += leftMargin - horizontalScrollOffset;
+    bounds.y += getLinePixel(lineIndex);
+    return bounds;
+}
+/**
+ * Returns the caret position relative to the start of the text.
+ *
+ * @return the caret position relative to the start of the text.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getCaretOffset() {
+    checkWidget();
+    return caretOffset;
+}
+/**
+ * Returns the caret width.
+ *
+ * @return the caret width, 0 if caret is null.
+ */
+int getCaretWidth() {
+    Caret caret = getCaret();
+    if (caret is null) return 0;
+    return caret.getSize().x;
+}
+Object getClipboardContent(int clipboardType) {
+    TextTransfer plainTextTransfer = TextTransfer.getInstance();
+    return clipboard.getContents(plainTextTransfer, clipboardType);
+}
+int getClusterNext(int offset, int lineIndex) {
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    offset -= lineOffset;
+    offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
+    offset += lineOffset;
+    renderer.disposeTextLayout(layout);
+    return offset;
+}
+int getClusterPrevious(int offset, int lineIndex) {
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    offset -= lineOffset;
+    offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
+    offset += lineOffset;
+    renderer.disposeTextLayout(layout);
+    return offset;
+}
+/**
+ * Returns the content implementation that is used for text storage.
+ *
+ * @return content the user defined content implementation that is used for
+ * text storage or the default content implementation if no user defined
+ * content implementation has been set.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public StyledTextContent getContent() {
+    checkWidget();
+    return content;
+}
+public override bool getDragDetect () {
+    checkWidget ();
+    return dragDetect_;
+}
+/**
+ * Returns whether the widget implements double click mouse behavior.
+ *
+ * @return true if double clicking a word selects the word, false if double clicks
+ * have the same effect as regular mouse clicks
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public bool getDoubleClickEnabled() {
+    checkWidget();
+    return doubleClickEnabled;
+}
+/**
+ * Returns whether the widget content can be edited.
+ *
+ * @return true if content can be edited, false otherwise
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public bool getEditable() {
+    checkWidget();
+    return editable;
+}
+public override Color getForeground() {
+    checkWidget();
+    if (foreground is null) {
+        return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+    }
+    return foreground;
+}
+/**
+ * Returns the horizontal scroll increment.
+ *
+ * @return horizontal scroll increment.
+ */
+int getHorizontalIncrement() {
+    return renderer.averageCharWidth;
+}
+/**
+ * Returns the horizontal scroll offset relative to the start of the line.
+ *
+ * @return horizontal scroll offset relative to the start of the line,
+ * measured in character increments starting at 0, if > 0 the content is scrolled
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getHorizontalIndex() {
+    checkWidget();
+    return horizontalScrollOffset / getHorizontalIncrement();
+}
+/**
+ * Returns the horizontal scroll offset relative to the start of the line.
+ *
+ * @return the horizontal scroll offset relative to the start of the line,
+ * measured in pixel starting at 0, if > 0 the content is scrolled.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getHorizontalPixel() {
+    checkWidget();
+    return horizontalScrollOffset;
+}
+/**
+ * Returns the line indentation of the widget.
+ *
+ * @return the line indentation
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #getLineIndent(int)
+ *
+ * @since 3.2
+ */
+public int getIndent() {
+    checkWidget();
+    return indent;
+}
+/**
+ * Returns whether the widget justifies lines.
+ *
+ * @return whether lines are justified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #getLineJustify(int)
+ *
+ * @since 3.2
+ */
+public bool getJustify() {
+    checkWidget();
+    return justify;
+}
+/**
+ * Returns the action assigned to the key.
+ * Returns SWT.NULL if there is no action associated with the key.
+ *
+ * @param key a key code defined in SWT.java or a character.
+ *  Optionally ORd with a state mask.  Preferred state masks are one or more of
+ *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
+ *  differences.  However, there may be cases where using the specific state masks
+ *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
+ * @return one of the predefined actions defined in ST.java or SWT.NULL
+ *  if there is no action associated with the key.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getKeyBinding(int key) {
+    checkWidget();
+    if( auto p = key in keyActionMap ){
+        return *p;
+    }
+    return SWT.NULL;
+}
+/**
+ * Gets the number of characters.
+ *
+ * @return number of characters in the widget
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getCharCount() {
+    checkWidget();
+    return content.getCharCount();
+}
+/**
+ * Returns the line at the given line index without delimiters.
+ * Index 0 is the first line of the content. When there are not
+ * any lines, getLine(0) is a valid call that answers an empty string.
+ * <p>
+ *
+ * @param lineIndex index of the line to return.
+ * @return the line text without delimiters
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
+ * </ul>
+ * @since 3.4
+ */
+public String getLine(int lineIndex) {
+    checkWidget();
+    if (lineIndex < 0 ||
+        (lineIndex > 0 && lineIndex >= content.getLineCount())) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    return content.getLine(lineIndex);
+}
+/**
+ * Returns the alignment of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line alignment
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getAlignment()
+ *
+ * @since 3.2
+ */
+public int getLineAlignment(int index) {
+    checkWidget();
+    if (index < 0 || index > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    return renderer.getLineAlignment(index, alignment);
+}
+/**
+ * Returns the line at the specified offset in the text
+ * where 0 &lt; offset &lt; getCharCount() so that getLineAtOffset(getCharCount())
+ * returns the line of the insert location.
+ *
+ * @param offset offset relative to the start of the content.
+ *  0 <= offset <= getCharCount()
+ * @return line at the specified offset in the text
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ */
+public int getLineAtOffset(int offset) {
+    checkWidget();
+    if (offset < 0 || offset > getCharCount()) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    return content.getLineAtOffset(offset);
+}
+/**
+ * Returns the background color of the line at the given index.
+ * Returns null if a LineBackgroundListener has been set or if no background
+ * color has been specified for the line. Should not be called if a
+ * LineBackgroundListener has been set since the listener maintains the
+ * line background colors.
+ *
+ * @param index the index of the line
+ * @return the background color of the line at the given index.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ */
+public Color getLineBackground(int index) {
+    checkWidget();
+    if (index < 0 || index > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    return isListening(LineGetBackground) ? null : renderer.getLineBackground(index, null);
+}
+/**
+ * Returns the bullet of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line bullet
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public Bullet getLineBullet(int index) {
+    checkWidget();
+    if (index < 0 || index > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    return isListening(LineGetStyle) ? null : renderer.getLineBullet(index, null);
+}
+/**
+ * Returns the line background data for the given line or null if
+ * there is none.
+ *
+ * @param lineOffset offset of the line start relative to the start
+ *  of the content.
+ * @param line line to get line background data for
+ * @return line background data for the given line.
+ */
+StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
+    return sendLineEvent(LineGetBackground, lineOffset, line);
+}
+/**
+ * Gets the number of text lines.
+ *
+ * @return the number of lines in the widget
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getLineCount() {
+    checkWidget();
+    return content.getLineCount();
+}
+/**
+ * Returns the number of lines that can be completely displayed in the
+ * widget client area.
+ *
+ * @return number of lines that can be completely displayed in the widget
+ *  client area.
+ */
+int getLineCountWhole() {
+    if (isFixedLineHeight()) {
+        int lineHeight = renderer.getLineHeight();
+        return lineHeight !is 0 ? clientAreaHeight / lineHeight : 1;
+    }
+    return getBottomIndex() - topIndex + 1;
+}
+/**
+ * Returns the line delimiter used for entering new lines by key down
+ * or paste operation.
+ *
+ * @return line delimiter used for entering new lines by key down
+ * or paste operation.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public String getLineDelimiter() {
+    checkWidget();
+    return content.getLineDelimiter();
+}
+/**
+ * Returns the line height.
+ * <p>
+ * Note: this API should not be used if a StyleRange attribute causes lines to
+ * have different heights (i.e. different fonts, rise, etc).
+ * </p>
+ *
+ * @return line height in pixel.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @see #getLineHeight(int)
+ */
+public int getLineHeight() {
+    checkWidget();
+    return renderer.getLineHeight();
+}
+/**
+ * Returns the line height at the given offset.
+ *
+ * @param offset the offset
+ *
+ * @return line height in pixels
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public int getLineHeight(int offset) {
+    checkWidget();
+    if (!(0 <= offset && offset <= content.getCharCount())) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    if (isFixedLineHeight()) {
+        return renderer.getLineHeight();
+    }
+    int lineIndex = content.getLineAtOffset(offset);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length));
+    int height = layout.getLineBounds(lineInParagraph).height;
+    renderer.disposeTextLayout(layout);
+    return height;
+}
+/**
+ * Returns the indentation of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line indentation
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getIndent()
+ *
+ * @since 3.2
+ */
+public int getLineIndent(int index) {
+    checkWidget();
+    if (index < 0 || index > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    return isListening(LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
+}
+/**
+ * Returns whether the line at the given index is justified.
+ *
+ * @param index the index of the line
+ *
+ * @return whether the line is justified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
+ * </ul>
+ *
+ * @see #getJustify()
+ *
+ * @since 3.2
+ */
+public bool getLineJustify(int index) {
+    checkWidget();
+    if (index < 0 || index > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    return isListening(LineGetStyle) ? false : renderer.getLineJustify(index, justify);
+}
+/**
+ * Returns the line spacing of the widget.
+ *
+ * @return the line spacing
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public int getLineSpacing() {
+    checkWidget();
+    return lineSpacing;
+}
+/**
+ * Returns the line style data for the given line or null if there is
+ * none.
+ * <p>
+ * If there is a LineStyleListener but it does not set any styles,
+ * the StyledTextEvent.styles field will be initialized to an empty
+ * array.
+ * </p>
+ *
+ * @param lineOffset offset of the line start relative to the start of
+ *  the content.
+ * @param line line to get line styles for
+ * @return line style data for the given line. Styles may start before
+ *  line start and end after line end
+ */
+StyledTextEvent getLineStyleData(int lineOffset, String line) {
+    return sendLineEvent(LineGetStyle, lineOffset, line);
+}
+/**
+ * Returns the top pixel, relative to the client area, of a given line.
+ * Clamps out of ranges index.
+ *
+ * @param lineIndex the line index, the max value is lineCount. If
+ * lineIndex is lineCount it returns the bottom pixel of the last line.
+ * It means this function can be used to retrieve the bottom pixel of any line.
+ *
+ * @return the top pixel of a given line index
+ *
+ * @since 3.2
+ */
+public int getLinePixel(int lineIndex) {
+    checkWidget();
+    int lineCount = content.getLineCount();
+    lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
+    if (isFixedLineHeight()) {
+        int lineHeight = renderer.getLineHeight();
+        return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
+    }
+    if (lineIndex is topIndex) return topIndexY + topMargin;
+    int height = topIndexY;
+    if (lineIndex > topIndex) {
+        for (int i = topIndex; i < lineIndex; i++) {
+            height += renderer.getLineHeight(i);
+        }
+    } else {
+        for (int i = topIndex - 1; i >= lineIndex; i--) {
+            height -= renderer.getLineHeight(i);
+        }
+    }
+    return height + topMargin;
+}
+/**
+ * Returns the line index for a y, relative to the client area.
+ * The line index returned is always in the range 0..lineCount - 1.
+ *
+ * @param y the y-coordinate pixel
+ *
+ * @return the line index for a given y-coordinate pixel
+ *
+ * @since 3.2
+ */
+public int getLineIndex(int y) {
+    checkWidget();
+    y -= topMargin;
+    if (isFixedLineHeight()) {
+        int lineHeight = renderer.getLineHeight();
+        int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
+        int lineCount = content.getLineCount();
+        lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
+        return lineIndex;
+    }
+    if (y is topIndexY) return topIndex;
+    int line = topIndex;
+    if (y < topIndexY) {
+        while (y < topIndexY && line > 0) {
+            y += renderer.getLineHeight(--line);
+        }
+    } else {
+        int lineCount = content.getLineCount();
+        int lineHeight = renderer.getLineHeight(line);
+        while (y - lineHeight >= topIndexY && line < lineCount - 1) {
+            y -= lineHeight;
+            lineHeight = renderer.getLineHeight(++line);
+        }
+    }
+    return line;
+}
+/**
+ * Returns the x, y location of the upper left corner of the character
+ * bounding box at the specified offset in the text. The point is
+ * relative to the upper left corner of the widget client area.
+ *
+ * @param offset offset relative to the start of the content.
+ *  0 <= offset <= getCharCount()
+ * @return x, y location of the upper left corner of the character
+ *  bounding box at the specified offset in the text.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
+ * </ul>
+ */
+public Point getLocationAtOffset(int offset) {
+    checkWidget();
+    if (offset < 0 || offset > getCharCount()) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    return getPointAtOffset(offset);
+}
+/**
+ * Returns the character offset of the first character of the given line.
+ *
+ * @param lineIndex index of the line, 0 based relative to the first
+ *  line in the content. 0 <= lineIndex < getLineCount(), except
+ *  lineIndex may always be 0
+ * @return offset offset of the first character of the line, relative to
+ *  the beginning of the document. The first character of the document is
+ *  at offset 0.
+ *  When there are not any lines, getOffsetAtLine(0) is a valid call that
+ *  answers 0.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
+ * </ul>
+ * @since 2.0
+ */
+public int getOffsetAtLine(int lineIndex) {
+    checkWidget();
+    if (lineIndex < 0 ||
+        (lineIndex > 0 && lineIndex >= content.getLineCount())) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    return content.getOffsetAtLine(lineIndex);
+}
+/**
+ * Returns the offset of the character at the given location relative
+ * to the first character in the document.
+ * <p>
+ * The return value reflects the character offset that the caret will
+ * be placed at if a mouse click occurred at the specified location.
+ * If the x coordinate of the location is beyond the center of a character
+ * the returned offset will be behind the character.
+ * </p>
+ *
+ * @param point the origin of character bounding box relative to
+ *  the origin of the widget client area.
+ * @return offset of the character at the given location relative
+ *  to the first character in the document.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_NULL_ARGUMENT when point is null</li>
+ *   <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
+ * </ul>
+ */
+public int getOffsetAtLocation(Point point) {
+    checkWidget();
+    if (point is null) {
+        SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    }
+    int[] trailing = new int[1];
+    int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
+    if (offset is -1) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    return offset + trailing[0];
+}
+int getOffsetAtPoint(int x, int y) {
+    int lineIndex = getLineIndex(y);
+    y -= getLinePixel(lineIndex);
+    return getOffsetAtPoint(x, y, lineIndex);
+}
+/**
+ * Returns the offset at the specified x location in the specified line.
+ *
+ * @param x x location of the mouse location
+ * @param line  line the mouse location is in
+ * @return the offset at the specified x location in the specified line,
+ *  relative to the beginning of the document
+ */
+int getOffsetAtPoint(int x, int y, int lineIndex) {
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    x += horizontalScrollOffset - leftMargin;
+    int[1] trailing;
+    int offsetInLine = layout.getOffset(x, y, trailing);
+    caretAlignment = OFFSET_LEADING;
+    if (trailing[0] !is 0) {
+        int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
+        int lineStart = layout.getLineOffsets()[lineInParagraph];
+        if (offsetInLine + trailing[0] is lineStart) {
+            offsetInLine += trailing[0];
+            caretAlignment = PREVIOUS_OFFSET_TRAILING;
+        } else {
+            String line = content.getLine(lineIndex);
+            int level;
+            int offset = offsetInLine;
+            while (offset > 0 && tango.text.Unicode.isDigit(line[offset])) offset--;
+            if (offset is 0 && tango.text.Unicode.isDigit(line[offset])) {
+                level = isMirrored() ? 1 : 0;
+            } else {
+                level = layout.getLevel(offset) & 0x1;
+            }
+            offsetInLine += trailing[0];
+            int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
+            if ((level ^ trailingLevel) !is 0) {
+                caretAlignment = PREVIOUS_OFFSET_TRAILING;
+            } else {
+                caretAlignment = OFFSET_LEADING;
+            }
+        }
+    }
+    renderer.disposeTextLayout(layout);
+    return offsetInLine + content.getOffsetAtLine(lineIndex);
+}
+int getOffsetAtPoint(int x, int y, int[] trailing, bool inTextOnly) {
+    if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
+        return -1;
+    }
+    int bottomIndex = getPartialBottomIndex();
+    int height = getLinePixel(bottomIndex + 1);
+    if (inTextOnly && y > height) {
+        return -1;
+    }
+    int lineIndex = getLineIndex(y);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    x += horizontalScrollOffset - leftMargin ;
+    y -= getLinePixel(lineIndex);
+    int offset = layout.getOffset(x, y, trailing);
+    Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
+    renderer.disposeTextLayout(layout);
+    if (inTextOnly && !(rect.x  <= x && x <=  rect.x + rect.width)) {
+        return -1;
+    }
+    return offset + lineOffset;
+}
+/**
+ * Returns the orientation of the receiver.
+ *
+ * @return the orientation style
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 2.1.2
+ */
+public int getOrientation () {
+    checkWidget();
+    return isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
+}
+/**
+ * Returns the index of the last partially visible line.
+ *
+ * @return index of the last partially visible line.
+ */
+int getPartialBottomIndex() {
+    if (isFixedLineHeight()) {
+        int lineHeight = renderer.getLineHeight();
+        int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
+        return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
+    }
+    return getLineIndex(clientAreaHeight - bottomMargin);
+}
+/**
+ * Returns the index of the first partially visible line.
+ *
+ * @return index of the first partially visible line.
+ */
+int getPartialTopIndex() {
+    if (isFixedLineHeight()) {
+        int lineHeight = renderer.getLineHeight();
+        return getVerticalScrollOffset() / lineHeight;
+    }
+    return topIndexY <= 0 ? topIndex : topIndex - 1;
+}
+/**
+ * Returns the content in the specified range using the platform line
+ * delimiter to separate lines.
+ *
+ * @param writer the TextWriter to write line text into
+ * @return the content in the specified range using the platform line
+ *  delimiter to separate lines as written by the specified TextWriter.
+ */
+String getPlatformDelimitedText(TextWriter writer) {
+    int end = writer.getStart() + writer.getCharCount();
+    int startLine = content.getLineAtOffset(writer.getStart());
+    int endLine = content.getLineAtOffset(end);
+    String endLineText = content.getLine(endLine);
+    int endLineOffset = content.getOffsetAtLine(endLine);
+
+    for (int i = startLine; i <= endLine; i++) {
+        writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
+        if (i < endLine) {
+            writer.writeLineDelimiter(PlatformLineDelimiter);
+        }
+    }
+    if (end > endLineOffset + endLineText.length) {
+        writer.writeLineDelimiter(PlatformLineDelimiter);
+    }
+    writer.close();
+    return writer.toString();
+}
+/**
+ * Returns all the ranges of text that have an associated StyleRange.
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * <p>
+ * The ranges array contains start and length pairs.  Each pair refers to
+ * the corresponding style in the styles array.  For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2] returned by <code>getStyleRanges(int, int, bool)</code>.
+ * </p>
+ *
+ * @return the ranges or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getStyleRanges(bool)
+ */
+public int[] getRanges() {
+    checkWidget();
+    if (!isListening(LineGetStyle)) {
+        int[] ranges = renderer.getRanges(0, content.getCharCount());
+        if (ranges !is null) return ranges;
+    }
+    return new int[0];
+}
+/**
+ * Returns the ranges of text that have an associated StyleRange.
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * <p>
+ * The ranges array contains start and length pairs.  Each pair refers to
+ * the corresponding style in the styles array.  For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2] returned by <code>getStyleRanges(int, int, bool)</code>.
+ * </p>
+ *
+ * @param start the start offset of the style ranges to return
+ * @param length the number of style ranges to return
+ *
+ * @return the ranges or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getStyleRanges(int, int, bool)
+ */
+public int[] getRanges(int start, int length) {
+    checkWidget();
+    int contentLength = getCharCount();
+    int end = start + length;
+    if (start > end || start < 0 || end > contentLength) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    if (!isListening(LineGetStyle)) {
+        int[] ranges = renderer.getRanges(start, length);
+        if (ranges !is null) return ranges;
+    }
+    return new int[0];
+}
+/**
+ * Returns the selection.
+ * <p>
+ * Text selections are specified in terms of caret positions.  In a text
+ * widget that contains N characters, there are N+1 caret positions,
+ * ranging from 0..N
+ * </p>
+ *
+ * @return start and end of the selection, x is the offset of the first
+ *  selected character, y is the offset after the last selected character.
+ *  The selection values returned are visual (i.e., x will always always be
+ *  <= y).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
+ *  (LtoR), compare the caretOffset to the start and end of the selection
+ *  (e.g., caretOffset is start of selection implies that the selection is RtoL).
+ * @see #getSelectionRange
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public Point getSelection() {
+    checkWidget();
+    return new Point(selection.x, selection.y);
+}
+/**
+ * Returns the selection.
+ *
+ * @return start and length of the selection, x is the offset of the
+ *  first selected character, relative to the first character of the
+ *  widget content. y is the length of the selection.
+ *  The selection values returned are visual (i.e., length will always always be
+ *  positive).  To determine if a selection is right-to-left (RtoL) vs. left-to-right
+ *  (LtoR), compare the caretOffset to the start and end of the selection
+ *  (e.g., caretOffset is start of selection implies that the selection is RtoL).
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public Point getSelectionRange() {
+    checkWidget();
+    return new Point(selection.x, selection.y - selection.x);
+}
+/**
+ * Returns the receiver's selection background color.
+ *
+ * @return the selection background color
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public Color getSelectionBackground() {
+    checkWidget();
+    if (selectionBackground is null) {
+        return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
+    }
+    return selectionBackground;
+}
+/**
+ * Gets the number of selected characters.
+ *
+ * @return the number of selected characters.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getSelectionCount() {
+    checkWidget();
+    return getSelectionRange().y;
+}
+/**
+ * Returns the receiver's selection foreground color.
+ *
+ * @return the selection foreground color
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public Color getSelectionForeground() {
+    checkWidget();
+    if (selectionForeground is null) {
+        return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
+    }
+    return selectionForeground;
+}
+/**
+ * Returns the selected text.
+ *
+ * @return selected text, or an empty String if there is no selection.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public String getSelectionText() {
+    checkWidget();
+    return content.getTextRange(selection.x, selection.y - selection.x);
+}
+public override int getStyle() {
+    int style = super.getStyle();
+    style &= ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.MIRRORED);
+    if (isMirrored()) {
+        style |= SWT.RIGHT_TO_LEFT | SWT.MIRRORED;
+    } else {
+        style |= SWT.LEFT_TO_RIGHT;
+    }
+    return style;
+}
+
+/**
+ * Returns the text segments that should be treated as if they
+ * had a different direction than the surrounding text.
+ *
+ * @param lineOffset offset of the first character in the line.
+ *  0 based from the beginning of the document.
+ * @param line text of the line to specify bidi segments for
+ * @return text segments that should be treated as if they had a
+ *  different direction than the surrounding text. Only the start
+ *  index of a segment is specified, relative to the start of the
+ *  line. Always starts with 0 and ends with the line length.
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the segment indices returned
+ *      by the listener do not start with 0, are not in ascending order,
+ *      exceed the line length or have duplicates</li>
+ * </ul>
+ */
+int [] getBidiSegments(int lineOffset, String line) {
+    if (!isBidi()) return null;
+    if (!isListening(LineGetSegments)) {
+        return getBidiSegmentsCompatibility(line, lineOffset);
+    }
+    StyledTextEvent event = sendLineEvent(LineGetSegments, lineOffset, line);
+    int lineLength = line.length;
+    int[] segments;
+    if (event is null || event.segments is null || event.segments.length is 0) {
+        segments = [0, lineLength];
+    } else {
+        int segmentCount = event.segments.length;
+
+        // test segment index consistency
+        if (event.segments[0] !is 0) {
+            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+        }
+        for (int i = 1; i < segmentCount; i++) {
+            if (event.segments[i] <= event.segments[i - 1] || event.segments[i] > lineLength) {
+                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+            }
+        }
+        // ensure that last segment index is line end offset
+        if (event.segments[segmentCount - 1] !is lineLength) {
+            segments = new int[segmentCount + 1];
+            System.arraycopy(event.segments, 0, segments, 0, segmentCount);
+            segments[segmentCount] = lineLength;
+        } else {
+            segments = event.segments;
+        }
+    }
+    return segments;
+}
+/**
+ * @see #getBidiSegments
+ * Supports deprecated setBidiColoring API. Remove when API is removed.
+ */
+int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
+    int lineLength = line.length;
+    if (!bidiColoring) {
+        return [0, lineLength];
+    }
+    StyleRange [] styles = null;
+    StyledTextEvent event = getLineStyleData(lineOffset, line);
+    if (event !is null) {
+        styles = event.styles;
+    } else {
+        styles = renderer.getStyleRanges(lineOffset, lineLength, true);
+    }
+    if (styles is null || styles.length is 0) {
+        return [0, lineLength];
+    }
+    int k=0, count = 1;
+    while (k < styles.length && styles[k].start is 0 && styles[k].length is lineLength) {
+        k++;
+    }
+    int[] offsets = new int[(styles.length - k) * 2 + 2];
+    for (int i = k; i < styles.length; i++) {
+        StyleRange style = styles[i];
+        int styleLineStart = Math.max(style.start - lineOffset, 0);
+        int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
+        styleLineEnd = Math.min (styleLineEnd, line.length );
+        if (i > 0 && count > 1 &&
+            ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
+             (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
+             style.similarTo(styles[i-1])) {
+            offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
+            offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
+        } else {
+            if (styleLineStart > offsets[count - 1]) {
+                offsets[count] = styleLineStart;
+                count++;
+            }
+            offsets[count] = styleLineEnd;
+            count++;
+        }
+    }
+    // add offset for last non-colored segment in line, if any
+    if (lineLength > offsets[count-1]) {
+        offsets [count] = lineLength;
+        count++;
+    }
+    if (count is offsets.length) {
+        return offsets;
+    }
+    int [] result = new int [count];
+    System.arraycopy (offsets, 0, result, 0, count);
+    return result;
+}
+/**
+ * Returns the style range at the given offset.
+ * <p>
+ * Returns null if a LineStyleListener has been set or if a style is not set
+ * for the offset.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param offset the offset to return the style for.
+ *  0 <= offset < getCharCount() must be true.
+ * @return a StyleRange with start is offset and length is 1, indicating
+ *  the style at the given offset. null if a LineStyleListener has been set
+ *  or if a style is not set for the given offset.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
+ * </ul>
+ */
+public StyleRange getStyleRangeAtOffset(int offset) {
+    checkWidget();
+    if (offset < 0 || offset >= getCharCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (!isListening(LineGetStyle)) {
+        StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
+        if (ranges !is null) return ranges[0];
+    }
+    return null;
+}
+/**
+ * Returns the styles.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * <p></p>
+ * Note: Because a StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>getStyleRanges(bool)</code>
+ * can be used to get the styles without the ranges.
+ * </p>
+ *
+ * @return the styles or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #getStyleRanges(bool)
+ */
+public StyleRange[] getStyleRanges() {
+    checkWidget();
+    return getStyleRanges(0, content.getCharCount(), true);
+}
+/**
+ * Returns the styles.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p><p>
+ * Note: When <code>includeRanges</code> is true, the start and length
+ * fields of each StyleRange will be valid, however the StyleRange
+ * objects may need to be cloned. When <code>includeRanges</code> is
+ * false, <code>getRanges(int, int)</code> can be used to get the
+ * associated ranges.
+ * </p>
+ *
+ * @param includeRanges whether the start and length field of the StyleRanges should be set.
+ *
+ * @return the styles or an empty array if a LineStyleListener has been set.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getRanges(int, int)
+ * @see #setStyleRanges(int[], StyleRange[])
+ */
+public StyleRange[] getStyleRanges(bool includeRanges) {
+    checkWidget();
+    return getStyleRanges(0, content.getCharCount(), includeRanges);
+}
+/**
+ * Returns the styles for the given text range.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p><p>
+ * Note: Because the StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>getStyleRanges(int, int, bool)</code>
+ * can be used to get the styles without the ranges.
+ * </p>
+ * @param start the start offset of the style ranges to return
+ * @param length the number of style ranges to return
+ *
+ * @return the styles or an empty array if a LineStyleListener has
+ *  been set.  The returned styles will reflect the given range.  The first
+ *  returned <code>StyleRange</code> will have a starting offset >= start
+ *  and the last returned <code>StyleRange</code> will have an ending
+ *  offset <= start + length - 1
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ *
+ * @see #getStyleRanges(int, int, bool)
+ *
+ * @since 3.0
+ */
+public StyleRange[] getStyleRanges(int start, int length) {
+    checkWidget();
+    return getStyleRanges(start, length, true);
+}
+/**
+ * Returns the styles for the given text range.
+ * <p>
+ * Returns an empty array if a LineStyleListener has been set.
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p><p>
+ * Note: When <code>includeRanges</code> is true, the start and length
+ * fields of each StyleRange will be valid, however the StyleRange
+ * objects may need to be cloned. When <code>includeRanges</code> is
+ * false, <code>getRanges(int, int)</code> can be used to get the
+ * associated ranges.
+ * </p>
+ *
+ * @param start the start offset of the style ranges to return
+ * @param length the number of style ranges to return
+ * @param includeRanges whether the start and length field of the StyleRanges should be set.
+ *
+ * @return the styles or an empty array if a LineStyleListener has
+ *  been set.  The returned styles will reflect the given range.  The first
+ *  returned <code>StyleRange</code> will have a starting offset >= start
+ *  and the last returned <code>StyleRange</code> will have an ending
+ *  offset <= start + length - 1
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ *
+ * @since 3.2
+ *
+ * @see #getRanges(int, int)
+ * @see #setStyleRanges(int[], StyleRange[])
+ */
+public StyleRange[] getStyleRanges(int start, int length, bool includeRanges) {
+    checkWidget();
+    int contentLength = getCharCount();
+    int end = start + length;
+    if (start > end || start < 0 || end > contentLength) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    if (!isListening(LineGetStyle)) {
+        StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
+        if (ranges !is null) return ranges;
+    }
+    return new StyleRange[0];
+}
+/**
+ * Returns the tab width measured in characters.
+ *
+ * @return tab width measured in characters
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getTabs() {
+    checkWidget();
+    return tabLength;
+}
+/**
+ * Returns a copy of the widget content.
+ *
+ * @return copy of the widget content
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public String getText() {
+    checkWidget();
+    return content.getTextRange(0, getCharCount());
+}
+/**
+ * Returns the widget content between the two offsets.
+ *
+ * @param start offset of the first character in the returned String
+ * @param end offset of the last character in the returned String
+ * @return widget content starting at start and ending at end
+ * @see #getTextRange(int,int)
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ */
+public String getText(int start, int end) {
+    checkWidget();
+    int contentLength = getCharCount();
+    if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    return content.getTextRange(start, end - start + 1);
+}
+/**
+ * Returns the smallest bounding rectangle that includes the characters between two offsets.
+ *
+ * @param start offset of the first character included in the bounding box
+ * @param end offset of the last character included in the bounding box
+ * @return bounding box of the text between start and end
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ * @since 3.1
+ */
+public Rectangle getTextBounds(int start, int end) {
+    checkWidget();
+    int contentLength = getCharCount();
+    if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    int lineStart = content.getLineAtOffset(start);
+    int lineEnd = content.getLineAtOffset(end);
+    Rectangle rect;
+    int y = getLinePixel(lineStart);
+    int height = 0;
+    int left = 0x7fffffff, right = 0;
+    for (int i = lineStart; i <= lineEnd; i++) {
+        int lineOffset = content.getOffsetAtLine(i);
+        TextLayout layout = renderer.getTextLayout(i);
+        int length = layout.getText().length;
+        if (length > 0) {
+            if (i is lineStart) {
+                if (i is lineEnd) {
+                    rect = layout.getBounds(start - lineOffset, end - lineOffset);
+                } else {
+                    rect = layout.getBounds(start - lineOffset, length);
+                }
+                y += rect.y;
+            } else if (i is lineEnd) {
+                rect = layout.getBounds(0, end - lineOffset);
+            } else {
+                rect = layout.getBounds();
+            }
+            left = Math.min(left, rect.x);
+            right = Math.max(right, rect.x + rect.width);
+            height += rect.height;
+        } else {
+            height += renderer.getLineHeight();
+        }
+        renderer.disposeTextLayout(layout);
+    }
+    rect = new Rectangle (left, y, right-left, height);
+    rect.x += leftMargin - horizontalScrollOffset;
+    return rect;
+}
+/**
+ * Returns the widget content starting at start for length characters.
+ *
+ * @param start offset of the first character in the returned String
+ * @param length number of characters to return
+ * @return widget content starting at start and extending length characters.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
+ * </ul>
+ */
+public String getTextRange(int start, int length) {
+    checkWidget();
+    int contentLength = getCharCount();
+    int end = start + length;
+    if (start > end || start < 0 || end > contentLength) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    return content.getTextRange(start, length);
+}
+/**
+ * Returns the maximum number of characters that the receiver is capable of holding.
+ *
+ * @return the text limit
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getTextLimit() {
+    checkWidget();
+    return textLimit;
+}
+/**
+ * Gets the top index.
+ * <p>
+ * The top index is the index of the fully visible line that is currently
+ * at the top of the widget or the topmost partially visible line if no line is fully visible.
+ * The top index changes when the widget is scrolled. Indexing is zero based.
+ * </p>
+ *
+ * @return the index of the top line
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getTopIndex() {
+    checkWidget();
+    return topIndex;
+}
+/**
+ * Gets the top pixel.
+ * <p>
+ * The top pixel is the pixel position of the line that is
+ * currently at the top of the widget. The text widget can be scrolled by pixels
+ * by dragging the scroll thumb so that a partial line may be displayed at the top
+ * the widget.  The top pixel changes when the widget is scrolled.  The top pixel
+ * does not include the widget trimming.
+ * </p>
+ *
+ * @return pixel position of the top line
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public int getTopPixel() {
+    checkWidget();
+    return getVerticalScrollOffset();
+}
+/**
+ * Returns the vertical scroll increment.
+ *
+ * @return vertical scroll increment.
+ */
+int getVerticalIncrement() {
+    return renderer.getLineHeight();
+}
+int getVerticalScrollOffset() {
+    if (verticalScrollOffset is -1) {
+        renderer.calculate(0, topIndex);
+        int height = 0;
+        for (int i = 0; i < topIndex; i++) {
+            height += renderer.getLineHeight(i);
+        }
+        height -= topIndexY;
+        verticalScrollOffset = height;
+    }
+    return verticalScrollOffset;
+}
+int getVisualLineIndex(TextLayout layout, int offsetInLine) {
+    int lineIndex = layout.getLineIndex(offsetInLine);
+    int[] offsets = layout.getLineOffsets();
+    if (lineIndex !is 0 && offsetInLine is offsets[lineIndex]) {
+        int lineY = layout.getLineBounds(lineIndex).y;
+        int caretY = getCaret().getLocation().y - topMargin - getLinePixel(getCaretLine());
+        if (lineY > caretY) lineIndex--;
+    }
+    return lineIndex;
+}
+int getCaretDirection() {
+    if (!isBidiCaret()) return SWT.DEFAULT;
+    if (ime.getCompositionOffset() !is -1) return SWT.DEFAULT;
+    if (!updateCaretDirection && caretDirection !is SWT.NULL) return caretDirection;
+    updateCaretDirection = false;
+    int caretLine = getCaretLine();
+    int lineOffset = content.getOffsetAtLine(caretLine);
+    String line = content.getLine(caretLine);
+    int offset = caretOffset - lineOffset;
+    int lineLength = line.length;
+    if (lineLength is 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT;
+    if (caretAlignment is PREVIOUS_OFFSET_TRAILING && offset > 0) offset--;
+    if (offset is lineLength && offset > 0) offset--;
+    while (offset > 0 && tango.text.Unicode.isDigit(line[offset])) offset--;
+    if (offset is 0 && tango.text.Unicode.isDigit(line[offset])) {
+        return isMirrored() ? SWT.RIGHT : SWT.LEFT;
+    }
+    TextLayout layout = renderer.getTextLayout(caretLine);
+    int level = layout.getLevel(offset);
+    renderer.disposeTextLayout(layout);
+    return ((level & 1) !is 0) ? SWT.RIGHT : SWT.LEFT;
+}
+/*
+ * Returns the index of the line the caret is on.
+ */
+int getCaretLine() {
+    return content.getLineAtOffset(caretOffset);
+}
+int getWrapWidth () {
+    if (wordWrap && !isSingleLine()) {
+        int width = clientAreaWidth - leftMargin - rightMargin - getCaretWidth();
+        return width > 0 ? width : 1;
+    }
+    return -1;
+}
+int getWordNext (int offset, int movement) {
+    int newOffset, lineOffset;
+    String lineText;
+    if (offset >= getCharCount()) {
+        newOffset = offset;
+        int lineIndex = content.getLineCount() - 1;
+        lineOffset = content.getOffsetAtLine(lineIndex);
+        lineText = content.getLine(lineIndex);
+    } else {
+        int lineIndex = content.getLineAtOffset(offset);
+        lineOffset = content.getOffsetAtLine(lineIndex);
+        lineText = content.getLine(lineIndex);
+        int lineLength = lineText.length;
+        if (offset is lineOffset + lineLength) {
+            newOffset = content.getOffsetAtLine(lineIndex + 1);
+        } else {
+            TextLayout layout = renderer.getTextLayout(lineIndex);
+            newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
+            renderer.disposeTextLayout(layout);
+        }
+    }
+    return sendWordBoundaryEvent(WordNext, movement, offset, newOffset, lineText, lineOffset);
+}
+int getWordPrevious(int offset, int movement) {
+    int newOffset, lineOffset;
+    String lineText;
+    if (offset <= 0) {
+        newOffset = 0;
+        int lineIndex = content.getLineAtOffset(newOffset);
+        lineOffset = content.getOffsetAtLine(lineIndex);
+        lineText = content.getLine(lineIndex);
+    } else {
+        int lineIndex = content.getLineAtOffset(offset);
+        lineOffset = content.getOffsetAtLine(lineIndex);
+        lineText = content.getLine(lineIndex);
+        if (offset is lineOffset) {
+            String nextLineText = content.getLine(lineIndex - 1);
+            int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
+            newOffset = nextLineOffset + nextLineText.length;
+        } else {
+            TextLayout layout = renderer.getTextLayout(lineIndex);
+            newOffset = lineOffset + layout.getPreviousOffset(offset - lineOffset, movement);
+            renderer.disposeTextLayout(layout);
+        }
+    }
+    return sendWordBoundaryEvent(WordPrevious, movement, offset, newOffset, lineText, lineOffset);
+}
+/**
+ * Returns whether the widget wraps lines.
+ *
+ * @return true if widget wraps lines, false otherwise
+ * @since 2.0
+ */
+public bool getWordWrap() {
+    checkWidget();
+    return wordWrap;
+}
+/**
+ * Returns the location of the given offset.
+ * <p>
+ * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
+ * </p>
+ *
+ * @return location of the character at the given offset in the line.
+ */
+Point getPointAtOffset(int offset) {
+    int lineIndex = content.getLineAtOffset(offset);
+    String line = content.getLine(lineIndex);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    int offsetInLine = offset - lineOffset;
+    int lineLength = line.length;
+    if (lineIndex < content.getLineCount() - 1) {
+        int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
+        if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
+            offsetInLine = lineLength;
+        }
+    }
+    Point point;
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    if (lineLength !is 0  && offsetInLine <= lineLength) {
+        if (offsetInLine is lineLength) {
+            // SWT: Instead of go back one byte, go back one codepoint
+            int offsetInLine_m1 = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
+            point = layout.getLocation(offsetInLine_m1, true);
+        } else {
+            switch (caretAlignment) {
+                case OFFSET_LEADING:
+                    point = layout.getLocation(offsetInLine, false);
+                    break;
+                case PREVIOUS_OFFSET_TRAILING:
+                default:
+                    if (offsetInLine is 0) {
+                        point = layout.getLocation(offsetInLine, false);
+                    } else {
+                        // SWT: Instead of go back one byte, go back one codepoint
+                        int offsetInLine_m1 = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
+                        point = layout.getLocation(offsetInLine_m1, true);
+                    }
+                    break;
+            }
+        }
+    } else {
+        point = new Point(layout.getIndent(), 0);
+    }
+    renderer.disposeTextLayout(layout);
+    point.x += leftMargin - horizontalScrollOffset;
+    point.y += getLinePixel(lineIndex);
+    return point;
+}
+/**
+ * Inserts a string.  The old selection is replaced with the new text.
+ *
+ * @param string the string
+ * @see #replaceTextRange(int,int,String)
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void insert(String string) {
+    checkWidget();
+    // SWT extension: allow null for zero length string
+//     if (string is null) {
+//         SWT.error(SWT.ERROR_NULL_ARGUMENT);
+//     }
+    Point sel = getSelectionRange();
+    replaceTextRange(sel.x, sel.y, string);
+}
+/**
+ * Creates content change listeners and set the default content model.
+ */
+void installDefaultContent() {
+    textChangeListener = new class() TextChangeListener {
+        public void textChanging(TextChangingEvent event) {
+            handleTextChanging(event);
+        }
+        public void textChanged(TextChangedEvent event) {
+            handleTextChanged(event);
+        }
+        public void textSet(TextChangedEvent event) {
+            handleTextSet(event);
+        }
+    };
+    content = new DefaultContent();
+    content.addTextChangeListener(textChangeListener);
+}
+/**
+ * Adds event listeners
+ */
+void installListeners() {
+    ScrollBar verticalBar = getVerticalBar();
+    ScrollBar horizontalBar = getHorizontalBar();
+
+    listener = new class() Listener {
+        public void handleEvent(Event event) {
+            switch (event.type) {
+                case SWT.Dispose: handleDispose(event); break;
+                case SWT.KeyDown: handleKeyDown(event); break;
+                case SWT.KeyUp: handleKeyUp(event); break;
+                case SWT.MouseDown: handleMouseDown(event); break;
+                case SWT.MouseUp: handleMouseUp(event); break;
+                case SWT.MouseMove: handleMouseMove(event); break;
+                case SWT.Paint: handlePaint(event); break;
+                case SWT.Resize: handleResize(event); break;
+                case SWT.Traverse: handleTraverse(event); break;
+                default:
+            }
+        }
+    };
+    addListener(SWT.Dispose, listener);
+    addListener(SWT.KeyDown, listener);
+    addListener(SWT.KeyUp, listener);
+    addListener(SWT.MouseDown, listener);
+    addListener(SWT.MouseUp, listener);
+    addListener(SWT.MouseMove, listener);
+    addListener(SWT.Paint, listener);
+    addListener(SWT.Resize, listener);
+    addListener(SWT.Traverse, listener);
+    ime.addListener(SWT.ImeComposition, new class() Listener {
+        public void handleEvent(Event event) {
+            switch (event.detail) {
+                case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break;
+                case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break;
+                case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break;
+                default:
+            }
+        }
+    });
+    if (verticalBar !is null) {
+        verticalBar.addListener(SWT.Selection, new class() Listener {
+            public void handleEvent(Event event) {
+                handleVerticalScroll(event);
+            }
+        });
+    }
+    if (horizontalBar !is null) {
+        horizontalBar.addListener(SWT.Selection, new class() Listener {
+            public void handleEvent(Event event) {
+                handleHorizontalScroll(event);
+            }
+        });
+    }
+}
+void internalRedrawRange(int start, int length) {
+    if (length <= 0) return;
+    int end = start + length;
+    int startLine = content.getLineAtOffset(start);
+    int endLine = content.getLineAtOffset(end);
+    int partialBottomIndex = getPartialBottomIndex();
+    int partialTopIndex = getPartialTopIndex();
+    if (startLine > partialBottomIndex || endLine < partialTopIndex) {
+        return;
+    }
+    if (partialTopIndex > startLine) {
+        startLine = partialTopIndex;
+        start = 0;
+    } else {
+        start -= content.getOffsetAtLine(startLine);
+    }
+    if (partialBottomIndex < endLine) {
+        endLine = partialBottomIndex + 1;
+        end = 0;
+    } else {
+        end -= content.getOffsetAtLine(endLine);
+    }
+
+    TextLayout layout = renderer.getTextLayout(startLine);
+    int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
+    int[] offsets = layout.getLineOffsets();
+    int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length));
+
+    /* Redraw end of line before start line if wrapped and start offset is first char */
+    if (wordWrap && startIndex > 0 && offsets[startIndex] is start) {
+        Rectangle rect = layout.getLineBounds(startIndex - 1);
+        rect.x = rect.width;
+        rect.width = clientAreaWidth - rightMargin - rect.x;
+        rect.x += lineX;
+        rect.y += startLineY;
+        super.redraw(rect.x, rect.y, rect.width, rect.height, false);
+    }
+
+    if (startLine is endLine) {
+        int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length));
+        if (startIndex is endIndex) {
+            /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
+            Rectangle rect = layout.getBounds(start, end - 1);
+            rect.x += lineX;
+            rect.y += startLineY;
+            super.redraw(rect.x, rect.y, rect.width, rect.height, false);
+            renderer.disposeTextLayout(layout);
+            return;
+        }
+    }
+
+    /* Redraw start line from the start offset to the end of client area */
+    Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1);
+    if (startRect.height is 0) {
+        Rectangle bounds = layout.getLineBounds(startIndex);
+        startRect.x = bounds.width;
+        startRect.y = bounds.y;
+        startRect.height = bounds.height;
+    }
+    startRect.x += lineX;
+    startRect.y += startLineY;
+    startRect.width = clientAreaWidth - rightMargin - startRect.x;
+    super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);
+
+    /* Redraw end line from the beginning of the line to the end offset */
+    if (startLine !is endLine) {
+        renderer.disposeTextLayout(layout);
+        layout = renderer.getTextLayout(endLine);
+        offsets = layout.getLineOffsets();
+    }
+    int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length));
+    Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1);
+    if (endRect.height is 0) {
+        Rectangle bounds = layout.getLineBounds(endIndex);
+        endRect.y = bounds.y;
+        endRect.height = bounds.height;
+    }
+    endRect.x += lineX;
+    endRect.y += getLinePixel(endLine);
+    super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
+    renderer.disposeTextLayout(layout);
+
+    /* Redraw all lines in between start and end line */
+    int y = startRect.y + startRect.height;
+    if (endRect.y > y) {
+        super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
+    }
+}
+void handleCompositionOffset (Event event) {
+    int[] trailing = new int [1];
+    event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
+    event.count = trailing[0];
+}
+void handleCompositionSelection (Event event) {
+    event.start = selection.x;
+    event.end = selection.y;
+    event.text = getSelectionText();
+}
+void handleCompositionChanged(Event event) {
+    String text = event.text;
+    int start = event.start;
+    int end = event.end;
+    int length = text.length;
+    if (length is ime.getCommitCount()) {
+        content.replaceTextRange(start, end - start, "");
+        caretOffset = ime.getCompositionOffset();
+        caretWidth = 0;
+        caretDirection = SWT.NULL;
+    } else {
+        content.replaceTextRange(start, end - start, text);
+        caretOffset = ime.getCaretOffset();
+        if (ime.getWideCaret()) {
+            start = ime.getCompositionOffset();
+            int lineIndex = getCaretLine();
+            int lineOffset = content.getOffsetAtLine(lineIndex);
+            TextLayout layout = renderer.getTextLayout(lineIndex);
+            caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width;
+            renderer.disposeTextLayout(layout);
+        }
+    }
+    showCaret();
+}
+/**
+ * Frees resources.
+ */
+void handleDispose(Event event) {
+    removeListener(SWT.Dispose, listener);
+    notifyListeners(SWT.Dispose, event);
+    event.type = SWT.None;
+
+    clipboard.dispose();
+    if (renderer !is null) {
+        renderer.dispose();
+        renderer = null;
+    }
+    if (content !is null) {
+        content.removeTextChangeListener(textChangeListener);
+        content = null;
+    }
+    if (defaultCaret !is null) {
+        defaultCaret.dispose();
+        defaultCaret = null;
+    }
+    if (leftCaretBitmap !is null) {
+        leftCaretBitmap.dispose();
+        leftCaretBitmap = null;
+    }
+    if (rightCaretBitmap !is null) {
+        rightCaretBitmap.dispose();
+        rightCaretBitmap = null;
+    }
+    if (isBidiCaret()) {
+        BidiUtil.removeLanguageListener(this);
+    }
+    selectionBackground = null;
+    selectionForeground = null;
+    textChangeListener = null;
+    selection = null;
+    doubleClickSelection = null;
+    keyActionMap = null;
+    background = null;
+    foreground = null;
+    clipboard = null;
+}
+/**
+ * Scrolls the widget horizontally.
+ */
+void handleHorizontalScroll(Event event) {
+    int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
+    scrollHorizontal(scrollPixel, false);
+}
+/**
+ * If an action has been registered for the key stroke execute the action.
+ * Otherwise, if a character has been entered treat it as new content.
+ *
+ * @param event keyboard event
+ */
+void handleKey(Event event) {
+    int action;
+    caretAlignment = PREVIOUS_OFFSET_TRAILING;
+    if (event.keyCode !is 0) {
+        // special key pressed (e.g., F1)
+        action = getKeyBinding(event.keyCode | event.stateMask);
+    } else {
+        // character key pressed
+        action = getKeyBinding(event.character | event.stateMask);
+        if (action is SWT.NULL) {
+            // see if we have a control character
+            if ((event.stateMask & SWT.CTRL) !is 0 && (event.character >= 0) && event.character <= 31) {
+                // get the character from the CTRL+char sequence, the control
+                // key subtracts 64 from the value of the key that it modifies
+                int c = event.character + 64;
+                action = getKeyBinding(c | event.stateMask);
+            }
+        }
+    }
+    if (action is SWT.NULL) {
+        bool ignore = false;
+
+        if (IS_CARBON) {
+            // Ignore accelerator key combinations (we do not want to
+            // insert a character in the text in this instance). Do not
+            // ignore COMMAND+ALT combinations since that key sequence
+            // produces characters on the mac.
+            ignore = (event.stateMask ^ SWT.COMMAND) is 0 ||
+                    (event.stateMask ^ (SWT.COMMAND | SWT.SHIFT)) is 0;
+        } else if (IS_MOTIF) {
+            // Ignore accelerator key combinations (we do not want to
+            // insert a character in the text in this instance). Do not
+            // ignore ALT combinations since this key sequence
+            // produces characters on motif.
+            ignore = (event.stateMask ^ SWT.CTRL) is 0 ||
+                    (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) is 0;
+        } else {
+            // Ignore accelerator key combinations (we do not want to
+            // insert a character in the text in this instance). Don't
+            // ignore CTRL+ALT combinations since that is the Alt Gr
+            // key on some keyboards.  See bug 20953.
+            ignore = (event.stateMask ^ SWT.ALT) is 0 ||
+                    (event.stateMask ^ SWT.CTRL) is 0 ||
+                    (event.stateMask ^ (SWT.ALT | SWT.SHIFT)) is 0 ||
+                    (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) is 0;
+        }
+        // -ignore anything below SPACE except for line delimiter keys and tab.
+        // -ignore DEL
+        if (!ignore && event.character > 31 && event.character !is SWT.DEL ||
+            event.character is SWT.CR || event.character is SWT.LF ||
+            event.character is TAB) {
+            doContent(event.character);
+            update();
+        }
+    } else {
+        invokeAction(action);
+    }
+}
+/**
+ * If a VerifyKey listener exists, verify that the key that was entered
+ * should be processed.
+ *
+ * @param event keyboard event
+ */
+void handleKeyDown(Event event) {
+    if (clipboardSelection is null) {
+        clipboardSelection = new Point(selection.x, selection.y);
+    }
+
+    Event verifyEvent = new Event();
+    verifyEvent.character = event.character;
+    verifyEvent.keyCode = event.keyCode;
+    verifyEvent.stateMask = event.stateMask;
+    verifyEvent.doit = true;
+    notifyListeners(VerifyKey, verifyEvent);
+    if (verifyEvent.doit) {
+        handleKey(event);
+    }
+}
+/**
+ * Update the Selection Clipboard.
+ *
+ * @param event keyboard event
+ */
+void handleKeyUp(Event event) {
+    if (clipboardSelection !is null) {
+        if (clipboardSelection.x !is selection.x || clipboardSelection.y !is selection.y) {
+            try {
+                if (selection.y - selection.x > 0) {
+                    setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
+                }
+            } catch (SWTError error) {
+                // Copy to clipboard failed. This happens when another application
+                // is accessing the clipboard while we copy. Ignore the error.
+                // Fixes 1GDQAVN
+                // Rethrow all other errors. Fixes bug 17578.
+                if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
+                    throw error;
+                }
+            }
+        }
+    }
+    clipboardSelection = null;
+}
+/**
+ * Updates the caret location and selection if mouse button 1 has been
+ * pressed.
+ */
+void handleMouseDown(Event event) {
+    //force focus (object support)
+    forceFocus();
+
+    //drag detect
+    if (dragDetect_ && checkDragDetect(event)) return;
+
+    //paste clipboard selection
+    if (event.button is 2) {
+        auto o = cast(ArrayWrapperString)getClipboardContent(DND.SELECTION_CLIPBOARD);
+        String text = o.array;
+        if (text !is null && text.length > 0) {
+            // position cursor
+            doMouseLocationChange(event.x, event.y, false);
+            // insert text
+            Event e = new Event();
+            e.start = selection.x;
+            e.end = selection.y;
+            e.text = getModelDelimitedText(text);
+            sendKeyEvent(e);
+        }
+    }
+
+    //set selection
+    if ((event.button !is 1) || (IS_CARBON && (event.stateMask & SWT.MOD4) !is 0)) {
+        return;
+    }
+    clickCount = event.count;
+    if (clickCount is 1) {
+        bool select = (event.stateMask & SWT.MOD2) !is 0;
+        doMouseLocationChange(event.x, event.y, select);
+    } else {
+        if (doubleClickEnabled) {
+            clearSelection(false);
+            int offset = getOffsetAtPoint(event.x, event.y);
+            int lineIndex = content.getLineAtOffset(offset);
+            int lineOffset = content.getOffsetAtLine(lineIndex);
+            int lineEnd = content.getCharCount();
+            if (lineIndex + 1 < content.getLineCount()) {
+                lineEnd = content.getOffsetAtLine(lineIndex + 1);
+            }
+            int start, end;
+            if ((clickCount & 1) is 0) {
+                start = Math.max(0, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
+                end = Math.min(content.getCharCount(), getWordNext(start, SWT.MOVEMENT_WORD_END));
+            } else {
+                start = lineOffset;
+                end = lineEnd;
+            }
+            caretOffset = start;
+            resetSelection();
+            caretOffset = end;
+            showCaret();
+            doMouseSelection();
+            doubleClickSelection = new Point(selection.x, selection.y);
+        }
+    }
+}
+/**
+ * Updates the caret location and selection if mouse button 1 is pressed
+ * during the mouse move.
+ */
+void handleMouseMove(Event event) {
+    if (clickCount is 0) return;
+    doMouseLocationChange(event.x, event.y, true);
+    update();
+    doAutoScroll(event);
+}
+/**
+ * Autoscrolling ends when the mouse button is released.
+ */
+void handleMouseUp(Event event) {
+    clickCount = 0;
+    endAutoScroll();
+    if (event.button is 1) {
+        try {
+            if (selection.y - selection.x > 0) {
+                setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
+            }
+        } catch (SWTError error) {
+            // Copy to clipboard failed. This happens when another application
+            // is accessing the clipboard while we copy. Ignore the error.
+            // Fixes 1GDQAVN
+            // Rethrow all other errors. Fixes bug 17578.
+            if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
+                throw error;
+            }
+        }
+    }
+}
+/**
+ * Renders the invalidated area specified in the paint event.
+ *
+ * @param event paint event
+ */
+void handlePaint(Event event) {
+    if (event.width is 0 || event.height is 0) return;
+    if (clientAreaWidth is 0 || clientAreaHeight is 0) return;
+
+    int startLine = getLineIndex(event.y);
+    int y = getLinePixel(startLine);
+    int endY = event.y + event.height;
+    GC gc = event.gc;
+    Color background = getBackground();
+    Color foreground = getForeground();
+    if (endY > 0) {
+        int lineCount = isSingleLine() ? 1 : content.getLineCount();
+        int x = leftMargin - horizontalScrollOffset;
+        for (int i = startLine; y < endY && i < lineCount; i++) {
+            y += renderer.drawLine(i, x, y, gc, background, foreground);
+        }
+        if (y < endY) {
+            gc.setBackground(background);
+            drawBackground(gc, 0, y, clientAreaWidth, endY - y);
+        }
+    }
+    // fill the margin background
+    gc.setBackground(background);
+    if (topMargin > 0) {
+        drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
+    }
+    if (bottomMargin > 0) {
+        drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
+    }
+    if (leftMargin > 0) {
+        drawBackground(gc, 0, 0, leftMargin, clientAreaHeight);
+    }
+    if (rightMargin > 0) {
+        drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
+    }
+}
+/**
+ * Recalculates the scroll bars. Rewraps all lines when in word
+ * wrap mode.
+ *
+ * @param event resize event
+ */
+void handleResize(Event event) {
+    int oldHeight = clientAreaHeight;
+    int oldWidth = clientAreaWidth;
+    Rectangle clientArea = getClientArea();
+    clientAreaHeight = clientArea.height;
+    clientAreaWidth = clientArea.width;
+    /* Redraw the old or new right/bottom margin if needed */
+    if (oldWidth !is clientAreaWidth) {
+        if (rightMargin > 0) {
+            int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
+            super.redraw(x, 0, rightMargin, oldHeight, false);
+        }
+    }
+    if (oldHeight !is clientAreaHeight) {
+        if (bottomMargin > 0) {
+            int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
+            super.redraw(0, y, oldWidth, bottomMargin, false);
+        }
+    }
+    if (wordWrap) {
+        if (oldWidth !is clientAreaWidth) {
+            renderer.reset(0, content.getLineCount());
+            verticalScrollOffset = -1;
+            renderer.calculateIdle();
+            super.redraw();
+        }
+        if (oldHeight !is clientAreaHeight) {
+            if (oldHeight is 0) topIndexY = 0;
+            setScrollBars(true);
+        }
+        setCaretLocation();
+    } else  {
+        renderer.calculateClientArea();
+        setScrollBars(true);
+        claimRightFreeSpace();
+        // StyledText allows any value for horizontalScrollOffset when clientArea is zero
+        // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
+        if (clientAreaWidth !is 0) {
+            ScrollBar horizontalBar = getHorizontalBar();
+            if (horizontalBar !is null && horizontalBar.getVisible()) {
+                if (horizontalScrollOffset !is horizontalBar.getSelection()) {
+                    horizontalBar.setSelection(horizontalScrollOffset);
+                    horizontalScrollOffset = horizontalBar.getSelection();
+                }
+            }
+        }
+    }
+    claimBottomFreeSpace();
+    //TODO FIX TOP INDEX DURING RESIZE
+//  if (oldHeight !is clientAreaHeight || wordWrap) {
+//      calculateTopIndex(0);
+//  }
+}
+/**
+ * Updates the caret position and selection and the scroll bars to reflect
+ * the content change.
+ */
+void handleTextChanged(TextChangedEvent event) {
+    int offset = ime.getCompositionOffset();
+    if (offset !is -1 && lastTextChangeStart < offset) {
+        ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount);
+    }
+    int firstLine = content.getLineAtOffset(lastTextChangeStart);
+    resetCache(firstLine, 0);
+    if (!isFixedLineHeight() && topIndex > firstLine) {
+        topIndex = firstLine;
+        topIndexY = 0;
+        super.redraw();
+    } else {
+        int lastLine = firstLine + lastTextChangeNewLineCount;
+        int firstLineTop = getLinePixel(firstLine);
+        int newLastLineBottom = getLinePixel(lastLine + 1);
+        if (lastLineBottom !is newLastLineBottom) {
+            super.redraw();
+        } else {
+            super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
+            redrawLinesBullet(renderer.redrawLines);
+        }
+    }
+    renderer.redrawLines = null;
+    // update selection/caret location after styles have been changed.
+    // otherwise any text measuring could be incorrect
+    //
+    // also, this needs to be done after all scrolling. Otherwise,
+    // selection redraw would be flushed during scroll which is wrong.
+    // in some cases new text would be drawn in scroll source area even
+    // though the intent is to scroll it.
+    updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
+    if (lastTextChangeReplaceLineCount > 0 || wordWrap) {
+        claimBottomFreeSpace();
+    }
+    if (lastTextChangeReplaceCharCount > 0) {
+        claimRightFreeSpace();
+    }
+}
+/**
+ * Updates the screen to reflect a pending content change.
+ *
+ * @param event .start the start offset of the change
+ * @param event .newText text that is going to be inserted or empty String
+ *  if no text will be inserted
+ * @param event .replaceCharCount length of text that is going to be replaced
+ * @param event .newCharCount length of text that is going to be inserted
+ * @param event .replaceLineCount number of lines that are going to be replaced
+ * @param event .newLineCount number of new lines that are going to be inserted
+ */
+void handleTextChanging(TextChangingEvent event) {
+    if (event.replaceCharCount < 0) {
+        event.start += event.replaceCharCount;
+        event.replaceCharCount *= -1;
+    }
+    lastTextChangeStart = event.start;
+    lastTextChangeNewLineCount = event.newLineCount;
+    lastTextChangeNewCharCount = event.newCharCount;
+    lastTextChangeReplaceLineCount = event.replaceLineCount;
+    lastTextChangeReplaceCharCount = event.replaceCharCount;
+    int lineIndex = content.getLineAtOffset(event.start);
+    int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
+    int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
+    lastLineBottom = destY;
+    if (srcY < 0 && destY < 0) {
+        lastLineBottom += srcY - destY;
+        verticalScrollOffset += destY - srcY;
+        calculateTopIndex(destY - srcY);
+        setScrollBars(true);
+    } else {
+        scrollText(srcY, destY);
+    }
+
+    renderer.textChanging(event);
+
+    // Update the caret offset if it is greater than the length of the content.
+    // This is necessary since style range API may be called between the
+    // handleTextChanging and handleTextChanged events and this API sets the
+    // caretOffset.
+    int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
+    if (caretOffset > newEndOfText) caretOffset = newEndOfText;
+}
+/**
+ * Called when the widget content is set programmatically, overwriting
+ * the old content. Resets the caret position, selection and scroll offsets.
+ * Recalculates the content width and scroll bars. Redraws the widget.
+ *
+ * @param event text change event.
+ */
+void handleTextSet(TextChangedEvent event) {
+    reset();
+}
+/**
+ * Called when a traversal key is pressed.
+ * Allow tab next traversal to occur when the widget is in single
+ * line mode or in multi line and non-editable mode .
+ * When in editable multi line mode we want to prevent the tab
+ * traversal and receive the tab key event instead.
+ *
+ * @param event the event
+ */
+void handleTraverse(Event event) {
+    switch (event.detail) {
+        case SWT.TRAVERSE_ESCAPE:
+        case SWT.TRAVERSE_PAGE_NEXT:
+        case SWT.TRAVERSE_PAGE_PREVIOUS:
+            event.doit = true;
+            break;
+        case SWT.TRAVERSE_RETURN:
+        case SWT.TRAVERSE_TAB_NEXT:
+        case SWT.TRAVERSE_TAB_PREVIOUS:
+            if ((getStyle() & SWT.SINGLE) !is 0) {
+                event.doit = true;
+            } else {
+                if (!editable || (event.stateMask & SWT.MODIFIER_MASK) !is 0) {
+                    event.doit = true;
+                }
+            }
+            break;
+        default:
+    }
+}
+/**
+ * Scrolls the widget vertically.
+ */
+void handleVerticalScroll(Event event) {
+    int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
+    scrollVertical(scrollPixel, false);
+}
+/**
+ * Add accessibility support for the widget.
+ */
+void initializeAccessible() {
+    final Accessible accessible = getAccessible();
+    accessible.addAccessibleListener(new class() AccessibleAdapter {
+        public void getName (AccessibleEvent e) {
+            String name = null;
+            Label label = getAssociatedLabel ();
+            if (label !is null) {
+                name = stripMnemonic (label.getText());
+            }
+            e.result = name;
+        }
+        public void getHelp(AccessibleEvent e) {
+            e.result = getToolTipText();
+        }
+        public void getKeyboardShortcut(AccessibleEvent e) {
+            String shortcut = null;
+            Label label = getAssociatedLabel ();
+            if (label !is null) {
+                String text = label.getText ();
+                if (text !is null) {
+                    dchar mnemonic = _findMnemonic (text);
+                    if (mnemonic !is '\0') {
+                        shortcut = "Alt+"~tango.text.convert.Utf.toString( [mnemonic] ); //$NON-NLS-1$
+                    }
+                }
+            }
+            e.result = shortcut;
+        }
+    });
+    accessible.addAccessibleTextListener(new class() AccessibleTextAdapter {
+        public void getCaretOffset(AccessibleTextEvent e) {
+            e.offset = this.outer.getCaretOffset();
+        }
+        public void getSelectionRange(AccessibleTextEvent e) {
+            Point selection = this.outer.getSelectionRange();
+            e.offset = selection.x;
+            e.length = selection.y;
+        }
+    });
+    accessible.addAccessibleControlListener(new class() AccessibleControlAdapter {
+        public void getRole(AccessibleControlEvent e) {
+            e.detail = ACC.ROLE_TEXT;
+        }
+        public void getState(AccessibleControlEvent e) {
+            int state = 0;
+            if (isEnabled()) state |= ACC.STATE_FOCUSABLE;
+            if (isFocusControl()) state |= ACC.STATE_FOCUSED;
+            if (!isVisible()) state |= ACC.STATE_INVISIBLE;
+            if (!getEditable()) state |= ACC.STATE_READONLY;
+            e.detail = state;
+        }
+        public void getValue(AccessibleControlEvent e) {
+            e.result = this.outer.getText();
+        }
+    });
+    addListener(SWT.FocusIn, new class(accessible) Listener {
+        Accessible acc;
+        this( Accessible acc ){ this.acc = acc; }
+        public void handleEvent(Event event) {
+            acc.setFocus(ACC.CHILDID_SELF);
+        }
+    });
+}
+/*
+ * Return the Label immediately preceding the receiver in the z-order,
+ * or null if none.
+ */
+Label getAssociatedLabel () {
+    Control[] siblings = getParent ().getChildren ();
+    for (int i = 0; i < siblings.length; i++) {
+        if (siblings [i] is this) {
+            if (i > 0 && ( null !is cast(Label)siblings [i-1])) {
+                return cast(Label) siblings [i-1];
+            }
+        }
+    }
+    return null;
+}
+String stripMnemonic (String string) {
+    int index = 0;
+    int length_ = string.length;
+    do {
+        while ((index < length_) && (string[index] !is '&')) index++;
+        if (++index >= length_) return string;
+        if (string[index] !is '&') {
+            return string.substring(0, index-1) ~ string.substring(index, length_);
+        }
+        index++;
+    } while (index < length_);
+    return string;
+}
+/*
+ * Return the lowercase of the first non-'&' character following
+ * an '&' character in the given string. If there are no '&'
+ * characters in the given string, return '\0'.
+ */
+dchar _findMnemonic (String string) {
+    if (string is null) return '\0';
+    int index = 0;
+    int length_ = string.length;
+    do {
+        while (index < length_ && string[index] !is '&') index++;
+        if (++index >= length_) return '\0';
+        if (string[index] !is '&') return CharacterFirstToLower(string[index .. $ ] );
+        index++;
+    } while (index < length_);
+    return '\0';
+}
+/**
+ * Executes the action.
+ *
+ * @param action one of the actions defined in ST.java
+ */
+public void invokeAction(int action) {
+    checkWidget();
+    updateCaretDirection = true;
+    switch (action) {
+        // Navigation
+        case ST.LINE_UP:
+            doLineUp(false);
+            clearSelection(true);
+            break;
+        case ST.LINE_DOWN:
+            doLineDown(false);
+            clearSelection(true);
+            break;
+        case ST.LINE_START:
+            doLineStart();
+            clearSelection(true);
+            break;
+        case ST.LINE_END:
+            doLineEnd();
+            clearSelection(true);
+            break;
+        case ST.COLUMN_PREVIOUS:
+            doCursorPrevious();
+            clearSelection(true);
+            break;
+        case ST.COLUMN_NEXT:
+            doCursorNext();
+            clearSelection(true);
+            break;
+        case ST.PAGE_UP:
+            doPageUp(false, -1);
+            clearSelection(true);
+            break;
+        case ST.PAGE_DOWN:
+            doPageDown(false, -1);
+            clearSelection(true);
+            break;
+        case ST.WORD_PREVIOUS:
+            doWordPrevious();
+            clearSelection(true);
+            break;
+        case ST.WORD_NEXT:
+            doWordNext();
+            clearSelection(true);
+            break;
+        case ST.TEXT_START:
+            doContentStart();
+            clearSelection(true);
+            break;
+        case ST.TEXT_END:
+            doContentEnd();
+            clearSelection(true);
+            break;
+        case ST.WINDOW_START:
+            doPageStart();
+            clearSelection(true);
+            break;
+        case ST.WINDOW_END:
+            doPageEnd();
+            clearSelection(true);
+            break;
+        // Selection
+        case ST.SELECT_LINE_UP:
+            doSelectionLineUp();
+            break;
+        case ST.SELECT_ALL:
+            selectAll();
+            break;
+        case ST.SELECT_LINE_DOWN:
+            doSelectionLineDown();
+            break;
+        case ST.SELECT_LINE_START:
+            doLineStart();
+            doSelection(ST.COLUMN_PREVIOUS);
+            break;
+        case ST.SELECT_LINE_END:
+            doLineEnd();
+            doSelection(ST.COLUMN_NEXT);
+            break;
+        case ST.SELECT_COLUMN_PREVIOUS:
+            doSelectionCursorPrevious();
+            doSelection(ST.COLUMN_PREVIOUS);
+            break;
+        case ST.SELECT_COLUMN_NEXT:
+            doSelectionCursorNext();
+            doSelection(ST.COLUMN_NEXT);
+            break;
+        case ST.SELECT_PAGE_UP:
+            doSelectionPageUp(-1);
+            break;
+        case ST.SELECT_PAGE_DOWN:
+            doSelectionPageDown(-1);
+            break;
+        case ST.SELECT_WORD_PREVIOUS:
+            doSelectionWordPrevious();
+            doSelection(ST.COLUMN_PREVIOUS);
+            break;
+        case ST.SELECT_WORD_NEXT:
+            doSelectionWordNext();
+            doSelection(ST.COLUMN_NEXT);
+            break;
+        case ST.SELECT_TEXT_START:
+            doContentStart();
+            doSelection(ST.COLUMN_PREVIOUS);
+            break;
+        case ST.SELECT_TEXT_END:
+            doContentEnd();
+            doSelection(ST.COLUMN_NEXT);
+            break;
+        case ST.SELECT_WINDOW_START:
+            doPageStart();
+            doSelection(ST.COLUMN_PREVIOUS);
+            break;
+        case ST.SELECT_WINDOW_END:
+            doPageEnd();
+            doSelection(ST.COLUMN_NEXT);
+            break;
+        // Modification
+        case ST.CUT:
+            cut();
+            break;
+        case ST.COPY:
+            copy();
+            break;
+        case ST.PASTE:
+            paste();
+            break;
+        case ST.DELETE_PREVIOUS:
+            doBackspace();
+            break;
+        case ST.DELETE_NEXT:
+            doDelete();
+            break;
+        case ST.DELETE_WORD_PREVIOUS:
+            doDeleteWordPrevious();
+            break;
+        case ST.DELETE_WORD_NEXT:
+            doDeleteWordNext();
+            break;
+        // Miscellaneous
+        case ST.TOGGLE_OVERWRITE:
+            overwrite = !overwrite;     // toggle insert/overwrite mode
+            break;
+        default:
+    }
+}
+/**
+ * Temporary until SWT provides this
+ */
+bool isBidi() {
+    return IS_GTK || IS_CARBON || BidiUtil.isBidiPlatform() || isMirrored_;
+}
+bool isBidiCaret() {
+    return BidiUtil.isBidiPlatform();
+}
+bool isFixedLineHeight() {
+    return fixedLineHeight;
+}
+/**
+ * Returns whether the given offset is inside a multi byte line delimiter.
+ * Example:
+ * "Line1\r\n" isLineDelimiter(5) is false but isLineDelimiter(6) is true
+ *
+ * @return true if the given offset is inside a multi byte line delimiter.
+ * false if the given offset is before or after a line delimiter.
+ */
+bool isLineDelimiter(int offset) {
+    int line = content.getLineAtOffset(offset);
+    int lineOffset = content.getOffsetAtLine(line);
+    int offsetInLine = offset - lineOffset;
+    // offsetInLine will be greater than line length if the line
+    // delimiter is longer than one character and the offset is set
+    // in between parts of the line delimiter.
+    return offsetInLine > content.getLine(line).length;
+}
+/**
+ * Returns whether the widget is mirrored (right oriented/right to left
+ * writing order).
+ *
+ * @return isMirrored true=the widget is right oriented, false=the widget
+ *  is left oriented
+ */
+bool isMirrored() {
+    return isMirrored_;
+}
+/**
+ * Returns whether the widget can have only one line.
+ *
+ * @return true if widget can have only one line, false if widget can have
+ *  multiple lines
+ */
+bool isSingleLine() {
+    return (getStyle() & SWT.SINGLE) !is 0;
+}
+/**
+ * Sends the specified verify event, replace/insert text as defined by
+ * the event and send a modify event.
+ *
+ * @param event the text change event.
+ *  <ul>
+ *  <li>event.start - the replace start offset</li>
+ *  <li>event.end - the replace end offset</li>
+ *  <li>event.text - the new text</li>
+ *  </ul>
+ * @param updateCaret whether or not he caret should be set behind
+ *  the new text
+ */
+void modifyContent(Event event, bool updateCaret) {
+    event.doit = true;
+    notifyListeners(SWT.Verify, event);
+    if (event.doit) {
+        StyledTextEvent styledTextEvent = null;
+        int replacedLength = event.end - event.start;
+        if (isListening(ExtendedModify)) {
+            styledTextEvent = new StyledTextEvent(content);
+            styledTextEvent.start = event.start;
+            styledTextEvent.end = event.start + event.text.length;
+            styledTextEvent.text = content.getTextRange(event.start, replacedLength);
+        }
+        if (updateCaret) {
+            //Fix advancing flag for delete/backspace key on direction boundary
+            if (event.text.length is 0) {
+                int lineIndex = content.getLineAtOffset(event.start);
+                int lineOffset = content.getOffsetAtLine(lineIndex);
+                TextLayout layout = renderer.getTextLayout(lineIndex);
+                int levelStart = layout.getLevel(event.start - lineOffset);
+                int lineIndexEnd = content.getLineAtOffset(event.end);
+                if (lineIndex !is lineIndexEnd) {
+                    renderer.disposeTextLayout(layout);
+                    lineOffset = content.getOffsetAtLine(lineIndexEnd);
+                    layout = renderer.getTextLayout(lineIndexEnd);
+                }
+                int levelEnd = layout.getLevel(event.end - lineOffset);
+                renderer.disposeTextLayout(layout);
+                if (levelStart !is levelEnd) {
+                    caretAlignment = PREVIOUS_OFFSET_TRAILING;
+                } else {
+                    caretAlignment = OFFSET_LEADING;
+                }
+            }
+        }
+        content.replaceTextRange(event.start, replacedLength, event.text);
+        // set the caret position prior to sending the modify event.
+        // fixes 1GBB8NJ
+        if (updateCaret) {
+            // always update the caret location. fixes 1G8FODP
+            setSelection(event.start + event.text.length, 0, true);
+            showCaret();
+        }
+        sendModifyEvent(event);
+        if (isListening(ExtendedModify)) {
+            notifyListeners(ExtendedModify, styledTextEvent);
+        }
+    }
+}
+void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) {
+    if (isListening(PaintObject)) {
+        StyledTextEvent event = new StyledTextEvent (content) ;
+        event.gc = gc;
+        event.x = x;
+        event.y = y;
+        event.ascent = ascent;
+        event.descent = descent;
+        event.style = style;
+        event.bullet = bullet;
+        event.bulletIndex = bulletIndex;
+        notifyListeners(PaintObject, event);
+    }
+}
+/**
+ * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
+ * clipboard  or, if there is no selection,  inserts the text at the current
+ * caret offset.   If the widget has the SWT.SINGLE style and the
+ * clipboard text contains more than one line, only the first line without
+ * line delimiters is  inserted in the widget.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void paste(){
+    checkWidget();
+    String text = null;
+    if( auto o = cast(ArrayWrapperString) getClipboardContent(DND.CLIPBOARD)){
+        text = o.array;
+    }
+    if (text !is null && text.length > 0) {
+        Event event = new Event();
+        event.start = selection.x;
+        event.end = selection.y;
+        event.text = getModelDelimitedText(text);
+        sendKeyEvent(event);
+    }
+}
+/**
+ * Prints the widget's text to the default printer.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void print() {
+    checkWidget();
+    Printer printer = new Printer();
+    StyledTextPrintOptions options = new StyledTextPrintOptions();
+    options.printTextForeground = true;
+    options.printTextBackground = true;
+    options.printTextFontStyle = true;
+    options.printLineBackground = true;
+    (new Printing(this, printer, options)).run();
+    printer.dispose();
+}
+/**
+ * Returns a runnable that will print the widget's text
+ * to the specified printer.
+ * <p>
+ * The runnable may be run in a non-UI thread.
+ * </p>
+ *
+ * @param printer the printer to print to
+ *
+ * @return a <code>Runnable</code> for printing the receiver's text
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when printer is null</li>
+ * </ul>
+ */
+public Runnable print(Printer printer) {
+    checkWidget();
+    if (printer is null) {
+        SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    }
+    StyledTextPrintOptions options = new StyledTextPrintOptions();
+    options.printTextForeground = true;
+    options.printTextBackground = true;
+    options.printTextFontStyle = true;
+    options.printLineBackground = true;
+    return print(printer, options);
+}
+/**
+ * Returns a runnable that will print the widget's text
+ * to the specified printer.
+ * <p>
+ * The runnable may be run in a non-UI thread.
+ * </p>
+ *
+ * @param printer the printer to print to
+ * @param options print options to use during printing
+ *
+ * @return a <code>Runnable</code> for printing the receiver's text
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
+ * </ul>
+ * @since 2.1
+ */
+public Runnable print(Printer printer, StyledTextPrintOptions options) {
+    checkWidget();
+    if (printer is null || options is null) {
+        SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    }
+    return new Printing(this, printer, options);
+}
+/**
+ * Causes the entire bounds of the receiver to be marked
+ * as needing to be redrawn. The next time a paint request
+ * is processed, the control will be completely painted.
+ * <p>
+ * Recalculates the content width for all lines in the bounds.
+ * When a <code>LineStyleListener</code> is used a redraw call
+ * is the only notification to the widget that styles have changed
+ * and that the content width may have changed.
+ * </p>
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see Control#update()
+ */
+public override void redraw() {
+    super.redraw();
+    int itemCount = getPartialBottomIndex() - topIndex + 1;
+    renderer.reset(topIndex, itemCount);
+    renderer.calculate(topIndex, itemCount);
+    setScrollBars(false);
+}
+/**
+ * Causes the rectangular area of the receiver specified by
+ * the arguments to be marked as needing to be redrawn.
+ * The next time a paint request is processed, that area of
+ * the receiver will be painted. If the <code>all</code> flag
+ * is <code>true</code>, any children of the receiver which
+ * intersect with the specified area will also paint their
+ * intersecting areas. If the <code>all</code> flag is
+ * <code>false</code>, the children will not be painted.
+ * <p>
+ * Marks the content width of all lines in the specified rectangle
+ * as unknown. Recalculates the content width of all visible lines.
+ * When a <code>LineStyleListener</code> is used a redraw call
+ * is the only notification to the widget that styles have changed
+ * and that the content width may have changed.
+ * </p>
+ *
+ * @param x the x coordinate of the area to draw
+ * @param y the y coordinate of the area to draw
+ * @param width the width of the area to draw
+ * @param height the height of the area to draw
+ * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see Control#update()
+ */
+public override void redraw(int x, int y, int width, int height, bool all) {
+    super.redraw(x, y, width, height, all);
+    if (height > 0) {
+        int firstLine = getLineIndex(y);
+        int lastLine = getLineIndex(y + height);
+        resetCache(firstLine, lastLine - firstLine + 1);
+    }
+}
+void redrawLines(int startLine, int lineCount) {
+    // do nothing if redraw range is completely invisible
+    int partialBottomIndex = getPartialBottomIndex();
+    if (startLine > partialBottomIndex || startLine + lineCount - 1 < topIndex) {
+        return;
+    }
+    // only redraw visible lines
+    if (startLine < topIndex) {
+        lineCount -= topIndex - startLine;
+        startLine = topIndex;
+    }
+    if (startLine + lineCount - 1 > partialBottomIndex) {
+        lineCount = partialBottomIndex - startLine + 1;
+    }
+    startLine -= topIndex;
+    int redrawTop = getLinePixel(startLine);
+    int redrawBottom = getLinePixel(startLine + lineCount);
+    int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
+    super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
+}
+void redrawLinesBullet (int[] redrawLines) {
+    if (redrawLines is null) return;
+    int topIndex = getPartialTopIndex();
+    int bottomIndex = getPartialBottomIndex();
+    for (int i = 0; i < redrawLines.length; i++) {
+        int lineIndex = redrawLines[i];
+        if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue;
+        int width = -1;
+        Bullet bullet = renderer.getLineBullet(lineIndex, null);
+        if (bullet !is null) {
+            StyleRange style = bullet.style;
+            GlyphMetrics metrics = style.metrics;
+            width = metrics.width;
+        }
+        if (width is -1) width = getClientArea().width;
+        int height = renderer.getLineHeight(lineIndex);
+        int y = getLinePixel(lineIndex);
+        super.redraw(0, y, width, height, false);
+    }
+}
+/**
+ * Redraws the specified text range.
+ *
+ * @param start offset of the first character to redraw
+ * @param length number of characters to redraw
+ * @param clearBackground true if the background should be cleared as
+ *  part of the redraw operation.  If true, the entire redraw range will
+ *  be cleared before anything is redrawn.  If the redraw range includes
+ *  the last character of a line (i.e., the entire line is redrawn) the
+ *  line is cleared all the way to the right border of the widget.
+ *  The redraw operation will be faster and smoother if clearBackground
+ *  is set to false.  Whether or not the flag can be set to false depends
+ *  on the type of change that has taken place.  If font styles or
+ *  background colors for the redraw range have changed, clearBackground
+ *  should be set to true.  If only foreground colors have changed for
+ *  the redraw range, clearBackground can be set to false.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
+ * </ul>
+ */
+public void redrawRange(int start, int length, bool clearBackground) {
+    checkWidget();
+    int end = start + length;
+    int contentLength = content.getCharCount();
+    if (start > end || start < 0 || end > contentLength) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    int firstLine = content.getLineAtOffset(start);
+    int lastLine = content.getLineAtOffset(end);
+    resetCache(firstLine, lastLine - firstLine + 1);
+    internalRedrawRange(start, length);
+}
+/**
+ * Removes the specified bidirectional segment listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ *
+ * @since 2.0
+ */
+public void removeBidiSegmentListener(BidiSegmentListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(LineGetSegments, listener);
+}
+/**
+ * Removes the specified extended modify listener.
+ *
+ * @param extendedModifyListener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
+    checkWidget();
+    if (extendedModifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(ExtendedModify, extendedModifyListener);
+}
+/**
+ * Removes the specified line background listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeLineBackgroundListener(LineBackgroundListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(LineGetBackground, listener);
+}
+/**
+ * Removes the specified line style listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeLineStyleListener(LineStyleListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(LineGetStyle, listener);
+}
+/**
+ * Removes the specified modify listener.
+ *
+ * @param modifyListener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeModifyListener(ModifyListener modifyListener) {
+    checkWidget();
+    if (modifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(SWT.Modify, modifyListener);
+}
+/**
+ * Removes the specified listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ * @since 3.2
+ */
+public void removePaintObjectListener(PaintObjectListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(PaintObject, listener);
+}
+/**
+ * Removes the listener from the collection of listeners who will
+ * be notified when the user changes the receiver's selection.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SelectionListener
+ * @see #addSelectionListener
+ */
+public void removeSelectionListener(SelectionListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(SWT.Selection, listener);
+}
+/**
+ * Removes the specified verify listener.
+ *
+ * @param verifyListener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeVerifyListener(VerifyListener verifyListener) {
+    checkWidget();
+    if (verifyListener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(SWT.Verify, verifyListener);
+}
+/**
+ * Removes the specified key verify listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeVerifyKeyListener(VerifyKeyListener listener) {
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(VerifyKey, listener);
+}
+/**
+ * Removes the specified word movement listener.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ *
+ * @see MovementEvent
+ * @see MovementListener
+ * @see #addWordMovementListener
+ *
+ * @since 3.3
+ */
+
+public void removeWordMovementListener(MovementListener listener) {
+    checkWidget();
+    if (listener is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    removeListener(WordNext, listener);
+    removeListener(WordPrevious, listener);
+}
+/**
+ * Replaces the styles in the given range with new styles.  This method
+ * effectively deletes the styles in the given range and then adds the
+ * the new styles.
+ * <p>
+ * Note: Because a StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
+ * can be used to share styles and reduce memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param start offset of first character where styles will be deleted
+ * @param length length of the range to delete styles in
+ * @param ranges StyleRange objects containing the new style information.
+ * The ranges should not overlap and should be within the specified start
+ * and length. The style rendering is undefined if the ranges do overlap
+ * or are ill-defined. Must not be null.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
+ * </ul>
+ *
+ * @since 2.0
+ *
+ * @see #setStyleRanges(int, int, int[], StyleRange[])
+ */
+public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    // SWT extension: allow null for zero length string
+    //if (ranges is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    setStyleRanges(start, length, null, ranges, false);
+}
+/**
+ * Replaces the given text range with new text.
+ * If the widget has the SWT.SINGLE style and "text" contains more than
+ * one line, only the first line is rendered but the text is stored
+ * unchanged. A subsequent call to getText will return the same text
+ * that was set. Note that only a single line of text should be set when
+ * the SWT.SINGLE style is used.
+ * <p>
+ * <b>NOTE:</b> During the replace operation the current selection is
+ * changed as follows:
+ * <ul>
+ * <li>selection before replaced text: selection unchanged
+ * <li>selection after replaced text: adjust the selection so that same text
+ * remains selected
+ * <li>selection intersects replaced text: selection is cleared and caret
+ * is placed after inserted text
+ * </ul>
+ * </p>
+ *
+ * @param start offset of first character to replace
+ * @param length number of characters to replace. Use 0 to insert text
+ * @param text new text. May be empty to delete text.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
+ *   <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
+ *      Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li>
+ * </ul>
+ */
+public void replaceTextRange(int start, int length, String text) {
+    checkWidget();
+    // SWT extension: allow null for zero length string
+//     if (text is null) {
+//         SWT.error(SWT.ERROR_NULL_ARGUMENT);
+//     }
+    int contentLength = getCharCount();
+    int end = start + length;
+    if (start > end || start < 0 || end > contentLength) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    Event event = new Event();
+    event.start = start;
+    event.end = end;
+    event.text = text;
+    modifyContent(event, false);
+}
+/**
+ * Resets the caret position, selection and scroll offsets. Recalculate
+ * the content width and scroll bars. Redraw the widget.
+ */
+void reset() {
+    ScrollBar verticalBar = getVerticalBar();
+    ScrollBar horizontalBar = getHorizontalBar();
+    caretOffset = 0;
+    topIndex = 0;
+    topIndexY = 0;
+    verticalScrollOffset = 0;
+    horizontalScrollOffset = 0;
+    resetSelection();
+    renderer.setContent(content);
+    if (verticalBar !is null) {
+        verticalBar.setSelection(0);
+    }
+    if (horizontalBar !is null) {
+        horizontalBar.setSelection(0);
+    }
+    resetCache(0, 0);
+    setCaretLocation();
+    super.redraw();
+}
+void resetCache(int firstLine, int count) {
+    int maxLineIndex = renderer.maxWidthLineIndex;
+    renderer.reset(firstLine, count);
+    renderer.calculateClientArea();
+    if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
+        renderer.calculate(maxLineIndex, 1);
+    }
+    setScrollBars(true);
+    if (!isFixedLineHeight()) {
+        if (topIndex > firstLine) {
+            verticalScrollOffset = -1;
+        }
+        renderer.calculateIdle();
+    }
+}
+/**
+ * Resets the selection.
+ */
+void resetSelection() {
+    selection.x = selection.y = caretOffset;
+    selectionAnchor = -1;
+}
+
+public override void scroll(int destX, int destY, int x, int y, int width, int height, bool all) {
+    super.scroll(destX, destY, x, y, width, height, false);
+    if (all) {
+        int deltaX = destX - x, deltaY = destY - y;
+        Control[] children = getChildren();
+        for (int i=0; i<children.length; i++) {
+            Control child = children[i];
+            Rectangle rect = child.getBounds();
+            child.setLocation(rect.x + deltaX, rect.y + deltaY);
+        }
+    }
+}
+
+/**
+ * Scrolls the widget horizontally.
+ *
+ * @param pixels number of pixels to scroll, > 0 = scroll left,
+ *  < 0 scroll right
+ * @param adjustScrollBar
+ *  true= the scroll thumb will be moved to reflect the new scroll offset.
+ *  false = the scroll thumb will not be moved
+ * @return
+ *  true=the widget was scrolled
+ *  false=the widget was not scrolled, the given offset is not valid.
+ */
+bool scrollHorizontal(int pixels, bool adjustScrollBar) {
+    if (pixels is 0) {
+        return false;
+    }
+    ScrollBar horizontalBar = getHorizontalBar();
+    if (horizontalBar !is null && adjustScrollBar) {
+        horizontalBar.setSelection(horizontalScrollOffset + pixels);
+    }
+    int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
+    if (pixels > 0) {
+        int sourceX = leftMargin + pixels;
+        int scrollWidth = clientAreaWidth - sourceX - rightMargin;
+        if (scrollWidth > 0) {
+            scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
+        }
+        if (sourceX > scrollWidth) {
+            super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
+        }
+    } else {
+        int destinationX = leftMargin - pixels;
+        int scrollWidth = clientAreaWidth - destinationX - rightMargin;
+        if (scrollWidth > 0) {
+            scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
+        }
+        if (destinationX > scrollWidth) {
+            super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
+        }
+    }
+    horizontalScrollOffset += pixels;
+    setCaretLocation();
+    return true;
+}
+/**
+ * Scrolls the widget vertically.
+ *
+ * @param pixel the new vertical scroll offset
+ * @param adjustScrollBar
+ *  true= the scroll thumb will be moved to reflect the new scroll offset.
+ *  false = the scroll thumb will not be moved
+ * @return
+ *  true=the widget was scrolled
+ *  false=the widget was not scrolled
+ */
+bool scrollVertical(int pixels, bool adjustScrollBar) {
+    if (pixels is 0) {
+        return false;
+    }
+    if (verticalScrollOffset !is -1) {
+        ScrollBar verticalBar = getVerticalBar();
+        if (verticalBar !is null && adjustScrollBar) {
+            verticalBar.setSelection(verticalScrollOffset + pixels);
+        }
+        int scrollWidth = clientAreaWidth - leftMargin - rightMargin;
+        if (pixels > 0) {
+            int sourceY = topMargin + pixels;
+            int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
+            if (scrollHeight > 0) {
+                scroll(leftMargin, topMargin, leftMargin, sourceY, scrollWidth, scrollHeight, true);
+            }
+            if (sourceY > scrollHeight) {
+                int redrawY = Math.max(0, topMargin + scrollHeight);
+                int redrawHeight = Math.min(clientAreaHeight, pixels - scrollHeight);
+                super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true);
+            }
+        } else {
+            int destinationY = topMargin - pixels;
+            int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
+            if (scrollHeight > 0) {
+                scroll(leftMargin, destinationY, leftMargin, topMargin, scrollWidth, scrollHeight, true);
+            }
+            if (destinationY > scrollHeight) {
+                int redrawY = Math.max(0, topMargin + scrollHeight);
+                int redrawHeight = Math.min(clientAreaHeight, -pixels - scrollHeight);
+                super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true);
+            }
+        }
+        verticalScrollOffset += pixels;
+        calculateTopIndex(pixels);
+    } else {
+        calculateTopIndex(pixels);
+        super.redraw();
+    }
+    setCaretLocation();
+    return true;
+}
+void scrollText(int srcY, int destY) {
+    if (srcY is destY) return;
+    int deltaY = destY - srcY;
+    int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
+    if (deltaY > 0) {
+        scrollHeight = clientAreaHeight - srcY - bottomMargin;
+    } else {
+        scrollHeight = clientAreaHeight - destY - bottomMargin;
+    }
+    scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
+    if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
+        super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
+    }
+    if ((0 < destY + scrollHeight) && (topMargin > destY)) {
+        super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
+    }
+    if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
+        super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
+    }
+    if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
+        super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
+    }
+}
+/**
+ * Selects all the text.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void selectAll() {
+    checkWidget();
+    setSelection(0, Math.max(getCharCount(),0));
+}
+/**
+ * Replaces/inserts text as defined by the event.
+ *
+ * @param event the text change event.
+ *  <ul>
+ *  <li>event.start - the replace start offset</li>
+ *  <li>event.end - the replace end offset</li>
+ *  <li>event.text - the new text</li>
+ *  </ul>
+ */
+void sendKeyEvent(Event event) {
+    if (editable) {
+        modifyContent(event, true);
+    }
+}
+/**
+ * Returns a StyledTextEvent that can be used to request data such
+ * as styles and background color for a line.
+ * <p>
+ * The specified line may be a visual (wrapped) line if in word
+ * wrap mode. The returned object will always be for a logical
+ * (unwrapped) line.
+ * </p>
+ *
+ * @param lineOffset offset of the line. This may be the offset of
+ *  a visual line if the widget is in word wrap mode.
+ * @param line line text. This may be the text of a visual line if
+ *  the widget is in word wrap mode.
+ * @return StyledTextEvent that can be used to request line data
+ *  for the given line.
+ */
+StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
+    StyledTextEvent event = null;
+    if (isListening(eventType)) {
+        event = new StyledTextEvent(content);
+        event.detail = lineOffset;
+        event.text = line;
+        event.alignment = alignment;
+        event.indent = indent;
+        event.justify = justify;
+        notifyListeners(eventType, event);
+    }
+    return event;
+}
+void sendModifyEvent(Event event) {
+    Accessible accessible = getAccessible();
+    if (event.text.length is 0) {
+        accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
+    } else {
+        if (event.start is event.end) {
+            accessible.textChanged(ACC.TEXT_INSERT, event.start, event.text.length);
+        } else {
+            accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
+            accessible.textChanged(ACC.TEXT_INSERT, event.start, event.text.length);
+        }
+    }
+    notifyListeners(SWT.Modify, event);
+}
+/**
+ * Sends the specified selection event.
+ */
+void sendSelectionEvent() {
+    getAccessible().textSelectionChanged();
+    Event event = new Event();
+    event.x = selection.x;
+    event.y = selection.y;
+    notifyListeners(SWT.Selection, event);
+}
+int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) {
+    if (isListening(eventType)) {
+        StyledTextEvent event = new StyledTextEvent(content);
+        event.detail = lineOffset;
+        event.text = lineText;
+        event.count = movement;
+        event.start = offset;
+        event.end = newOffset;
+        notifyListeners(eventType, event);
+        offset = event.end;
+        if (offset !is newOffset) {
+            int length = getCharCount();
+            if (offset < 0) {
+                offset = 0;
+            } else if (offset > length) {
+                offset = length;
+            } else {
+                if (isLineDelimiter(offset)) {
+                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+                }
+            }
+        }
+        return offset;
+    }
+    return newOffset;
+}
+/**
+ * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>,
+ * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines.
+ * </p><p>
+ * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
+ * in order to stabilize the right edge before setting alignment.
+ * </p>
+ *
+ * @param alignment the new alignment
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #setLineAlignment(int, int, int)
+ *
+ * @since 3.2
+ */
+public void setAlignment(int alignment) {
+    checkWidget();
+    alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
+    if (alignment is 0 || this.alignment is alignment) return;
+    this.alignment = alignment;
+    resetCache(0, content.getLineCount());
+    setCaretLocation();
+    super.redraw();
+}
+/**
+ * @see Control#setBackground(Color)
+ */
+public override void setBackground(Color color) {
+    checkWidget();
+    background = color;
+    super.setBackground(color);
+    super.redraw();
+}
+/**
+ * Sets the receiver's caret.  Set the caret's height and location.
+ *
+ * </p>
+ * @param caret the new caret for the receiver
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public override void setCaret(Caret caret) {
+    checkWidget ();
+    super.setCaret(caret);
+    caretDirection = SWT.NULL;
+    if (caret !is null) {
+        setCaretLocation();
+    }
+}
+/**
+ * Sets the BIDI coloring mode.  When true the BIDI text display
+ * algorithm is applied to segments of text that are the same
+ * color.
+ *
+ * @param mode the new coloring mode
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @deprecated use BidiSegmentListener instead.
+ */
+public void setBidiColoring(bool mode) {
+    checkWidget();
+    bidiColoring = mode;
+}
+/**
+ * Moves the Caret to the current caret offset.
+ */
+void setCaretLocation() {
+    Point newCaretPos = getPointAtOffset(caretOffset);
+    setCaretLocation(newCaretPos, getCaretDirection());
+}
+void setCaretLocation(Point location, int direction) {
+    Caret caret = getCaret();
+    if (caret !is null) {
+        bool isDefaultCaret = caret is defaultCaret;
+        int lineHeight = renderer.getLineHeight();
+        int caretHeight = lineHeight;
+        if (!isFixedLineHeight() && isDefaultCaret) {
+            caretHeight = getBoundsAtOffset(caretOffset).height;
+            if (caretHeight !is lineHeight) {
+                direction = SWT.DEFAULT;
+            }
+        }
+        int imageDirection = direction;
+        if (isMirrored()) {
+            if (imageDirection is SWT.LEFT) {
+                imageDirection = SWT.RIGHT;
+            } else if (imageDirection is SWT.RIGHT) {
+                imageDirection = SWT.LEFT;
+            }
+        }
+        if (isDefaultCaret && imageDirection is SWT.RIGHT) {
+            location.x -= (caret.getSize().x - 1);
+        }
+        if (isDefaultCaret) {
+            caret.setBounds(location.x, location.y, caretWidth, caretHeight);
+        } else {
+            caret.setLocation(location);
+        }
+        getAccessible().textCaretMoved(getCaretOffset());
+        if (direction !is caretDirection) {
+            caretDirection = direction;
+            if (isDefaultCaret) {
+                if (imageDirection is SWT.DEFAULT) {
+                    defaultCaret.setImage(null);
+                } else if (imageDirection is SWT.LEFT) {
+                    defaultCaret.setImage(leftCaretBitmap);
+                } else if (imageDirection is SWT.RIGHT) {
+                    defaultCaret.setImage(rightCaretBitmap);
+                }
+            }
+            if (caretDirection is SWT.LEFT) {
+                BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
+            } else if (caretDirection is SWT.RIGHT) {
+                BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
+            }
+        }
+    }
+    columnX = location.x;
+}
+/**
+ * Sets the caret offset.
+ *
+ * @param offset caret offset, relative to the first character in the text.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setCaretOffset(int offset) {
+    checkWidget();
+    int length = getCharCount();
+    if (length > 0 && offset !is caretOffset) {
+        if (offset < 0) {
+            caretOffset = 0;
+        } else if (offset > length) {
+            caretOffset = length;
+        } else {
+            if (isLineDelimiter(offset)) {
+                // offset is inside a multi byte line delimiter. This is an
+                // illegal operation and an exception is thrown. Fixes 1GDKK3R
+                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+            }
+            caretOffset = offset;
+        }
+        caretAlignment = PREVIOUS_OFFSET_TRAILING;
+        // clear the selection if the caret is moved.
+        // don't notify listeners about the selection change.
+        clearSelection(false);
+    }
+    setCaretLocation();
+}
+/**
+ * Copies the specified text range to the clipboard.  The text will be placed
+ * in the clipboard in plain text format and RTF format.
+ *
+ * @param start start index of the text
+ * @param length length of text to place in clipboard
+ *
+ * @exception SWTError, see Clipboard.setContents
+ * @see org.eclipse.swt.dnd.Clipboard#setContents
+ */
+void setClipboardContent(int start, int length, int clipboardType) {
+    if (clipboardType is DND.SELECTION_CLIPBOARD && !(IS_MOTIF || IS_GTK)) return;
+    TextTransfer plainTextTransfer = TextTransfer.getInstance();
+    TextWriter plainTextWriter = new TextWriter(start, length);
+    String plainText = getPlatformDelimitedText(plainTextWriter);
+    Object[] data;
+    Transfer[] types;
+    if (clipboardType is DND.SELECTION_CLIPBOARD) {
+        data = [ cast(Object) new ArrayWrapperString(plainText) ];
+        types = [plainTextTransfer];
+    } else {
+        RTFTransfer rtfTransfer = RTFTransfer.getInstance();
+        RTFWriter rtfWriter = new RTFWriter(start, length);
+        String rtfText = getPlatformDelimitedText(rtfWriter);
+        data = [ cast(Object) new ArrayWrapperString(rtfText), new ArrayWrapperString(plainText) ];
+        types = [ cast(Transfer)rtfTransfer, plainTextTransfer];
+    }
+    clipboard.setContents(data, types, clipboardType);
+}
+/**
+ * Sets the content implementation to use for text storage.
+ *
+ * @param newContent StyledTextContent implementation to use for text storage.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void setContent(StyledTextContent newContent) {
+    checkWidget();
+    if (newContent is null) {
+        SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    }
+    if (content !is null) {
+        content.removeTextChangeListener(textChangeListener);
+    }
+    content = newContent;
+    content.addTextChangeListener(textChangeListener);
+    reset();
+}
+/**
+ * Sets the receiver's cursor to the cursor specified by the
+ * argument.  Overridden to handle the null case since the
+ * StyledText widget uses an ibeam as its default cursor.
+ *
+ * @see Control#setCursor(Cursor)
+ */
+public override void setCursor (Cursor cursor) {
+    if (cursor is null) {
+        Display display = getDisplay();
+        super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
+    } else {
+        super.setCursor(cursor);
+    }
+}
+/**
+ * Sets whether the widget : double click mouse behavior.
+ * </p>
+ *
+ * @param enable if true double clicking a word selects the word, if false
+ *  double clicks have the same effect as regular mouse clicks.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setDoubleClickEnabled(bool enable) {
+    checkWidget();
+    doubleClickEnabled = enable;
+}
+public override void setDragDetect (bool dragDetect_) {
+    checkWidget ();
+    this.dragDetect_ = dragDetect_;
+}
+/**
+ * Sets whether the widget content can be edited.
+ * </p>
+ *
+ * @param editable if true content can be edited, if false content can not be
+ *  edited
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setEditable(bool editable) {
+    checkWidget();
+    this.editable = editable;
+}
+/**
+ * Sets a new font to render text with.
+ * <p>
+ * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
+ * and the same baseline as regular fonts.
+ * </p>
+ *
+ * @param font new font
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public override void setFont(Font font) {
+    checkWidget();
+    int oldLineHeight = renderer.getLineHeight();
+    super.setFont(font);
+    renderer.setFont(getFont(), tabLength);
+    // keep the same top line visible. fixes 5815
+    if (isFixedLineHeight()) {
+        int lineHeight = renderer.getLineHeight();
+        if (lineHeight !is oldLineHeight) {
+            int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
+            scrollVertical(vscroll, true);
+        }
+    }
+    resetCache(0, content.getLineCount());
+    claimBottomFreeSpace();
+    calculateScrollBars();
+    if (isBidiCaret()) createCaretBitmaps();
+    caretDirection = SWT.NULL;
+    setCaretLocation();
+    super.redraw();
+}
+public override void setForeground(Color color) {
+    checkWidget();
+    foreground = color;
+    super.setForeground(getForeground());
+    super.redraw();
+}
+/**
+ * Sets the horizontal scroll offset relative to the start of the line.
+ * Do nothing if there is no text set.
+ * <p>
+ * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
+ * widget.
+ * </p>
+ *
+ * @param offset horizontal scroll offset relative to the start
+ *  of the line, measured in character increments starting at 0, if
+ *  equal to 0 the content is not scrolled, if > 0 = the content is scrolled.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setHorizontalIndex(int offset) {
+    checkWidget();
+    if (getCharCount() is 0) {
+        return;
+    }
+    if (offset < 0) {
+        offset = 0;
+    }
+    offset *= getHorizontalIncrement();
+    // allow any value if client area width is unknown or 0.
+    // offset will be checked in resize handler.
+    // don't use isVisible since width is known even if widget
+    // is temporarily invisible
+    if (clientAreaWidth > 0) {
+        int width = renderer.getWidth();
+        // prevent scrolling if the content fits in the client area.
+        // align end of longest line with right border of client area
+        // if offset is out of range.
+        if (offset > width - clientAreaWidth) {
+            offset = Math.max(0, width - clientAreaWidth);
+        }
+    }
+    scrollHorizontal(offset - horizontalScrollOffset, true);
+}
+/**
+ * Sets the horizontal pixel offset relative to the start of the line.
+ * Do nothing if there is no text set.
+ * <p>
+ * <b>NOTE:</b> The horizontal pixel offset is reset to 0 when new text
+ * is set in the widget.
+ * </p>
+ *
+ * @param pixel horizontal pixel offset relative to the start
+ *  of the line.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.0
+ */
+public void setHorizontalPixel(int pixel) {
+    checkWidget();
+    if (getCharCount() is 0) {
+        return;
+    }
+    if (pixel < 0) {
+        pixel = 0;
+    }
+    // allow any value if client area width is unknown or 0.
+    // offset will be checked in resize handler.
+    // don't use isVisible since width is known even if widget
+    // is temporarily invisible
+    if (clientAreaWidth > 0) {
+        int width = renderer.getWidth();
+        // prevent scrolling if the content fits in the client area.
+        // align end of longest line with right border of client area
+        // if offset is out of range.
+        if (pixel > width - clientAreaWidth) {
+            pixel = Math.max(0, width - clientAreaWidth);
+        }
+    }
+    scrollHorizontal(pixel - horizontalScrollOffset, true);
+}
+/**
+ * Sets the line indentation of the widget.
+ * <p>
+ * It is the amount of blank space, in pixels, at the beginning of each line.
+ * When a line wraps in several lines only the first one is indented.
+ * </p>
+ *
+ * @param indent the new indent
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #setLineIndent(int, int, int)
+ *
+ * @since 3.2
+ */
+public void setIndent(int indent) {
+    checkWidget();
+    if (this.indent is indent || indent < 0) return;
+    this.indent = indent;
+    resetCache(0, content.getLineCount());
+    setCaretLocation();
+    super.redraw();
+}
+/**
+ * Sets whether the widget should justify lines.
+ *
+ * @param justify whether lines should be justified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see #setLineJustify(int, int, bool)
+ *
+ * @since 3.2
+ */
+public void setJustify(bool justify) {
+    checkWidget();
+    if (this.justify is justify) return;
+    this.justify = justify;
+    resetCache(0, content.getLineCount());
+    setCaretLocation();
+    super.redraw();
+}
+/**
+ * Maps a key to an action.
+ * <p>
+ * One action can be associated with N keys. However, each key can only
+ * have one action (key:action is N:1 relation).
+ * </p>
+ *
+ * @param key a key code defined in SWT.java or a character.
+ *  Optionally ORd with a state mask.  Preferred state masks are one or more of
+ *  SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
+ *  differences.  However, there may be cases where using the specific state masks
+ *  (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
+ * @param action one of the predefined actions defined in ST.java.
+ *  Use SWT.NULL to remove a key binding.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setKeyBinding(int key, int action) {
+    checkWidget();
+    int modifierValue = key & SWT.MODIFIER_MASK;
+    char keyChar = cast(char)(key & SWT.KEY_MASK);
+    if (Compatibility.isLetter(keyChar)) {
+        // make the keybinding case insensitive by adding it
+        // in its upper and lower case form
+        char ch = CharacterToUpper(keyChar);
+        int newKey = ch | modifierValue;
+        if (action is SWT.NULL) {
+            keyActionMap.remove(newKey);
+        } else {
+            keyActionMap[newKey] = action;
+        }
+        ch = CharacterToLower(keyChar);
+        newKey = ch | modifierValue;
+        if (action is SWT.NULL) {
+            keyActionMap.remove(newKey);
+        } else {
+            keyActionMap[newKey] = action;
+        }
+    } else {
+        if (action is SWT.NULL) {
+            keyActionMap.remove(key);
+        } else {
+            keyActionMap[key]=action;
+        }
+    }
+}
+/**
+ * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>,
+ * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>.
+ * <p><p>
+ * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
+ * in order to stabilize the right edge before setting alignment.
+ * </p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ *
+ * @param startLine first line the alignment is applied to, 0 based
+ * @param lineCount number of lines the alignment applies to.
+ * @param alignment line alignment
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setAlignment(int)
+ * @since 3.2
+ */
+public void setLineAlignment(int startLine, int lineCount, int alignment) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+
+    renderer.setLineAlignment(startLine, lineCount, alignment);
+    resetCache(startLine, lineCount);
+    redrawLines(startLine, lineCount);
+    int caretLine = getCaretLine();
+    if (startLine <= caretLine && caretLine < startLine + lineCount) {
+        setCaretLocation();
+    }
+}
+/**
+ * Sets the background color of the specified lines.
+ * <p>
+ * The background color is drawn for the width of the widget. All
+ * line background colors are discarded when setText is called.
+ * The text background color if defined in a StyleRange overlays the
+ * line background color.
+ * </p><p>
+ * Should not be called if a LineBackgroundListener has been set since the
+ * listener maintains the line backgrounds.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the color is applied to, 0 based
+ * @param lineCount number of lines the color applies to.
+ * @param background line background color
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ */
+public void setLineBackground(int startLine, int lineCount, Color background) {
+    checkWidget();
+    if (isListening(LineGetBackground)) return;
+    if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (background !is null) {
+        renderer.setLineBackground(startLine, lineCount, background);
+    } else {
+        renderer.clearLineBackground(startLine, lineCount);
+    }
+    redrawLines(startLine, lineCount);
+}
+/**
+ * Sets the bullet of the specified lines.
+ * <p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the bullet is applied to, 0 based
+ * @param lineCount number of lines the bullet applies to.
+ * @param bullet line bullet
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @since 3.2
+ */
+public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+
+    renderer.setLineBullet(startLine, lineCount, bullet);
+    resetCache(startLine, lineCount);
+    redrawLines(startLine, lineCount);
+    int caretLine = getCaretLine();
+    if (startLine <= caretLine && caretLine < startLine + lineCount) {
+        setCaretLocation();
+    }
+}
+void setVariableLineHeight () {
+    if (!fixedLineHeight) return;
+    fixedLineHeight = false;
+    renderer.calculateIdle();
+}
+/**
+ * Sets the indent of the specified lines.
+ * <p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the indent is applied to, 0 based
+ * @param lineCount number of lines the indent applies to.
+ * @param indent line indent
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setIndent(int)
+ * @since 3.2
+ */
+public void setLineIndent(int startLine, int lineCount, int indent) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+
+    renderer.setLineIndent(startLine, lineCount, indent);
+    resetCache(startLine, lineCount);
+    redrawLines(startLine, lineCount);
+    int caretLine = getCaretLine();
+    if (startLine <= caretLine && caretLine < startLine + lineCount) {
+        setCaretLocation();
+    }
+}
+/**
+ * Sets the justify of the specified lines.
+ * <p>
+ * Should not be called if a LineStyleListener has been set since the listener
+ * maintains the line attributes.
+ * </p><p>
+ * All line attributes are maintained relative to the line text, not the
+ * line index that is specified in this method call.
+ * During text changes, when entire lines are inserted or removed, the line
+ * attributes that are associated with the lines after the change
+ * will "move" with their respective text. An entire line is defined as
+ * extending from the first character on a line to the last and including the
+ * line delimiter.
+ * </p><p>
+ * When two lines are joined by deleting a line delimiter, the top line
+ * attributes take precedence and the attributes of the bottom line are deleted.
+ * For all other text changes line attributes will remain unchanged.
+ * </p>
+ *
+ * @param startLine first line the justify is applied to, 0 based
+ * @param lineCount number of lines the justify applies to.
+ * @param justify true if lines should be justified
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
+ * </ul>
+ * @see #setJustify(bool)
+ * @since 3.2
+ */
+public void setLineJustify(int startLine, int lineCount, bool justify) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+
+    renderer.setLineJustify(startLine, lineCount, justify);
+    resetCache(startLine, lineCount);
+    redrawLines(startLine, lineCount);
+    int caretLine = getCaretLine();
+    if (startLine <= caretLine && caretLine < startLine + lineCount) {
+        setCaretLocation();
+    }
+}
+/**
+ * Sets the line spacing of the widget. The line spacing applies for all lines.
+ *
+ * @param lineSpacing the line spacing
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 3.2
+ */
+public void setLineSpacing(int lineSpacing) {
+    checkWidget();
+    if (this.lineSpacing is lineSpacing || lineSpacing < 0) return;
+    this.lineSpacing = lineSpacing;
+    setVariableLineHeight();
+    resetCache(0, content.getLineCount());
+    setCaretLocation();
+    super.redraw();
+}
+void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
+    checkWidget();
+    this.leftMargin = leftMargin;
+    this.topMargin = topMargin;
+    this.rightMargin = rightMargin;
+    this.bottomMargin = bottomMargin;
+    setCaretLocation();
+}
+/**
+ * Flips selection anchor based on word selection direction.
+ */
+void setMouseWordSelectionAnchor() {
+    if (clickCount > 1) {
+        if (caretOffset < doubleClickSelection.x) {
+            selectionAnchor = doubleClickSelection.y;
+        } else if (caretOffset > doubleClickSelection.y) {
+            selectionAnchor = doubleClickSelection.x;
+        }
+    }
+}
+/**
+ * Sets the orientation of the receiver, which must be one
+ * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
+ *
+ * @param orientation new orientation style
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @since 2.1.2
+ */
+public void setOrientation(int orientation) {
+    if ((orientation & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT)) is 0) {
+        return;
+    }
+    if ((orientation & SWT.RIGHT_TO_LEFT) !is 0 && (orientation & SWT.LEFT_TO_RIGHT) !is 0) {
+        return;
+    }
+    if ((orientation & SWT.RIGHT_TO_LEFT) !is 0 && isMirrored()) {
+        return;
+    }
+    if ((orientation & SWT.LEFT_TO_RIGHT) !is 0 && !isMirrored()) {
+        return;
+    }
+    if (!BidiUtil.setOrientation(this, orientation)) {
+        return;
+    }
+    isMirrored_ = (orientation & SWT.RIGHT_TO_LEFT) !is 0;
+    caretDirection = SWT.NULL;
+    resetCache(0, content.getLineCount());
+    setCaretLocation();
+    keyActionMap = null;
+    createKeyBindings();
+    super.redraw();
+}
+/**
+ * Adjusts the maximum and the page size of the scroll bars to
+ * reflect content width/length changes.
+ *
+ * @param vertical indicates if the vertical scrollbar also needs to be set
+ */
+void setScrollBars(bool vertical) {
+    int inactive = 1;
+    if (vertical || !isFixedLineHeight()) {
+        ScrollBar verticalBar = getVerticalBar();
+        if (verticalBar !is null) {
+            int maximum = renderer.getHeight();
+            // only set the real values if the scroll bar can be used
+            // (ie. because the thumb size is less than the scroll maximum)
+            // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
+            if (clientAreaHeight < maximum) {
+                verticalBar.setMaximum(maximum);
+                verticalBar.setThumb(clientAreaHeight);
+                verticalBar.setPageIncrement(clientAreaHeight);
+            } else if (verticalBar.getThumb() !is inactive || verticalBar.getMaximum() !is inactive) {
+                verticalBar.setValues(
+                    verticalBar.getSelection(),
+                    verticalBar.getMinimum(),
+                    inactive,
+                    inactive,
+                    verticalBar.getIncrement(),
+                    inactive);
+            }
+        }
+    }
+    ScrollBar horizontalBar = getHorizontalBar();
+    if (horizontalBar !is null && horizontalBar.getVisible()) {
+        int maximum = renderer.getWidth();
+        // only set the real values if the scroll bar can be used
+        // (ie. because the thumb size is less than the scroll maximum)
+        // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
+        if (clientAreaWidth < maximum) {
+            horizontalBar.setMaximum(maximum);
+            horizontalBar.setThumb(clientAreaWidth - leftMargin - rightMargin);
+            horizontalBar.setPageIncrement(clientAreaWidth - leftMargin - rightMargin);
+        } else if (horizontalBar.getThumb() !is inactive || horizontalBar.getMaximum() !is inactive) {
+            horizontalBar.setValues(
+                horizontalBar.getSelection(),
+                horizontalBar.getMinimum(),
+                inactive,
+                inactive,
+                horizontalBar.getIncrement(),
+                inactive);
+        }
+    }
+}
+/**
+ * Sets the selection to the given position and scrolls it into view.  Equivalent to setSelection(start,start).
+ *
+ * @param start new caret position
+ * @see #setSelection(int,int)
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelection(int start) {
+    // checkWidget test done in setSelectionRange
+    setSelection(start, start);
+}
+/**
+ * Sets the selection and scrolls it into view.
+ * <p>
+ * Indexing is zero based.  Text selections are specified in terms of
+ * caret positions.  In a text widget that contains N characters, there are
+ * N+1 caret positions, ranging from 0..N
+ * </p>
+ *
+ * @param point x=selection start offset, y=selection end offset
+ *  The caret will be placed at the selection start when x > y.
+ * @see #setSelection(int,int)
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_NULL_ARGUMENT when point is null</li>
+ *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelection(Point point) {
+    checkWidget();
+    if (point is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+    setSelection(point.x, point.y);
+}
+/**
+ * Sets the receiver's selection background color to the color specified
+ * by the argument, or to the default system color for the control
+ * if the argument is null.
+ *
+ * @param color the new color (or null)
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public void setSelectionBackground (Color color) {
+    checkWidget ();
+    if (color !is null) {
+        if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    selectionBackground = color;
+    super.redraw();
+}
+/**
+ * Sets the receiver's selection foreground color to the color specified
+ * by the argument, or to the default system color for the control
+ * if the argument is null.
+ * <p>
+ * Note that this is a <em>HINT</em>. Some platforms do not allow the application
+ * to change the selection foreground color.
+ * </p>
+ * @param color the new color (or null)
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.1
+ */
+public void setSelectionForeground (Color color) {
+    checkWidget ();
+    if (color !is null) {
+        if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    selectionForeground = color;
+    super.redraw();
+}
+/**
+ * Sets the selection and scrolls it into view.
+ * <p>
+ * Indexing is zero based.  Text selections are specified in terms of
+ * caret positions.  In a text widget that contains N characters, there are
+ * N+1 caret positions, ranging from 0..N
+ * </p>
+ *
+ * @param start selection start offset. The caret will be placed at the
+ *  selection start when start > end.
+ * @param end selection end offset
+ * @see #setSelectionRange(int,int)
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelection(int start, int end) {
+    setSelectionRange(start, end - start);
+    showSelection();
+}
+/**
+ * Sets the selection.
+ * <p>
+ * The new selection may not be visible. Call showSelection to scroll
+ * the selection into view.
+ * </p>
+ *
+ * @param start offset of the first selected character, start >= 0 must be true.
+ * @param length number of characters to select, 0 <= start + length
+ *  <= getCharCount() must be true.
+ *  A negative length places the caret at the selection start.
+ * @param sendEvent a Selection event is sent when set to true and when
+ *  the selection is reset.
+ */
+void setSelection(int start, int length, bool sendEvent) {
+    int end = start + length;
+    start = content.utf8AdjustOffset(start);
+    end = content.utf8AdjustOffset(end);
+    if (start > end) {
+        int temp = end;
+        end = start;
+        start = temp;
+    }
+    // is the selection range different or is the selection direction
+    // different?
+    if (selection.x !is start || selection.y !is end ||
+        (length > 0 && selectionAnchor !is selection.x) ||
+        (length < 0 && selectionAnchor !is selection.y)) {
+        clearSelection(sendEvent);
+        if (length < 0) {
+            selectionAnchor = selection.y = end;
+            caretOffset = selection.x = start;
+        } else {
+            selectionAnchor = selection.x = start;
+            caretOffset = selection.y = end;
+        }
+        caretAlignment = PREVIOUS_OFFSET_TRAILING;
+        internalRedrawRange(selection.x, selection.y - selection.x);
+    }
+}
+/**
+ * Sets the selection.
+ * <p>
+ * The new selection may not be visible. Call showSelection to scroll the selection
+ * into view. A negative length places the caret at the visual start of the selection.
+ * </p>
+ *
+ * @param start offset of the first selected character
+ * @param length number of characters to select
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
+ * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
+ * </ul>
+ */
+public void setSelectionRange(int start, int length) {
+    checkWidget();
+    int contentLength = getCharCount();
+    start = Math.max(0, Math.min (start, contentLength));
+    int end = start + length;
+    if (end < 0) {
+        length = -start;
+    } else {
+        if (end > contentLength) length = contentLength - start;
+    }
+    if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
+        // the start offset or end offset of the selection range is inside a
+        // multi byte line delimiter. This is an illegal operation and an exception
+        // is thrown. Fixes 1GDKK3R
+        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    }
+    setSelection(start, length, false);
+    setCaretLocation();
+}
+/**
+ * Adds the specified style.
+ * <p>
+ * The new style overwrites existing styles for the specified range.
+ * Existing style ranges are adjusted if they partially overlap with
+ * the new style. To clear an individual style, call setStyleRange
+ * with a StyleRange that has null attributes.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param range StyleRange object containing the style information.
+ * Overwrites the old style in the given range. May be null to delete
+ * all styles.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li>
+ * </ul>
+ */
+public void setStyleRange(StyleRange range) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (range !is null) {
+        if (range.isUnstyled()) {
+            setStyleRanges(range.start, range.length, null, null, false);
+        } else {
+            setStyleRanges(range.start, 0, null, [range], false);
+        }
+    } else {
+        setStyleRanges(0, 0, null, null, true);
+    }
+}
+/**
+ * Clears the styles in the range specified by <code>start</code> and
+ * <code>length</code> and adds the new styles.
+ * <p>
+ * The ranges array contains start and length pairs.  Each pair refers to
+ * the corresponding style in the styles array.  For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2].  The range fields within each StyleRange are ignored.
+ * If ranges or styles is null, the specified range is cleared.
+ * </p><p>
+ * Note: It is expected that the same instance of a StyleRange will occur
+ * multiple times within the styles array, reducing memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param start offset of first character where styles will be deleted
+ * @param length length of the range to delete styles in
+ * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
+ * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
+ *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 is styles.length)</li>
+ *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
+ *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (ranges is null || styles is null) {
+        setStyleRanges(start, length, null, null, false);
+    } else {
+        setStyleRanges(start, length, ranges, styles, false);
+    }
+}
+/**
+ * Sets styles to be used for rendering the widget content.
+ * <p>
+ * All styles in the widget will be replaced with the given set of ranges and styles.
+ * The ranges array contains start and length pairs.  Each pair refers to
+ * the corresponding style in the styles array.  For example, the pair
+ * that starts at ranges[n] with length ranges[n+1] uses the style
+ * at styles[n/2].  The range fields within each StyleRange are ignored.
+ * If either argument is null, the styles are cleared.
+ * </p><p>
+ * Note: It is expected that the same instance of a StyleRange will occur
+ * multiple times within the styles array, reducing memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param ranges the array of ranges.  The ranges must not overlap and must be in order.
+ * @param styles the array of StyleRanges.  The range fields within the StyleRange are unused.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
+ *    <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 is styles.length)</li>
+ *    <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
+ *    <li>ERROR_INVALID_RANGE when a range overlaps</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public void setStyleRanges(int[] ranges, StyleRange[] styles) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (ranges is null || styles is null) {
+        setStyleRanges(0, 0, null, null, true);
+    } else {
+        setStyleRanges(0, 0, ranges, styles, true);
+    }
+}
+void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, bool reset) {
+    int charCount = content.getCharCount();
+    int end = start + length;
+    if (start > end || start < 0) {
+        SWT.error(SWT.ERROR_INVALID_RANGE);
+    }
+    if (styles !is null) {
+        if (end > charCount) {
+            SWT.error(SWT.ERROR_INVALID_RANGE);
+        }
+        if (ranges !is null) {
+            if (ranges.length !is styles.length << 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+        }
+        int lastOffset = 0;
+        bool variableHeight = false;
+        for (int i = 0; i < styles.length; i ++) {
+            if (styles[i] is null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+            int rangeStart, rangeLength;
+            if (ranges !is null) {
+                rangeStart = ranges[i << 1];
+                rangeLength = ranges[(i << 1) + 1];
+            } else {
+                rangeStart = styles[i].start;
+                rangeLength = styles[i].length;
+            }
+            if (rangeLength < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+            if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+            if (lastOffset > rangeStart) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+            variableHeight |= styles[i].isVariableHeight();
+            lastOffset = rangeStart + rangeLength;
+        }
+        if (variableHeight) setVariableLineHeight();
+    }
+    int rangeStart = start, rangeEnd = end;
+    if (styles !is null && styles.length > 0) {
+        if (ranges !is null) {
+            rangeStart = ranges[0];
+            rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
+        } else {
+            rangeStart = styles[0].start;
+            rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
+        }
+    }
+    int lastLineBottom = 0;
+    if (!isFixedLineHeight() && !reset) {
+        int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
+        int partialTopIndex = getPartialTopIndex();
+        int partialBottomIndex = getPartialBottomIndex();
+        if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
+            lastLineBottom = getLinePixel(lineEnd + 1);
+        }
+    }
+    if (reset) {
+        renderer.setStyleRanges(null, null);
+    } else {
+        renderer.updateRanges(start, length, length);
+    }
+    if (styles !is null && styles.length > 0) {
+        renderer.setStyleRanges(ranges, styles);
+    }
+    if (reset) {
+        resetCache(0, content.getLineCount());
+        super.redraw();
+    } else {
+        int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
+        int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
+        resetCache(lineStart, lineEnd - lineStart + 1);
+        int partialTopIndex = getPartialTopIndex();
+        int partialBottomIndex = getPartialBottomIndex();
+        if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
+            int y = 0;
+            int height = clientAreaHeight;
+            if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
+                int lineTop = Math.max(y, getLinePixel(lineStart));
+                y = lineTop;
+                height -= lineTop;
+            }
+            if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
+                int newLastLineBottom = getLinePixel(lineEnd + 1);
+                if (!isFixedLineHeight()) {
+                    scrollText(lastLineBottom, newLastLineBottom);
+                }
+                height = newLastLineBottom - y;
+            }
+            super.redraw(0, y, clientAreaWidth, height, false);
+        }
+    }
+    setCaretLocation();
+}
+/**
+ * Sets styles to be used for rendering the widget content. All styles
+ * in the widget will be replaced with the given set of styles.
+ * <p>
+ * Note: Because a StyleRange includes the start and length, the
+ * same instance cannot occur multiple times in the array of styles.
+ * If the same style attributes, such as font and color, occur in
+ * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
+ * can be used to share styles and reduce memory usage.
+ * </p><p>
+ * Should not be called if a LineStyleListener has been set since the
+ * listener maintains the styles.
+ * </p>
+ *
+ * @param ranges StyleRange objects containing the style information.
+ * The ranges should not overlap. The style rendering is undefined if
+ * the ranges do overlap. Must not be null. The styles need to be in order.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
+ * </ul>
+ *
+ * @see #setStyleRanges(int[], StyleRange[])
+ */
+public void setStyleRanges(StyleRange[] ranges) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    // SWT extension: allow null for zero length string
+    //if (ranges is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    setStyleRanges(0, 0, null, ranges, true);
+}
+/**
+ * Sets the tab width.
+ *
+ * @param tabs tab width measured in characters.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setTabs(int tabs) {
+    checkWidget();
+    tabLength = tabs;
+    renderer.setFont(null, tabs);
+    resetCache(0, content.getLineCount());
+    setCaretLocation();
+    super.redraw();
+}
+/**
+ * Sets the widget content.
+ * If the widget has the SWT.SINGLE style and "text" contains more than
+ * one line, only the first line is rendered but the text is stored
+ * unchanged. A subsequent call to getText will return the same text
+ * that was set.
+ * <p>
+ * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE
+ * style is used.
+ * </p>
+ *
+ * @param text new widget content. Replaces existing content. Line styles
+ *  that were set using StyledText API are discarded.  The
+ *  current selection is also discarded.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setText(String text) {
+    checkWidget();
+    // SWT extension: allow null for zero length string
+//     if (text is null) {
+//         SWT.error(SWT.ERROR_NULL_ARGUMENT);
+//     }
+    Event event = new Event();
+    event.start = 0;
+    event.end = getCharCount();
+    event.text = text;
+    event.doit = true;
+    notifyListeners(SWT.Verify, event);
+    if (event.doit) {
+        StyledTextEvent styledTextEvent = null;
+        if (isListening(ExtendedModify)) {
+            styledTextEvent = new StyledTextEvent(content);
+            styledTextEvent.start = event.start;
+            styledTextEvent.end = event.start + event.text.length;
+            styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
+        }
+        content.setText(event.text);
+        sendModifyEvent(event);
+        if (styledTextEvent !is null) {
+            notifyListeners(ExtendedModify, styledTextEvent);
+        }
+    }
+}
+/**
+ * Sets the text limit to the specified number of characters.
+ * <p>
+ * The text limit specifies the amount of text that
+ * the user can type into the widget.
+ * </p>
+ *
+ * @param limit the new text limit.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_CANNOT_BE_ZERO when limit is 0</li>
+ * </ul>
+ */
+public void setTextLimit(int limit) {
+    checkWidget();
+    if (limit is 0) {
+        SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
+    }
+    textLimit = limit;
+}
+/**
+ * Sets the top index. Do nothing if there is no text set.
+ * <p>
+ * The top index is the index of the line that is currently at the top
+ * of the widget. The top index changes when the widget is scrolled.
+ * Indexing starts from zero.
+ * Note: The top index is reset to 0 when new text is set in the widget.
+ * </p>
+ *
+ * @param topIndex new top index. Must be between 0 and
+ *  getLineCount() - fully visible lines per page. If no lines are fully
+ *  visible the maximum value is getLineCount() - 1. An out of range
+ *  index will be adjusted accordingly.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void setTopIndex(int topIndex) {
+    checkWidget();
+    if (getCharCount() is 0) {
+        return;
+    }
+    int lineCount = content.getLineCount(), pixel;
+    if (isFixedLineHeight()) {
+        int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
+        if (topIndex < 0) {
+            topIndex = 0;
+        } else if (topIndex > lineCount - pageSize) {
+            topIndex = lineCount - pageSize;
+        }
+        pixel = getLinePixel(topIndex);
+    } else {
+        topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
+        pixel = getLinePixel(topIndex);
+        if (pixel > 0) {
+            pixel = getAvailableHeightBellow(pixel);
+        } else {
+            pixel = getAvailableHeightAbove(pixel);
+        }
+    }
+    scrollVertical(pixel, true);
+}
+/**
+ * Sets the top pixel offset. Do nothing if there is no text set.
+ * <p>
+ * The top pixel offset is the vertical pixel offset of the widget. The
+ * widget is scrolled so that the given pixel position is at the top.
+ * The top index is adjusted to the corresponding top line.
+ * Note: The top pixel is reset to 0 when new text is set in the widget.
+ * </p>
+ *
+ * @param pixel new top pixel offset. Must be between 0 and
+ *  (getLineCount() - visible lines per page) / getLineHeight()). An out
+ *  of range offset will be adjusted accordingly.
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @since 2.0
+ */
+public void setTopPixel(int pixel) {
+    checkWidget();
+    if (getCharCount() is 0) {
+        return;
+    }
+    if (pixel < 0) pixel = 0;
+    int lineCount = content.getLineCount();
+    int height = clientAreaHeight - topMargin - bottomMargin;
+    int verticalOffset = getVerticalScrollOffset();
+    if (isFixedLineHeight()) {
+        int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
+        if (pixel > maxTopPixel) pixel = maxTopPixel;
+        pixel -= verticalOffset;
+    } else {
+        pixel -= verticalOffset;
+        if (pixel > 0) {
+            pixel = getAvailableHeightBellow(pixel);
+        }
+    }
+    scrollVertical(pixel, true);
+}
+/**
+ * Sets whether the widget wraps lines.
+ * <p>
+ * This overrides the creation style bit SWT.WRAP.
+ * </p>
+ *
+ * @param wrap true=widget wraps lines, false=widget does not wrap lines
+ * @since 2.0
+ */
+public void setWordWrap(bool wrap) {
+    checkWidget();
+    if ((getStyle() & SWT.SINGLE) !is 0) return;
+    if (wordWrap is wrap) return;
+    wordWrap = wrap;
+    setVariableLineHeight();
+    resetCache(0, content.getLineCount());
+    horizontalScrollOffset = 0;
+    ScrollBar horizontalBar = getHorizontalBar();
+    if (horizontalBar !is null) {
+        horizontalBar.setVisible(!wordWrap);
+    }
+    setScrollBars(true);
+    setCaretLocation();
+    super.redraw();
+}
+// SWT: If necessary, scroll to show the location
+bool showLocation(Rectangle rect, bool scrollPage) {
+    int clientAreaWidth = this.clientAreaWidth - leftMargin - rightMargin;
+    int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
+    bool scrolled = false;
+    if (rect.y <= topMargin) {
+        scrolled = scrollVertical(rect.y - topMargin, true);
+    } else if (rect.y + rect.height > clientAreaHeight) {
+        if (clientAreaHeight is 0) {
+            scrolled = scrollVertical(rect.y, true);
+        } else {
+            scrolled = scrollVertical(rect.y + rect.height - clientAreaHeight, true);
+        }
+    }
+    if (clientAreaWidth > 0) {
+        int minScroll = scrollPage ? clientAreaWidth / 4 : 0;
+        if (rect.x < leftMargin) {
+            int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
+            int maxScroll = horizontalScrollOffset;
+            scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
+        } else if (rect.x + rect.width > clientAreaWidth) {
+            int scrollWidth =  Math.max(rect.x + rect.width - clientAreaWidth, minScroll);
+            int maxScroll = renderer.getWidth() - horizontalScrollOffset - this.clientAreaWidth;
+            scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
+        }
+    }
+    return scrolled;
+}
+/**
+ * Sets the caret location and scrolls the caret offset into view.
+ */
+void showCaret() {
+    Rectangle bounds = getBoundsAtOffset(caretOffset);
+    if (!showLocation(bounds, true)) {
+        setCaretLocation();
+    }
+}
+/**
+ * Scrolls the selection into view.
+ * <p>
+ * The end of the selection will be scrolled into view.
+ * Note that if a right-to-left selection exists, the end of the selection is
+ * the visual beginning of the selection (i.e., where the caret is located).
+ * </p>
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+public void showSelection() {
+    checkWidget();
+    // is selection from right-to-left?
+    bool rightToLeft = caretOffset is selection.x;
+    int startOffset, endOffset;
+    if (rightToLeft) {
+        startOffset = selection.y;
+        endOffset = selection.x;
+    } else {
+        startOffset = selection.x;
+        endOffset = selection.y;
+    }
+
+    Rectangle startBounds = getBoundsAtOffset(startOffset);
+    Rectangle endBounds = getBoundsAtOffset(endOffset);
+
+    // can the selection be fully displayed within the widget's visible width?
+    int w = clientAreaWidth - leftMargin - rightMargin;
+    bool selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
+    if (selectionFits) {
+        // show as much of the selection as possible by first showing
+        // the start of the selection
+        if (showLocation(startBounds, false)) {
+            // endX value could change if showing startX caused a scroll to occur
+            endBounds = getBoundsAtOffset(endOffset);
+        }
+        // the character at endOffset is not part of the selection
+        endBounds.width = 0;
+        showLocation(endBounds, false);
+    } else {
+        // just show the end of the selection since the selection start
+        // will not be visible
+        showLocation(endBounds, true);
+    }
+}
+/**
+ * Updates the selection and caret position depending on the text change.
+ * <p>
+ * If the selection intersects with the replaced text, the selection is
+ * reset and the caret moved to the end of the new text.
+ * If the selection is behind the replaced text it is moved so that the
+ * same text remains selected.  If the selection is before the replaced text
+ * it is left unchanged.
+ * </p>
+ *
+ * @param startOffset offset of the text change
+ * @param replacedLength length of text being replaced
+ * @param newLength length of new text
+ */
+void updateSelection(int startOffset, int replacedLength, int newLength) {
+    if (selection.y <= startOffset) {
+        // selection ends before text change
+        if (wordWrap) setCaretLocation();
+        return;
+    }
+    if (selection.x < startOffset) {
+        // clear selection fragment before text change
+        internalRedrawRange(selection.x, startOffset - selection.x);
+    }
+    if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
+        // clear selection fragment after text change.
+        // do this only when the selection is actually affected by the
+        // change. Selection is only affected if it intersects the change (1GDY217).
+        int netNewLength = newLength - replacedLength;
+        int redrawStart = startOffset + newLength;
+        internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
+    }
+    if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
+        // selection intersects replaced text. set caret behind text change
+        setSelection(startOffset + newLength, 0, true);
+    } else {
+        // move selection to keep same text selected
+        setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true);
+    }
+    setCaretLocation();
+}
+}