changeset 155:a5afe31f5cdd

Added custom controls
author Frank Benoit <benoit@tionex.de>
date Thu, 14 Feb 2008 19:18:37 +0100
parents b0279a69d976
children fed5552a24a3
files dwt/custom/AnimatedProgress.d dwt/custom/BidiSegmentEvent.d dwt/custom/BidiSegmentListener.d dwt/custom/Bullet.d dwt/custom/BusyIndicator.d dwt/custom/CBanner.d dwt/custom/CBannerLayout.d dwt/custom/CCombo.d dwt/custom/CLabel.d dwt/custom/CLayoutData.d dwt/custom/CTabFolder.d dwt/custom/CTabFolder2Adapter.d dwt/custom/CTabFolder2Listener.d dwt/custom/CTabFolderAdapter.d dwt/custom/CTabFolderEvent.d dwt/custom/CTabFolderLayout.d dwt/custom/CTabFolderListener.d dwt/custom/CTabItem.d dwt/custom/ControlEditor.d dwt/custom/DefaultContent.d dwt/custom/ExtendedModifyEvent.d dwt/custom/ExtendedModifyListener.d dwt/custom/LineBackgroundEvent.d dwt/custom/LineBackgroundListener.d dwt/custom/LineStyleEvent.d dwt/custom/LineStyleListener.d dwt/custom/MovementEvent.d dwt/custom/MovementListener.d dwt/custom/PaintObjectEvent.d dwt/custom/PaintObjectListener.d dwt/custom/PopupList.d dwt/custom/ST.d dwt/custom/ScrolledComposite.d dwt/custom/ScrolledCompositeLayout.d dwt/custom/StackLayout.d dwt/custom/StyleRange.d dwt/custom/StyledText.d dwt/custom/StyledTextContent.d dwt/custom/StyledTextDropTargetEffect.d dwt/custom/StyledTextEvent.d dwt/custom/StyledTextListener.d dwt/custom/StyledTextPrintOptions.d dwt/custom/StyledTextRenderer.d dwt/custom/TableCursor.d dwt/custom/TableEditor.d dwt/custom/TableTree.d dwt/custom/TableTreeEditor.d dwt/custom/TableTreeItem.d dwt/custom/TextChangeListener.d dwt/custom/TextChangedEvent.d dwt/custom/TextChangingEvent.d dwt/custom/TreeEditor.d dwt/custom/VerifyKeyListener.d dwt/custom/ViewForm.d dwt/custom/ViewFormLayout.d dwt/internal/BidiUtil.d
diffstat 56 files changed, 27425 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/AnimatedProgress.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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 dwt.custom.AnimatedProgress;
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.events.ControlAdapter;
+import dwt.events.ControlEvent;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Canvas;
+import dwt.widgets.Composite;
+import dwt.widgets.Display;
+import dwt.dwthelper.Runnable;
+
+/**
+ * A control for showing progress feedback for a long running operation.
+ *
+ * @deprecated As of Eclipse 2.1, use ProgressBar with the style DWT.INDETERMINATE
+ *
+ * <dl>
+ * <dt><b>Styles:</b><dd>VERTICAL, HORIZONTAL, BORDER
+ * </dl>
+ */
+public class AnimatedProgress : Canvas {
+
+    alias Canvas.computeSize computeSize;
+
+    static const int SLEEP = 70;
+    static const int DEFAULT_WIDTH = 160;
+    static const int DEFAULT_HEIGHT = 18;
+    bool active = false;
+    bool showStripes = false;
+    int value;
+    int orientation = DWT.HORIZONTAL;
+    bool showBorder = false;
+
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#VERTICAL
+ * @see DWT#HORIZONTAL
+ * @see DWT#BORDER
+ * @see #getStyle()
+ */
+public this(Composite parent, int style) {
+    super(parent, checkStyle(style));
+
+    if ((style & DWT.VERTICAL) !is 0) {
+        orientation = DWT.VERTICAL;
+    }
+    showBorder = (style & DWT.BORDER) !is 0;
+
+    addControlListener(new class() ControlAdapter {
+        public void controlResized(ControlEvent e) {
+            redraw();
+        }
+    });
+    addPaintListener(new class() PaintListener {
+        public void paintControl(PaintEvent e) {
+            paint(e);
+        }
+    });
+    addDisposeListener(new class() DisposeListener {
+        public void widgetDisposed(DisposeEvent e){
+            stop();
+        }
+    });
+}
+private static int checkStyle (int style) {
+    int mask = DWT.NONE;
+    return style & mask;
+}
+/**
+ * Stop the animation if it is not already stopped and
+ * reset the presentation to a blank appearance.
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 synchronized void clear(){
+    checkWidget();
+    if (active) stop();
+    showStripes = false;
+    redraw();
+}
+public override Point computeSize(int wHint, int hHint, bool changed) {
+    checkWidget();
+    Point size = null;
+    if (orientation is DWT.HORIZONTAL) {
+        size = new Point(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+    } else {
+        size = new Point(DEFAULT_HEIGHT, DEFAULT_WIDTH);
+    }
+    if (wHint !is DWT.DEFAULT) size.x = wHint;
+    if (hHint !is DWT.DEFAULT) size.y = hHint;
+
+    return size;
+}
+private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright) {
+    gc.setForeground(topleft);
+    gc.drawLine(x, y, x+w-1, y);
+    gc.drawLine(x, y, x, y+h-1);
+
+    gc.setForeground(bottomright);
+    gc.drawLine(x+w, y, x+w, y+h);
+    gc.drawLine(x, y+h, x+w, y+h);
+}
+void paint(PaintEvent event) {
+    GC gc = event.gc;
+    Display disp= getDisplay();
+
+    Rectangle rect= getClientArea();
+    gc.fillRectangle(rect);
+    if (showBorder) {
+        drawBevelRect(gc, rect.x, rect.y, rect.width-1, rect.height-1,
+            disp.getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW),
+            disp.getSystemColor(DWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
+    }
+
+    paintStripes(gc);
+}
+void paintStripes(GC gc) {
+
+    if (!showStripes) return;
+
+    Rectangle rect= getClientArea();
+    // Subtracted border painted by paint.
+    rect = new Rectangle(rect.x+2, rect.y+2, rect.width-4, rect.height-4);
+
+    gc.setLineWidth(2);
+    gc.setClipping(rect);
+    Color color = getDisplay().getSystemColor(DWT.COLOR_LIST_SELECTION);
+    gc.setBackground(color);
+    gc.fillRectangle(rect);
+    gc.setForeground(this.getBackground());
+    int step = 12;
+    int foregroundValue = value is 0 ? step - 2 : value - 2;
+    if (orientation is DWT.HORIZONTAL) {
+        int y = rect.y - 1;
+        int w = rect.width;
+        int h = rect.height + 2;
+        for (int i= 0; i < w; i+= step) {
+            int x = i + foregroundValue;
+            gc.drawLine(x, y, x, h);
+        }
+    } else {
+        int x = rect.x - 1;
+        int w = rect.width + 2;
+        int h = rect.height;
+
+        for (int i= 0; i < h; i+= step) {
+            int y = i + foregroundValue;
+            gc.drawLine(x, y, w, y);
+        }
+    }
+
+    if (active) {
+        value = (value + 2) % step;
+    }
+}
+/**
+* Start the animation.
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 synchronized void start() {
+    checkWidget();
+    if (active) return;
+
+    active = true;
+    showStripes = true;
+
+    Display display = getDisplay();
+    Runnable [] timer = new Runnable [1];
+
+    timer [0] = new class( display, timer ) Runnable {
+        Display disp;
+        Runnable [] runs;
+        this( Display disp, Runnable[] runs ){
+            this.disp = disp;
+            this.runs = runs;
+        }
+        public void run () {
+            if (!active) return;
+            GC gc = new GC(this.outer);
+            paintStripes(gc);
+            gc.dispose();
+            disp.timerExec (SLEEP, runs [0]);
+        }
+    };
+    display.timerExec (SLEEP, timer [0]);
+}
+/**
+* Stop the animation.   Freeze the presentation at its current appearance.
+*/
+public synchronized void stop() {
+    //checkWidget();
+    active = false;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/BidiSegmentEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.BidiSegmentEvent;
+
+
+import dwt.events.TypedEvent;
+import dwt.custom.StyledTextEvent;
+
+/**
+ * This event is sent to BidiSegmentListeners when a line is to
+ * be measured or rendered in a bidi locale.  The segments field is
+ * used to specify text ranges in the line that should be treated as
+ * separate segments for bidi reordering.  Each segment will be reordered
+ * and rendered separately.
+ * <p>
+ * The elements in the segments field specify the start offset of
+ * a segment relative to the start of the line. They must follow
+ * the following rules:
+ * <ul>
+ * <li>first element must be 0
+ * <li>elements must be in ascending order and must not have duplicates
+ * <li>elements must not exceed the line length
+ * </ul>
+ * In addition, the last element may be set to the end of the line
+ * but this is not required.
+ *
+ * The segments field may be left null if the entire line should
+ * be reordered as is.
+ * </p>
+ * A BidiSegmentListener may be used when adjacent segments of
+ * right-to-left text should not be reordered relative to each other.
+ * For example, within a Java editor, you may wish multiple
+ * right-to-left string literals to be reordered differently than the
+ * bidi algorithm specifies.
+ *
+ * Example:
+ * <pre>
+ *  stored line = "R1R2R3" + "R4R5R6"
+ *      R1 to R6 are right-to-left characters. The quotation marks
+ *      are part of the line text. The line is 13 characters long.
+ *
+ *  segments = null:
+ *      entire line will be reordered and thus the two R2L segments
+ *      swapped (as per the bidi algorithm).
+ *      visual line (rendered on screen) = "R6R5R4" + "R3R2R1"
+ *
+ *  segments = [0, 5, 8]
+ *      "R1R2R3" will be reordered, followed by [blank]+[blank] and
+ *      "R4R5R6".
+ *      visual line = "R3R2R1" + "R6R5R4"
+ * </pre>
+ */
+public class BidiSegmentEvent : TypedEvent {
+
+    /**
+     * line start offset
+     */
+    public int lineOffset;
+
+    /**
+     * line text
+     */
+    public char[] lineText;
+
+    /**
+     * bidi segments, see above
+     */
+    public int[] segments;
+
+this(StyledTextEvent e) {
+    super(cast(Object)e);
+    lineOffset = e.detail;
+    lineText = e.text;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/BidiSegmentListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.BidiSegmentListener;
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.BidiSegmentEvent;
+
+/**
+ * This listener interface may be implemented in order to receive
+ * BidiSegmentEvents.
+ * @see BidiSegmentEvent
+ */
+public interface BidiSegmentListener : DWTEventListener {
+
+/**
+ * This method is called when a line needs to be reordered for
+ * measuring or rendering in a bidi locale.
+ * <p>
+ * The following event fields are used:<ul>
+ * <li>event.lineOffset line start offset (input)</li>
+ * <li>event.lineText line text (input)</li>
+ * <li>event.segments text segments that should be reordered
+ *  separately. (output)</li>
+ * </ul>
+ *
+ * @param event the given event
+ *  separately. (output)
+ * @see BidiSegmentEvent
+ */
+public void lineGetSegments(BidiSegmentEvent event);
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/Bullet.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.Bullet;
+
+import dwt.DWT;
+import dwt.custom.StyleRange;
+import dwt.custom.ST;
+import dwt.dwthelper.System;
+
+/**
+ * Instances of this class represent bullets in the <code>StyledText</code>.
+ * <p>
+ * The hashCode() method in this class uses the values of the public
+ * fields to compute the hash value. When storing instances of the
+ * class in hashed collections, do not modify these fields after the
+ * object has been inserted.
+ * </p>
+ * <p>
+ * Application code does <em>not</em> need to explicitly release the
+ * resources managed by each instance when those instances are no longer
+ * required, and thus no <code>dispose()</code> method is provided.
+ * </p>
+ *
+ * @see StyledText#setLineBullet(int, int, Bullet)
+ *
+ * @since 3.2
+ */
+public class Bullet {
+    public int type;
+    public StyleRange style;
+    public char[] text;
+    int[] linesIndices;
+    int count;
+
+/**
+ * Create a new bullet the specified style, the type is set to ST.BULLET_DOT.
+ * The style must have a glyph metrics set.
+ *
+ * @param style the style
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when the style or the glyph metrics are null</li>
+ * </ul>
+ */
+public this(StyleRange style) {
+    this(ST.BULLET_DOT, style);
+}
+/**
+ * Create a new bullet the specified style and type.
+ * The style must have a glyph metrics set.
+ *
+ * @param style the style
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when the style or the glyph metrics are null</li>
+ * </ul>
+ */
+public this(int type, StyleRange style) {
+    if (style is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    if (style.metrics is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    this.type = type;
+    this.style = style;
+}
+void addIndices (int startLine, int lineCount) {
+    if (linesIndices is null) {
+        linesIndices = new int[lineCount];
+        count = lineCount;
+        for (int i = 0; i < lineCount; i++) linesIndices[i] = startLine + i;
+    } else {
+        int modifyStart = 0;
+        while (modifyStart < count) {
+            if (startLine <= linesIndices[modifyStart]) break;
+            modifyStart++;
+        }
+        int modifyEnd = modifyStart;
+        while (modifyEnd < count) {
+            if (startLine + lineCount <= linesIndices[modifyEnd]) break;
+            modifyEnd++;
+        }
+        int newSize = modifyStart + lineCount + count - modifyEnd;
+        if (newSize > linesIndices.length) {
+            int[] newLinesIndices = new int[newSize];
+            System.arraycopy(linesIndices, 0, newLinesIndices, 0, count);
+            linesIndices = newLinesIndices;
+        }
+        System.arraycopy(linesIndices, modifyEnd, linesIndices, modifyStart + lineCount, count - modifyEnd);
+        for (int i = 0; i < lineCount; i++) linesIndices[modifyStart + i] = startLine + i;
+        count = newSize;
+    }
+}
+int indexOf (int lineIndex) {
+    for (int i = 0; i < count; i++) {
+        if (linesIndices[i] is lineIndex) return i;
+    }
+    return -1;
+}
+public override hash_t toHash() {
+    return style.toHash() ^ type;
+}
+int[] removeIndices (int startLine, int replaceLineCount, int newLineCount, bool update) {
+    if (count is 0) return null;
+    if (startLine > linesIndices[count - 1]) return null;
+    int endLine = startLine + replaceLineCount;
+    int delta = newLineCount - replaceLineCount;
+    for (int i = 0; i < count; i++) {
+        int index = linesIndices[i];
+        if (startLine <= index) {
+            int j = i;
+            while (j < count) {
+                if (linesIndices[j] >= endLine) break;
+                j++;
+            }
+            if (update) {
+                for (int k = j; k < count; k++) linesIndices[k] += delta;
+            }
+            int[] redrawLines = new int[count - j];
+            System.arraycopy(linesIndices, j, redrawLines, 0, count - j);
+            System.arraycopy(linesIndices, j, linesIndices, i, count - j);
+            count -= (j - i);
+            return redrawLines;
+        }
+    }
+    for (int i = 0; i < count; i++) linesIndices[i] += delta;
+    return null;
+}
+int size() {
+    return count;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/BusyIndicator.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.BusyIndicator;
+
+
+
+import dwt.DWT;
+import dwt.graphics.Cursor;
+import dwt.widgets.Display;
+import dwt.widgets.Shell;
+import dwt.dwthelper.Integer;
+import dwt.dwthelper.Runnable;
+
+/**
+ * Support for showing a Busy Cursor during a long running process.
+ */
+public class BusyIndicator {
+
+    static int nextBusyId = 1;
+    static const char[] BUSYID_NAME = "DWT BusyIndicator"; //$NON-NLS-1$
+    static const char[] BUSY_CURSOR = "DWT BusyIndicator Cursor"; //$NON-NLS-1$
+
+/**
+ * Runs the given <code>Runnable</code> while providing
+ * busy feedback using this busy indicator.
+ *
+ * @param display the display on which the busy feedback should be
+ *        displayed.  If the display is null, the Display for the current
+ *        thread will be used.  If there is no Display for the current thread,
+ *        the runnable code will be executed and no busy feedback will be displayed.
+ * @param runnable the runnable for which busy feedback is to be shown.
+ *        Must not be null.
+ *
+* @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the runnable is null</li>
+ * </ul>
+ */
+
+public static void showWhile(Display display, Runnable runnable) {
+    if (runnable is null)
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    if (display is null) {
+        display = Display.getCurrent();
+        if (display is null) {
+            runnable.run();
+            return;
+        }
+    }
+
+    Integer busyId = new Integer(nextBusyId);
+    nextBusyId++;
+    Cursor cursor = display.getSystemCursor(DWT.CURSOR_WAIT);
+    Shell[] shells = display.getShells();
+    for (int i = 0; i < shells.length; i++) {
+        Integer id = cast(Integer)shells[i].getData(BUSYID_NAME);
+        if (id is null) {
+            shells[i].setCursor(cursor);
+            shells[i].setData(BUSYID_NAME, busyId);
+        }
+    }
+
+    try {
+        runnable.run();
+    } finally {
+        shells = display.getShells();
+        for (int i = 0; i < shells.length; i++) {
+            Integer id = cast(Integer)shells[i].getData(BUSYID_NAME);
+            if ( id !is null && id == busyId) {
+                shells[i].setCursor(null);
+                shells[i].setData(BUSYID_NAME, null);
+            }
+        }
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CBanner.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,558 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.CBanner;
+
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.graphics.Color;
+import dwt.graphics.Cursor;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.RGB;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.custom.CBannerLayout;
+
+import Math = tango.math.Math;
+
+
+/**
+ * Instances of this class implement a Composite that lays out its
+ * children and allows programmatic control of the layout. It draws
+ * a separator between the left and right children which can be dragged
+ * to resize the right control.
+ * CBanner is used in the workbench to layout the toolbar area and
+ * perspective switching toolbar.
+ * <p>
+ * Note that although this class is a subclass of <code>Composite</code>,
+ * it does not make sense to set a layout on it.
+ * </p><p>
+ * <dl>
+ * <dt><b>Styles:</b></dt>
+ * <dd>NONE</dd>
+ * <dt><b>Events:</b></dt>
+ * <dd>(None)</dd>
+ * </dl>
+ * <p>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </p>
+ *
+ * @since 3.0
+ */
+
+public class CBanner : Composite {
+
+    Control left;
+    Control right;
+    Control bottom;
+
+    bool simple = true;
+
+    int[] curve;
+    int curveStart = 0;
+    Rectangle curveRect;
+    int curve_width = 5;
+    int curve_indent = -2;
+
+    int rightWidth = DWT.DEFAULT;
+    int rightMinWidth = 0;
+    int rightMinHeight = 0;
+    Cursor resizeCursor;
+    bool dragging = false;
+    int rightDragDisplacement = 0;
+
+    static const int OFFSCREEN = -200;
+    static const int BORDER_BOTTOM = 2;
+    static const int BORDER_TOP = 3;
+    static const int BORDER_STRIPE = 1;
+    static const int CURVE_TAIL = 200;
+    static const int BEZIER_RIGHT = 30;
+    static const int BEZIER_LEFT = 30;
+    static const int MIN_LEFT = 10;
+    static int BORDER1 = DWT.COLOR_WIDGET_HIGHLIGHT_SHADOW;
+
+
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ */
+public this(Composite parent, int style) {
+    curveRect = new Rectangle(0, 0, 0, 0);
+    super(parent, checkStyle(style));
+    super.setLayout(new CBannerLayout());
+    resizeCursor = new Cursor(getDisplay(), DWT.CURSOR_SIZEWE);
+
+    Listener listener = new class() Listener {
+        public void handleEvent(Event e) {
+            switch (e.type) {
+                case DWT.Dispose:
+                    onDispose(); break;
+                case DWT.MouseDown:
+                    onMouseDown (e.x, e.y); break;
+                case DWT.MouseExit:
+                    onMouseExit(); break;
+                case DWT.MouseMove:
+                    onMouseMove(e.x, e.y); break;
+                case DWT.MouseUp:
+                    onMouseUp(); break;
+                case DWT.Paint:
+                    onPaint(e.gc); break;
+                case DWT.Resize:
+                    onResize(); break;
+                default:
+            }
+        }
+    };
+    int[] events = [DWT.Dispose, DWT.MouseDown, DWT.MouseExit, DWT.MouseMove, DWT.MouseUp, DWT.Paint, DWT.Resize];
+    for (int i = 0; i < events.length; i++) {
+        addListener(events[i], listener);
+    }
+}
+static int[] bezier(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, int count) {
+    // The parametric equations for a Bezier curve for x[t] and y[t] where  0 <= t <=1 are:
+    // x[t] = x0+3(x1-x0)t+3(x0+x2-2x1)t^2+(x3-x0+3x1-3x2)t^3
+    // y[t] = y0+3(y1-y0)t+3(y0+y2-2y1)t^2+(y3-y0+3y1-3y2)t^3
+    double a0 = x0;
+    double a1 = 3*(x1 - x0);
+    double a2 = 3*(x0 + x2 - 2*x1);
+    double a3 = x3 - x0 + 3*x1 - 3*x2;
+    double b0 = y0;
+    double b1 = 3*(y1 - y0);
+    double b2 = 3*(y0 + y2 - 2*y1);
+    double b3 = y3 - y0 + 3*y1 - 3*y2;
+
+    int[] polygon = new int[2*count + 2];
+    for (int i = 0; i <= count; i++) {
+        double t = cast(double)i / cast(double)count;
+        polygon[2*i] = cast(int)(a0 + a1*t + a2*t*t + a3*t*t*t);
+        polygon[2*i + 1] = cast(int)(b0 + b1*t + b2*t*t + b3*t*t*t);
+    }
+    return polygon;
+}
+static int checkStyle (int style) {
+    return DWT.NONE;
+}
+/**
+* Returns the Control that appears on the bottom side of the banner.
+*
+* @return the control that appears on the bottom side of the banner or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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
+*/
+public Control getBottom() {
+    checkWidget();
+    return bottom;
+}
+public override Rectangle getClientArea() {
+    return new Rectangle(0, 0, 0, 0);
+}
+
+/**
+* Returns the Control that appears on the left side of the banner.
+*
+* @return the control that appears on the left side of the banner or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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
+*/
+public Control getLeft() {
+    checkWidget();
+    return left;
+}
+
+/**
+* Returns the Control that appears on the right side of the banner.
+*
+* @return the control that appears on the right side of the banner or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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
+*/
+public Control getRight() {
+    checkWidget();
+    return right;
+}
+/**
+ * Returns the minimum size of the control that appears on the right of the banner.
+ *
+ * @return the minimum size of the control that appears on the right of the banner
+ *
+ * @since 3.1
+ */
+public Point getRightMinimumSize() {
+    checkWidget();
+    return new Point(rightMinWidth, rightMinHeight);
+}
+/**
+ * Returns the width of the control that appears on the right of the banner.
+ *
+ * @return the width of the control that appears on the right of the banner
+ *
+ * @since 3.0
+ */
+public int getRightWidth() {
+    checkWidget();
+    if (right is null) return 0;
+    if (rightWidth is DWT.DEFAULT) {
+        Point size = right.computeSize(DWT.DEFAULT, DWT.DEFAULT, false);
+        return size.x;
+    }
+    return rightWidth;
+}
+/**
+ * Returns <code>true</code> if the CBanner is rendered
+ * with a simple, traditional shape.
+ *
+ * @return <code>true</code> if the CBanner is rendered with a simple shape
+ *
+ * @since 3.0
+ */
+public bool getSimple() {
+    checkWidget();
+    return simple;
+}
+void onDispose() {
+    if (resizeCursor !is null) resizeCursor.dispose();
+    resizeCursor = null;
+    left = null;
+    right = null;
+    bottom = null;
+}
+void onMouseDown (int x, int y) {
+    if (curveRect.contains(x, y)) {
+        dragging = true;
+        rightDragDisplacement = curveStart - x + curve_width - curve_indent;
+    }
+}
+void onMouseExit() {
+    if (!dragging) setCursor(null);
+}
+void onMouseMove(int x, int y) {
+    if (dragging) {
+        Point size = getSize();
+        if (!(0 < x && x < size.x)) return;
+        rightWidth = Math.max(0, size.x - x - rightDragDisplacement);
+        if (rightMinWidth is DWT.DEFAULT) {
+            Point minSize = right.computeSize(rightMinWidth, rightMinHeight);
+            rightWidth = Math.max(minSize.x, rightWidth);
+        } else {
+            rightWidth = Math.max(rightMinWidth, rightWidth);
+        }
+        layout(false);
+        return;
+    }
+    if (curveRect.contains(x, y)) {
+        setCursor(resizeCursor);
+    } else {
+        setCursor(null);
+    }
+}
+void onMouseUp () {
+    dragging = false;
+}
+void onPaint(GC gc) {
+//   Useful for debugging paint problems
+//  {
+//  Point size = getSize();
+//  gc.setBackground(getDisplay().getSystemColor(DWT.COLOR_GREEN));
+//  gc.fillRectangle(-10, -10, size.x+20, size.y+20);
+//  }
+    if (left is null && right is null) return;
+    Point size = getSize();
+    Color border1 = getDisplay().getSystemColor(BORDER1);
+    if (bottom !is null) {
+        int y = bottom.getBounds().y - BORDER_STRIPE - 1;
+        gc.setForeground(border1);
+        gc.drawLine(0, y, size.x, y);
+    }
+    if (left is null || right is null) return;
+    int[] line1 = new int[curve.length+6];
+    int index = 0;
+    int x = curveStart;
+    line1[index++] = x + 1;
+    line1[index++] = size.y - BORDER_STRIPE;
+    for (int i = 0; i < curve.length/2; i++) {
+        line1[index++]=x+curve[2*i];
+        line1[index++]=curve[2*i+1];
+    }
+    line1[index++] = x + curve_width;
+    line1[index++] = 0;
+    line1[index++] = size.x;
+    line1[index++] = 0;
+
+    Color background = getBackground();
+
+    if (getDisplay().getDepth() >= 15) {
+        // Anti- aliasing
+        int[] line2 = new int[line1.length];
+        index = 0;
+        for (int i = 0; i < line1.length/2; i++) {
+            line2[index] = line1[index++]  - 1;
+            line2[index] = line1[index++];
+        }
+        int[] line3 = new int[line1.length];
+        index = 0;
+        for (int i = 0; i < line1.length/2; i++) {
+            line3[index] = line1[index++] + 1;
+            line3[index] = line1[index++];
+        }
+        RGB from = border1.getRGB();
+        RGB to = background.getRGB();
+        int red = from.red + 3*(to.red - from.red)/4;
+        int green = from.green + 3*(to.green - from.green)/4;
+        int blue = from.blue + 3*(to.blue - from.blue)/4;
+        Color color = new Color(getDisplay(), red, green, blue);
+        gc.setForeground(color);
+        gc.drawPolyline(line2);
+        gc.drawPolyline(line3);
+        color.dispose();
+
+        // draw tail fading to background
+        int x1 = Math.max(0, curveStart - CURVE_TAIL);
+        gc.setForeground(background);
+        gc.setBackground(border1);
+        gc.fillGradientRectangle(x1, size.y - BORDER_STRIPE, curveStart-x1+1, 1, false);
+    } else {
+        // draw solid tail
+        int x1 = Math.max(0, curveStart - CURVE_TAIL);
+        gc.setForeground(border1);
+        gc.drawLine(x1, size.y - BORDER_STRIPE, curveStart+1, size.y - BORDER_STRIPE);
+    }
+
+    // draw border
+    gc.setForeground(border1);
+    gc.drawPolyline(line1);
+}
+
+void onResize() {
+    updateCurve(getSize().y);
+}
+/**
+* Set the control that appears on the bottom side of the banner.
+* The bottom control is optional.  Setting the bottom control to null will remove it from
+* the banner - however, the creator of the control must dispose of the control.
+*
+* @param control the control to be displayed on the bottom or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+*    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+*    <li>ERROR_INVALID_ARGUMENT - if the bottom control was not created as a child of the receiver</li>
+* </ul>
+*
+* @since 3.0
+*/
+public void setBottom(Control control) {
+    checkWidget();
+    if (control !is null && control.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (bottom !is null && !bottom.isDisposed()) {
+        Point size = bottom.getSize();
+        bottom.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
+    }
+    bottom = control;
+    layout(false);
+}
+/**
+ * Sets the layout which is associated with the receiver to be
+ * the argument which may be null.
+ * <p>
+ * Note: No Layout can be set on this Control because it already
+ * manages the size and position of its children.
+ * </p>
+ *
+ * @param layout the receiver's new layout or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setLayout (Layout layout) {
+    checkWidget();
+    return;
+}
+
+/**
+* Set the control that appears on the left side of the banner.
+* The left control is optional.  Setting the left control to null will remove it from
+* the banner - however, the creator of the control must dispose of the control.
+*
+* @param control the control to be displayed on the left or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+*    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+*    <li>ERROR_INVALID_ARGUMENT - if the left control was not created as a child of the receiver</li>
+* </ul>
+*
+* @since 3.0
+*/
+public void setLeft(Control control) {
+    checkWidget();
+    if (control !is null && control.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (left !is null && !left.isDisposed()) {
+        Point size = left.getSize();
+        left.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
+    }
+    left = control;
+    layout(false);
+}
+/**
+* Set the control that appears on the right side of the banner.
+* The right control is optional.  Setting the right control to null will remove it from
+* the banner - however, the creator of the control must dispose of the control.
+*
+* @param control the control to be displayed on the right or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+*    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+*    <li>ERROR_INVALID_ARGUMENT - if the right control was not created as a child of the receiver</li>
+* </ul>
+*
+* @since 3.0
+*/
+public void setRight(Control control) {
+    checkWidget();
+    if (control !is null && control.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (right !is null && !right.isDisposed()) {
+        Point size = right.getSize();
+        right.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
+    }
+    right = control;
+    layout(false);
+}
+/**
+ * Set the minimum height of the control that appears on the right side of the banner.
+ *
+ * @param size the minimum size of the control on the right
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the size is null or the values of size are less than DWT.DEFAULT</li>
+ * </ul>
+ *
+ * @since 3.1
+ */
+public void setRightMinimumSize(Point size) {
+    checkWidget();
+    if (size is null || size.x < DWT.DEFAULT || size.y < DWT.DEFAULT) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    rightMinWidth = size.x;
+    rightMinHeight = size.y;
+    layout(false);
+}
+/**
+ * Set the width of the control that appears on the right side of the banner.
+ *
+ * @param width the width of the control on the right
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if width is less than DWT.DEFAULT</li>
+ * </ul>
+ *
+ * @since 3.0
+ */
+public void setRightWidth(int width) {
+    checkWidget();
+    if (width < DWT.DEFAULT) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    rightWidth = width;
+    layout(false);
+}
+/**
+ * Sets the shape that the CBanner will use to render itself.
+ *
+ * @param simple <code>true</code> if the CBanner should render itself in a simple, traditional style
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setSimple(bool simple) {
+    checkWidget();
+    if (this.simple !is simple) {
+        this.simple = simple;
+        if (simple) {
+            curve_width = 5;
+            curve_indent = -2;
+        } else {
+            curve_width = 50;
+            curve_indent = 5;
+        }
+        updateCurve(getSize().y);
+        layout(false);
+        redraw();
+    }
+}
+void updateCurve(int height) {
+    int h = height - BORDER_STRIPE;
+    if (simple) {
+        curve = [0,h, 1,h, 2,h-1, 3,h-2,
+                                   3,2, 4,1, 5,0];
+    } else {
+        curve = bezier(0, h+1, BEZIER_LEFT, h+1,
+                             curve_width-BEZIER_RIGHT, 0, curve_width, 0,
+                             curve_width);
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CBannerLayout.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.CBannerLayout;
+
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Layout;
+import dwt.widgets.Scrollable;
+import dwt.custom.CBanner;
+import dwt.custom.CLayoutData;
+
+import Math = tango.math.Math;
+
+/**
+ * This class provides the layout for CBanner
+ *
+ * @see CBanner
+ */
+class CBannerLayout : Layout {
+
+protected override Point computeSize(Composite composite, int wHint, int hHint, bool flushCache) {
+    CBanner banner = cast(CBanner)composite;
+    Control left = banner.left;
+    Control right = banner.right;
+    Control bottom = banner.bottom;
+    bool showCurve = left !is null && right !is null;
+    int height = hHint;
+    int width = wHint;
+
+    // Calculate component sizes
+    Point bottomSize = new Point(0, 0);
+    if (bottom !is null) {
+        int trim = computeTrim(bottom);
+        int w = wHint is DWT.DEFAULT ? DWT.DEFAULT : Math.max(0, width - trim);
+        bottomSize = computeChildSize(bottom, w, DWT.DEFAULT, flushCache);
+    }
+    Point rightSize = new Point(0, 0);
+    if (right !is null) {
+        int trim = computeTrim(right);
+        int w = DWT.DEFAULT;
+        if (banner.rightWidth !is DWT.DEFAULT) {
+            w = banner.rightWidth - trim;
+            if (left !is null) {
+                w = Math.min(w, width - banner.curve_width + 2* banner.curve_indent - CBanner.MIN_LEFT - trim);
+            }
+            w = Math.max(0, w);
+        }
+        rightSize = computeChildSize(right, w, DWT.DEFAULT, flushCache);
+        if (wHint !is DWT.DEFAULT) {
+            width -= rightSize.x + banner.curve_width - 2* banner.curve_indent;
+        }
+    }
+    Point leftSize = new Point(0, 0);
+    if (left !is null) {
+        int trim = computeTrim(left);
+        int w = wHint is DWT.DEFAULT ? DWT.DEFAULT : Math.max(0, width - trim);
+        leftSize = computeChildSize(left, w, DWT.DEFAULT, flushCache);
+    }
+
+    // Add up sizes
+    width = leftSize.x + rightSize.x;
+    height = bottomSize.y;
+    if (bottom !is null && (left !is null || right !is null)) {
+        height += CBanner.BORDER_STRIPE + 2;
+    }
+    if (left !is null) {
+        if (right is null) {
+            height += leftSize.y;
+        } else {
+            height += Math.max(leftSize.y, banner.rightMinHeight is DWT.DEFAULT ? rightSize.y : banner.rightMinHeight);
+        }
+    } else {
+        height += rightSize.y;
+    }
+    if (showCurve) {
+        width += banner.curve_width - 2*banner.curve_indent;
+        height +=  CBanner.BORDER_TOP + CBanner.BORDER_BOTTOM + 2*CBanner.BORDER_STRIPE;
+    }
+
+    if (wHint !is DWT.DEFAULT) width = wHint;
+    if (hHint !is DWT.DEFAULT) height = hHint;
+
+    return new Point(width, height);
+}
+Point computeChildSize(Control control, int wHint, int hHint, bool flushCache) {
+    Object data = control.getLayoutData();
+    if (data is null || !( null !is cast(CLayoutData)data)) {
+        data = new CLayoutData();
+        control.setLayoutData(data);
+    }
+    return (cast(CLayoutData)data).computeSize(control, wHint, hHint, flushCache);
+}
+int computeTrim(Control c) {
+    if ( auto s = cast(Scrollable)c) {
+        Rectangle rect = s.computeTrim (0, 0, 0, 0);
+        return rect.width;
+    }
+    return c.getBorderWidth () * 2;
+}
+protected override bool flushCache(Control control) {
+    Object data = control.getLayoutData();
+    if ( auto ld = cast(CLayoutData)data ) ld.flushCache();
+    return true;
+}
+protected override void layout(Composite composite, bool flushCache) {
+    CBanner banner = cast(CBanner)composite;
+    Control left = banner.left;
+    Control right = banner.right;
+    Control bottom = banner.bottom;
+
+    Point size = banner.getSize();
+    bool showCurve = left !is null && right !is null;
+    int width = size.x - 2*banner.getBorderWidth();
+    int height = size.y - 2*banner.getBorderWidth();
+
+    Point bottomSize = new Point(0, 0);
+    if (bottom !is null) {
+        int trim = computeTrim(bottom);
+        int w = Math.max(0, width - trim);
+        bottomSize = computeChildSize(bottom, w, DWT.DEFAULT, flushCache);
+        height -= bottomSize.y + CBanner.BORDER_STRIPE + 2;
+    }
+    if (showCurve) height -=  CBanner.BORDER_TOP + CBanner.BORDER_BOTTOM + 2*CBanner.BORDER_STRIPE;
+    height = Math.max(0, height);
+    Point rightSize = new Point(0,0);
+    if (right !is null) {
+        int trim = computeTrim(right);
+        int w = DWT.DEFAULT;
+        if (banner.rightWidth !is DWT.DEFAULT) {
+            w = banner.rightWidth - trim;
+            if (left !is null) {
+                w = Math.min(w, width - banner.curve_width + 2* banner.curve_indent - CBanner.MIN_LEFT - trim);
+            }
+            w = Math.max(0, w);
+        }
+        rightSize = computeChildSize(right, w, DWT.DEFAULT, flushCache);
+        width = width - (rightSize.x - banner.curve_indent + banner.curve_width - banner.curve_indent);
+    }
+    Point leftSize = new Point(0, 0);
+    if (left !is null) {
+        int trim = computeTrim(left);
+        int w = Math.max(0, width - trim);
+        leftSize = computeChildSize(left, w, DWT.DEFAULT, flushCache);
+    }
+
+    int x = 0;
+    int y = 0;
+    int oldStart = banner.curveStart;
+    Rectangle leftRect = null;
+    Rectangle rightRect = null;
+    Rectangle bottomRect = null;
+    if (bottom !is null) {
+        bottomRect = new Rectangle(x, y+size.y-bottomSize.y, bottomSize.x, bottomSize.y);
+    }
+    if (showCurve) y += CBanner.BORDER_TOP + CBanner.BORDER_STRIPE;
+    if(left !is null) {
+        leftRect = new Rectangle(x, y, leftSize.x, leftSize.y);
+        banner.curveStart = x + leftSize.x - banner.curve_indent;
+        x += leftSize.x - banner.curve_indent + banner.curve_width - banner.curve_indent;
+    }
+    if (right !is null) {
+        if (left !is null) {
+            rightSize.y = Math.max(leftSize.y, banner.rightMinHeight is DWT.DEFAULT ? rightSize.y : banner.rightMinHeight);
+        }
+        rightRect = new Rectangle(x, y, rightSize.x, rightSize.y);
+    }
+    if (banner.curveStart < oldStart) {
+        banner.redraw(banner.curveStart - CBanner.CURVE_TAIL, 0, oldStart + banner.curve_width - banner.curveStart + CBanner.CURVE_TAIL + 5, size.y, false);
+    }
+    if (banner.curveStart > oldStart) {
+        banner.redraw(oldStart - CBanner.CURVE_TAIL, 0, banner.curveStart + banner.curve_width - oldStart + CBanner.CURVE_TAIL + 5, size.y, false);
+    }
+    /*
+     * The paint events must be flushed in order to make the curve draw smoothly
+     * while the user drags the divider.
+     * On Windows, it is necessary to flush the paints before the children are
+     * resized because otherwise the children (particularly toolbars) will flash.
+     */
+    banner.update();
+    banner.curveRect = new Rectangle(banner.curveStart, 0, banner.curve_width, size.y);
+    if (bottomRect !is null) bottom.setBounds(bottomRect);
+    if (rightRect !is null) right.setBounds(rightRect);
+    if (leftRect !is null) left.setBounds(leftRect);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CCombo.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,1641 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.CCombo;
+
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.accessibility.ACC;
+import dwt.accessibility.AccessibleAdapter;
+import dwt.accessibility.AccessibleControlAdapter;
+import dwt.accessibility.AccessibleControlEvent;
+import dwt.accessibility.AccessibleEvent;
+import dwt.accessibility.AccessibleTextAdapter;
+import dwt.accessibility.AccessibleTextEvent;
+import dwt.events.ModifyListener;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.events.VerifyListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Button;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Label;
+import dwt.widgets.Layout;
+import dwt.widgets.List;
+import dwt.widgets.Listener;
+import dwt.widgets.Menu;
+import dwt.widgets.Shell;
+import dwt.widgets.Text;
+import dwt.widgets.TypedListener;
+import dwt.widgets.Widget;
+
+static import tango.text.convert.Utf;
+static import tango.text.Unicode;
+static import tango.text.convert.Format;
+import dwt.dwthelper.utils;
+
+/**
+ * The CCombo class represents a selectable user interface object
+ * that combines a text field and a list and issues notification
+ * when an item is selected from the list.
+ * <p>
+ * CCombo was written to work around certain limitations in the native
+ * combo box. Specifically, on win32, the height of a CCombo can be set;
+ * attempts to set the height of a Combo are ignored. CCombo can be used
+ * anywhere that having the increased flexibility is more important than
+ * getting native L&F, but the decision should not be taken lightly.
+ * There is no is no strict requirement that CCombo look or behave
+ * the same as the native combo box.
+ * </p>
+ * <p>
+ * Note that although this class is a subclass of <code>Composite</code>,
+ * it does not make sense to add children to it, or set a layout on it.
+ * </p>
+ * <dl>
+ * <dt><b>Styles:</b>
+ * <dd>BORDER, READ_ONLY, FLAT</dd>
+ * <dt><b>Events:</b>
+ * <dd>DefaultSelection, Modify, Selection, Verify</dd>
+ * </dl>
+ */
+public final class CCombo : Composite {
+
+    alias Composite.computeSize computeSize;
+
+    Text text;
+    List list;
+    int visibleItemCount = 5;
+    Shell popup;
+    Button arrow;
+    bool hasFocus;
+    Listener listener, filter;
+    Color foreground, background;
+    Font font;
+
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#BORDER
+ * @see DWT#READ_ONLY
+ * @see DWT#FLAT
+ * @see Widget#getStyle()
+ */
+public this (Composite parent, int style) {
+    super (parent, style = checkStyle (style));
+
+    int textStyle = DWT.SINGLE;
+    if ((style & DWT.READ_ONLY) !is 0) textStyle |= DWT.READ_ONLY;
+    if ((style & DWT.FLAT) !is 0) textStyle |= DWT.FLAT;
+    text = new Text (this, textStyle);
+    int arrowStyle = DWT.ARROW | DWT.DOWN;
+    if ((style & DWT.FLAT) !is 0) arrowStyle |= DWT.FLAT;
+    arrow = new Button (this, arrowStyle);
+
+    listener = new class() Listener {
+        public void handleEvent (Event event) {
+            if (popup is event.widget) {
+                popupEvent (event);
+                return;
+            }
+            if (text is event.widget) {
+                textEvent (event);
+                return;
+            }
+            if (list is event.widget) {
+                listEvent (event);
+                return;
+            }
+            if (arrow is event.widget) {
+                arrowEvent (event);
+                return;
+            }
+            if (this.outer is event.widget) {
+                comboEvent (event);
+                return;
+            }
+            if (getShell () is event.widget) {
+                handleFocus (DWT.FocusOut);
+            }
+        }
+    };
+    filter = new class() Listener {
+        public void handleEvent(Event event) {
+            Shell shell = (cast(Control)event.widget).getShell ();
+            if (shell is this.outer.getShell ()) {
+                handleFocus (DWT.FocusOut);
+            }
+        }
+    };
+
+    int [] comboEvents = [DWT.Dispose, DWT.Move, DWT.Resize];
+    for (int i=0; i<comboEvents.length; i++) this.addListener (comboEvents [i], listener);
+
+    int [] textEvents = [DWT.KeyDown, DWT.KeyUp, DWT.MenuDetect, DWT.Modify, DWT.MouseDown, DWT.MouseUp, DWT.Traverse, DWT.FocusIn, DWT.Verify];
+    for (int i=0; i<textEvents.length; i++) text.addListener (textEvents [i], listener);
+
+    int [] arrowEvents = [DWT.Selection, DWT.FocusIn];
+    for (int i=0; i<arrowEvents.length; i++) arrow.addListener (arrowEvents [i], listener);
+
+    createPopup(null, -1);
+    initAccessible();
+}
+static int checkStyle (int style) {
+    int mask = DWT.BORDER | DWT.READ_ONLY | DWT.FLAT | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
+    return style & mask;
+}
+/**
+ * Adds the argument to the end of the receiver's list.
+ *
+ * @param string the new item
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 #add(String,int)
+ */
+public void add (char[] string) {
+    checkWidget();
+    if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    list.add (string);
+}
+/**
+ * Adds the argument to the receiver's list at the given
+ * zero-relative index.
+ * <p>
+ * Note: To add an item at the end of the list, use the
+ * result of calling <code>getItemCount()</code> as the
+ * index or use <code>add(String)</code>.
+ * </p>
+ *
+ * @param string the new item
+ * @param index the index for the item
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
+ *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list (inclusive)</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 #add(String)
+ */
+public void add (char[] string, int index) {
+    checkWidget();
+    if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    list.add (string, index);
+}
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when the receiver's text is modified, by sending
+ * it one of the messages defined in the <code>ModifyListener</code>
+ * interface.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 ModifyListener
+ * @see #removeModifyListener
+ */
+public void addModifyListener (ModifyListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    TypedListener typedListener = new TypedListener (listener);
+    addListener (DWT.Modify, typedListener);
+}
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when the user changes the receiver's selection, by sending
+ * it one of the messages defined in the <code>SelectionListener</code>
+ * interface.
+ * <p>
+ * <code>widgetSelected</code> is called when the combo's list selection changes.
+ * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed the combo's text area.
+ * </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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    TypedListener typedListener = new TypedListener (listener);
+    addListener (DWT.Selection,typedListener);
+    addListener (DWT.DefaultSelection,typedListener);
+}
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when the receiver's text is verified, by sending
+ * it one of the messages defined in the <code>VerifyListener</code>
+ * interface.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 VerifyListener
+ * @see #removeVerifyListener
+ *
+ * @since 3.3
+ */
+public void addVerifyListener (VerifyListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    TypedListener typedListener = new TypedListener (listener);
+    addListener (DWT.Verify,typedListener);
+}
+void arrowEvent (Event event) {
+    switch (event.type) {
+        case DWT.FocusIn: {
+            handleFocus (DWT.FocusIn);
+            break;
+        }
+        case DWT.Selection: {
+            dropDown (!isDropped ());
+            break;
+        }
+        default:
+    }
+}
+/**
+ * Sets the selection in the receiver's text field to an empty
+ * selection starting just before the first character. If the
+ * text field is editable, this has the effect of placing the
+ * i-beam at the start of the text.
+ * <p>
+ * Note: To clear the selected items in the receiver's list,
+ * use <code>deselectAll()</code>.
+ * </p>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 #deselectAll
+ */
+public void clearSelection () {
+    checkWidget ();
+    text.clearSelection ();
+    list.deselectAll ();
+}
+void comboEvent (Event event) {
+    switch (event.type) {
+        case DWT.Dispose:
+            if (popup !is null && !popup.isDisposed ()) {
+                list.removeListener (DWT.Dispose, listener);
+                popup.dispose ();
+            }
+            Shell shell = getShell ();
+            shell.removeListener (DWT.Deactivate, listener);
+            Display display = getDisplay ();
+            display.removeFilter (DWT.FocusIn, filter);
+            popup = null;
+            text = null;
+            list = null;
+            arrow = null;
+            break;
+        case DWT.Move:
+            dropDown (false);
+            break;
+        case DWT.Resize:
+            internalLayout (false);
+            break;
+        default:
+    }
+}
+
+public override Point computeSize (int wHint, int hHint, bool changed) {
+    checkWidget ();
+    int width = 0, height = 0;
+    char[][] items = list.getItems ();
+    GC gc = new GC (text);
+    int spacer = gc.stringExtent (" ").x; //$NON-NLS-1$
+    int textWidth = gc.stringExtent (text.getText ()).x;
+    for (int i = 0; i < items.length; i++) {
+        textWidth = Math.max (gc.stringExtent (items[i]).x, textWidth);
+    }
+    gc.dispose ();
+    Point textSize = text.computeSize (DWT.DEFAULT, DWT.DEFAULT, changed);
+    Point arrowSize = arrow.computeSize (DWT.DEFAULT, DWT.DEFAULT, changed);
+    Point listSize = list.computeSize (DWT.DEFAULT, DWT.DEFAULT, changed);
+    int borderWidth = getBorderWidth ();
+
+    height = Math.max (textSize.y, arrowSize.y);
+    width = Math.max (textWidth + 2*spacer + arrowSize.x + 2*borderWidth, listSize.x);
+    if (wHint !is DWT.DEFAULT) width = wHint;
+    if (hHint !is DWT.DEFAULT) height = hHint;
+    return new Point (width + 2*borderWidth, height + 2*borderWidth);
+}
+/**
+ * Copies the selected text.
+ * <p>
+ * The current selection is copied to the clipboard.
+ * </p>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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.3
+ */
+public void copy () {
+    checkWidget ();
+    text.copy ();
+}
+void createPopup(char[][] items, int selectionIndex) {
+    // create shell and list
+    popup = new Shell (getShell (), DWT.NO_TRIM | DWT.ON_TOP);
+    int style = getStyle ();
+    int listStyle = DWT.SINGLE | DWT.V_SCROLL;
+    if ((style & DWT.FLAT) !is 0) listStyle |= DWT.FLAT;
+    if ((style & DWT.RIGHT_TO_LEFT) !is 0) listStyle |= DWT.RIGHT_TO_LEFT;
+    if ((style & DWT.LEFT_TO_RIGHT) !is 0) listStyle |= DWT.LEFT_TO_RIGHT;
+    list = new List (popup, listStyle);
+    if (font !is null) list.setFont (font);
+    if (foreground !is null) list.setForeground (foreground);
+    if (background !is null) list.setBackground (background);
+
+    int [] popupEvents = [DWT.Close, DWT.Paint, DWT.Deactivate];
+    for (int i=0; i<popupEvents.length; i++) popup.addListener (popupEvents [i], listener);
+    int [] listEvents = [DWT.MouseUp, DWT.Selection, DWT.Traverse, DWT.KeyDown, DWT.KeyUp, DWT.FocusIn, DWT.Dispose];
+    for (int i=0; i<listEvents.length; i++) list.addListener (listEvents [i], listener);
+
+    if (items !is null) list.setItems (items);
+    if (selectionIndex !is -1) list.setSelection (selectionIndex);
+}
+/**
+ * Cuts the selected text.
+ * <p>
+ * The current selection is first copied to the
+ * clipboard and then deleted from the widget.
+ * </p>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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.3
+ */
+public void cut () {
+    checkWidget ();
+    text.cut ();
+}
+/**
+ * Deselects the item at the given zero-relative index in the receiver's
+ * list.  If the item at the index was already deselected, it remains
+ * deselected. Indices that are out of range are ignored.
+ *
+ * @param index the index of the item to deselect
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 deselect (int index) {
+    checkWidget ();
+    list.deselect (index);
+}
+/**
+ * Deselects all selected items in the receiver's list.
+ * <p>
+ * Note: To clear the selection in the receiver's text field,
+ * use <code>clearSelection()</code>.
+ * </p>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 #clearSelection
+ */
+public void deselectAll () {
+    checkWidget ();
+    list.deselectAll ();
+}
+void dropDown (bool drop) {
+    if (drop is isDropped ()) return;
+    if (!drop) {
+        popup.setVisible (false);
+        if (!isDisposed ()&& arrow.isFocusControl()) {
+            text.setFocus();
+        }
+        return;
+    }
+
+    if (getShell() !is popup.getParent ()) {
+        char[][] items = list.getItems ();
+        int selectionIndex = list.getSelectionIndex ();
+        list.removeListener (DWT.Dispose, listener);
+        popup.dispose();
+        popup = null;
+        list = null;
+        createPopup (items, selectionIndex);
+    }
+
+    Point size = getSize ();
+    int itemCount = list.getItemCount ();
+    itemCount = (itemCount is 0) ? visibleItemCount : Math.min(visibleItemCount, itemCount);
+    int itemHeight = list.getItemHeight () * itemCount;
+    Point listSize = list.computeSize (DWT.DEFAULT, itemHeight, false);
+    list.setBounds (1, 1, Math.max (size.x - 2, listSize.x), listSize.y);
+
+    int index = list.getSelectionIndex ();
+    if (index !is -1) list.setTopIndex (index);
+    Display display = getDisplay ();
+    Rectangle listRect = list.getBounds ();
+    Rectangle parentRect = display.map (getParent (), null, getBounds ());
+    Point comboSize = getSize ();
+    Rectangle displayRect = getMonitor ().getClientArea ();
+    int width = Math.max (comboSize.x, listRect.width + 2);
+    int height = listRect.height + 2;
+    int x = parentRect.x;
+    int y = parentRect.y + comboSize.y;
+    if (y + height > displayRect.y + displayRect.height) y = parentRect.y - height;
+    if (x + width > displayRect.x + displayRect.width) x = displayRect.x + displayRect.width - listRect.width;
+    popup.setBounds (x, y, width, height);
+    popup.setVisible (true);
+    list.setFocus ();
+}
+/*
+ * 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 (char[] 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 '&') {
+            dchar[1] d; uint ate;
+            auto d2 = tango.text.convert.Utf.toString32( string[ index .. Math.min( index +4, string.length )], d, &ate );
+            auto d3 = tango.text.Unicode.toLower( d2, d2 );
+            return d3[0];
+        }
+        index++;
+    } while (index < length);
+    return '\0';
+}
+/*
+ * 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;
+}
+public override Control [] getChildren () {
+    checkWidget();
+    return new Control [0];
+}
+/**
+ * Gets the editable state.
+ *
+ * @return whether or not the receiver is editable
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public bool getEditable () {
+    checkWidget ();
+    return text.getEditable();
+}
+/**
+ * Returns the item at the given, zero-relative index in the
+ * receiver's list. Throws an exception if the index is out
+ * of range.
+ *
+ * @param index the index of the item to return
+ * @return the item at the given index
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] getItem (int index) {
+    checkWidget();
+    return list.getItem (index);
+}
+/**
+ * Returns the number of items contained in the receiver's list.
+ *
+ * @return the number of items
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getItemCount () {
+    checkWidget ();
+    return list.getItemCount ();
+}
+/**
+ * Returns the height of the area which would be used to
+ * display <em>one</em> of the items in the receiver's list.
+ *
+ * @return the height of one item
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getItemHeight () {
+    checkWidget ();
+    return list.getItemHeight ();
+}
+/**
+ * Returns an array of <code>String</code>s which are the items
+ * in the receiver's list.
+ * <p>
+ * Note: This is not the actual structure used by the receiver
+ * to maintain its list of items, so modifying the array will
+ * not affect the receiver.
+ * </p>
+ *
+ * @return the items in the receiver's list
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] [] getItems () {
+    checkWidget ();
+    return list.getItems ();
+}
+public override Menu getMenu() {
+    return text.getMenu();
+}
+/**
+ * Returns a <code>Point</code> whose x coordinate is the start
+ * of the selection in the receiver's text field, and whose y
+ * coordinate is the end of the selection. The returned values
+ * are zero-relative. An "empty" selection as indicated by
+ * the the x and y coordinates having the same value.
+ *
+ * @return a point representing the selection start and end
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 text.getSelection ();
+}
+/**
+ * Returns the zero-relative index of the item which is currently
+ * selected in the receiver's list, or -1 if no item is selected.
+ *
+ * @return the index of the selected item
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getSelectionIndex () {
+    checkWidget ();
+    return list.getSelectionIndex ();
+}
+public override int getStyle () {
+    int style = super.getStyle ();
+    style &= ~DWT.READ_ONLY;
+    if (!text.getEditable()) style |= DWT.READ_ONLY;
+    return style;
+}
+/**
+ * Returns a string containing a copy of the contents of the
+ * receiver's text field.
+ *
+ * @return the receiver's text
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] getText () {
+    checkWidget ();
+    return text.getText ();
+}
+/**
+ * Returns the height of the receivers's text field.
+ *
+ * @return the text height
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getTextHeight () {
+    checkWidget ();
+    return text.getLineHeight ();
+}
+/**
+ * Returns the maximum number of characters that the receiver's
+ * text field is capable of holding. If this has not been changed
+ * by <code>setTextLimit()</code>, it will be the constant
+ * <code>Combo.LIMIT</code>.
+ *
+ * @return the text limit
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 text.getTextLimit ();
+}
+/**
+ * Gets the number of items that are visible in the drop
+ * down portion of the receiver's list.
+ *
+ * @return the number of items that are visible
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public int getVisibleItemCount () {
+    checkWidget ();
+    return visibleItemCount;
+}
+void handleFocus (int type) {
+    if (isDisposed ()) return;
+    switch (type) {
+        case DWT.FocusIn: {
+            if (hasFocus) return;
+            if (getEditable ()) text.selectAll ();
+            hasFocus = true;
+            Shell shell = getShell ();
+            shell.removeListener (DWT.Deactivate, listener);
+            shell.addListener (DWT.Deactivate, listener);
+            Display display = getDisplay ();
+            display.removeFilter (DWT.FocusIn, filter);
+            display.addFilter (DWT.FocusIn, filter);
+            Event e = new Event ();
+            notifyListeners (DWT.FocusIn, e);
+            break;
+        }
+        case DWT.FocusOut: {
+            if (!hasFocus) return;
+            Control focusControl = getDisplay ().getFocusControl ();
+            if (focusControl is arrow || focusControl is list || focusControl is text) return;
+            hasFocus = false;
+            Shell shell = getShell ();
+            shell.removeListener(DWT.Deactivate, listener);
+            Display display = getDisplay ();
+            display.removeFilter (DWT.FocusIn, filter);
+            Event e = new Event ();
+            notifyListeners (DWT.FocusOut, e);
+            break;
+        }
+        default:
+    }
+}
+/**
+ * Searches the receiver's list starting at the first item
+ * (index 0) until an item is found that is equal to the
+ * argument, and returns the index of that item. If no item
+ * is found, returns -1.
+ *
+ * @param string the search item
+ * @return the index of the item
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 indexOf (char[] string) {
+    checkWidget ();
+    if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    return list.indexOf (string);
+}
+/**
+ * Searches the receiver's list starting at the given,
+ * zero-relative index until an item is found that is equal
+ * to the argument, and returns the index of that item. If
+ * no item is found or the starting index is out of range,
+ * returns -1.
+ *
+ * @param string the search item
+ * @param start the zero-relative index at which to begin the search
+ * @return the index of the item
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 indexOf (char[] string, int start) {
+    checkWidget ();
+    if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    return list.indexOf (string, start);
+}
+
+void initAccessible() {
+    AccessibleAdapter accessibleAdapter = new class() AccessibleAdapter {
+        public void getName (AccessibleEvent e) {
+            char[] name = null;
+            Label label = getAssociatedLabel ();
+            if (label !is null) {
+                name = stripMnemonic (label.getText());
+            }
+            e.result = name;
+        }
+        public void getKeyboardShortcut(AccessibleEvent e) {
+            char[] shortcut = null;
+            Label label = getAssociatedLabel ();
+            if (label !is null) {
+                char[] text = label.getText ();
+                if (text !is null) {
+                    dchar mnemonic = _findMnemonic (text);
+                    if (mnemonic !is '\0') {
+                        shortcut = tango.text.convert.Format.Format( "Alt+{}", mnemonic ); //$NON-NLS-1$
+                    }
+                }
+            }
+            e.result = shortcut;
+        }
+        public void getHelp (AccessibleEvent e) {
+            e.result = getToolTipText ();
+        }
+    };
+    getAccessible ().addAccessibleListener (accessibleAdapter);
+    text.getAccessible ().addAccessibleListener (accessibleAdapter);
+    list.getAccessible ().addAccessibleListener (accessibleAdapter);
+
+    arrow.getAccessible ().addAccessibleListener (new class() AccessibleAdapter {
+        public void getName (AccessibleEvent e) {
+            e.result = isDropped () ? DWT.getMessage ("SWT_Close") : DWT.getMessage ("SWT_Open"); //$NON-NLS-1$ //$NON-NLS-2$
+        }
+        public void getKeyboardShortcut (AccessibleEvent e) {
+            e.result = "Alt+Down Arrow"; //$NON-NLS-1$
+        }
+        public void getHelp (AccessibleEvent e) {
+            e.result = getToolTipText ();
+        }
+    });
+
+    getAccessible().addAccessibleTextListener (new class() AccessibleTextAdapter {
+        public void getCaretOffset (AccessibleTextEvent e) {
+            e.offset = text.getCaretPosition ();
+        }
+        public void getSelectionRange(AccessibleTextEvent e) {
+            Point sel = text.getSelection();
+            e.offset = sel.x;
+            e.length = sel.y - sel.x;
+        }
+    });
+
+    getAccessible().addAccessibleControlListener (new class() AccessibleControlAdapter {
+        public void getChildAtPoint (AccessibleControlEvent e) {
+            Point testPoint = toControl (e.x, e.y);
+            if (getBounds ().contains (testPoint)) {
+                e.childID = ACC.CHILDID_SELF;
+            }
+        }
+
+        public void getLocation (AccessibleControlEvent e) {
+            Rectangle location = getBounds ();
+            Point pt = toDisplay (location.x, location.y);
+            e.x = pt.x;
+            e.y = pt.y;
+            e.width = location.width;
+            e.height = location.height;
+        }
+
+        public void getChildCount (AccessibleControlEvent e) {
+            e.detail = 0;
+        }
+
+        public void getRole (AccessibleControlEvent e) {
+            e.detail = ACC.ROLE_COMBOBOX;
+        }
+
+        public void getState (AccessibleControlEvent e) {
+            e.detail = ACC.STATE_NORMAL;
+        }
+
+        public void getValue (AccessibleControlEvent e) {
+            e.result = getText ();
+        }
+    });
+
+    text.getAccessible ().addAccessibleControlListener (new class() AccessibleControlAdapter {
+        public void getRole (AccessibleControlEvent e) {
+            e.detail = text.getEditable () ? ACC.ROLE_TEXT : ACC.ROLE_LABEL;
+        }
+    });
+
+    arrow.getAccessible ().addAccessibleControlListener (new class() AccessibleControlAdapter {
+        public void getDefaultAction (AccessibleControlEvent e) {
+            e.result = isDropped () ? DWT.getMessage ("SWT_Close") : DWT.getMessage ("SWT_Open"); //$NON-NLS-1$ //$NON-NLS-2$
+        }
+    });
+}
+bool isDropped () {
+    return popup.getVisible ();
+}
+public override bool isFocusControl () {
+    checkWidget();
+    if (text.isFocusControl () || arrow.isFocusControl () || list.isFocusControl () || popup.isFocusControl ()) {
+        return true;
+    }
+    return super.isFocusControl ();
+}
+void internalLayout (bool changed) {
+    if (isDropped ()) dropDown (false);
+    Rectangle rect = getClientArea ();
+    int width = rect.width;
+    int height = rect.height;
+    Point arrowSize = arrow.computeSize (DWT.DEFAULT, height, changed);
+    text.setBounds (0, 0, width - arrowSize.x, height);
+    arrow.setBounds (width - arrowSize.x, 0, arrowSize.x, arrowSize.y);
+}
+void listEvent (Event event) {
+    switch (event.type) {
+        case DWT.Dispose:
+            if (getShell () !is popup.getParent ()) {
+                char[][] items = list.getItems ();
+                int selectionIndex = list.getSelectionIndex ();
+                popup = null;
+                list = null;
+                createPopup (items, selectionIndex);
+            }
+            break;
+        case DWT.FocusIn: {
+            handleFocus (DWT.FocusIn);
+            break;
+        }
+        case DWT.MouseUp: {
+            if (event.button !is 1) return;
+            dropDown (false);
+            break;
+        }
+        case DWT.Selection: {
+            int index = list.getSelectionIndex ();
+            if (index is -1) return;
+            text.setText (list.getItem (index));
+            text.selectAll ();
+            list.setSelection (index);
+            Event e = new Event ();
+            e.time = event.time;
+            e.stateMask = event.stateMask;
+            e.doit = event.doit;
+            notifyListeners (DWT.Selection, e);
+            event.doit = e.doit;
+            break;
+        }
+        case DWT.Traverse: {
+            switch (event.detail) {
+                case DWT.TRAVERSE_RETURN:
+                case DWT.TRAVERSE_ESCAPE:
+                case DWT.TRAVERSE_ARROW_PREVIOUS:
+                case DWT.TRAVERSE_ARROW_NEXT:
+                    event.doit = false;
+                    break;
+                default:
+            }
+            Event e = new Event ();
+            e.time = event.time;
+            e.detail = event.detail;
+            e.doit = event.doit;
+            e.character = event.character;
+            e.keyCode = event.keyCode;
+            notifyListeners (DWT.Traverse, e);
+            event.doit = e.doit;
+            event.detail = e.detail;
+            break;
+        }
+        case DWT.KeyUp: {
+            Event e = new Event ();
+            e.time = event.time;
+            e.character = event.character;
+            e.keyCode = event.keyCode;
+            e.stateMask = event.stateMask;
+            notifyListeners (DWT.KeyUp, e);
+            break;
+        }
+        case DWT.KeyDown: {
+            if (event.character is DWT.ESC) {
+                // Escape key cancels popup list
+                dropDown (false);
+            }
+            if ((event.stateMask & DWT.ALT) !is 0 && (event.keyCode is DWT.ARROW_UP || event.keyCode is DWT.ARROW_DOWN)) {
+                dropDown (false);
+            }
+            if (event.character is DWT.CR) {
+                // Enter causes default selection
+                dropDown (false);
+                Event e = new Event ();
+                e.time = event.time;
+                e.stateMask = event.stateMask;
+                notifyListeners (DWT.DefaultSelection, e);
+            }
+            // At this point the widget may have been disposed.
+            // If so, do not continue.
+            if (isDisposed ()) break;
+            Event e = new Event();
+            e.time = event.time;
+            e.character = event.character;
+            e.keyCode = event.keyCode;
+            e.stateMask = event.stateMask;
+            notifyListeners(DWT.KeyDown, e);
+            break;
+
+        }
+        default:
+    }
+}
+/**
+ * Pastes text from clipboard.
+ * <p>
+ * The selected text is deleted from the widget
+ * and new text inserted from the clipboard.
+ * </p>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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.3
+ */
+public void paste () {
+    checkWidget ();
+    text.paste ();
+}
+void popupEvent(Event event) {
+    switch (event.type) {
+        case DWT.Paint:
+            // draw black rectangle around list
+            Rectangle listRect = list.getBounds();
+            Color black = getDisplay().getSystemColor(DWT.COLOR_BLACK);
+            event.gc.setForeground(black);
+            event.gc.drawRectangle(0, 0, listRect.width + 1, listRect.height + 1);
+            break;
+        case DWT.Close:
+            event.doit = false;
+            dropDown (false);
+            break;
+        case DWT.Deactivate:
+            dropDown (false);
+            break;
+        default:
+    }
+}
+public override void redraw () {
+    super.redraw();
+    text.redraw();
+    arrow.redraw();
+    if (popup.isVisible()) list.redraw();
+}
+public override void redraw (int x, int y, int width, int height, bool all) {
+    super.redraw(x, y, width, height, true);
+}
+
+/**
+ * Removes the item from the receiver's list at the given
+ * zero-relative index.
+ *
+ * @param index the index for the item
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 remove (int index) {
+    checkWidget();
+    list.remove (index);
+}
+/**
+ * Removes the items from the receiver's list which are
+ * between the given zero-relative start and end
+ * indices (inclusive).
+ *
+ * @param start the start of the range
+ * @param end the end of the range
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if either the start or end are not between 0 and the number of elements in the list minus 1 (inclusive)</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 remove (int start, int end) {
+    checkWidget();
+    list.remove (start, end);
+}
+/**
+ * Searches the receiver's list starting at the first item
+ * until an item is found that is equal to the argument,
+ * and removes that item from the list.
+ *
+ * @param string the item to remove
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the string is not found in the list</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 remove (char[] string) {
+    checkWidget();
+    if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    list.remove (string);
+}
+/**
+ * Removes all of the items from the receiver's list and clear the
+ * contents of receiver's text field.
+ * <p>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 removeAll () {
+    checkWidget();
+    text.setText (""); //$NON-NLS-1$
+    list.removeAll ();
+}
+/**
+ * Removes the listener from the collection of listeners who will
+ * be notified when the receiver's text is modified.
+ *
+ * @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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 ModifyListener
+ * @see #addModifyListener
+ */
+public void removeModifyListener (ModifyListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Modify, 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Selection, listener);
+    removeListener(DWT.DefaultSelection,listener);
+}
+/**
+ * Removes the listener from the collection of listeners who will
+ * be notified when the control is verified.
+ *
+ * @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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 VerifyListener
+ * @see #addVerifyListener
+ *
+ * @since 3.3
+ */
+public void removeVerifyListener (VerifyListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Verify, listener);
+}
+/**
+ * Selects the item at the given zero-relative index in the receiver's
+ * list.  If the item at the index was already selected, it remains
+ * selected. Indices that are out of range are ignored.
+ *
+ * @param index the index of the item to select
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 select (int index) {
+    checkWidget();
+    if (index is -1) {
+        list.deselectAll ();
+        text.setText (""); //$NON-NLS-1$
+        return;
+    }
+    if (0 <= index && index < list.getItemCount()) {
+        if (index !is getSelectionIndex()) {
+            text.setText (list.getItem (index));
+            text.selectAll ();
+            list.select (index);
+            list.showSelection ();
+        }
+    }
+}
+public override void setBackground (Color color) {
+    super.setBackground(color);
+    background = color;
+    if (text !is null) text.setBackground(color);
+    if (list !is null) list.setBackground(color);
+    if (arrow !is null) arrow.setBackground(color);
+}
+/**
+ * Sets the editable state.
+ *
+ * @param editable the new editable state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setEditable (bool editable) {
+    checkWidget ();
+    text.setEditable(editable);
+}
+public override void setEnabled (bool enabled) {
+    super.setEnabled(enabled);
+    if (popup !is null) popup.setVisible (false);
+    if (text !is null) text.setEnabled(enabled);
+    if (arrow !is null) arrow.setEnabled(enabled);
+}
+public override bool setFocus () {
+    checkWidget();
+    if (isFocusControl ()) return true;
+    return text.setFocus ();
+}
+public override void setFont (Font font) {
+    super.setFont (font);
+    this.font = font;
+    text.setFont (font);
+    list.setFont (font);
+    internalLayout (true);
+}
+public override void setForeground (Color color) {
+    super.setForeground(color);
+    foreground = color;
+    if (text !is null) text.setForeground(color);
+    if (list !is null) list.setForeground(color);
+    if (arrow !is null) arrow.setForeground(color);
+}
+/**
+ * Sets the text of the item in the receiver's list at the given
+ * zero-relative index to the string argument. This is equivalent
+ * to <code>remove</code>'ing the old item at the index, and then
+ * <code>add</code>'ing the new item at that index.
+ *
+ * @param index the index for the item
+ * @param string the new text for the item
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
+ *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setItem (int index, char[] string) {
+    checkWidget();
+    list.setItem (index, string);
+}
+/**
+ * Sets the receiver's list to be the given array of items.
+ *
+ * @param items the array of items
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the items array is null</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if an item in the items array is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setItems (char[] [] items) {
+    checkWidget ();
+    list.setItems (items);
+    if (!text.getEditable ()) text.setText (""); //$NON-NLS-1$
+}
+/**
+ * Sets the layout which is associated with the receiver to be
+ * the argument which may be null.
+ * <p>
+ * Note: No Layout can be set on this Control because it already
+ * manages the size and position of its children.
+ * </p>
+ *
+ * @param layout the receiver's new layout or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setLayout (Layout layout) {
+    checkWidget ();
+    return;
+}
+public override void setMenu(Menu menu) {
+    text.setMenu(menu);
+}
+/**
+ * Sets the selection in the receiver's text field to the
+ * range specified by the argument whose x coordinate is the
+ * start of the selection and whose y coordinate is the end
+ * of the selection.
+ *
+ * @param selection a point representing the new selection start and end
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setSelection (Point selection) {
+    checkWidget();
+    if (selection is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    text.setSelection (selection.x, selection.y);
+}
+
+/**
+ * Sets the contents of the receiver's text field to the
+ * given string.
+ * <p>
+ * Note: The text field in a <code>Combo</code> is typically
+ * only capable of displaying a single line of text. Thus,
+ * setting the text to a string containing line breaks or
+ * other special characters will probably cause it to
+ * display incorrectly.
+ * </p>
+ *
+ * @param string the new text
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 (char[] string) {
+    checkWidget();
+    if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    int index = list.indexOf (string);
+    if (index is -1) {
+        list.deselectAll ();
+        text.setText (string);
+        return;
+    }
+    text.setText (string);
+    text.selectAll ();
+    list.setSelection (index);
+    list.showSelection ();
+}
+/**
+ * Sets the maximum number of characters that the receiver's
+ * text field is capable of holding to be the argument.
+ *
+ * @param limit new text limit
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_CANNOT_BE_ZERO - if the limit is zero</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setTextLimit (int limit) {
+    checkWidget();
+    text.setTextLimit (limit);
+}
+
+public override void setToolTipText (char[] string) {
+    checkWidget();
+    super.setToolTipText(string);
+    arrow.setToolTipText (string);
+    text.setToolTipText (string);
+}
+
+public override void setVisible (bool visible) {
+    super.setVisible(visible);
+    /*
+     * At this point the widget may have been disposed in a FocusOut event.
+     * If so then do not continue.
+     */
+    if (isDisposed ()) return;
+    if (!visible) popup.setVisible(false);
+}
+/**
+ * Sets the number of items that are visible in the drop
+ * down portion of the receiver's list.
+ *
+ * @param count the new number of items to be visible
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setVisibleItemCount (int count) {
+    checkWidget ();
+    if (count < 0) return;
+    visibleItemCount = count;
+}
+char[] stripMnemonic (char[] 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[0 .. index-1] ~ string[index .. length_];
+        }
+        index++;
+    } while (index < length_);
+    return string;
+}
+void textEvent (Event event) {
+    switch (event.type) {
+        case DWT.FocusIn: {
+            handleFocus (DWT.FocusIn);
+            break;
+        }
+        case DWT.KeyDown: {
+            Event keyEvent = new Event ();
+            keyEvent.time = event.time;
+            keyEvent.character = event.character;
+            keyEvent.keyCode = event.keyCode;
+            keyEvent.stateMask = event.stateMask;
+            notifyListeners (DWT.KeyDown, keyEvent);
+            if (isDisposed ()) break;
+            event.doit = keyEvent.doit;
+            if (!event.doit) break;
+
+            if (event.character is DWT.CR) {
+                dropDown (false);
+                Event selectionEvent = new Event ();
+                selectionEvent.time = event.time;
+                selectionEvent.stateMask = event.stateMask;
+                notifyListeners (DWT.DefaultSelection, selectionEvent);
+                if (isDisposed ()) break;
+            }
+
+            if (event.keyCode is DWT.ARROW_UP || event.keyCode is DWT.ARROW_DOWN) {
+                event.doit = false;
+                if ((event.stateMask & DWT.ALT) !is 0) {
+                    bool dropped = isDropped ();
+                    text.selectAll ();
+                    if (!dropped) setFocus ();
+                    dropDown (!dropped);
+                    break;
+                }
+
+                int oldIndex = getSelectionIndex ();
+                if (event.keyCode is DWT.ARROW_UP) {
+                    select (Math.max (oldIndex - 1, 0));
+                } else {
+                    select (Math.min (oldIndex + 1, getItemCount () - 1));
+                }
+                if (oldIndex !is getSelectionIndex ()) {
+                    Event e = new Event();
+                    e.time = event.time;
+                    e.stateMask = event.stateMask;
+                    notifyListeners (DWT.Selection, e);
+                }
+                if (isDisposed ()) break;
+            }
+
+            // Further work : Need to add support for incremental search in
+            // pop up list as characters typed in text widget
+            break;
+        }
+        case DWT.KeyUp: {
+            Event e = new Event ();
+            e.time = event.time;
+            e.character = event.character;
+            e.keyCode = event.keyCode;
+            e.stateMask = event.stateMask;
+            notifyListeners (DWT.KeyUp, e);
+            event.doit = e.doit;
+            break;
+        }
+        case DWT.MenuDetect: {
+            Event e = new Event ();
+            e.time = event.time;
+            notifyListeners (DWT.MenuDetect, e);
+            break;
+        }
+        case DWT.Modify: {
+            list.deselectAll ();
+            Event e = new Event ();
+            e.time = event.time;
+            notifyListeners (DWT.Modify, e);
+            break;
+        }
+        case DWT.MouseDown: {
+            if (event.button !is 1) return;
+            if (text.getEditable ()) return;
+            bool dropped = isDropped ();
+            text.selectAll ();
+            if (!dropped) setFocus ();
+            dropDown (!dropped);
+            break;
+        }
+        case DWT.MouseUp: {
+            if (event.button !is 1) return;
+            if (text.getEditable ()) return;
+            text.selectAll ();
+            break;
+        }
+        case DWT.Traverse: {
+            switch (event.detail) {
+                case DWT.TRAVERSE_RETURN:
+                case DWT.TRAVERSE_ARROW_PREVIOUS:
+                case DWT.TRAVERSE_ARROW_NEXT:
+                    // The enter causes default selection and
+                    // the arrow keys are used to manipulate the list contents so
+                    // do not use them for traversal.
+                    event.doit = false;
+                    break;
+                default:
+            }
+
+            Event e = new Event ();
+            e.time = event.time;
+            e.detail = event.detail;
+            e.doit = event.doit;
+            e.character = event.character;
+            e.keyCode = event.keyCode;
+            notifyListeners (DWT.Traverse, e);
+            event.doit = e.doit;
+            event.detail = e.detail;
+            break;
+        }
+        case DWT.Verify: {
+            Event e = new Event ();
+            e.text = event.text;
+            e.start = event.start;
+            e.end = event.end;
+            e.character = event.character;
+            e.keyCode = event.keyCode;
+            e.stateMask = event.stateMask;
+            notifyListeners (DWT.Verify, e);
+            event.doit = e.doit;
+            break;
+        }
+        default:
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CLabel.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,837 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.CLabel;
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.accessibility.ACC;
+import dwt.accessibility.Accessible;
+import dwt.accessibility.AccessibleAdapter;
+import dwt.accessibility.AccessibleControlAdapter;
+import dwt.accessibility.AccessibleControlEvent;
+import dwt.accessibility.AccessibleEvent;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.events.TraverseEvent;
+import dwt.events.TraverseListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Canvas;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+
+static import tango.text.Unicode;
+static import tango.text.convert.Utf;
+import dwt.dwthelper.utils;
+
+/**
+ * A Label which supports aligned text and/or an image and different border styles.
+ * <p>
+ * If there is not enough space a CLabel uses the following strategy to fit the
+ * information into the available space:
+ * <pre>
+ *      ignores the indent in left align mode
+ *      ignores the image and the gap
+ *      shortens the text by replacing the center portion of the label with an ellipsis
+ *      shortens the text by removing the center portion of the label
+ * </pre>
+ * <p>
+ * <dl>
+ * <dt><b>Styles:</b>
+ * <dd>LEFT, RIGHT, CENTER, SHADOW_IN, SHADOW_OUT, SHADOW_NONE</dd>
+ * <dt><b>Events:</b>
+ * <dd></dd>
+ * </dl>
+ *
+ * </p><p>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </p>
+ */
+public class CLabel : Canvas {
+
+    alias Canvas.computeSize computeSize;
+
+    /** Gap between icon and text */
+    private static const int GAP = 5;
+    /** Left and right margins */
+    private static const int INDENT = 3;
+    /** a string inserted in the middle of text that has been shortened */
+    private static const char[] ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026"
+    /** the alignment. Either CENTER, RIGHT, LEFT. Default is LEFT*/
+    private int align_ = DWT.LEFT;
+    private int hIndent = INDENT;
+    private int vIndent = INDENT;
+    /** the current text */
+    private char[] text;
+    /** the current icon */
+    private Image image;
+    // The tooltip is used for two purposes - the application can set
+    // a tooltip or the tooltip can be used to display the full text when the
+    // the text has been truncated due to the label being too short.
+    // The appToolTip stores the tooltip set by the application.  Control.tooltiptext
+    // contains whatever tooltip is currently being displayed.
+    private char[] appToolTipText;
+
+    private Image backgroundImage;
+    private Color[] gradientColors;
+    private int[] gradientPercents;
+    private bool gradientVertical;
+    private Color background;
+
+    private static int DRAW_FLAGS = DWT.DRAW_MNEMONIC | DWT.DRAW_TAB | DWT.DRAW_TRANSPARENT | DWT.DRAW_DELIMITER;
+
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#LEFT
+ * @see DWT#RIGHT
+ * @see DWT#CENTER
+ * @see DWT#SHADOW_IN
+ * @see DWT#SHADOW_OUT
+ * @see DWT#SHADOW_NONE
+ * @see #getStyle()
+ */
+public this(Composite parent, int style) {
+    super(parent, checkStyle(style));
+    if ((style & (DWT.CENTER | DWT.RIGHT)) is 0) style |= DWT.LEFT;
+    if ((style & DWT.CENTER) !is 0) align_ = DWT.CENTER;
+    if ((style & DWT.RIGHT) !is 0)  align_ = DWT.RIGHT;
+    if ((style & DWT.LEFT) !is 0)   align_ = DWT.LEFT;
+
+    addPaintListener(new class() PaintListener{
+        public void paintControl(PaintEvent event) {
+            onPaint(event);
+        }
+    });
+
+    addDisposeListener(new class() DisposeListener{
+        public void widgetDisposed(DisposeEvent event) {
+            onDispose(event);
+        }
+    });
+
+    addTraverseListener(new class() TraverseListener {
+        public void keyTraversed(TraverseEvent event) {
+            if (event.detail is DWT.TRAVERSE_MNEMONIC) {
+                onMnemonic(event);
+            }
+        }
+    });
+
+    initAccessible();
+
+}
+/**
+ * Check the style bits to ensure that no invalid styles are applied.
+ */
+private static int checkStyle (int style) {
+    if ((style & DWT.BORDER) !is 0) style |= DWT.SHADOW_IN;
+    int mask = DWT.SHADOW_IN | DWT.SHADOW_OUT | DWT.SHADOW_NONE | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
+    style = style & mask;
+    return style |= DWT.NO_FOCUS | DWT.DOUBLE_BUFFERED;
+}
+
+//protected void checkSubclass () {
+//  String name = getClass().getName ();
+//  String validName = CLabel.class.getName();
+//  if (!validName.equals(name)) {
+//      DWT.error (DWT.ERROR_INVALID_SUBCLASS);
+//  }
+//}
+
+public override Point computeSize(int wHint, int hHint, bool changed) {
+    checkWidget();
+    Point e = getTotalSize(image, text);
+    if (wHint is DWT.DEFAULT){
+        e.x += 2*hIndent;
+    } else {
+        e.x = wHint;
+    }
+    if (hHint is DWT.DEFAULT) {
+        e.y += 2*vIndent;
+    } else {
+        e.y = hHint;
+    }
+    return e;
+}
+/**
+ * Draw a rectangle in the given colors.
+ */
+private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright) {
+    gc.setForeground(bottomright);
+    gc.drawLine(x+w, y,   x+w, y+h);
+    gc.drawLine(x,   y+h, x+w, y+h);
+
+    gc.setForeground(topleft);
+    gc.drawLine(x, y, x+w-1, y);
+    gc.drawLine(x, y, x,     y+h-1);
+}
+/*
+ * 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 (char[] 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 '&') {
+            dchar[1] tmp; uint ate;
+            dchar[] tmp2 = tango.text.convert.Utf.toString32( string[index .. Math.min( index + 4, string.length ) ], tmp, &ate );
+            assert( tmp2.length == 1 );
+            return tango.text.Unicode.toLower( tmp2 )[0];
+        }
+        index++;
+    } while (index < length);
+    return '\0';
+}
+/**
+ * Returns the alignment.
+ * The alignment style (LEFT, CENTER or RIGHT) is returned.
+ *
+ * @return DWT.LEFT, DWT.RIGHT or DWT.CENTER
+ */
+public int getAlignment() {
+    //checkWidget();
+    return align_;
+}
+/**
+ * Return the CLabel's image or <code>null</code>.
+ *
+ * @return the image of the label or null
+ */
+public Image getImage() {
+    //checkWidget();
+    return image;
+}
+/**
+ * Compute the minimum size.
+ */
+private Point getTotalSize(Image image, char[] text) {
+    Point size = new Point(0, 0);
+
+    if (image !is null) {
+        Rectangle r = image.getBounds();
+        size.x += r.width;
+        size.y += r.height;
+    }
+
+    GC gc = new GC(this);
+    if (text !is null && text.length > 0) {
+        Point e = gc.textExtent(text, DRAW_FLAGS);
+        size.x += e.x;
+        size.y = Math.max(size.y, e.y);
+        if (image !is null) size.x += GAP;
+    } else {
+        size.y = Math.max(size.y, gc.getFontMetrics().getHeight());
+    }
+    gc.dispose();
+
+    return size;
+}
+public override int getStyle () {
+    int style = super.getStyle();
+    switch (align_) {
+        case DWT.RIGHT: style |= DWT.RIGHT; break;
+        case DWT.CENTER: style |= DWT.CENTER; break;
+        case DWT.LEFT: style |= DWT.LEFT; break;
+        default:
+    }
+    return style;
+}
+
+/**
+ * Return the Label's text.
+ *
+ * @return the text of the label or null
+ */
+public char[] getText() {
+    //checkWidget();
+    return text;
+}
+public override char[] getToolTipText () {
+    checkWidget();
+    return appToolTipText;
+}
+private void initAccessible() {
+    Accessible accessible = getAccessible();
+    accessible.addAccessibleListener(new class() AccessibleAdapter {
+        public void getName(AccessibleEvent e) {
+            e.result = getText();
+        }
+
+        public void getHelp(AccessibleEvent e) {
+            e.result = getToolTipText();
+        }
+
+        public void getKeyboardShortcut(AccessibleEvent e) {
+            dchar mnemonic = _findMnemonic(this.outer.text);
+            if (mnemonic !is '\0') {
+                dchar[1] d;
+                d[0] = mnemonic;
+                e.result = "Alt+" ~ tango.text.convert.Utf.toString(d); //$NON-NLS-1$
+            }
+        }
+    });
+
+    accessible.addAccessibleControlListener(new class() AccessibleControlAdapter {
+        public void getChildAtPoint(AccessibleControlEvent e) {
+            e.childID = ACC.CHILDID_SELF;
+        }
+
+        public void getLocation(AccessibleControlEvent e) {
+            Rectangle rect = getDisplay().map(getParent(), null, getBounds());
+            e.x = rect.x;
+            e.y = rect.y;
+            e.width = rect.width;
+            e.height = rect.height;
+        }
+
+        public void getChildCount(AccessibleControlEvent e) {
+            e.detail = 0;
+        }
+
+        public void getRole(AccessibleControlEvent e) {
+            e.detail = ACC.ROLE_LABEL;
+        }
+
+        public void getState(AccessibleControlEvent e) {
+            e.detail = ACC.STATE_READONLY;
+        }
+    });
+}
+void onDispose(DisposeEvent event) {
+    gradientColors = null;
+    gradientPercents = null;
+    backgroundImage = null;
+    text = null;
+    image = null;
+    appToolTipText = null;
+}
+void onMnemonic(TraverseEvent event) {
+    dchar mnemonic = _findMnemonic(text);
+    if (mnemonic is '\0') return;
+    dchar[1] d; uint ate;
+    auto r = tango.text.convert.Utf.toString32( [event.character][], d, &ate );
+    if (tango.text.Unicode.toLower(r)[0] !is mnemonic) return;
+    Composite control = this.getParent();
+    while (control !is null) {
+        Control [] children = control.getChildren();
+        int index = 0;
+        while (index < children.length) {
+            if (children [index] is this) break;
+            index++;
+        }
+        index++;
+        if (index < children.length) {
+            if (children [index].setFocus ()) {
+                event.doit = true;
+                event.detail = DWT.TRAVERSE_NONE;
+            }
+        }
+        control = control.getParent();
+    }
+}
+
+void onPaint(PaintEvent event) {
+    Rectangle rect = getClientArea();
+    if (rect.width is 0 || rect.height is 0) return;
+
+    bool shortenText_ = false;
+    char[] t = text;
+    Image img = image;
+    int availableWidth = Math.max(0, rect.width - 2*hIndent);
+    Point extent = getTotalSize(img, t);
+    if (extent.x > availableWidth) {
+        img = null;
+        extent = getTotalSize(img, t);
+        if (extent.x > availableWidth) {
+            shortenText_ = true;
+        }
+    }
+
+    GC gc = event.gc;
+    char[][] lines = text is null ? null : splitString(text);
+
+    // shorten the text
+    if (shortenText_) {
+        extent.x = 0;
+        for(int i = 0; i < lines.length; i++) {
+            Point e = gc.textExtent(lines[i], DRAW_FLAGS);
+            if (e.x > availableWidth) {
+                lines[i] = shortenText(gc, lines[i], availableWidth);
+                extent.x = Math.max(extent.x, getTotalSize(null, lines[i]).x);
+            } else {
+                extent.x = Math.max(extent.x, e.x);
+            }
+        }
+        if (appToolTipText is null) {
+            super.setToolTipText(text);
+        }
+    } else {
+        super.setToolTipText(appToolTipText);
+    }
+
+    // determine horizontal position
+    int x = rect.x + hIndent;
+    if (align_ is DWT.CENTER) {
+        x = (rect.width - extent.x)/2;
+    }
+    if (align_ is DWT.RIGHT) {
+        x = rect.width - hIndent - extent.x;
+    }
+
+    // draw a background image behind the text
+    try {
+        if (backgroundImage !is null) {
+            // draw a background image behind the text
+            Rectangle imageRect = backgroundImage.getBounds();
+            // tile image to fill space
+            gc.setBackground(getBackground());
+            gc.fillRectangle(rect);
+            int xPos = 0;
+            while (xPos < rect.width) {
+                int yPos = 0;
+                while (yPos < rect.height) {
+                    gc.drawImage(backgroundImage, xPos, yPos);
+                    yPos += imageRect.height;
+                }
+                xPos += imageRect.width;
+            }
+        } else if (gradientColors !is null) {
+            // draw a gradient behind the text
+            final Color oldBackground = gc.getBackground();
+            if (gradientColors.length is 1) {
+                if (gradientColors[0] !is null) gc.setBackground(gradientColors[0]);
+                gc.fillRectangle(0, 0, rect.width, rect.height);
+            } else {
+                final Color oldForeground = gc.getForeground();
+                Color lastColor = gradientColors[0];
+                if (lastColor is null) lastColor = oldBackground;
+                int pos = 0;
+                for (int i = 0; i < gradientPercents.length; ++i) {
+                    gc.setForeground(lastColor);
+                    lastColor = gradientColors[i + 1];
+                    if (lastColor is null) lastColor = oldBackground;
+                    gc.setBackground(lastColor);
+                    if (gradientVertical) {
+                        final int gradientHeight = (gradientPercents[i] * rect.height / 100) - pos;
+                        gc.fillGradientRectangle(0, pos, rect.width, gradientHeight, true);
+                        pos += gradientHeight;
+                    } else {
+                        final int gradientWidth = (gradientPercents[i] * rect.width / 100) - pos;
+                        gc.fillGradientRectangle(pos, 0, gradientWidth, rect.height, false);
+                        pos += gradientWidth;
+                    }
+                }
+                if (gradientVertical && pos < rect.height) {
+                    gc.setBackground(getBackground());
+                    gc.fillRectangle(0, pos, rect.width, rect.height - pos);
+                }
+                if (!gradientVertical && pos < rect.width) {
+                    gc.setBackground(getBackground());
+                    gc.fillRectangle(pos, 0, rect.width - pos, rect.height);
+                }
+                gc.setForeground(oldForeground);
+            }
+            gc.setBackground(oldBackground);
+        } else {
+            if (background !is null || (getStyle() & DWT.DOUBLE_BUFFERED) is 0) {
+                gc.setBackground(getBackground());
+                gc.fillRectangle(rect);
+            }
+        }
+    } catch (DWTException e) {
+        if ((getStyle() & DWT.DOUBLE_BUFFERED) is 0) {
+            gc.setBackground(getBackground());
+            gc.fillRectangle(rect);
+        }
+    }
+
+    // draw border
+    int style = getStyle();
+    if ((style & DWT.SHADOW_IN) !is 0 || (style & DWT.SHADOW_OUT) !is 0) {
+        paintBorder(gc, rect);
+    }
+
+    // draw the image
+    if (img !is null) {
+        Rectangle imageRect = img.getBounds();
+        gc.drawImage(img, 0, 0, imageRect.width, imageRect.height,
+                        x, (rect.height-imageRect.height)/2, imageRect.width, imageRect.height);
+        x +=  imageRect.width + GAP;
+        extent.x -= imageRect.width + GAP;
+    }
+    // draw the text
+    if (lines !is null) {
+        int lineHeight = gc.getFontMetrics().getHeight();
+        int textHeight = lines.length * lineHeight;
+        int lineY = Math.max(vIndent, rect.y + (rect.height - textHeight) / 2);
+        gc.setForeground(getForeground());
+        for (int i = 0; i < lines.length; i++) {
+            int lineX = x;
+            if (lines.length > 1) {
+                if (align_ is DWT.CENTER) {
+                    int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x;
+                    lineX = x + Math.max(0, (extent.x - lineWidth) / 2);
+                }
+                if (align_ is DWT.RIGHT) {
+                    int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x;
+                    lineX = Math.max(x, rect.x + rect.width - hIndent - lineWidth);
+                }
+            }
+            gc.drawText(lines[i], lineX, lineY, DRAW_FLAGS);
+            lineY += lineHeight;
+        }
+    }
+}
+/**
+ * Paint the Label's border.
+ */
+private void paintBorder(GC gc, Rectangle r) {
+    Display disp= getDisplay();
+
+    Color c1 = null;
+    Color c2 = null;
+
+    int style = getStyle();
+    if ((style & DWT.SHADOW_IN) !is 0) {
+        c1 = disp.getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW);
+        c2 = disp.getSystemColor(DWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
+    }
+    if ((style & DWT.SHADOW_OUT) !is 0) {
+        c1 = disp.getSystemColor(DWT.COLOR_WIDGET_LIGHT_SHADOW);
+        c2 = disp.getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW);
+    }
+
+    if (c1 !is null && c2 !is null) {
+        gc.setLineWidth(1);
+        drawBevelRect(gc, r.x, r.y, r.width-1, r.height-1, c1, c2);
+    }
+}
+/**
+ * Set the alignment of the CLabel.
+ * Use the values LEFT, CENTER and RIGHT to align image and text within the available space.
+ *
+ * @param align the alignment style of LEFT, RIGHT or CENTER
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the value of align is not one of DWT.LEFT, DWT.RIGHT or DWT.CENTER</li>
+ * </ul>
+ */
+public void setAlignment(int align_) {
+    checkWidget();
+    if (align_ !is DWT.LEFT && align_ !is DWT.RIGHT && align_ !is DWT.CENTER) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (this.align_ !is align_) {
+        this.align_ = align_;
+        redraw();
+    }
+}
+
+public override void setBackground (Color color) {
+    super.setBackground (color);
+    // Are these settings the same as before?
+    if (backgroundImage is null &&
+        gradientColors is null &&
+        gradientPercents is null) {
+        if (color is null) {
+            if (background is null) return;
+        } else {
+            if (color ==/*eq*/ background) return;
+        }
+    }
+    background = color;
+    backgroundImage = null;
+    gradientColors = null;
+    gradientPercents = null;
+    redraw ();
+}
+
+/**
+ * Specify a gradient of colours to be drawn in the background of the CLabel.
+ * <p>For example, to draw a gradient that varies from dark blue to blue and then to
+ * white and stays white for the right half of the label, use the following call
+ * to setBackground:</p>
+ * <pre>
+ *  clabel.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE)},
+ *                     new int[] {25, 50, 100});
+ * </pre>
+ *
+ * @param colors an array of Color that specifies the colors to appear in the gradient
+ *               in order of appearance from left to right;  The value <code>null</code>
+ *               clears the background gradient; the value <code>null</code> can be used
+ *               inside the array of Color to specify the background color.
+ * @param percents an array of integers between 0 and 100 specifying the percent of the width
+ *                 of the widget at which the color should change; the size of the percents
+ *                 array must be one less than the size of the colors array.
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li>
+ * </ul>
+ */
+public void setBackground(Color[] colors, int[] percents) {
+    setBackground(colors, percents, false);
+}
+/**
+ * Specify a gradient of colours to be drawn in the background of the CLabel.
+ * <p>For example, to draw a gradient that varies from dark blue to white in the vertical,
+ * direction use the following call
+ * to setBackground:</p>
+ * <pre>
+ *  clabel.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE)},
+ *                       new int[] {100}, true);
+ * </pre>
+ *
+ * @param colors an array of Color that specifies the colors to appear in the gradient
+ *               in order of appearance from left/top to right/bottom;  The value <code>null</code>
+ *               clears the background gradient; the value <code>null</code> can be used
+ *               inside the array of Color to specify the background color.
+ * @param percents an array of integers between 0 and 100 specifying the percent of the width/height
+ *                 of the widget at which the color should change; the size of the percents
+ *                 array must be one less than the size of the colors array.
+ * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li>
+ * </ul>
+ *
+ * @since 3.0
+ */
+public void setBackground(Color[] colors, int[] percents, bool vertical) {
+    checkWidget();
+    if (colors !is null) {
+        if (percents is null || percents.length !is colors.length - 1) {
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+        }
+        if (getDisplay().getDepth() < 15) {
+            // Don't use gradients on low color displays
+            colors = [colors[colors.length - 1]];
+            percents = null;
+        }
+        for (int i = 0; i < percents.length; i++) {
+            if (percents[i] < 0 || percents[i] > 100) {
+                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            }
+            if (i > 0 && percents[i] < percents[i-1]) {
+                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            }
+        }
+    }
+
+    // Are these settings the same as before?
+    final Color background = getBackground();
+    if (backgroundImage is null) {
+        if ((gradientColors !is null) && (colors !is null) &&
+            (gradientColors.length is colors.length)) {
+            bool same = false;
+            for (int i = 0; i < gradientColors.length; i++) {
+                same = (gradientColors[i] is colors[i]) ||
+                    ((gradientColors[i] is null) && (colors[i] is background)) ||
+                    ((gradientColors[i] is background) && (colors[i] is null));
+                if (!same) break;
+            }
+            if (same) {
+                for (int i = 0; i < gradientPercents.length; i++) {
+                    same = gradientPercents[i] is percents[i];
+                    if (!same) break;
+                }
+            }
+            if (same && this.gradientVertical is vertical) return;
+        }
+    } else {
+        backgroundImage = null;
+    }
+    // Store the new settings
+    if (colors is null) {
+        gradientColors = null;
+        gradientPercents = null;
+        gradientVertical = false;
+    } else {
+        gradientColors = new Color[colors.length];
+        for (int i = 0; i < colors.length; ++i)
+            gradientColors[i] = (colors[i] !is null) ? colors[i] : background;
+        gradientPercents = new int[percents.length];
+        for (int i = 0; i < percents.length; ++i)
+            gradientPercents[i] = percents[i];
+        gradientVertical = vertical;
+    }
+    // Refresh with the new settings
+    redraw();
+}
+/**
+ * Set the image to be drawn in the background of the label.
+ *
+ * @param image the image to be drawn in the background
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setBackground(Image image) {
+    checkWidget();
+    if (image is backgroundImage) return;
+    if (image !is null) {
+        gradientColors = null;
+        gradientPercents = null;
+    }
+    backgroundImage = image;
+    redraw();
+
+}
+public override void setFont(Font font) {
+    super.setFont(font);
+    redraw();
+}
+/**
+ * Set the label's Image.
+ * The value <code>null</code> clears it.
+ *
+ * @param image the image to be displayed in the label or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setImage(Image image) {
+    checkWidget();
+    if (image !is this.image) {
+        this.image = image;
+        redraw();
+    }
+}
+/**
+ * Set the label's text.
+ * The value <code>null</code> clears it.
+ *
+ * @param text the text to be displayed in the label or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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(char[] text) {
+    checkWidget();
+    if (text is null) text = ""; //$NON-NLS-1$
+    if ( text !=/*eq*/ this.text) {
+        this.text = text;
+        redraw();
+    }
+}
+public override void setToolTipText (char[] string) {
+    super.setToolTipText (string);
+    appToolTipText = super.getToolTipText();
+}
+/**
+ * Shorten the given text <code>t</code> so that its length doesn't exceed
+ * the given width. The default implementation replaces characters in the
+ * center of the original string with an ellipsis ("...").
+ * Override if you need a different strategy.
+ *
+ * @param gc the gc to use for text measurement
+ * @param t the text to shorten
+ * @param width the width to shorten the text to, in pixels
+ * @return the shortened text
+ */
+protected char[] shortenText(GC gc, char[] t, int width) {
+    if (t is null) return null;
+    int w = gc.textExtent(ELLIPSIS, DRAW_FLAGS).x;
+    if (width<=w) return t;
+    int l = t.length;
+    int max = l/2;
+    int min = 0;
+    int mid = (max+min)/2 - 1;
+    if (mid <= 0) return t;
+    while (min < mid && mid < max) {
+        char[] s1 = t[0 .. mid].dup;
+        char[] s2 = t[l-mid .. l].dup;
+        int l1 = gc.textExtent(s1, DRAW_FLAGS).x;
+        int l2 = gc.textExtent(s2, DRAW_FLAGS).x;
+        if (l1+w+l2 > width) {
+            max = mid;
+            mid = (max+min)/2;
+        } else if (l1+w+l2 < width) {
+            min = mid;
+            mid = (max+min)/2;
+        } else {
+            min = max;
+        }
+    }
+    if (mid is 0) return t;
+    return t[ 0 .. mid ] ~ ELLIPSIS ~ t[ l-mid .. l ];
+}
+
+private char[][] splitString(char[] text) {
+    char[][] lines = new char[][1];
+    int start = 0, pos;
+    do {
+        pos = tango.text.Util.locate( text, '\n', start);
+        if (pos is text.length ) {
+            lines[lines.length - 1] = text[start .. $ ];
+        } else {
+            bool crlf = (pos > 0) && (text[ pos - 1 ] is '\r');
+            lines[lines.length - 1] = text[ start .. pos - (crlf ? 1 : 0)];
+            start = pos + 1;
+            char[][] newLines = new char[][lines.length+1];
+            System.arraycopy(lines, 0, newLines, 0, lines.length);
+            lines = newLines;
+        }
+    } while (pos !is text.length);
+    return lines;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CLayoutData.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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 dwt.custom.CLayoutData;
+
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.widgets.Control;
+
+class CLayoutData {
+
+    int defaultWidth = -1, defaultHeight = -1;
+    int currentWhint, currentHhint, currentWidth = -1, currentHeight = -1;
+
+Point computeSize (Control control, int wHint, int hHint, bool flushCache_) {
+    if (flushCache_) flushCache();
+    if (wHint is DWT.DEFAULT && hHint is DWT.DEFAULT) {
+        if (defaultWidth is -1 || defaultHeight is -1) {
+            Point size = control.computeSize (wHint, hHint, flushCache_);
+            defaultWidth = size.x;
+            defaultHeight = size.y;
+        }
+        return new Point(defaultWidth, defaultHeight);
+    }
+    if (currentWidth is -1 || currentHeight is -1 || wHint !is currentWhint || hHint !is currentHhint) {
+        Point size = control.computeSize (wHint, hHint, flushCache_);
+        currentWhint = wHint;
+        currentHhint = hHint;
+        currentWidth = size.x;
+        currentHeight = size.y;
+    }
+    return new Point(currentWidth, currentHeight);
+}
+void flushCache () {
+    defaultWidth = defaultHeight = -1;
+    currentWidth = currentHeight = -1;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabFolder.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,4061 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.CTabFolder;
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.accessibility.ACC;
+import dwt.accessibility.Accessible;
+import dwt.accessibility.AccessibleAdapter;
+import dwt.accessibility.AccessibleControlAdapter;
+import dwt.accessibility.AccessibleControlEvent;
+import dwt.accessibility.AccessibleEvent;
+import dwt.events.SelectionAdapter;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.FontData;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.RGB;
+import dwt.graphics.Rectangle;
+import dwt.graphics.Region;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.widgets.Menu;
+import dwt.widgets.MenuItem;
+import dwt.widgets.TypedListener;
+import dwt.custom.CTabItem;
+import dwt.custom.CTabFolder2Listener;
+import dwt.custom.CTabFolderListener;
+import dwt.custom.CTabFolderLayout;
+import dwt.custom.CTabFolderEvent;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Integer;
+import tango.util.Convert;
+static import tango.text.convert.Utf;
+
+/**
+ *
+ * Instances of this class implement the notebook user interface
+ * metaphor.  It allows the user to select a notebook page from
+ * set of pages.
+ * <p>
+ * The item children that may be added to instances of this class
+ * must be of type <code>CTabItem</code>.
+ * <code>Control</code> children are created and then set into a
+ * tab item using <code>CTabItem#setControl</code>.
+ * </p><p>
+ * Note that although this class is a subclass of <code>Composite</code>,
+ * it does not make sense to set a layout on it.
+ * </p><p>
+ * <dl>
+ * <dt><b>Styles:</b></dt>
+ * <dd>CLOSE, TOP, BOTTOM, FLAT, BORDER, SINGLE, MULTI</dd>
+ * <dt><b>Events:</b></dt>
+ * <dd>Selection</dd>
+ * <dd>"CTabFolder2"</dd>
+ * </dl>
+ * <p>
+ * Note: Only one of the styles TOP and BOTTOM
+ * may be specified.
+ * </p><p>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </p>
+ */
+
+public class CTabFolder : Composite {
+
+    /**
+     * marginWidth specifies the number of pixels of horizontal margin
+     * that will be placed along the left and right edges of the form.
+     *
+     * The default value is 0.
+     */
+    public int marginWidth = 0;
+    /**
+     * marginHeight specifies the number of pixels of vertical margin
+     * that will be placed along the top and bottom edges of the form.
+     *
+     * The default value is 0.
+     */
+    public int marginHeight = 0;
+
+    /**
+     * A multiple of the tab height that specifies the minimum width to which a tab
+     * will be compressed before scrolling arrows are used to navigate the tabs.
+     *
+     * NOTE This field is badly named and can not be fixed for backwards compatibility.
+     * It should not be capitalized.
+     *
+     * @deprecated This field is no longer used.  See setMinimumCharacters(int)
+     */
+    public int MIN_TAB_WIDTH = 4;
+
+    /**
+     * Color of innermost line of drop shadow border.
+     *
+     * NOTE This field is badly named and can not be fixed for backwards compatibility.
+     * It should be capitalized.
+     *
+     * @deprecated drop shadow border is no longer drawn in 3.0
+     */
+    public static RGB borderInsideRGB;
+    /**
+     * Color of middle line of drop shadow border.
+     *
+     * NOTE This field is badly named and can not be fixed for backwards compatibility.
+     * It should be capitalized.
+     *
+     * @deprecated drop shadow border is no longer drawn in 3.0
+     */
+    public static RGB borderMiddleRGB;
+    /**
+     * Color of outermost line of drop shadow border.
+     *
+     * NOTE This field is badly named and can not be fixed for backwards compatibility.
+     * It should be capitalized.
+     *
+     * @deprecated drop shadow border is no longer drawn in 3.0
+     */
+    public static RGB borderOutsideRGB;
+
+    /* sizing, positioning */
+    int xClient, yClient;
+    bool onBottom = false;
+    bool single = false;
+    bool simple = true;
+    int fixedTabHeight = DWT.DEFAULT;
+    int tabHeight;
+    int minChars = 20;
+
+    /* item management */
+    CTabItem items[];
+    int firstIndex = -1; // index of the left most visible tab.
+    int selectedIndex = -1;
+    int[] priority;
+    bool mru = false;
+    Listener listener;
+
+    /* External Listener management */
+    CTabFolder2Listener[] folderListeners;
+    // support for deprecated listener mechanism
+    CTabFolderListener[] tabListeners;
+
+    /* Selected item appearance */
+    Image selectionBgImage;
+    Color[] selectionGradientColors;
+    int[] selectionGradientPercents;
+    bool selectionGradientVertical;
+    Color selectionForeground;
+    Color selectionBackground;  //selection fade end
+    Color selectionFadeStart;
+
+    Color selectionHighlightGradientBegin = null;  //null is no highlight
+    //Although we are given new colours all the time to show different states (active, etc),
+    //some of which may have a highlight and some not, we'd like to retain the highlight colours
+    //as a cache so that we can reuse them if we're again told to show the highlight.
+    //We are relying on the fact that only one tab state usually gets a highlight, so only
+    //a single cache is required. If that happens to not be true, cache simply becomes less effective,
+    //but we don't leak colours.
+    Color[] selectionHighlightGradientColorsCache = null;  //null is a legal value, check on access
+
+    /* Unselected item appearance */
+    Image bgImage;
+    Color[] gradientColors;
+    int[] gradientPercents;
+    bool gradientVertical;
+    bool showUnselectedImage = true;
+
+    static Color borderColor;
+
+    // close, min/max and chevron buttons
+    bool showClose = false;
+    bool showUnselectedClose = true;
+
+    Rectangle chevronRect;
+    int chevronImageState = NORMAL;
+    bool showChevron = false;
+    Menu showMenu;
+
+    bool showMin = false;
+    Rectangle minRect;
+    bool minimized = false;
+    int minImageState = NORMAL;
+
+    bool showMax = false;
+    Rectangle maxRect;
+    bool maximized = false;
+    int maxImageState = NORMAL;
+
+    Control topRight;
+    Rectangle topRightRect;
+    int topRightAlignment = DWT.RIGHT;
+
+    // borders and shapes
+    int borderLeft = 0;
+    int borderRight = 0;
+    int borderTop = 0;
+    int borderBottom = 0;
+
+    int highlight_margin = 0;
+    int highlight_header = 0;
+
+    int[] curve;
+    int[] topCurveHighlightStart;
+    int[] topCurveHighlightEnd;
+    int curveWidth = 0;
+    int curveIndent = 0;
+
+    // when disposing CTabFolder, don't try to layout the items or
+    // change the selection as each child is destroyed.
+    bool inDispose = false;
+
+    // keep track of size changes in order to redraw only affected area
+    // on Resize
+    Point oldSize;
+    Font oldFont;
+
+    // internal constants
+    static const int DEFAULT_WIDTH = 64;
+    static const int DEFAULT_HEIGHT = 64;
+    static const int BUTTON_SIZE = 18;
+
+    static const int[] TOP_LEFT_CORNER = [0,6, 1,5, 1,4, 4,1, 5,1, 6,0];
+
+    //TOP_LEFT_CORNER_HILITE is laid out in reverse (ie. top to bottom)
+    //so can fade in same direction as right swoop curve
+    static const int[] TOP_LEFT_CORNER_HILITE = [5,2, 4,2, 3,3, 2,4, 2,5, 1,6];
+
+    static const int[] TOP_RIGHT_CORNER = [-6,0, -5,1, -4,1, -1,4, -1,5, 0,6];
+    static const int[] BOTTOM_LEFT_CORNER = [0,-6, 1,-5, 1,-4, 4,-1, 5,-1, 6,0];
+    static const int[] BOTTOM_RIGHT_CORNER = [-6,0, -5,-1, -4,-1, -1,-4, -1,-5, 0,-6];
+
+    static const int[] SIMPLE_TOP_LEFT_CORNER = [0,2, 1,1, 2,0];
+    static const int[] SIMPLE_TOP_RIGHT_CORNER = [-2,0, -1,1, 0,2];
+    static const int[] SIMPLE_BOTTOM_LEFT_CORNER = [0,-2, 1,-1, 2,0];
+    static const int[] SIMPLE_BOTTOM_RIGHT_CORNER = [-2,0, -1,-1, 0,-2];
+    static const int[] SIMPLE_UNSELECTED_INNER_CORNER = [0,0];
+
+    static const int[] TOP_LEFT_CORNER_BORDERLESS = [0,6, 1,5, 1,4, 4,1, 5,1, 6,0];
+    static const int[] TOP_RIGHT_CORNER_BORDERLESS = [-7,0, -6,1, -5,1, -2,4, -2,5, -1,6];
+    static const int[] BOTTOM_LEFT_CORNER_BORDERLESS = [0,-6, 1,-6, 1,-5, 2,-4, 4,-2, 5,-1, 6,-1, 6,0];
+    static const int[] BOTTOM_RIGHT_CORNER_BORDERLESS = [-7,0, -7,-1, -6,-1, -5,-2, -3,-4, -2,-5, -2,-6, -1,-6];
+
+    static const int[] SIMPLE_TOP_LEFT_CORNER_BORDERLESS = [0,2, 1,1, 2,0];
+    static const int[] SIMPLE_TOP_RIGHT_CORNER_BORDERLESS= [-3,0, -2,1, -1,2];
+    static const int[] SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS = [0,-3, 1,-2, 2,-1, 3,0];
+    static const int[] SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS = [-4,0, -3,-1, -2,-2, -1,-3];
+
+    static const int SELECTION_FOREGROUND = DWT.COLOR_LIST_FOREGROUND;
+    static const int SELECTION_BACKGROUND = DWT.COLOR_LIST_BACKGROUND;
+    static const int BORDER1_COLOR = DWT.COLOR_WIDGET_NORMAL_SHADOW;
+    static const int FOREGROUND = DWT.COLOR_WIDGET_FOREGROUND;
+    static const int BACKGROUND = DWT.COLOR_WIDGET_BACKGROUND;
+    static const int BUTTON_BORDER = DWT.COLOR_WIDGET_DARK_SHADOW;
+    static const int BUTTON_FILL = DWT.COLOR_LIST_BACKGROUND;
+
+    static const int NONE = 0;
+    static const int NORMAL = 1;
+    static const int HOT = 2;
+    static const int SELECTED = 3;
+    static const RGB CLOSE_FILL;
+
+    static const int CHEVRON_CHILD_ID = 0;
+    static const int MINIMIZE_CHILD_ID = 1;
+    static const int MAXIMIZE_CHILD_ID = 2;
+    static const int EXTRA_CHILD_ID_COUNT = 3;
+
+static this(){
+    borderInsideRGB  = new RGB (132, 130, 132);
+    borderMiddleRGB  = new RGB (143, 141, 138);
+    borderOutsideRGB = new RGB (171, 168, 165);
+    CLOSE_FILL = new RGB(252, 160, 160);
+}
+
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#TOP
+ * @see DWT#BOTTOM
+ * @see DWT#FLAT
+ * @see DWT#BORDER
+ * @see DWT#SINGLE
+ * @see DWT#MULTI
+ * @see #getStyle()
+ */
+public this(Composite parent, int style) {
+    chevronRect = new Rectangle(0, 0, 0, 0);
+    minRect = new Rectangle(0, 0, 0, 0);
+    maxRect = new Rectangle(0, 0, 0, 0);
+    topRightRect = new Rectangle(0, 0, 0, 0);
+    super(parent, checkStyle (parent, style));
+    super.setLayout(new CTabFolderLayout());
+    int style2 = super.getStyle();
+    oldFont = getFont();
+    onBottom = (style2 & DWT.BOTTOM) !is 0;
+    showClose = (style2 & DWT.CLOSE) !is 0;
+//  showMin = (style2 & DWT.MIN) !is 0; - conflicts with DWT.TOP
+//  showMax = (style2 & DWT.MAX) !is 0; - conflicts with DWT.BOTTOM
+    single = (style2 & DWT.SINGLE) !is 0;
+    borderLeft = borderRight = (style & DWT.BORDER) !is 0 ? 1 : 0;
+    borderTop = onBottom ? borderLeft : 0;
+    borderBottom = onBottom ? 0 : borderLeft;
+    highlight_header = (style & DWT.FLAT) !is 0 ? 1 : 3;
+    highlight_margin = (style & DWT.FLAT) !is 0 ? 0 : 2;
+    //set up default colors
+    Display display = getDisplay();
+    selectionForeground = display.getSystemColor(SELECTION_FOREGROUND);
+    selectionBackground = display.getSystemColor(SELECTION_BACKGROUND);
+    borderColor = display.getSystemColor(BORDER1_COLOR);
+    updateTabHeight(false);
+
+    initAccessible();
+
+    // Add all listeners
+    listener = new class() Listener {
+        public void handleEvent(Event event) {
+            switch (event.type) {
+                case DWT.Dispose:          onDispose(event); break;
+                case DWT.DragDetect:       onDragDetect(event); break;
+                case DWT.FocusIn:          onFocus(event);  break;
+                case DWT.FocusOut:         onFocus(event);  break;
+                case DWT.KeyDown:          onKeyDown(event); break;
+                case DWT.MouseDoubleClick: onMouseDoubleClick(event); break;
+                case DWT.MouseDown:        onMouse(event);  break;
+                case DWT.MouseEnter:       onMouse(event);  break;
+                case DWT.MouseExit:        onMouse(event);  break;
+                case DWT.MouseMove:        onMouse(event); break;
+                case DWT.MouseUp:          onMouse(event); break;
+                case DWT.Paint:            onPaint(event);  break;
+                case DWT.Resize:           onResize();  break;
+                case DWT.Traverse:         onTraverse(event); break;
+                default:
+            }
+        }
+    };
+
+    int[] folderEvents = [
+        DWT.Dispose,
+        DWT.DragDetect,
+        DWT.FocusIn,
+        DWT.FocusOut,
+        DWT.KeyDown,
+        DWT.MouseDoubleClick,
+        DWT.MouseDown,
+        DWT.MouseEnter,
+        DWT.MouseExit,
+        DWT.MouseMove,
+        DWT.MouseUp,
+        DWT.Paint,
+        DWT.Resize,
+        DWT.Traverse,
+    ];
+    for (int i = 0; i < folderEvents.length; i++) {
+        addListener(folderEvents[i], listener);
+    }
+}
+static int checkStyle (Composite parent, int style) {
+    int mask = DWT.CLOSE | DWT.TOP | DWT.BOTTOM | DWT.FLAT | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT | DWT.SINGLE | DWT.MULTI;
+    style = style & mask;
+    // TOP and BOTTOM are mutually exclusive.
+    // TOP is the default
+    if ((style & DWT.TOP) !is 0) style = style & ~DWT.BOTTOM;
+    // SINGLE and MULTI are mutually exclusive.
+    // MULTI is the default
+    if ((style & DWT.MULTI) !is 0) style = style & ~DWT.SINGLE;
+    // reduce the flash by not redrawing the entire area on a Resize event
+    style |= DWT.NO_REDRAW_RESIZE;
+    //TEMPORARY CODE
+    /*
+     * The default background on carbon and some GTK themes is not a solid color
+     * but a texture.  To show the correct default background, we must allow
+     * the operating system to draw it and therefore, we can not use the
+     * NO_BACKGROUND style.  The NO_BACKGROUND style is not required on platforms
+     * that use double buffering which is true in both of these cases.
+     */
+    char[] platform = DWT.getPlatform();
+    if ("carbon"==platform || "gtk"==platform) return style; //$NON-NLS-1$ //$NON-NLS-2$
+
+    //TEMPORARY CODE
+    /*
+     * In Right To Left orientation on Windows, all GC calls that use a brush are drawing
+     * offset by one pixel.  This results in some parts of the CTabFolder not drawing correctly.
+     * To alleviate some of the appearance problems, allow the OS to draw the background.
+     * This does not draw correctly but the result is less obviously wrong.
+     */
+    if ((style & DWT.RIGHT_TO_LEFT) !is 0) return style;
+    if ((parent.getStyle() & DWT.MIRRORED) !is 0 && (style & DWT.LEFT_TO_RIGHT) is 0) return style;
+
+    return style | DWT.NO_BACKGROUND;
+}
+static void fillRegion(GC gc, Region region) {
+    // NOTE: region passed in to this function will be modified
+    Region clipping = new Region();
+    gc.getClipping(clipping);
+    region.intersect(clipping);
+    gc.setClipping(region);
+    gc.fillRectangle(region.getBounds());
+    gc.setClipping(clipping);
+    clipping.dispose();
+}
+/**
+ *
+ * Adds the listener to the collection of listeners who will
+ * be notified when a tab item is closed, minimized, maximized,
+ * restored, or to show the list of items that are not
+ * currently visible.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * </ul>
+ *
+ * @see CTabFolder2Listener
+ * @see #removeCTabFolder2Listener(CTabFolder2Listener)
+ *
+ * @since 3.0
+ */
+public void addCTabFolder2Listener(CTabFolder2Listener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    // add to array
+    CTabFolder2Listener[] newListeners = new CTabFolder2Listener[folderListeners.length + 1];
+    SimpleType!(CTabFolder2Listener).arraycopy(folderListeners, 0, newListeners, 0, folderListeners.length);
+    folderListeners = newListeners;
+    folderListeners[folderListeners.length - 1] = listener;
+}
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when a tab item is closed.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * </ul>
+ *
+ * @see CTabFolderListener
+ * @see #removeCTabFolderListener(CTabFolderListener)
+ *
+ * @deprecated use addCTabFolder2Listener(CTabFolder2Listener)
+ */
+public void addCTabFolderListener(CTabFolderListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    // add to array
+    CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length + 1];
+    SimpleType!(CTabFolderListener).arraycopy(tabListeners, 0, newTabListeners, 0, tabListeners.length);
+    tabListeners = newTabListeners;
+    tabListeners[tabListeners.length - 1] = listener;
+    // display close button to be backwards compatible
+    if (!showClose) {
+        showClose = true;
+        updateItems();
+        redraw();
+    }
+}
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when the user changes the receiver's selection, by sending
+ * it one of the messages defined in the <code>SelectionListener</code>
+ * interface.
+ * <p>
+ * <code>widgetSelected</code> is called when the user changes the selected tab.
+ * <code>widgetDefaultSelected</code> is not called.
+ * </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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    }
+    TypedListener typedListener = new TypedListener(listener);
+    addListener(DWT.Selection, typedListener);
+    addListener(DWT.DefaultSelection, typedListener);
+}
+void antialias (int[] shape, RGB lineRGB, RGB innerRGB, RGB outerRGB, GC gc){
+    // Don't perform anti-aliasing on Mac and WPF because the platform
+    // already does it.  The simple style also does not require anti-aliasing.
+    if (simple || "carbon"==DWT.getPlatform() || "wpf"==DWT.getPlatform()) return; //$NON-NLS-1$
+    // Don't perform anti-aliasing on low resolution displays
+    if (getDisplay().getDepth() < 15) return;
+    if (outerRGB !is null) {
+        int index = 0;
+        bool left = true;
+        int oldY = onBottom ? 0 : getSize().y;
+        int[] outer = new int[shape.length];
+        for (int i = 0; i < shape.length/2; i++) {
+            if (left && (index + 3 < shape.length)) {
+                left = onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3];
+                oldY = shape[index+1];
+            }
+            outer[index] = shape[index++] + (left ? -1 : +1);
+            outer[index] = shape[index++];
+        }
+        RGB from = lineRGB;
+        RGB to = outerRGB;
+        int red = from.red + 2*(to.red - from.red)/3;
+        int green = from.green + 2*(to.green - from.green)/3;
+        int blue = from.blue + 2*(to.blue - from.blue)/3;
+        Color color = new Color(getDisplay(), red, green, blue);
+        gc.setForeground(color);
+        gc.drawPolyline(outer);
+        color.dispose();
+    }
+    if (innerRGB !is null) {
+        int[] inner = new int[shape.length];
+        int index = 0;
+        bool left = true;
+        int oldY = onBottom ? 0 : getSize().y;
+        for (int i = 0; i < shape.length/2; i++) {
+            if (left && (index + 3 < shape.length)) {
+                left = onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3];
+                oldY = shape[index+1];
+            }
+            inner[index] = shape[index++] + (left ? +1 : -1);
+            inner[index] = shape[index++];
+        }
+        RGB from = lineRGB;
+        RGB to = innerRGB;
+        int red = from.red + 2*(to.red - from.red)/3;
+        int green = from.green + 2*(to.green - from.green)/3;
+        int blue = from.blue + 2*(to.blue - from.blue)/3;
+        Color color = new Color(getDisplay(), red, green, blue);
+        gc.setForeground(color);
+        gc.drawPolyline(inner);
+        color.dispose();
+    }
+}
+public override Rectangle computeTrim (int x, int y, int width, int height) {
+    checkWidget();
+    int trimX = x - marginWidth - highlight_margin - borderLeft;
+    int trimWidth = width + borderLeft + borderRight + 2*marginWidth + 2*highlight_margin;
+    if (minimized) {
+        int trimY = onBottom ? y - borderTop : y - highlight_header - tabHeight - borderTop;
+        int trimHeight = borderTop + borderBottom + tabHeight + highlight_header;
+        return new Rectangle (trimX, trimY, trimWidth, trimHeight);
+    } else {
+        int trimY = onBottom ? y - marginHeight - highlight_margin - borderTop: y - marginHeight - highlight_header - tabHeight - borderTop;
+        int trimHeight = height + borderTop + borderBottom + 2*marginHeight + tabHeight + highlight_header + highlight_margin;
+        return new Rectangle (trimX, trimY, trimWidth, trimHeight);
+    }
+}
+void createItem (CTabItem item, int index) {
+    if (0 > index || index > getItemCount ())DWT.error (DWT.ERROR_INVALID_RANGE);
+    item.parent = this;
+    CTabItem[] newItems = new CTabItem [items.length + 1];
+    System.arraycopy(items, 0, newItems, 0, index);
+    newItems[index] = item;
+    System.arraycopy(items, index, newItems, index + 1, items.length - index);
+    items = newItems;
+    if (selectedIndex >= index) selectedIndex ++;
+    int[] newPriority = new int[priority.length + 1];
+    int next = 0,  priorityIndex = priority.length;
+    for (int i = 0; i < priority.length; i++) {
+        if (!mru && priority[i] is index) {
+            priorityIndex = next++;
+        }
+        newPriority[next++] = priority[i] >= index ? priority[i] + 1 : priority[i];
+    }
+    newPriority[priorityIndex] = index;
+    priority = newPriority;
+
+    if (items.length is 1) {
+        if (!updateTabHeight(false)) updateItems();
+        redraw();
+    } else {
+        updateItems();
+        redrawTabs();
+    }
+}
+void destroyItem (CTabItem item) {
+    if (inDispose) return;
+    int index = indexOf(item);
+    if (index is -1) return;
+
+    if (items.length is 1) {
+        items = new CTabItem[0];
+        priority = new int[0];
+        firstIndex = -1;
+        selectedIndex = -1;
+
+        Control control = item.getControl();
+        if (control !is null && !control.isDisposed()) {
+            control.setVisible(false);
+        }
+        setToolTipText(null);
+        setButtonBounds();
+        redraw();
+        return;
+    }
+
+    CTabItem[] newItems = new CTabItem [items.length - 1];
+    System.arraycopy(items, 0, newItems, 0, index);
+    System.arraycopy(items, index + 1, newItems, index, items.length - index - 1);
+    items = newItems;
+
+    int[] newPriority = new int[priority.length - 1];
+    int next = 0;
+    for (int i = 0; i < priority.length; i++) {
+        if (priority [i] is index) continue;
+        newPriority[next++] = priority[i] > index ? priority[i] - 1 : priority [i];
+    }
+    priority = newPriority;
+
+    // move the selection if this item is selected
+    if (selectedIndex is index) {
+        Control control = item.getControl();
+        selectedIndex = -1;
+        int nextSelection = mru ? priority[0] : Math.max(0, index - 1);
+        setSelection(nextSelection, true);
+        if (control !is null && !control.isDisposed()) {
+            control.setVisible(false);
+        }
+    } else if (selectedIndex > index) {
+        selectedIndex --;
+    }
+
+    updateItems();
+    redrawTabs();
+}
+void drawBackground(GC gc, int[] shape, bool selected) {
+    Color defaultBackground = selected ? selectionBackground : getBackground();
+    Image image = selected ? selectionBgImage : bgImage;
+    Color[] colors = selected ? selectionGradientColors : gradientColors;
+    int[] percents = selected ? selectionGradientPercents : gradientPercents;
+    bool vertical = selected ? selectionGradientVertical : gradientVertical;
+    Point size = getSize();
+    int width = size.x;
+    int height = tabHeight + highlight_header;
+    int x = 0;
+    if (borderLeft > 0) {
+        x += 1; width -= 2;
+    }
+    int y = onBottom ? size.y - borderBottom - height : borderTop;
+    drawBackground(gc, shape, x, y, width, height, defaultBackground, image, colors, percents, vertical);
+}
+void drawBackground(GC gc, int[] shape, int x, int y, int width, int height, Color defaultBackground, Image image, Color[] colors, int[] percents, bool vertical) {
+    Region clipping = new Region();
+    gc.getClipping(clipping);
+    Region region = new Region();
+    region.add(shape);
+    region.intersect(clipping);
+    gc.setClipping(region);
+
+    if (image !is null) {
+        // draw the background image in shape
+        gc.setBackground(defaultBackground);
+        gc.fillRectangle(x, y, width, height);
+        Rectangle imageRect = image.getBounds();
+        gc.drawImage(image, imageRect.x, imageRect.y, imageRect.width, imageRect.height, x, y, width, height);
+    } else if (colors !is null) {
+        // draw gradient
+        if (colors.length is 1) {
+            Color background = colors[0] !is null ? colors[0] : defaultBackground;
+            gc.setBackground(background);
+            gc.fillRectangle(x, y, width, height);
+        } else {
+            if (vertical) {
+                if (onBottom) {
+                    int pos = 0;
+                    if (percents[percents.length - 1] < 100) {
+                        pos = percents[percents.length - 1] * height / 100;
+                        gc.setBackground(defaultBackground);
+                        gc.fillRectangle(x, y, width, pos);
+                    }
+                    Color lastColor = colors[colors.length-1];
+                    if (lastColor is null) lastColor = defaultBackground;
+                    for (int i = percents.length-1; i >= 0; i--) {
+                        gc.setForeground(lastColor);
+                        lastColor = colors[i];
+                        if (lastColor is null) lastColor = defaultBackground;
+                        gc.setBackground(lastColor);
+                        int gradientHeight = percents[i] * height / 100;
+                        gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true);
+                        pos += gradientHeight;
+                    }
+                } else {
+                    Color lastColor = colors[0];
+                    if (lastColor is null) lastColor = defaultBackground;
+                    int pos = 0;
+                    for (int i = 0; i < percents.length; i++) {
+                        gc.setForeground(lastColor);
+                        lastColor = colors[i + 1];
+                        if (lastColor is null) lastColor = defaultBackground;
+                        gc.setBackground(lastColor);
+                        int gradientHeight = percents[i] * height / 100;
+                        gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true);
+                        pos += gradientHeight;
+                    }
+                    if (pos < height) {
+                        gc.setBackground(defaultBackground);
+                        gc.fillRectangle(x, pos, width, height-pos+1);
+                    }
+                }
+            } else { //horizontal gradient
+                y = 0;
+                height = getSize().y;
+                Color lastColor = colors[0];
+                if (lastColor is null) lastColor = defaultBackground;
+                int pos = 0;
+                for (int i = 0; i < percents.length; ++i) {
+                    gc.setForeground(lastColor);
+                    lastColor = colors[i + 1];
+                    if (lastColor is null) lastColor = defaultBackground;
+                    gc.setBackground(lastColor);
+                    int gradientWidth = (percents[i] * width / 100) - pos;
+                    gc.fillGradientRectangle(x+pos, y, gradientWidth, height, false);
+                    pos += gradientWidth;
+                }
+                if (pos < width) {
+                    gc.setBackground(defaultBackground);
+                    gc.fillRectangle(x+pos, y, width-pos, height);
+                }
+            }
+        }
+    } else {
+        // draw a solid background using default background in shape
+        if ((getStyle() & DWT.NO_BACKGROUND) !is 0 || defaultBackground!=getBackground()) {
+            gc.setBackground(defaultBackground);
+            gc.fillRectangle(x, y, width, height);
+        }
+    }
+    gc.setClipping(clipping);
+    clipping.dispose();
+    region.dispose();
+}
+void drawBody(Event event) {
+    GC gc = event.gc;
+    Point size = getSize();
+
+    // fill in body
+    if (!minimized){
+        int width = size.x  - borderLeft - borderRight - 2*highlight_margin;
+        int height = size.y - borderTop - borderBottom - tabHeight - highlight_header - highlight_margin;
+        // Draw highlight margin
+        if (highlight_margin > 0) {
+            int[] shape = null;
+            if (onBottom) {
+                int x1 = borderLeft;
+                int y1 = borderTop;
+                int x2 = size.x - borderRight;
+                int y2 = size.y - borderBottom - tabHeight - highlight_header;
+                shape = [x1,y1, x2,y1, x2,y2, x2-highlight_margin,y2,
+                                   x2-highlight_margin, y1+highlight_margin, x1+highlight_margin,y1+highlight_margin,
+                                   x1+highlight_margin,y2, x1,y2];
+            } else {
+                int x1 = borderLeft;
+                int y1 = borderTop + tabHeight + highlight_header;
+                int x2 = size.x - borderRight;
+                int y2 = size.y - borderBottom;
+                shape = [x1,y1, x1+highlight_margin,y1, x1+highlight_margin,y2-highlight_margin,
+                                   x2-highlight_margin,y2-highlight_margin, x2-highlight_margin,y1,
+                                   x2,y1, x2,y2, x1,y2];
+            }
+            // If horizontal gradient, show gradient across the whole area
+            if (selectedIndex !is -1 && selectionGradientColors !is null && selectionGradientColors.length > 1 && !selectionGradientVertical) {
+                drawBackground(gc, shape, true);
+            } else if (selectedIndex is -1 && gradientColors !is null && gradientColors.length > 1 && !gradientVertical) {
+                drawBackground(gc, shape, false);
+            } else {
+                gc.setBackground(selectedIndex is -1 ? getBackground() : selectionBackground);
+                gc.fillPolygon(shape);
+            }
+        }
+        //Draw client area
+        if ((getStyle() & DWT.NO_BACKGROUND) !is 0) {
+            gc.setBackground(getBackground());
+            gc.fillRectangle(xClient - marginWidth, yClient - marginHeight, width, height);
+        }
+    } else {
+        if ((getStyle() & DWT.NO_BACKGROUND) !is 0) {
+            int height = borderTop + tabHeight + highlight_header + borderBottom;
+            if (size.y > height) {
+                gc.setBackground(getParent().getBackground());
+                gc.fillRectangle(0, height, size.x, size.y - height);
+            }
+        }
+    }
+
+    //draw 1 pixel border around outside
+    if (borderLeft > 0) {
+        gc.setForeground(borderColor);
+        int x1 = borderLeft - 1;
+        int x2 = size.x - borderRight;
+        int y1 = onBottom ? borderTop - 1 : borderTop + tabHeight;
+        int y2 = onBottom ? size.y - tabHeight - borderBottom - 1 : size.y - borderBottom;
+        gc.drawLine(x1, y1, x1, y2); // left
+        gc.drawLine(x2, y1, x2, y2); // right
+        if (onBottom) {
+            gc.drawLine(x1, y1, x2, y1); // top
+        } else {
+            gc.drawLine(x1, y2, x2, y2); // bottom
+        }
+    }
+}
+
+void drawChevron(GC gc) {
+    if (chevronRect.width is 0 || chevronRect.height is 0) return;
+    // draw chevron (10x7)
+    Display display = getDisplay();
+    Point dpi = display.getDPI();
+    int fontHeight = 72 * 10 / dpi.y;
+    FontData fd = getFont().getFontData()[0];
+    fd.setHeight(fontHeight);
+    Font f = new Font(display, fd);
+    int fHeight = f.getFontData()[0].getHeight() * dpi.y / 72;
+    int indent = Math.max(2, (chevronRect.height - fHeight - 4) /2);
+    int x = chevronRect.x + 2;
+    int y = chevronRect.y + indent;
+    int count;
+    if (single) {
+        count = selectedIndex is -1 ? items.length : items.length - 1;
+    } else {
+        int showCount = 0;
+        while (showCount < priority.length && items[priority[showCount]].showing) {
+            showCount++;
+        }
+        count = items.length - showCount;
+    }
+    char[] chevronString = count > 99 ? "99+" : to!(char[])(count); //$NON-NLS-1$
+    switch (chevronImageState) {
+        case NORMAL: {
+            Color chevronBorder = single ? getSelectionForeground() : getForeground();
+            gc.setForeground(chevronBorder);
+            gc.setFont(f);
+            gc.drawLine(x,y,     x+2,y+2);
+            gc.drawLine(x+2,y+2, x,y+4);
+            gc.drawLine(x+1,y,   x+3,y+2);
+            gc.drawLine(x+3,y+2, x+1,y+4);
+            gc.drawLine(x+4,y,   x+6,y+2);
+            gc.drawLine(x+6,y+2, x+5,y+4);
+            gc.drawLine(x+5,y,   x+7,y+2);
+            gc.drawLine(x+7,y+2, x+4,y+4);
+            gc.drawString(chevronString, x+7, y+3, true);
+            break;
+        }
+        case HOT: {
+            gc.setForeground(display.getSystemColor(BUTTON_BORDER));
+            gc.setBackground(display.getSystemColor(BUTTON_FILL));
+            gc.setFont(f);
+            gc.fillRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, 6, 6);
+            gc.drawRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width - 1, chevronRect.height - 1, 6, 6);
+            gc.drawLine(x,y,     x+2,y+2);
+            gc.drawLine(x+2,y+2, x,y+4);
+            gc.drawLine(x+1,y,   x+3,y+2);
+            gc.drawLine(x+3,y+2, x+1,y+4);
+            gc.drawLine(x+4,y,   x+6,y+2);
+            gc.drawLine(x+6,y+2, x+5,y+4);
+            gc.drawLine(x+5,y,   x+7,y+2);
+            gc.drawLine(x+7,y+2, x+4,y+4);
+            gc.drawString(chevronString, x+7, y+3, true);
+            break;
+        }
+        case SELECTED: {
+            gc.setForeground(display.getSystemColor(BUTTON_BORDER));
+            gc.setBackground(display.getSystemColor(BUTTON_FILL));
+            gc.setFont(f);
+            gc.fillRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, 6, 6);
+            gc.drawRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width - 1, chevronRect.height - 1, 6, 6);
+            gc.drawLine(x+1,y+1, x+3,y+3);
+            gc.drawLine(x+3,y+3, x+1,y+5);
+            gc.drawLine(x+2,y+1, x+4,y+3);
+            gc.drawLine(x+4,y+3, x+2,y+5);
+            gc.drawLine(x+5,y+1, x+7,y+3);
+            gc.drawLine(x+7,y+3, x+6,y+5);
+            gc.drawLine(x+6,y+1, x+8,y+3);
+            gc.drawLine(x+8,y+3, x+5,y+5);
+            gc.drawString(chevronString, x+8, y+4, true);
+            break;
+        }
+        default:
+    }
+    f.dispose();
+}
+void drawMaximize(GC gc) {
+    if (maxRect.width is 0 || maxRect.height is 0) return;
+    Display display = getDisplay();
+    // 5x4 or 7x9
+    int x = maxRect.x + (CTabFolder.BUTTON_SIZE - 10)/2;
+    int y = maxRect.y + 3;
+
+    gc.setForeground(display.getSystemColor(BUTTON_BORDER));
+    gc.setBackground(display.getSystemColor(BUTTON_FILL));
+
+    switch (maxImageState) {
+        case NORMAL: {
+            if (!maximized) {
+                gc.fillRectangle(x, y, 9, 9);
+                gc.drawRectangle(x, y, 9, 9);
+                gc.drawLine(x+1, y+2, x+8, y+2);
+            } else {
+                gc.fillRectangle(x, y+3, 5, 4);
+                gc.fillRectangle(x+2, y, 5, 4);
+                gc.drawRectangle(x, y+3, 5, 4);
+                gc.drawRectangle(x+2, y, 5, 4);
+                gc.drawLine(x+3, y+1, x+6, y+1);
+                gc.drawLine(x+1, y+4, x+4, y+4);
+            }
+            break;
+        }
+        case HOT: {
+            gc.fillRoundRectangle(maxRect.x, maxRect.y, maxRect.width, maxRect.height, 6, 6);
+            gc.drawRoundRectangle(maxRect.x, maxRect.y, maxRect.width - 1, maxRect.height - 1, 6, 6);
+            if (!maximized) {
+                gc.fillRectangle(x, y, 9, 9);
+                gc.drawRectangle(x, y, 9, 9);
+                gc.drawLine(x+1, y+2, x+8, y+2);
+            } else {
+                gc.fillRectangle(x, y+3, 5, 4);
+                gc.fillRectangle(x+2, y, 5, 4);
+                gc.drawRectangle(x, y+3, 5, 4);
+                gc.drawRectangle(x+2, y, 5, 4);
+                gc.drawLine(x+3, y+1, x+6, y+1);
+                gc.drawLine(x+1, y+4, x+4, y+4);
+            }
+            break;
+        }
+        case SELECTED: {
+            gc.fillRoundRectangle(maxRect.x, maxRect.y, maxRect.width, maxRect.height, 6, 6);
+            gc.drawRoundRectangle(maxRect.x, maxRect.y, maxRect.width - 1, maxRect.height - 1, 6, 6);
+            if (!maximized) {
+                gc.fillRectangle(x+1, y+1, 9, 9);
+                gc.drawRectangle(x+1, y+1, 9, 9);
+                gc.drawLine(x+2, y+3, x+9, y+3);
+            } else {
+                gc.fillRectangle(x+1, y+4, 5, 4);
+                gc.fillRectangle(x+3, y+1, 5, 4);
+                gc.drawRectangle(x+1, y+4, 5, 4);
+                gc.drawRectangle(x+3, y+1, 5, 4);
+                gc.drawLine(x+4, y+2, x+7, y+2);
+                gc.drawLine(x+2, y+5, x+5, y+5);
+            }
+            break;
+        }
+        default:
+    }
+}
+void drawMinimize(GC gc) {
+    if (minRect.width is 0 || minRect.height is 0) return;
+    Display display = getDisplay();
+    // 5x4 or 9x3
+    int x = minRect.x + (BUTTON_SIZE - 10)/2;
+    int y = minRect.y + 3;
+
+    gc.setForeground(display.getSystemColor(BUTTON_BORDER));
+    gc.setBackground(display.getSystemColor(BUTTON_FILL));
+
+    switch (minImageState) {
+        case NORMAL: {
+            if (!minimized) {
+                gc.fillRectangle(x, y, 9, 3);
+                gc.drawRectangle(x, y, 9, 3);
+            } else {
+                gc.fillRectangle(x, y+3, 5, 4);
+                gc.fillRectangle(x+2, y, 5, 4);
+                gc.drawRectangle(x, y+3, 5, 4);
+                gc.drawRectangle(x+2, y, 5, 4);
+                gc.drawLine(x+3, y+1, x+6, y+1);
+                gc.drawLine(x+1, y+4, x+4, y+4);
+            }
+            break;
+        }
+        case HOT: {
+            gc.fillRoundRectangle(minRect.x, minRect.y, minRect.width, minRect.height, 6, 6);
+            gc.drawRoundRectangle(minRect.x, minRect.y, minRect.width - 1, minRect.height - 1, 6, 6);
+            if (!minimized) {
+                gc.fillRectangle(x, y, 9, 3);
+                gc.drawRectangle(x, y, 9, 3);
+            } else {
+                gc.fillRectangle(x, y+3, 5, 4);
+                gc.fillRectangle(x+2, y, 5, 4);
+                gc.drawRectangle(x, y+3, 5, 4);
+                gc.drawRectangle(x+2, y, 5, 4);
+                gc.drawLine(x+3, y+1, x+6, y+1);
+                gc.drawLine(x+1, y+4, x+4, y+4);
+            }
+            break;
+        }
+        case SELECTED: {
+            gc.fillRoundRectangle(minRect.x, minRect.y, minRect.width, minRect.height, 6, 6);
+            gc.drawRoundRectangle(minRect.x, minRect.y, minRect.width - 1, minRect.height - 1, 6, 6);
+            if (!minimized) {
+                gc.fillRectangle(x+1, y+1, 9, 3);
+                gc.drawRectangle(x+1, y+1, 9, 3);
+            } else {
+                gc.fillRectangle(x+1, y+4, 5, 4);
+                gc.fillRectangle(x+3, y+1, 5, 4);
+                gc.drawRectangle(x+1, y+4, 5, 4);
+                gc.drawRectangle(x+3, y+1, 5, 4);
+                gc.drawLine(x+4, y+2, x+7, y+2);
+                gc.drawLine(x+2, y+5, x+5, y+5);
+            }
+            break;
+        }
+        default:
+    }
+}
+void drawTabArea(Event event) {
+    GC gc = event.gc;
+    Point size = getSize();
+    int[] shape = null;
+
+    if (tabHeight is 0) {
+        int style = getStyle();
+        if ((style & DWT.FLAT) !is 0 && (style & DWT.BORDER) is 0) return;
+        int x1 = borderLeft - 1;
+        int x2 = size.x - borderRight;
+        int y1 = onBottom ? size.y - borderBottom - highlight_header - 1 : borderTop + highlight_header;
+        int y2 = onBottom ? size.y - borderBottom : borderTop;
+        if (borderLeft > 0 && onBottom) y2 -= 1;
+
+        shape = [x1, y1, x1,y2, x2,y2, x2,y1];
+
+        // If horizontal gradient, show gradient across the whole area
+        if (selectedIndex !is -1 && selectionGradientColors !is null && selectionGradientColors.length > 1 && !selectionGradientVertical) {
+            drawBackground(gc, shape, true);
+        } else if (selectedIndex is -1 && gradientColors !is null && gradientColors.length > 1 && !gradientVertical) {
+            drawBackground(gc, shape, false);
+        } else {
+            gc.setBackground(selectedIndex is -1 ? getBackground() : selectionBackground);
+            gc.fillPolygon(shape);
+        }
+
+        //draw 1 pixel border
+        if (borderLeft > 0) {
+            gc.setForeground(borderColor);
+            gc.drawPolyline(shape);
+        }
+        return;
+    }
+
+    int x = Math.max(0, borderLeft - 1);
+    int y = onBottom ? size.y - borderBottom - tabHeight : borderTop;
+    int width = size.x - borderLeft - borderRight + 1;
+    int height = tabHeight - 1;
+
+    // Draw Tab Header
+    if (onBottom) {
+        int[] left, right;
+        if ((getStyle() & DWT.BORDER) !is 0) {
+            left = simple ? SIMPLE_BOTTOM_LEFT_CORNER : BOTTOM_LEFT_CORNER;
+            right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER : BOTTOM_RIGHT_CORNER;
+        } else {
+            left = simple ? SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS : BOTTOM_LEFT_CORNER_BORDERLESS;
+            right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS : BOTTOM_RIGHT_CORNER_BORDERLESS;
+        }
+        shape = new int[left.length + right.length + 4];
+        int index = 0;
+        shape[index++] = x;
+        shape[index++] = y-highlight_header;
+        for (int i = 0; i < left.length/2; i++) {
+            shape[index++] = x+left[2*i];
+            shape[index++] = y+height+left[2*i+1];
+            if (borderLeft is 0) shape[index-1] += 1;
+        }
+        for (int i = 0; i < right.length/2; i++) {
+            shape[index++] = x+width+right[2*i];
+            shape[index++] = y+height+right[2*i+1];
+            if (borderLeft is 0) shape[index-1] += 1;
+        }
+        shape[index++] = x+width;
+        shape[index++] = y-highlight_header;
+    } else {
+        int[] left, right;
+        if ((getStyle() & DWT.BORDER) !is 0) {
+            left = simple ? SIMPLE_TOP_LEFT_CORNER : TOP_LEFT_CORNER;
+            right = simple ? SIMPLE_TOP_RIGHT_CORNER : TOP_RIGHT_CORNER;
+        } else {
+            left = simple ? SIMPLE_TOP_LEFT_CORNER_BORDERLESS : TOP_LEFT_CORNER_BORDERLESS;
+            right = simple ? SIMPLE_TOP_RIGHT_CORNER_BORDERLESS : TOP_RIGHT_CORNER_BORDERLESS;
+        }
+        shape = new int[left.length + right.length + 4];
+        int index = 0;
+        shape[index++] = x;
+        shape[index++] = y+height+highlight_header + 1;
+        for (int i = 0; i < left.length/2; i++) {
+            shape[index++] = x+left[2*i];
+            shape[index++] = y+left[2*i+1];
+        }
+        for (int i = 0; i < right.length/2; i++) {
+            shape[index++] = x+width+right[2*i];
+            shape[index++] = y+right[2*i+1];
+        }
+        shape[index++] = x+width;
+        shape[index++] = y+height+highlight_header + 1;
+    }
+    // Fill in background
+    bool bkSelected = single && selectedIndex !is -1;
+    drawBackground(gc, shape, bkSelected);
+    // Fill in parent background for non-rectangular shape
+    Region r = new Region();
+    r.add(new Rectangle(x, y, width + 1, height + 1));
+    r.subtract(shape);
+    gc.setBackground(getParent().getBackground());
+    fillRegion(gc, r);
+    r.dispose();
+
+    // Draw the unselected tabs.
+    if (!single) {
+        for (int i=0; i < items.length; i++) {
+            if (i !is selectedIndex && event.getBounds().intersects(items[i].getBounds())) {
+                items[i].onPaint(gc, false);
+            }
+        }
+    }
+
+    // Draw selected tab
+    if (selectedIndex !is -1) {
+        CTabItem item = items[selectedIndex];
+        item.onPaint(gc, true);
+    } else {
+        // if no selected tab - draw line across bottom of all tabs
+        int x1 = borderLeft;
+        int y1 = (onBottom) ? size.y - borderBottom - tabHeight - 1 : borderTop + tabHeight;
+        int x2 = size.x - borderRight;
+        gc.setForeground(borderColor);
+        gc.drawLine(x1, y1, x2, y1);
+    }
+
+    // Draw Buttons
+    drawChevron(gc);
+    drawMinimize(gc);
+    drawMaximize(gc);
+
+    // Draw border line
+    if (borderLeft > 0) {
+        RGB outside = getParent().getBackground().getRGB();
+        antialias(shape, borderColor.getRGB(), null, outside, gc);
+        gc.setForeground(borderColor);
+        gc.drawPolyline(shape);
+    }
+}
+/**
+ * Returns <code>true</code> if the receiver's border is visible.
+ *
+ * @return the receiver's border visibility state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public bool getBorderVisible() {
+    checkWidget();
+    return borderLeft is 1;
+}
+public override Rectangle getClientArea() {
+    checkWidget();
+    if (minimized) return new Rectangle(xClient, yClient, 0, 0);
+    Point size = getSize();
+    int width = size.x  - borderLeft - borderRight - 2*marginWidth - 2*highlight_margin;
+    int height = size.y - borderTop - borderBottom - 2*marginHeight - highlight_margin - highlight_header;
+    height -= tabHeight;
+    return new Rectangle(xClient, yClient, width, height);
+}
+/**
+ * Return the tab that is located at the specified index.
+ *
+ * @param index the index of the tab item
+ * @return the item at the specified index
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if the index is out of range</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * </ul>
+ */
+public CTabItem getItem (int index) {
+    //checkWidget();
+    if (index  < 0 || index >= items.length)
+        DWT.error(DWT.ERROR_INVALID_RANGE);
+    return items [index];
+}
+/**
+ * Gets the item at a point in the widget.
+ *
+ * @param pt the point in coordinates relative to the CTabFolder
+ * @return the item at a point or null
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public CTabItem getItem (Point pt) {
+    //checkWidget();
+    if (items.length is 0) return null;
+    Point size = getSize();
+    if (size.x <= borderLeft + borderRight) return null;
+    if (showChevron && chevronRect.contains(pt)) return null;
+    for (int i = 0; i < priority.length; i++) {
+        CTabItem item = items[priority[i]];
+        Rectangle rect = item.getBounds();
+        if (rect.contains(pt)) return item;
+    }
+    return null;
+}
+/**
+ * Return the number of tabs in the folder.
+ *
+ * @return the number of tabs in the folder
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public int getItemCount(){
+    //checkWidget();
+    return items.length;
+}
+/**
+ * Return the tab items.
+ *
+ * @return the tab items
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public CTabItem [] getItems() {
+    //checkWidget();
+    CTabItem[] tabItems = new CTabItem [items.length];
+    System.arraycopy(items, 0, tabItems, 0, items.length);
+    return tabItems;
+}
+/*
+ * 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 (char[] 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 CharacterToLower(getFirstCodepoint( string[index..$]));
+        index++;
+    } while (index < length_);
+    return '\0';
+}
+char[] stripMnemonic (char[] 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[0 .. index-1] ~ string[index .. length_];
+        }
+        index++;
+    } while (index < length_);
+    return string;
+}
+/**
+ * Returns <code>true</code> if the receiver is minimized.
+ *
+ * @return the receiver's minimized state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public bool getMinimized() {
+    checkWidget();
+    return minimized;
+}
+/**
+ * Returns <code>true</code> if the minimize button
+ * is visible.
+ *
+ * @return the visibility of the minimized button
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public bool getMinimizeVisible() {
+    checkWidget();
+    return showMin;
+}
+/**
+ * Returns the number of characters that will
+ * appear in a fully compressed tab.
+ *
+ * @return number of characters that will appear in a fully compressed tab
+ *
+ * @since 3.0
+ */
+public int getMinimumCharacters() {
+    checkWidget();
+    return minChars;
+}
+/**
+ * Returns <code>true</code> if the receiver is maximized.
+ * <p>
+ *
+ * @return the receiver's maximized state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public bool getMaximized() {
+    checkWidget();
+    return maximized;
+}
+/**
+ * Returns <code>true</code> if the maximize button
+ * is visible.
+ *
+ * @return the visibility of the maximized button
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public bool getMaximizeVisible() {
+    checkWidget();
+    return showMax;
+}
+/**
+ * Returns <code>true</code> if the receiver displays most
+ * recently used tabs and <code>false</code> otherwise.
+ * <p>
+ * When there is not enough horizontal space to show all the tabs,
+ * by default, tabs are shown sequentially from left to right in
+ * order of their index.  When the MRU visibility is turned on,
+ * the tabs that are visible will be the tabs most recently selected.
+ * Tabs will still maintain their left to right order based on index
+ * but only the most recently selected tabs are visible.
+ * <p>
+ * For example, consider a CTabFolder that contains "Tab 1", "Tab 2",
+ * "Tab 3" and "Tab 4" (in order by index).  The user selects
+ * "Tab 1" and then "Tab 3".  If the CTabFolder is now
+ * compressed so that only two tabs are visible, by default,
+ * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently
+ * selected and "Tab 2" because it is the previous item in index order).
+ * If MRU visibility is enabled, the two visible tabs will be "Tab 1"
+ * and "Tab 3" (in that order from left to right).</p>
+ *
+ * @return the receiver's header's visibility state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 bool getMRUVisible() {
+    checkWidget();
+    return mru;
+}
+int getRightItemEdge (){
+    int x = getSize().x - borderRight - 3;
+    if (showMin) x -= BUTTON_SIZE;
+    if (showMax) x -= BUTTON_SIZE;
+    if (showChevron) x -= 3*BUTTON_SIZE/2;
+    if (topRight !is null && topRightAlignment !is DWT.FILL) {
+        Point rightSize = topRight.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+        x -= rightSize.x + 3;
+    }
+    return Math.max(0, x);
+}
+/**
+ * Return the selected tab item, or null if there is no selection.
+ *
+ * @return the selected tab item, or null if none has been selected
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public CTabItem getSelection() {
+    //checkWidget();
+    if (selectedIndex is -1) return null;
+    return items[selectedIndex];
+}
+/**
+ * Returns the receiver's selection background color.
+ *
+ * @return the selection background color of the receiver
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public Color getSelectionBackground() {
+    checkWidget();
+    return selectionBackground;
+}
+/**
+ * Returns the receiver's selection foreground color.
+ *
+ * @return the selection foreground color of the receiver
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public Color getSelectionForeground() {
+    checkWidget();
+    return selectionForeground;
+}
+/**
+ * Return the index of the selected tab item, or -1 if there
+ * is no selection.
+ *
+ * @return the index of the selected tab item or -1
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public int getSelectionIndex() {
+    //checkWidget();
+    return selectedIndex;
+}
+/**
+ * Returns <code>true</code> if the CTabFolder is rendered
+ * with a simple, traditional shape.
+ *
+ * @return <code>true</code> if the CTabFolder is rendered with a simple shape
+ *
+ * @since 3.0
+ */
+public bool getSimple() {
+    checkWidget();
+    return simple;
+}
+/**
+ * Returns <code>true</code> if the CTabFolder only displays the selected tab
+ * and <code>false</code> if the CTabFolder displays multiple tabs.
+ *
+ * @return <code>true</code> if the CTabFolder only displays the selected tab and <code>false</code> if the CTabFolder displays multiple tabs
+ *
+ * @since 3.0
+ */
+public bool getSingle() {
+    checkWidget();
+    return single;
+}
+
+public override int getStyle() {
+    int style = super.getStyle();
+    style &= ~(DWT.TOP | DWT.BOTTOM);
+    style |= onBottom ? DWT.BOTTOM : DWT.TOP;
+    style &= ~(DWT.SINGLE | DWT.MULTI);
+    style |= single ? DWT.SINGLE : DWT.MULTI;
+    if (borderLeft !is 0) style |= DWT.BORDER;
+    return style;
+}
+/**
+ * Returns the height of the tab
+ *
+ * @return the height of the tab
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public int getTabHeight(){
+    checkWidget();
+    if (fixedTabHeight !is DWT.DEFAULT) return fixedTabHeight;
+    return tabHeight - 1; // -1 for line drawn across top of tab
+}
+/**
+ * Returns the position of the tab.  Possible values are DWT.TOP or DWT.BOTTOM.
+ *
+ * @return the position of the tab
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public int getTabPosition(){
+    checkWidget();
+    return onBottom ? DWT.BOTTOM : DWT.TOP;
+}
+/**
+ * Returns the control in the top right corner of the tab folder.
+ * Typically this is a close button or a composite with a menu and close button.
+ *
+ * @return the control in the top right corner of the tab folder or null
+ *
+ * @exception  DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ *
+ * @since 2.1
+ */
+public Control getTopRight() {
+    checkWidget();
+    return topRight;
+}
+/**
+ * Returns <code>true</code> if the close button appears
+ * when the user hovers over an unselected tabs.
+ *
+ * @return <code>true</code> if the close button appears on unselected tabs
+ *
+ * @since 3.0
+ */
+public bool getUnselectedCloseVisible() {
+    checkWidget();
+    return showUnselectedClose;
+}
+/**
+ * Returns <code>true</code> if an image appears
+ * in unselected tabs.
+ *
+ * @return <code>true</code> if an image appears in unselected tabs
+ *
+ * @since 3.0
+ */
+public bool getUnselectedImageVisible() {
+    checkWidget();
+    return showUnselectedImage;
+}
+/**
+ * Return the index of the specified tab or -1 if the tab is not
+ * in the receiver.
+ *
+ * @param item the tab item for which the index is required
+ *
+ * @return the index of the specified tab item or -1
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * </ul>
+ */
+public int indexOf(CTabItem item) {
+    checkWidget();
+    if (item is null) {
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    }
+    for (int i = 0; i < items.length; i++) {
+        if (items[i] is item) return i;
+    }
+    return -1;
+}
+void initAccessible() {
+    final Accessible accessible = getAccessible();
+    accessible.addAccessibleListener(new class() AccessibleAdapter {
+        public void getName(AccessibleEvent e) {
+            char[] name = null;
+            int childID = e.childID;
+            if (childID >= 0 && childID < items.length) {
+                name = stripMnemonic(items[childID].getText());
+            } else if (childID is items.length + CHEVRON_CHILD_ID) {
+                name = DWT.getMessage("SWT_ShowList"); //$NON-NLS-1$
+            } else if (childID is items.length + MINIMIZE_CHILD_ID) {
+                name = minimized ? DWT.getMessage("SWT_Restore") : DWT.getMessage("SWT_Minimize"); //$NON-NLS-1$ //$NON-NLS-2$
+            } else if (childID is items.length + MAXIMIZE_CHILD_ID) {
+                name = maximized ? DWT.getMessage("SWT_Restore") : DWT.getMessage("SWT_Maximize"); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+            e.result = name;
+        }
+
+        public void getHelp(AccessibleEvent e) {
+            char[] help = null;
+            int childID = e.childID;
+            if (childID is ACC.CHILDID_SELF) {
+                help = getToolTipText();
+            } else if (childID >= 0 && childID < items.length) {
+                help = items[childID].getToolTipText();
+            }
+            e.result = help;
+        }
+
+        public void getKeyboardShortcut(AccessibleEvent e) {
+            char[] shortcut = null;
+            int childID = e.childID;
+            if (childID >= 0 && childID < items.length) {
+                char[] text = items[childID].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.addAccessibleControlListener(new class() AccessibleControlAdapter {
+        public void getChildAtPoint(AccessibleControlEvent e) {
+            Point testPoint = toControl(e.x, e.y);
+            int childID = ACC.CHILDID_NONE;
+            for (int i = 0; i < items.length; i++) {
+                if (items[i].getBounds().contains(testPoint)) {
+                    childID = i;
+                    break;
+                }
+            }
+            if (childID is ACC.CHILDID_NONE) {
+                if (showChevron && chevronRect.contains(testPoint)) {
+                    childID = items.length + CHEVRON_CHILD_ID;
+                } else if (showMin && minRect.contains(testPoint)) {
+                    childID = items.length + MINIMIZE_CHILD_ID;
+                } else if (showMax && maxRect.contains(testPoint)) {
+                    childID = items.length + MAXIMIZE_CHILD_ID;
+                } else {
+                    Rectangle location = getBounds();
+                    location.height = location.height - getClientArea().height;
+                    if (location.contains(testPoint)) {
+                        childID = ACC.CHILDID_SELF;
+                    }
+                }
+            }
+            e.childID = childID;
+        }
+
+        public void getLocation(AccessibleControlEvent e) {
+            Rectangle location = null;
+            int childID = e.childID;
+            if (childID is ACC.CHILDID_SELF) {
+                location = getBounds();
+            } else if (childID >= 0 && childID < items.length) {
+                location = items[childID].getBounds();
+            } else if (showChevron && childID is items.length + CHEVRON_CHILD_ID) {
+                location = chevronRect;
+            } else if (showMin && childID is items.length + MINIMIZE_CHILD_ID) {
+                location = minRect;
+            } else if (showMax && childID is items.length + MAXIMIZE_CHILD_ID) {
+                location = maxRect;
+            }
+            if (location !is null) {
+                Point pt = toDisplay(location.x, location.y);
+                e.x = pt.x;
+                e.y = pt.y;
+                e.width = location.width;
+                e.height = location.height;
+            }
+        }
+
+        public void getChildCount(AccessibleControlEvent e) {
+            e.detail = items.length + EXTRA_CHILD_ID_COUNT;
+        }
+
+        public void getDefaultAction(AccessibleControlEvent e) {
+            char[] action = null;
+            int childID = e.childID;
+            if (childID >= 0 && childID < items.length) {
+                action = DWT.getMessage ("SWT_Switch"); //$NON-NLS-1$
+            }
+            if (childID >= items.length && childID < items.length + EXTRA_CHILD_ID_COUNT) {
+                action = DWT.getMessage ("SWT_Press"); //$NON-NLS-1$
+            }
+            e.result = action;
+        }
+
+        public void getFocus(AccessibleControlEvent e) {
+            int childID = ACC.CHILDID_NONE;
+            if (isFocusControl()) {
+                if (selectedIndex is -1) {
+                    childID = ACC.CHILDID_SELF;
+                } else {
+                    childID = selectedIndex;
+                }
+            }
+            e.childID = childID;
+        }
+
+        public void getRole(AccessibleControlEvent e) {
+            int role = 0;
+            int childID = e.childID;
+            if (childID is ACC.CHILDID_SELF) {
+                role = ACC.ROLE_TABFOLDER;
+            } else if (childID >= 0 && childID < items.length) {
+                role = ACC.ROLE_TABITEM;
+            } else if (childID >= items.length && childID < items.length + EXTRA_CHILD_ID_COUNT) {
+                role = ACC.ROLE_PUSHBUTTON;
+            }
+            e.detail = role;
+        }
+
+        public void getSelection(AccessibleControlEvent e) {
+            e.childID = (selectedIndex is -1) ? ACC.CHILDID_NONE : selectedIndex;
+        }
+
+        public void getState(AccessibleControlEvent e) {
+            int state = 0;
+            int childID = e.childID;
+            if (childID is ACC.CHILDID_SELF) {
+                state = ACC.STATE_NORMAL;
+            } else if (childID >= 0 && childID < items.length) {
+                state = ACC.STATE_SELECTABLE;
+                if (isFocusControl()) {
+                    state |= ACC.STATE_FOCUSABLE;
+                }
+                if (selectedIndex is childID) {
+                    state |= ACC.STATE_SELECTED;
+                    if (isFocusControl()) {
+                        state |= ACC.STATE_FOCUSED;
+                    }
+                }
+            } else if (childID is items.length + CHEVRON_CHILD_ID) {
+                state = showChevron ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE;
+            } else if (childID is items.length + MINIMIZE_CHILD_ID) {
+                state = showMin ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE;
+            } else if (childID is items.length + MAXIMIZE_CHILD_ID) {
+                state = showMax ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE;
+            }
+            e.detail = state;
+        }
+
+        public void getChildren(AccessibleControlEvent e) {
+            int childIdCount = items.length + EXTRA_CHILD_ID_COUNT;
+            Object[] children = new Object[childIdCount];
+            for (int i = 0; i < childIdCount; i++) {
+                children[i] = new Integer(i);
+            }
+            e.children = children;
+        }
+    });
+
+    addListener(DWT.Selection, new class(accessible) Listener {
+        Accessible acc;
+        this( Accessible acc ){ this.acc = acc; }
+        public void handleEvent(Event event) {
+            if (isFocusControl()) {
+                if (selectedIndex is -1) {
+                    acc.setFocus(ACC.CHILDID_SELF);
+                } else {
+                    acc.setFocus(selectedIndex);
+                }
+            }
+        }
+    });
+
+    addListener(DWT.FocusIn, new class(accessible) Listener {
+        Accessible acc;
+        this( Accessible acc ){ this.acc = acc; }
+        public void handleEvent(Event event) {
+            if (selectedIndex is -1) {
+                acc.setFocus(ACC.CHILDID_SELF);
+            } else {
+                acc.setFocus(selectedIndex);
+            }
+        }
+    });
+}
+void onKeyDown (Event event) {
+    switch (event.keyCode) {
+        case DWT.ARROW_LEFT:
+        case DWT.ARROW_RIGHT:
+            int count = items.length;
+            if (count is 0) return;
+            if (selectedIndex  is -1) return;
+            int leadKey = (getStyle() & DWT.RIGHT_TO_LEFT) !is 0 ? DWT.ARROW_RIGHT : DWT.ARROW_LEFT;
+            int offset =  event.keyCode is leadKey ? -1 : 1;
+            int index;
+            if (!mru) {
+                index = selectedIndex + offset;
+            } else {
+                int[] visible = new int[items.length];
+                int idx = 0;
+                int current = -1;
+                for (int i = 0; i < items.length; i++) {
+                    if (items[i].showing) {
+                        if (i is selectedIndex) current = idx;
+                        visible [idx++] = i;
+                    }
+                }
+                if (current + offset >= 0 && current + offset < idx){
+                    index = visible [current + offset];
+                } else {
+                    if (showChevron) {
+                        CTabFolderEvent e = new CTabFolderEvent(this);
+                        e.widget = this;
+                        e.time = event.time;
+                        e.x = chevronRect.x;
+                        e.y = chevronRect.y;
+                        e.width = chevronRect.width;
+                        e.height = chevronRect.height;
+                        e.doit = true;
+                        for (int i = 0; i < folderListeners.length; i++) {
+                            folderListeners[i].showList(e);
+                        }
+                        if (e.doit && !isDisposed()) {
+                            showList(chevronRect);
+                        }
+                    }
+                    return;
+                }
+            }
+            if (index < 0 || index >= count) return;
+            setSelection (index, true);
+            forceFocus();
+        default:
+    }
+}
+void onDispose(Event event) {
+    removeListener(DWT.Dispose, listener);
+    notifyListeners(DWT.Dispose, event);
+    event.type = DWT.None;
+    /*
+     * Usually when an item is disposed, destroyItem will change the size of the items array,
+     * reset the bounds of all the tabs and manage the widget associated with the tab.
+     * Since the whole folder is being disposed, this is not necessary.  For speed
+     * the inDispose flag is used to skip over this part of the item dispose.
+     */
+    inDispose = true;
+
+    if (showMenu !is null && !showMenu.isDisposed()) {
+        showMenu.dispose();
+        showMenu = null;
+    }
+    int length = items.length;
+    for (int i = 0; i < length; i++) {
+        if (items[i] !is null) {
+            items[i].dispose();
+        }
+    }
+
+    selectionGradientColors = null;
+    selectionGradientPercents = null;
+    selectionBgImage = null;
+
+    selectionBackground = null;
+    selectionForeground = null;
+    disposeSelectionHighlightGradientColors();
+}
+void onDragDetect(Event event) {
+    bool consume = false;
+    if (chevronRect.contains(event.x, event.y) ||
+        minRect.contains(event.x, event.y) ||
+        maxRect.contains(event.x, event.y)){
+        consume = true;
+    } else {
+        for (int i = 0; i < items.length; i++) {
+            if (items[i].closeRect.contains(event.x, event.y)) {
+                    consume = true;
+                    break;
+            }
+        }
+    }
+    if (consume) {
+        event.type = DWT.None;
+    }
+}
+void onFocus(Event event) {
+    checkWidget();
+    if (selectedIndex >= 0) {
+        redraw();
+    } else {
+        setSelection(0, true);
+    }
+}
+bool onMnemonic (Event event) {
+    auto key = event.character;
+    for (int i = 0; i < items.length; i++) {
+        if (items[i] !is null) {
+            auto mnemonic = _findMnemonic (items[i].getText ());
+            if (mnemonic !is '\0') {
+                if ( CharacterToLower(key) is mnemonic) {
+                    setSelection(i, true);
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+void onMouseDoubleClick(Event event) {
+    if (event.button !is 1 ||
+        (event.stateMask & DWT.BUTTON2) !is 0 ||
+        (event.stateMask & DWT.BUTTON3) !is 0) return;
+    Event e = new Event();
+    e.item = getItem(new Point(event.x, event.y));
+    if (e.item !is null) {
+        notifyListeners(DWT.DefaultSelection, e);
+    }
+}
+void onMouse(Event event) {
+    int x = event.x, y = event.y;
+    switch (event.type) {
+        case DWT.MouseEnter: {
+            setToolTipText(null);
+            break;
+        }
+        case DWT.MouseExit: {
+            if (minImageState !is NORMAL) {
+                minImageState = NORMAL;
+                redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
+            }
+            if (maxImageState !is NORMAL) {
+                maxImageState = NORMAL;
+                redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
+            }
+            if (chevronImageState !is NORMAL) {
+                chevronImageState = NORMAL;
+                redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
+            }
+            for (int i=0; i<items.length; i++) {
+                CTabItem item = items[i];
+                if (i !is selectedIndex && item.closeImageState !is NONE) {
+                    item.closeImageState = NONE;
+                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                }
+                if (i is selectedIndex && item.closeImageState !is NORMAL) {
+                    item.closeImageState = NORMAL;
+                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                }
+            }
+            break;
+        }
+        case DWT.MouseDown: {
+            if (minRect.contains(x, y)) {
+                if (event.button !is 1) return;
+                minImageState = SELECTED;
+                redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
+                update();
+                return;
+            }
+            if (maxRect.contains(x, y)) {
+                if (event.button !is 1) return;
+                maxImageState = SELECTED;
+                redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
+                update();
+                return;
+            }
+            if (chevronRect.contains(x, y)) {
+                if (event.button !is 1) return;
+                if (chevronImageState !is HOT) {
+                    chevronImageState = HOT;
+                } else {
+                    chevronImageState = SELECTED;
+                }
+                redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
+                update();
+                return;
+            }
+            CTabItem item = null;
+            if (single) {
+                if (selectedIndex !is -1) {
+                    Rectangle bounds = items[selectedIndex].getBounds();
+                    if (bounds.contains(x, y)){
+                        item = items[selectedIndex];
+                    }
+                }
+            } else {
+                for (int i=0; i<items.length; i++) {
+                    Rectangle bounds = items[i].getBounds();
+                    if (bounds.contains(x, y)){
+                        item = items[i];
+                    }
+                }
+            }
+            if (item !is null) {
+                if (item.closeRect.contains(x,y)){
+                    if (event.button !is 1) return;
+                    item.closeImageState = SELECTED;
+                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                    update();
+                    return;
+                }
+                int index = indexOf(item);
+                if (item.showing){
+                    setSelection(index, true);
+                }
+                return;
+            }
+            break;
+        }
+        case DWT.MouseMove: {
+            _setToolTipText(event.x, event.y);
+            bool close = false, minimize = false, maximize = false, chevron = false;
+            if (minRect.contains(x, y)) {
+                minimize = true;
+                if (minImageState !is SELECTED && minImageState !is HOT) {
+                    minImageState = HOT;
+                    redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
+                }
+            }
+            if (maxRect.contains(x, y)) {
+                maximize = true;
+                if (maxImageState !is SELECTED && maxImageState !is HOT) {
+                    maxImageState = HOT;
+                    redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
+                }
+            }
+            if (chevronRect.contains(x, y)) {
+                chevron = true;
+                if (chevronImageState !is SELECTED && chevronImageState !is HOT) {
+                    chevronImageState = HOT;
+                    redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
+                }
+            }
+            if (minImageState !is NORMAL && !minimize) {
+                minImageState = NORMAL;
+                redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
+            }
+            if (maxImageState !is NORMAL && !maximize) {
+                maxImageState = NORMAL;
+                redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
+            }
+            if (chevronImageState !is NORMAL && !chevron) {
+                chevronImageState = NORMAL;
+                redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
+            }
+            for (int i=0; i<items.length; i++) {
+                CTabItem item = items[i];
+                close = false;
+                if (item.getBounds().contains(x, y)) {
+                    close = true;
+                    if (item.closeRect.contains(x, y)) {
+                        if (item.closeImageState !is SELECTED && item.closeImageState !is HOT) {
+                            item.closeImageState = HOT;
+                            redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                        }
+                    } else {
+                        if (item.closeImageState !is NORMAL) {
+                            item.closeImageState = NORMAL;
+                            redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                        }
+                    }
+                }
+                if (i !is selectedIndex && item.closeImageState !is NONE && !close) {
+                    item.closeImageState = NONE;
+                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                }
+                if (i is selectedIndex && item.closeImageState !is NORMAL && !close) {
+                    item.closeImageState = NORMAL;
+                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                }
+            }
+            break;
+        }
+        case DWT.MouseUp: {
+            if (event.button !is 1) return;
+            if (chevronRect.contains(x, y)) {
+                bool selected = chevronImageState is SELECTED;
+                if (!selected) return;
+                CTabFolderEvent e = new CTabFolderEvent(this);
+                e.widget = this;
+                e.time = event.time;
+                e.x = chevronRect.x;
+                e.y = chevronRect.y;
+                e.width = chevronRect.width;
+                e.height = chevronRect.height;
+                e.doit = true;
+                for (int i = 0; i < folderListeners.length; i++) {
+                    folderListeners[i].showList(e);
+                }
+                if (e.doit && !isDisposed()) {
+                    showList(chevronRect);
+                }
+                return;
+            }
+            if (minRect.contains(x, y)) {
+                bool selected = minImageState is SELECTED;
+                minImageState = HOT;
+                redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
+                if (!selected) return;
+                CTabFolderEvent e = new CTabFolderEvent(this);
+                e.widget = this;
+                e.time = event.time;
+                for (int i = 0; i < folderListeners.length; i++) {
+                    if (minimized) {
+                        folderListeners[i].restore(e);
+                    } else {
+                        folderListeners[i].minimize(e);
+                    }
+                }
+                return;
+            }
+            if (maxRect.contains(x, y)) {
+                bool selected = maxImageState is SELECTED;
+                maxImageState = HOT;
+                redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
+                if (!selected) return;
+                CTabFolderEvent e = new CTabFolderEvent(this);
+                e.widget = this;
+                e.time = event.time;
+                for (int i = 0; i < folderListeners.length; i++) {
+                    if (maximized) {
+                        folderListeners[i].restore(e);
+                    } else {
+                        folderListeners[i].maximize(e);
+                    }
+                }
+                return;
+            }
+            CTabItem item = null;
+            if (single) {
+                if (selectedIndex !is -1) {
+                    Rectangle bounds = items[selectedIndex].getBounds();
+                    if (bounds.contains(x, y)){
+                        item = items[selectedIndex];
+                    }
+                }
+            } else {
+                for (int i=0; i<items.length; i++) {
+                    Rectangle bounds = items[i].getBounds();
+                    if (bounds.contains(x, y)){
+                        item = items[i];
+                    }
+                }
+            }
+            if (item !is null) {
+                if (item.closeRect.contains(x,y)) {
+                    bool selected = item.closeImageState is SELECTED;
+                    item.closeImageState = HOT;
+                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
+                    if (!selected) return;
+                    CTabFolderEvent e = new CTabFolderEvent(this);
+                    e.widget = this;
+                    e.time = event.time;
+                    e.item = item;
+                    e.doit = true;
+                    for (int j = 0; j < folderListeners.length; j++) {
+                        CTabFolder2Listener listener = folderListeners[j];
+                        listener.close(e);
+                    }
+                    for (int j = 0; j < tabListeners.length; j++) {
+                        CTabFolderListener listener = tabListeners[j];
+                        listener.itemClosed(e);
+                    }
+                    if (e.doit) {
+                        item.dispose();
+                        Display display = getDisplay();
+                        Point pt = display.getCursorLocation();
+                        pt = display.map(null, this, pt.x, pt.y);
+                        CTabItem nextItem = getItem(pt);
+                        if (nextItem !is null) {
+                            if (nextItem.closeRect.contains(pt)) {
+                                if (nextItem.closeImageState !is SELECTED && nextItem.closeImageState !is HOT) {
+                                    nextItem.closeImageState = HOT;
+                                    redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width, nextItem.closeRect.height, false);
+                                }
+                            } else {
+                                if (nextItem.closeImageState !is NORMAL) {
+                                    nextItem.closeImageState = NORMAL;
+                                    redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width, nextItem.closeRect.height, false);
+                                }
+                            }
+                        }
+                    }
+                    return;
+                }
+            }
+        default:
+        }
+    }
+}
+bool onPageTraversal(Event event) {
+    int count = items.length;
+    if (count is 0) return false;
+    int index = selectedIndex;
+    if (index  is -1) {
+        index = 0;
+    } else {
+        int offset = (event.detail is DWT.TRAVERSE_PAGE_NEXT) ? 1 : -1;
+        if (!mru) {
+            index = (selectedIndex + offset + count) % count;
+        } else {
+            int[] visible = new int[items.length];
+            int idx = 0;
+            int current = -1;
+            for (int i = 0; i < items.length; i++) {
+                if (items[i].showing) {
+                    if (i is selectedIndex) current = idx;
+                    visible [idx++] = i;
+                }
+            }
+            if (current + offset >= 0 && current + offset < idx){
+                index = visible [current + offset];
+            } else {
+                if (showChevron) {
+                    CTabFolderEvent e = new CTabFolderEvent(this);
+                    e.widget = this;
+                    e.time = event.time;
+                    e.x = chevronRect.x;
+                    e.y = chevronRect.y;
+                    e.width = chevronRect.width;
+                    e.height = chevronRect.height;
+                    e.doit = true;
+                    for (int i = 0; i < folderListeners.length; i++) {
+                        folderListeners[i].showList(e);
+                    }
+                    if (e.doit && !isDisposed()) {
+                        showList(chevronRect);
+                    }
+                }
+                return true;
+            }
+        }
+    }
+    setSelection (index, true);
+    return true;
+}
+void onPaint(Event event) {
+    if (inDispose) return;
+    Font font = getFont();
+    if (oldFont is null || oldFont!=font) {
+        // handle case where  default font changes
+        oldFont = font;
+        if (!updateTabHeight(false)) {
+            updateItems();
+            redraw();
+            return;
+        }
+    }
+
+    GC gc = event.gc;
+    Font gcFont = gc.getFont();
+    Color gcBackground = gc.getBackground();
+    Color gcForeground = gc.getForeground();
+
+// Useful for debugging paint problems
+//{
+//Point size = getSize();
+//gc.setBackground(getDisplay().getSystemColor(DWT.COLOR_GREEN));
+//gc.fillRectangle(-10, -10, size.x + 20, size.y+20);
+//}
+
+    drawBody(event);
+
+    gc.setFont(gcFont);
+    gc.setForeground(gcForeground);
+    gc.setBackground(gcBackground);
+
+    drawTabArea(event);
+
+    gc.setFont(gcFont);
+    gc.setForeground(gcForeground);
+    gc.setBackground(gcBackground);
+}
+
+void onResize() {
+    if (updateItems()) redrawTabs();
+
+    Point size = getSize();
+    if (oldSize is null) {
+        redraw();
+    } else {
+        if (onBottom && size.y !is oldSize.y) {
+            redraw();
+        } else {
+            int x1 = Math.min(size.x, oldSize.x);
+            if (size.x !is oldSize.x) x1 -= borderRight + highlight_margin + 2;
+            if (!simple) x1 -= 5; // rounded top right corner
+            int y1 = Math.min(size.y, oldSize.y);
+            if (size.y !is oldSize.y) y1 -= borderBottom + highlight_margin;
+            int x2 = Math.max(size.x, oldSize.x);
+            int y2 = Math.max(size.y, oldSize.y);
+            redraw(0, y1, x2, y2 - y1, false);
+            redraw(x1, 0, x2 - x1, y2, false);
+        }
+    }
+    oldSize = size;
+}
+void onTraverse (Event event) {
+    switch (event.detail) {
+        case DWT.TRAVERSE_ESCAPE:
+        case DWT.TRAVERSE_RETURN:
+        case DWT.TRAVERSE_TAB_NEXT:
+        case DWT.TRAVERSE_TAB_PREVIOUS:
+            Control focusControl = getDisplay().getFocusControl();
+            if (focusControl is this) event.doit = true;
+            break;
+        case DWT.TRAVERSE_MNEMONIC:
+            event.doit = onMnemonic(event);
+            if (event.doit) event.detail = DWT.TRAVERSE_NONE;
+            break;
+        case DWT.TRAVERSE_PAGE_NEXT:
+        case DWT.TRAVERSE_PAGE_PREVIOUS:
+            event.doit = onPageTraversal(event);
+            event.detail = DWT.TRAVERSE_NONE;
+            break;
+        default:
+    }
+}
+void redrawTabs() {
+    Point size = getSize();
+    if (onBottom) {
+        redraw(0, size.y - borderBottom - tabHeight - highlight_header - 1, size.x, borderBottom + tabHeight + highlight_header + 1, false);
+    } else {
+        redraw(0, 0, size.x, borderTop + tabHeight + highlight_header + 1, false);
+    }
+}
+/**
+ * Removes the listener.
+ *
+ * @param listener the listener
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * </ul>
+ *
+ * @see #addCTabFolder2Listener(CTabFolder2Listener)
+ *
+ * @since 3.0
+ */
+public void removeCTabFolder2Listener(CTabFolder2Listener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    if (folderListeners.length is 0) return;
+    int index = -1;
+    for (int i = 0; i < folderListeners.length; i++) {
+        if (listener is folderListeners[i]){
+            index = i;
+            break;
+        }
+    }
+    if (index is -1) return;
+    if (folderListeners.length is 1) {
+        folderListeners = new CTabFolder2Listener[0];
+        return;
+    }
+    CTabFolder2Listener[] newTabListeners = new CTabFolder2Listener[folderListeners.length - 1];
+    SimpleType!(CTabFolder2Listener).arraycopy(folderListeners, 0, newTabListeners, 0, index);
+    SimpleType!(CTabFolder2Listener).arraycopy(folderListeners, index + 1, newTabListeners, index, folderListeners.length - index - 1);
+    folderListeners = newTabListeners;
+}
+/**
+ * Removes the listener.
+ *
+ * @param listener the listener
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * </ul>
+ *
+ * @deprecated see removeCTabFolderCloseListener(CTabFolderListener)
+ */
+public void removeCTabFolderListener(CTabFolderListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    if (tabListeners.length is 0) return;
+    int index = -1;
+    for (int i = 0; i < tabListeners.length; i++) {
+        if (listener is tabListeners[i]){
+            index = i;
+            break;
+        }
+    }
+    if (index is -1) return;
+    if (tabListeners.length is 1) {
+        tabListeners = new CTabFolderListener[0];
+        return;
+    }
+    CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length - 1];
+    SimpleType!(CTabFolderListener).arraycopy(tabListeners, 0, newTabListeners, 0, index);
+    SimpleType!(CTabFolderListener).arraycopy(tabListeners, index + 1, newTabListeners, index, tabListeners.length - index - 1);
+    tabListeners = newTabListeners;
+}
+/**
+ * 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    }
+    removeListener(DWT.Selection, listener);
+    removeListener(DWT.DefaultSelection, listener);
+}
+public override void setBackground (Color color) {
+    super.setBackground(color);
+    redraw();
+}
+/**
+ * Specify a gradient of colours to be drawn in the background of the unselected tabs.
+ * For example to draw a gradient that varies from dark blue to blue and then to
+ * white, use the following call to setBackground:
+ * <pre>
+ *  cfolder.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE)},
+ *                     new int[] {25, 50, 100});
+ * </pre>
+ *
+ * @param colors an array of Color that specifies the colors to appear in the gradient
+ *               in order of appearance left to right.  The value <code>null</code> clears the
+ *               background gradient. The value <code>null</code> can be used inside the array of
+ *               Color to specify the background color.
+ * @param percents an array of integers between 0 and 100 specifying the percent of the width
+ *                 of the widget at which the color should change.  The size of the percents array must be one
+ *                 less than the size of the colors array.
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ *
+ * @since 3.0
+ */
+void setBackground(Color[] colors, int[] percents) {
+    setBackground(colors, percents, false);
+}
+/**
+ * Specify a gradient of colours to be drawn in the background of the unselected tab.
+ * For example to draw a vertical gradient that varies from dark blue to blue and then to
+ * white, use the following call to setBackground:
+ * <pre>
+ *  cfolder.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE)},
+ *                        new int[] {25, 50, 100}, true);
+ * </pre>
+ *
+ * @param colors an array of Color that specifies the colors to appear in the gradient
+ *               in order of appearance left to right.  The value <code>null</code> clears the
+ *               background gradient. The value <code>null</code> can be used inside the array of
+ *               Color to specify the background color.
+ * @param percents an array of integers between 0 and 100 specifying the percent of the width
+ *                 of the widget at which the color should change.  The size of the percents array must be one
+ *                 less than the size of the colors array.
+ *
+ * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ *
+ * @since 3.0
+ */
+void setBackground(Color[] colors, int[] percents, bool vertical) {
+    checkWidget();
+    if (colors !is null) {
+        if (percents is null || percents.length !is colors.length - 1) {
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+        }
+        for (int i = 0; i < percents.length; i++) {
+            if (percents[i] < 0 || percents[i] > 100) {
+                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            }
+            if (i > 0 && percents[i] < percents[i-1]) {
+                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            }
+        }
+        if (getDisplay().getDepth() < 15) {
+            // Don't use gradients on low color displays
+            colors = [colors[colors.length - 1]];
+            percents = null;
+        }
+    }
+
+    // Are these settings the same as before?
+    if (bgImage is null) {
+        if ((gradientColors !is null) && (colors !is null) &&
+            (gradientColors.length is colors.length)) {
+            bool same = false;
+            for (int i = 0; i < gradientColors.length; i++) {
+                if (gradientColors[i] is null) {
+                    same = colors[i] is null;
+                } else {
+                    same = cast(bool)(gradientColors[i]==colors[i]);
+                }
+                if (!same) break;
+            }
+            if (same) {
+                for (int i = 0; i < gradientPercents.length; i++) {
+                    same = gradientPercents[i] is percents[i];
+                    if (!same) break;
+                }
+            }
+            if (same && this.gradientVertical is vertical) return;
+        }
+    } else {
+        bgImage = null;
+    }
+    // Store the new settings
+    if (colors is null) {
+        gradientColors = null;
+        gradientPercents = null;
+        gradientVertical = false;
+        setBackground(cast(Color)null);
+    } else {
+        gradientColors = new Color[colors.length];
+        for (int i = 0; i < colors.length; ++i) {
+            gradientColors[i] = colors[i];
+        }
+        gradientPercents = new int[percents.length];
+        for (int i = 0; i < percents.length; ++i) {
+            gradientPercents[i] = percents[i];
+        }
+        gradientVertical = vertical;
+        setBackground(gradientColors[gradientColors.length-1]);
+    }
+
+    // Refresh with the new settings
+    redraw();
+}
+
+/**
+ * Set the image to be drawn in the background of the unselected tab.  Image
+ * is stretched or compressed to cover entire unselected tab area.
+ *
+ * @param image the image to be drawn in the background
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+void setBackground(Image image) {
+    checkWidget();
+    if (image is bgImage) return;
+    if (image !is null) {
+        gradientColors = null;
+        gradientPercents = null;
+    }
+    bgImage = image;
+    redraw();
+}
+/**
+ * Toggle the visibility of the border
+ *
+ * @param show true if the border should be displayed
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setBorderVisible(bool show) {
+    checkWidget();
+    if ((borderLeft is 1) is show) return;
+    borderLeft = borderRight = show ? 1 : 0;
+    borderTop = onBottom ? borderLeft : 0;
+    borderBottom = onBottom ? 0 : borderLeft;
+    Rectangle rectBefore = getClientArea();
+    updateItems();
+    Rectangle rectAfter = getClientArea();
+    if (rectBefore!=rectAfter) {
+        notifyListeners(DWT.Resize, new Event());
+    }
+    redraw();
+}
+void setButtonBounds() {
+    Point size = getSize();
+    int oldX, oldY, oldWidth, oldHeight;
+    // max button
+    oldX = maxRect.x;
+    oldY = maxRect.y;
+    oldWidth = maxRect.width;
+    oldHeight = maxRect.height;
+    maxRect.x = maxRect.y = maxRect.width = maxRect.height = 0;
+    if (showMax) {
+        maxRect.x = size.x - borderRight - BUTTON_SIZE - 3;
+        if (borderRight > 0) maxRect.x += 1;
+        maxRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
+        maxRect.width = BUTTON_SIZE;
+        maxRect.height = BUTTON_SIZE;
+    }
+    if (oldX !is maxRect.x || oldWidth !is maxRect.width ||
+        oldY !is maxRect.y || oldHeight !is maxRect.height) {
+        int left = Math.min(oldX, maxRect.x);
+        int right = Math.max(oldX + oldWidth, maxRect.x + maxRect.width);
+        int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
+        redraw(left, top, right - left, tabHeight, false);
+    }
+
+    // min button
+    oldX = minRect.x;
+    oldY = minRect.y;
+    oldWidth = minRect.width;
+    oldHeight = minRect.height;
+    minRect.x = minRect.y = minRect.width = minRect.height = 0;
+    if (showMin) {
+        minRect.x = size.x - borderRight - maxRect.width - BUTTON_SIZE - 3;
+        if (borderRight > 0) minRect.x += 1;
+        minRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
+        minRect.width = BUTTON_SIZE;
+        minRect.height = BUTTON_SIZE;
+    }
+    if (oldX !is minRect.x || oldWidth !is minRect.width ||
+        oldY !is minRect.y || oldHeight !is minRect.height) {
+        int left = Math.min(oldX, minRect.x);
+        int right = Math.max(oldX + oldWidth, minRect.x + minRect.width);
+        int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
+        redraw(left, top, right - left, tabHeight, false);
+    }
+
+    // top right control
+    oldX = topRightRect.x;
+    oldY = topRightRect.y;
+    oldWidth = topRightRect.width;
+    oldHeight = topRightRect.height;
+    topRightRect.x = topRightRect.y = topRightRect.width = topRightRect.height = 0;
+    if (topRight !is null) {
+        switch (topRightAlignment) {
+            case DWT.FILL: {
+                int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width;
+                if (!simple && borderRight > 0 && !showMax && !showMin) rightEdge -= 2;
+                if (single) {
+                    if (items.length is 0 || selectedIndex is -1) {
+                        topRightRect.x = borderLeft + 3;
+                        topRightRect.width = rightEdge - topRightRect.x;
+                    } else {
+                        // fill size is 0 if item compressed
+                        CTabItem item = items[selectedIndex];
+                        if (item.x + item.width + 7 + 3*BUTTON_SIZE/2 >= rightEdge) break;
+                        topRightRect.x = item.x + item.width + 7 + 3*BUTTON_SIZE/2;
+                        topRightRect.width = rightEdge - topRightRect.x;
+                    }
+                } else {
+                    // fill size is 0 if chevron showing
+                    if (showChevron) break;
+                    if (items.length is 0) {
+                        topRightRect.x = borderLeft + 3;
+                    } else {
+                        CTabItem item = items[items.length - 1];
+                        topRightRect.x = item.x + item.width;
+                        if (!simple && items.length - 1 is selectedIndex) topRightRect.x += curveWidth - curveIndent;
+                    }
+                    topRightRect.width = Math.max(0, rightEdge - topRightRect.x);
+                }
+                topRightRect.y = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
+                topRightRect.height = tabHeight - 1;
+                break;
+            }
+            case DWT.RIGHT: {
+                Point topRightSize = topRight.computeSize(DWT.DEFAULT, tabHeight, false);
+                int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width;
+                if (!simple && borderRight > 0 && !showMax && !showMin) rightEdge -= 2;
+                topRightRect.x = rightEdge - topRightSize.x;
+                topRightRect.width = topRightSize.x;
+                topRightRect.y = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
+                topRightRect.height = tabHeight - 1;
+            }
+            default:
+        }
+        topRight.setBounds(topRightRect);
+    }
+    if (oldX !is topRightRect.x || oldWidth !is topRightRect.width ||
+        oldY !is topRightRect.y || oldHeight !is topRightRect.height) {
+        int left = Math.min(oldX, topRightRect.x);
+        int right = Math.max(oldX + oldWidth, topRightRect.x + topRightRect.width);
+        int top = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1;
+        redraw(left, top, right - left, tabHeight, false);
+    }
+
+    // chevron button
+    oldX = chevronRect.x;
+    oldY = chevronRect.y;
+    oldWidth = chevronRect.width;
+    oldHeight = chevronRect.height;
+    chevronRect.x = chevronRect.y = chevronRect.height = chevronRect.width = 0;
+    if (single) {
+        if (selectedIndex is -1 || items.length > 1) {
+            chevronRect.width = 3*BUTTON_SIZE/2;
+            chevronRect.height = BUTTON_SIZE;
+            chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height)/2 : borderTop + (tabHeight - chevronRect.height)/2;
+            if (selectedIndex is -1) {
+                chevronRect.x = size.x - borderRight - 3 - minRect.width - maxRect.width - topRightRect.width - chevronRect.width;
+            } else {
+                CTabItem item = items[selectedIndex];
+                int w = size.x - borderRight - 3 - minRect.width - maxRect.width - chevronRect.width;
+                if (topRightRect.width > 0) w -= topRightRect.width + 3;
+                chevronRect.x = Math.min(item.x + item.width + 3, w);
+            }
+            if (borderRight > 0) chevronRect.x += 1;
+        }
+    } else {
+        if (showChevron) {
+            chevronRect.width = 3*BUTTON_SIZE/2;
+            chevronRect.height = BUTTON_SIZE;
+            int i = 0, lastIndex = -1;
+            while (i < priority.length && items[priority[i]].showing) {
+                lastIndex = Math.max(lastIndex, priority[i++]);
+            }
+            if (lastIndex is -1) lastIndex = firstIndex;
+            CTabItem lastItem = items[lastIndex];
+            int w = lastItem.x + lastItem.width + 3;
+            if (!simple && lastIndex is selectedIndex) w += curveWidth - 2*curveIndent;
+            chevronRect.x = Math.min(w, getRightItemEdge());
+            chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height)/2 : borderTop + (tabHeight - chevronRect.height)/2;
+        }
+    }
+    if (oldX !is chevronRect.x || oldWidth !is chevronRect.width ||
+        oldY !is chevronRect.y || oldHeight !is chevronRect.height) {
+        int left = Math.min(oldX, chevronRect.x);
+        int right = Math.max(oldX + oldWidth, chevronRect.x + chevronRect.width);
+        int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
+        redraw(left, top, right - left, tabHeight, false);
+    }
+}
+public override void setFont(Font font) {
+    checkWidget();
+    if (font !is null && font==getFont()) return;
+    super.setFont(font);
+    oldFont = getFont();
+    if (!updateTabHeight(false)) {
+        updateItems();
+        redraw();
+    }
+}
+public override void setForeground (Color color) {
+    super.setForeground(color);
+    redraw();
+}
+/**
+ * Display an insert marker before or after the specified tab item.
+ *
+ * A value of null will clear the mark.
+ *
+ * @param item the item with which the mark is associated or null
+ *
+ * @param after true if the mark should be displayed after the specified item
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setInsertMark(CTabItem item, bool after) {
+    checkWidget();
+}
+/**
+ * Display an insert marker before or after the specified tab item.
+ *
+ * A value of -1 will clear the mark.
+ *
+ * @param index the index of the item with which the mark is associated or null
+ *
+ * @param after true if the mark should be displayed after the specified item
+ *
+ * @exception IllegalArgumentException<ul>
+ * </ul>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setInsertMark(int index, bool after) {
+    checkWidget();
+    if (index < -1 || index >= getItemCount()) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+}
+bool setItemLocation() {
+    bool changed = false;
+    if (items.length is 0) return false;
+    Point size = getSize();
+    int y = onBottom ? Math.max(borderBottom, size.y - borderBottom - tabHeight) : borderTop;
+    if (single) {
+        int defaultX = getDisplay().getBounds().width + 10; // off screen
+        for (int i = 0; i < items.length; i++) {
+            CTabItem item = items[i];
+            if (i is selectedIndex) {
+                firstIndex = selectedIndex;
+                int oldX = item.x, oldY = item.y;
+                item.x = borderLeft;
+                item.y = y;
+                item.showing = true;
+                if (showClose || item.showClose) {
+                    item.closeRect.x = borderLeft + CTabItem.LEFT_MARGIN;
+                    item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
+                }
+                if (item.x !is oldX || item.y !is oldY) changed = true;
+            } else {
+                item.x = defaultX;
+                item.showing = false;
+            }
+        }
+    } else {
+        int rightItemEdge = getRightItemEdge();
+        int maxWidth = rightItemEdge - borderLeft;
+        int width = 0;
+        for (int i = 0; i < priority.length; i++) {
+            CTabItem item = items[priority[i]];
+            width += item.width;
+            item.showing = i is 0 ? true : item.width > 0 && width <= maxWidth;
+            if (!simple && priority[i] is selectedIndex) width += curveWidth - 2*curveIndent;
+        }
+        int x = 0;
+        int defaultX = getDisplay().getBounds().width + 10; // off screen
+        firstIndex = items.length - 1;
+        for (int i = 0; i < items.length; i++) {
+            CTabItem item = items[i];
+            if (!item.showing) {
+                if (item.x !is defaultX) changed = true;
+                item.x = defaultX;
+            } else {
+                firstIndex = Math.min(firstIndex, i);
+                if (item.x !is x || item.y !is y) changed = true;
+                item.x = x;
+                item.y = y;
+                if (i is selectedIndex) {
+                    int edge = Math.min(item.x + item.width, rightItemEdge);
+                    item.closeRect.x = edge - CTabItem.RIGHT_MARGIN - BUTTON_SIZE;
+                } else {
+                    item.closeRect.x = item.x + item.width - CTabItem.RIGHT_MARGIN - BUTTON_SIZE;
+                }
+                item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
+                x = x + item.width;
+                if (!simple && i is selectedIndex) x += curveWidth - 2*curveIndent;
+            }
+        }
+    }
+    return changed;
+}
+bool setItemSize() {
+    bool changed = false;
+    if (isDisposed()) return changed;
+    Point size = getSize();
+    if (size.x <= 0 || size.y <= 0) return changed;
+    xClient = borderLeft + marginWidth + highlight_margin;
+    if (onBottom) {
+        yClient = borderTop + highlight_margin + marginHeight;
+    } else {
+        yClient = borderTop + tabHeight + highlight_header + marginHeight;
+    }
+    showChevron = false;
+    if (single) {
+        showChevron = true;
+        if (selectedIndex !is -1) {
+            CTabItem tab = items[selectedIndex];
+            GC gc = new GC(this);
+            int width = tab.preferredWidth(gc, true, false);
+            gc.dispose();
+            width = Math.min(width, getRightItemEdge() - borderLeft);
+            if (tab.height !is tabHeight || tab.width !is width) {
+                changed = true;
+                tab.shortenedText = null;
+                tab.shortenedTextWidth = 0;
+                tab.height = tabHeight;
+                tab.width = width;
+                tab.closeRect.width = tab.closeRect.height = 0;
+                if (showClose || tab.showClose) {
+                    tab.closeRect.width = BUTTON_SIZE;
+                    tab.closeRect.height = BUTTON_SIZE;
+                }
+            }
+        }
+        return changed;
+    }
+
+    if (items.length is 0) return changed;
+
+    int[] widths;
+    GC gc = new GC(this);
+    int tabAreaWidth = size.x - borderLeft - borderRight - 3;
+    if (showMin) tabAreaWidth -= BUTTON_SIZE;
+    if (showMax) tabAreaWidth -= BUTTON_SIZE;
+    if (topRightAlignment is DWT.RIGHT && topRight !is null) {
+        Point rightSize = topRight.computeSize(DWT.DEFAULT, DWT.DEFAULT, false);
+        tabAreaWidth -= rightSize.x + 3;
+    }
+    if (!simple) tabAreaWidth -= curveWidth - 2*curveIndent;
+    tabAreaWidth = Math.max(0, tabAreaWidth);
+
+    // First, try the minimum tab size at full compression.
+    int minWidth = 0;
+    int[] minWidths = new int[items.length];
+    for (int i = 0; i < priority.length; i++) {
+        int index = priority[i];
+        minWidths[index] = items[index].preferredWidth(gc, index is selectedIndex, true);
+        minWidth += minWidths[index];
+        if (minWidth > tabAreaWidth) break;
+    }
+    if (minWidth > tabAreaWidth) {
+        // full compression required and a chevron
+        showChevron = items.length > 1;
+        if (showChevron) tabAreaWidth -= 3*BUTTON_SIZE/2;
+        widths = minWidths;
+        int index = selectedIndex !is -1 ? selectedIndex : 0;
+        if (tabAreaWidth < widths[index]) {
+            widths[index] = Math.max(0, tabAreaWidth);
+        }
+    } else {
+        int maxWidth = 0;
+        int[] maxWidths = new int[items.length];
+        for (int i = 0; i < items.length; i++) {
+            maxWidths[i] = items[i].preferredWidth(gc, i is selectedIndex, false);
+            maxWidth += maxWidths[i];
+        }
+        if (maxWidth <= tabAreaWidth) {
+            // no compression required
+            widths = maxWidths;
+        } else {
+            // determine compression for each item
+            int extra = (tabAreaWidth - minWidth) / items.length;
+            while (true) {
+                int large = 0, totalWidth = 0;
+                for (int i = 0 ; i < items.length; i++) {
+                    if (maxWidths[i] > minWidths[i] + extra) {
+                        totalWidth += minWidths[i] + extra;
+                        large++;
+                    } else {
+                        totalWidth += maxWidths[i];
+                    }
+                }
+                if (totalWidth >= tabAreaWidth) {
+                    extra--;
+                    break;
+                }
+                if (large is 0 || tabAreaWidth - totalWidth < large) break;
+                extra++;
+            }
+            widths = new int[items.length];
+            for (int i = 0; i < items.length; i++) {
+                widths[i] = Math.min(maxWidths[i], minWidths[i] + extra);
+            }
+        }
+    }
+    gc.dispose();
+
+    for (int i = 0; i < items.length; i++) {
+        CTabItem tab = items[i];
+        int width = widths[i];
+        if (tab.height !is tabHeight || tab.width !is width) {
+            changed = true;
+            tab.shortenedText = null;
+            tab.shortenedTextWidth = 0;
+            tab.height = tabHeight;
+            tab.width = width;
+            tab.closeRect.width = tab.closeRect.height = 0;
+            if (showClose || tab.showClose) {
+                if (i is selectedIndex || showUnselectedClose) {
+                    tab.closeRect.width = BUTTON_SIZE;
+                    tab.closeRect.height = BUTTON_SIZE;
+                }
+            }
+        }
+    }
+    return changed;
+}
+/**
+ * Marks the receiver's maximize button as visible if the argument is <code>true</code>,
+ * and marks it invisible otherwise.
+ *
+ * @param visible the new visibility state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setMaximizeVisible(bool visible) {
+    checkWidget();
+    if (showMax is visible) return;
+    // display maximize button
+    showMax = visible;
+    updateItems();
+    redraw();
+}
+/**
+ * Sets the layout which is associated with the receiver to be
+ * the argument which may be null.
+ * <p>
+ * Note: No Layout can be set on this Control because it already
+ * manages the size and position of its children.
+ * </p>
+ *
+ * @param layout the receiver's new layout or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setLayout (Layout layout) {
+    checkWidget();
+    return;
+}
+/**
+ * Sets the maximized state of the receiver.
+ *
+ * @param maximize the new maximized state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setMaximized(bool maximize) {
+    checkWidget ();
+    if (this.maximized is maximize) return;
+    if (maximize && this.minimized) setMinimized(false);
+    this.maximized = maximize;
+    redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
+}
+/**
+ * Marks the receiver's minimize button as visible if the argument is <code>true</code>,
+ * and marks it invisible otherwise.
+ *
+ * @param visible the new visibility state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setMinimizeVisible(bool visible) {
+    checkWidget();
+    if (showMin is visible) return;
+    // display minimize button
+    showMin = visible;
+    updateItems();
+    redraw();
+}
+/**
+ * Sets the minimized state of the receiver.
+ *
+ * @param minimize the new minimized state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setMinimized(bool minimize) {
+    checkWidget ();
+    if (this.minimized is minimize) return;
+    if (minimize && this.maximized) setMaximized(false);
+    this.minimized = minimize;
+    redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
+}
+
+/**
+ * Sets the minimum number of characters that will
+ * be displayed in a fully compressed tab.
+ *
+ * @param count the minimum number of characters that will be displayed in a fully compressed tab
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_RANGE - if the count is less than zero</li>
+ * </ul>
+ *
+ * @since 3.0
+ */
+public void setMinimumCharacters(int count) {
+    checkWidget ();
+    if (count < 0) DWT.error(DWT.ERROR_INVALID_RANGE);
+    if (minChars is count) return;
+    minChars = count;
+    if (updateItems()) redrawTabs();
+}
+
+/**
+ * When there is not enough horizontal space to show all the tabs,
+ * by default, tabs are shown sequentially from left to right in
+ * order of their index.  When the MRU visibility is turned on,
+ * the tabs that are visible will be the tabs most recently selected.
+ * Tabs will still maintain their left to right order based on index
+ * but only the most recently selected tabs are visible.
+ * <p>
+ * For example, consider a CTabFolder that contains "Tab 1", "Tab 2",
+ * "Tab 3" and "Tab 4" (in order by index).  The user selects
+ * "Tab 1" and then "Tab 3".  If the CTabFolder is now
+ * compressed so that only two tabs are visible, by default,
+ * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently
+ * selected and "Tab 2" because it is the previous item in index order).
+ * If MRU visibility is enabled, the two visible tabs will be "Tab 1"
+ * and "Tab 3" (in that order from left to right).</p>
+ *
+ * @param show the new visibility state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setMRUVisible(bool show) {
+    checkWidget();
+    if (mru is show) return;
+    mru = show;
+    if (!mru) {
+        int idx = firstIndex;
+        int next = 0;
+        for (int i = firstIndex; i < items.length; i++) {
+            priority[next++] = i;
+        }
+        for (int i = 0; i < idx; i++) {
+            priority[next++] = i;
+        }
+        if (updateItems()) redrawTabs();
+    }
+}
+/**
+ * Set the selection to the tab at the specified item.
+ *
+ * @param item the tab item to be selected
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * </ul>
+ */
+public void setSelection(CTabItem item) {
+    checkWidget();
+    if (item is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    int index = indexOf(item);
+    setSelection(index);
+}
+/**
+ * Set the selection to the tab at the specified index.
+ *
+ * @param index the index of the tab item to be selected
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setSelection(int index) {
+    checkWidget();
+    if (index < 0 || index >= items.length) return;
+    CTabItem selection = items[index];
+    if (selectedIndex is index) {
+        showItem(selection);
+        return;
+    }
+
+    int oldIndex = selectedIndex;
+    selectedIndex = index;
+    if (oldIndex !is -1) {
+        items[oldIndex].closeImageState = NONE;
+    }
+    selection.closeImageState = NORMAL;
+    selection.showing = false;
+
+    Control control = selection.control;
+    if (control !is null && !control.isDisposed()) {
+        control.setBounds(getClientArea());
+        control.setVisible(true);
+    }
+
+    if (oldIndex !is -1) {
+        control = items[oldIndex].control;
+        if (control !is null && !control.isDisposed()) {
+            control.setVisible(false);
+        }
+    }
+    showItem(selection);
+    redraw();
+}
+void setSelection(int index, bool notify) {
+    int oldSelectedIndex = selectedIndex;
+    setSelection(index);
+    if (notify && selectedIndex !is oldSelectedIndex && selectedIndex !is -1) {
+        Event event = new Event();
+        event.item = getItem(selectedIndex);
+        notifyListeners(DWT.Selection, event);
+    }
+}
+/**
+ * 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setSelectionBackground (Color color) {
+    checkWidget();
+    setSelectionHighlightGradientColor(null);
+    if (selectionBackground is color) return;
+    if (color is null) color = getDisplay().getSystemColor(SELECTION_BACKGROUND);
+    selectionBackground = color;
+    if (selectedIndex > -1) redraw();
+}
+/**
+ * Specify a gradient of colours to be draw in the background of the selected tab.
+ * For example to draw a gradient that varies from dark blue to blue and then to
+ * white, use the following call to setBackground:
+ * <pre>
+ *  cfolder.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE)},
+ *                     new int[] {25, 50, 100});
+ * </pre>
+ *
+ * @param colors an array of Color that specifies the colors to appear in the gradient
+ *               in order of appearance left to right.  The value <code>null</code> clears the
+ *               background gradient. The value <code>null</code> can be used inside the array of
+ *               Color to specify the background color.
+ * @param percents an array of integers between 0 and 100 specifying the percent of the width
+ *                 of the widget at which the color should change.  The size of the percents array must be one
+ *                 less than the size of the colors array.
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public void setSelectionBackground(Color[] colors, int[] percents) {
+    setSelectionBackground(colors, percents, false);
+}
+/**
+ * Specify a gradient of colours to be draw in the background of the selected tab.
+ * For example to draw a vertical gradient that varies from dark blue to blue and then to
+ * white, use the following call to setBackground:
+ * <pre>
+ *  cfolder.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_BLUE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE),
+ *                                 display.getSystemColor(DWT.COLOR_WHITE)},
+ *                        new int[] {25, 50, 100}, true);
+ * </pre>
+ *
+ * @param colors an array of Color that specifies the colors to appear in the gradient
+ *               in order of appearance left to right.  The value <code>null</code> clears the
+ *               background gradient. The value <code>null</code> can be used inside the array of
+ *               Color to specify the background color.
+ * @param percents an array of integers between 0 and 100 specifying the percent of the width
+ *                 of the widget at which the color should change.  The size of the percents array must be one
+ *                 less than the size of the colors array.
+ *
+ * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ *
+ * @since 3.0
+ */
+public void setSelectionBackground(Color[] colors, int[] percents, bool vertical) {
+    checkWidget();
+    int colorsLength;
+    Color highlightBeginColor = null;  //null is no highlight
+
+    if (colors !is null) {
+        //The colors array can optionally have an extra entry which describes the highlight top color
+        //Thus its either one or two larger than the percents array
+        if (percents is null ||
+                ! ((percents.length is colors.length - 1) || (percents.length is colors.length - 2))){
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+        }
+        for (int i = 0; i < percents.length; i++) {
+            if (percents[i] < 0 || percents[i] > 100) {
+                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            }
+            if (i > 0 && percents[i] < percents[i-1]) {
+                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            }
+        }
+        //If the colors is exactly two more than percents then last is highlight
+        //Keep track of *real* colorsLength (minus the highlight)
+        if(percents.length is colors.length - 2) {
+            highlightBeginColor = colors[colors.length - 1];
+            colorsLength = colors.length - 1;
+        } else {
+            colorsLength = colors.length;
+        }
+        if (getDisplay().getDepth() < 15) {
+            // Don't use gradients on low color displays
+            colors = [colors[colorsLength - 1]];
+            colorsLength = colors.length;
+            percents = null;
+        }
+    } else {
+        colorsLength = 0;
+    }
+
+    // Are these settings the same as before?
+    if (selectionBgImage is null) {
+        if ((selectionGradientColors !is null) && (colors !is null) &&
+            (selectionGradientColors.length is colorsLength)) {
+            bool same = false;
+            for (int i = 0; i < selectionGradientColors.length; i++) {
+                if (selectionGradientColors[i] is null) {
+                    same = colors[i] is null;
+                } else {
+                    same = cast(bool)(selectionGradientColors[i]==colors[i]);
+                }
+                if (!same) break;
+            }
+            if (same) {
+                for (int i = 0; i < selectionGradientPercents.length; i++) {
+                    same = selectionGradientPercents[i] is percents[i];
+                    if (!same) break;
+                }
+            }
+            if (same && this.selectionGradientVertical is vertical) return;
+        }
+    } else {
+        selectionBgImage = null;
+    }
+    // Store the new settings
+    if (colors is null) {
+        selectionGradientColors = null;
+        selectionGradientPercents = null;
+        selectionGradientVertical = false;
+        setSelectionBackground(cast(Color)null);
+        setSelectionHighlightGradientColor(null);
+    } else {
+        selectionGradientColors = new Color[colorsLength];
+        for (int i = 0; i < colorsLength; ++i) {
+            selectionGradientColors[i] = colors[i];
+        }
+        selectionGradientPercents = new int[percents.length];
+        for (int i = 0; i < percents.length; ++i) {
+            selectionGradientPercents[i] = percents[i];
+        }
+        selectionGradientVertical = vertical;
+        setSelectionBackground(selectionGradientColors[selectionGradientColors.length-1]);
+        setSelectionHighlightGradientColor(highlightBeginColor);
+    }
+
+    // Refresh with the new settings
+    if (selectedIndex > -1) redraw();
+}
+
+/*
+ * Set the color for the highlight start for selected tabs.
+ * Update the cache of highlight gradient colors if required.
+ */
+
+void setSelectionHighlightGradientColor(Color start) {
+    //Set to null to match all the early return cases.
+    //For early returns, don't realloc the cache, we may get a cache hit next time we're given the highlight
+    selectionHighlightGradientBegin = null;
+
+    if(start is null)
+        return;
+
+    //don't bother on low colour
+    if (getDisplay().getDepth() < 15)
+        return;
+
+    //don't bother if we don't have a background gradient
+    if(selectionGradientColors.length < 2)
+        return;
+
+    //OK we know its a valid gradient now
+    selectionHighlightGradientBegin = start;
+
+    if(! isSelectionHighlightColorsCacheHit(start))
+        createSelectionHighlightGradientColors(start);  //if no cache hit then compute new ones
+}
+
+/*
+ * Return true if given start color, the cache of highlight colors we have
+ * would match the highlight colors we'd compute.
+ */
+bool isSelectionHighlightColorsCacheHit(Color start) {
+
+    if(selectionHighlightGradientColorsCache is null)
+        return false;
+
+    //this case should never happen but check to be safe before accessing array indexes
+    if(selectionHighlightGradientColorsCache.length < 2)
+        return false;
+
+    Color highlightBegin = selectionHighlightGradientColorsCache[0];
+    Color highlightEnd = selectionHighlightGradientColorsCache[selectionHighlightGradientColorsCache.length - 1];
+
+    if( highlightBegin!=start)
+        return false;
+
+    //Compare number of colours we have vs. we'd compute
+    if(selectionHighlightGradientColorsCache.length !is tabHeight)
+        return false;
+
+    //Compare existing highlight end to what it would be (selectionBackground)
+    if( highlightEnd!=selectionBackground)
+        return false;
+
+    return true;
+}
+
+/**
+ * Set the image to be drawn in the background of the selected tab.  Image
+ * is stretched or compressed to cover entire selection tab area.
+ *
+ * @param image the image to be drawn in the background
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setSelectionBackground(Image image) {
+    checkWidget();
+    setSelectionHighlightGradientColor(null);
+    if (image is selectionBgImage) return;
+    if (image !is null) {
+        selectionGradientColors = null;
+        selectionGradientPercents = null;
+        disposeSelectionHighlightGradientColors();
+    }
+    selectionBgImage = image;
+    if (selectedIndex > -1) redraw();
+}
+/**
+ * Set the foreground color of the selected tab.
+ *
+ * @param color the color of the text displayed in the selected tab
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setSelectionForeground (Color color) {
+    checkWidget();
+    if (selectionForeground is color) return;
+    if (color is null) color = getDisplay().getSystemColor(SELECTION_FOREGROUND);
+    selectionForeground = color;
+    if (selectedIndex > -1) redraw();
+}
+
+/*
+ * Allocate colors for the highlight line.
+ * Colours will be a gradual blend ranging from to.
+ * Blend length will be tab height.
+ * Recompute this if tab height changes.
+ * Could remain null if there'd be no gradient (start=end or low colour display)
+ */
+void createSelectionHighlightGradientColors(Color start) {
+    disposeSelectionHighlightGradientColors(); //dispose if existing
+
+    if(start is null)  //shouldn't happen but just to be safe
+        return;
+
+    //alloc colours for entire height to ensure it matches wherever we stop drawing
+    int fadeGradientSize = tabHeight;
+
+    RGB from = start.getRGB();
+    RGB to = selectionBackground.getRGB();
+
+    selectionHighlightGradientColorsCache = new Color[fadeGradientSize];
+    int denom = fadeGradientSize - 1;
+
+    for (int i = 0; i < fadeGradientSize; i++) {
+        int propFrom = denom - i;
+        int propTo = i;
+        int red = (to.red * propTo + from.red * propFrom) / denom;
+        int green = (to.green * propTo  + from.green * propFrom) / denom;
+        int blue = (to.blue * propTo  + from.blue * propFrom) / denom;
+        selectionHighlightGradientColorsCache[i] = new Color(getDisplay(), red, green, blue);
+    }
+}
+
+void disposeSelectionHighlightGradientColors() {
+    if(selectionHighlightGradientColorsCache is null)
+        return;
+    for (int i = 0; i < selectionHighlightGradientColorsCache.length; i++) {
+        selectionHighlightGradientColorsCache[i].dispose();
+    }
+    selectionHighlightGradientColorsCache = null;
+}
+
+/*
+ * Return the gradient start color for selected tabs, which is the start of the tab fade
+ * (end is selectionBackground).
+ */
+Color getSelectionBackgroundGradientBegin() {
+    if (selectionGradientColors is null)
+        return getSelectionBackground();
+    if (selectionGradientColors.length is 0)
+        return getSelectionBackground();
+    return selectionGradientColors[0];
+}
+
+/**
+ * Sets the shape that the CTabFolder will use to render itself.
+ *
+ * @param simple <code>true</code> if the CTabFolder should render itself in a simple, traditional style
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setSimple(bool simple) {
+    checkWidget();
+    if (this.simple !is simple) {
+        this.simple = simple;
+        Rectangle rectBefore = getClientArea();
+        updateItems();
+        Rectangle rectAfter = getClientArea();
+        if (rectBefore!=rectAfter) {
+            notifyListeners(DWT.Resize, new Event());
+        }
+        redraw();
+    }
+}
+/**
+ * Sets the number of tabs that the CTabFolder should display
+ *
+ * @param single <code>true</code> if only the selected tab should be displayed otherwise, multiple tabs will be shown.
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setSingle(bool single) {
+    checkWidget();
+    if (this.single !is single) {
+        this.single = single;
+        if (!single) {
+            for (int i = 0; i < items.length; i++) {
+                if (i !is selectedIndex && items[i].closeImageState is NORMAL) {
+                    items[i].closeImageState = NONE;
+                }
+            }
+        }
+        Rectangle rectBefore = getClientArea();
+        updateItems();
+        Rectangle rectAfter = getClientArea();
+        if (rectBefore!=rectAfter) {
+            notifyListeners(DWT.Resize, new Event());
+        }
+        redraw();
+    }
+}
+/**
+ * Specify a fixed height for the tab items.  If no height is specified,
+ * the default height is the height of the text or the image, whichever
+ * is greater. Specifying a height of -1 will revert to the default height.
+ *
+ * @param height the pixel value of the height or -1
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if called with a height of less than 0</li>
+ * </ul>
+ */
+public void setTabHeight(int height) {
+    checkWidget();
+    if (height < -1) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    fixedTabHeight = height;
+    updateTabHeight(false);
+}
+/**
+ * Specify whether the tabs should appear along the top of the folder
+ * or along the bottom of the folder.
+ *
+ * @param position <code>DWT.TOP</code> for tabs along the top or <code>DWT.BOTTOM</code> for tabs along the bottom
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the position value is not either DWT.TOP or DWT.BOTTOM</li>
+ * </ul>
+ *
+ * @since 3.0
+ */
+public void setTabPosition(int position) {
+    checkWidget();
+    if (position !is DWT.TOP && position !is DWT.BOTTOM) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (onBottom !is (position is DWT.BOTTOM)) {
+        onBottom = position is DWT.BOTTOM;
+        borderTop = onBottom ? borderLeft : 0;
+        borderBottom = onBottom ? 0 : borderRight;
+        updateTabHeight(true);
+        Rectangle rectBefore = getClientArea();
+        updateItems();
+        Rectangle rectAfter = getClientArea();
+        if (rectBefore!=rectAfter) {
+            notifyListeners(DWT.Resize, new Event());
+        }
+        redraw();
+    }
+}
+/**
+ * Set the control that appears in the top right corner of the tab folder.
+ * Typically this is a close button or a composite with a Menu and close button.
+ * The topRight control is optional.  Setting the top right control to null will
+ * remove it from the tab folder.
+ *
+ * @param control the control to be displayed in the top right corner or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li>
+ * </ul>
+ *
+ * @since 2.1
+ */
+public void setTopRight(Control control) {
+    setTopRight(control, DWT.RIGHT);
+}
+/**
+ * Set the control that appears in the top right corner of the tab folder.
+ * Typically this is a close button or a composite with a Menu and close button.
+ * The topRight control is optional.  Setting the top right control to null
+ * will remove it from the tab folder.
+ * <p>
+ * The alignment parameter sets the layout of the control in the tab area.
+ * <code>DWT.RIGHT</code> will cause the control to be positioned on the far
+ * right of the folder and it will have its default size.  <code>DWT.FILL</code>
+ * will size the control to fill all the available space to the right of the
+ * last tab.  If there is no available space, the control will not be visible.
+ * </p>
+ *
+ * @param control the control to be displayed in the top right corner or null
+ * @param alignment <code>DWT.RIGHT</code> or <code>DWT.FILL</code>
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li>
+ * </ul>
+ *
+ * @since 3.0
+ */
+public void setTopRight(Control control, int alignment) {
+    checkWidget();
+    if (alignment !is DWT.RIGHT && alignment !is DWT.FILL) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (control !is null && control.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    topRight = control;
+    topRightAlignment = alignment;
+    if (updateItems()) redraw();
+}
+/**
+ * Specify whether the close button appears
+ * when the user hovers over an unselected tabs.
+ *
+ * @param visible <code>true</code> makes the close button appear
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setUnselectedCloseVisible(bool visible) {
+    checkWidget();
+    if (showUnselectedClose is visible) return;
+    // display close button when mouse hovers
+    showUnselectedClose = visible;
+    updateItems();
+    redraw();
+}
+/**
+ * Specify whether the image appears on unselected tabs.
+ *
+ * @param visible <code>true</code> makes the image appear
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setUnselectedImageVisible(bool visible) {
+    checkWidget();
+    if (showUnselectedImage is visible) return;
+    // display image on unselected items
+    showUnselectedImage = visible;
+    updateItems();
+    redraw();
+}
+/**
+ * Shows the item.  If the item is already showing in the receiver,
+ * this method simply returns.  Otherwise, the items are scrolled until
+ * the item is visible.
+ *
+ * @param item the item to be shown
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 CTabFolder#showSelection()
+ *
+ * @since 2.0
+ */
+public void showItem (CTabItem item) {
+    checkWidget();
+    if (item is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    if (item.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    int index = indexOf(item);
+    if (index is -1) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    int idx = -1;
+    for (int i = 0; i < priority.length; i++) {
+        if (priority[i] is index) {
+            idx = i;
+            break;
+        }
+    }
+    if (mru) {
+        // move to front of mru order
+        int[] newPriority = new int[priority.length];
+        System.arraycopy(priority, 0, newPriority, 1, idx);
+        System.arraycopy(priority, idx+1, newPriority, idx+1, priority.length - idx - 1);
+        newPriority[0] = index;
+        priority = newPriority;
+    }
+    if (item.isShowing()) return;
+    updateItems(index);
+    redrawTabs();
+}
+void showList (Rectangle rect) {
+    if (items.length is 0 || !showChevron) return;
+    if (showMenu is null || showMenu.isDisposed()) {
+        showMenu = new Menu(this);
+    } else {
+        MenuItem[] items = showMenu.getItems();
+        for (int i = 0; i < items.length; i++) {
+            items[i].dispose();
+        }
+    }
+    static const char[] id = "CTabFolder_showList_Index"; //$NON-NLS-1$
+    for (int i = 0; i < items.length; i++) {
+        CTabItem tab = items[i];
+        if (tab.showing) continue;
+        MenuItem item = new MenuItem(showMenu, DWT.NONE);
+        item.setText(tab.getText());
+        item.setImage(tab.getImage());
+        item.setData(id, tab);
+        item.addSelectionListener(new class() SelectionAdapter {
+            public void widgetSelected(SelectionEvent e) {
+                MenuItem menuItem = cast(MenuItem)e.widget;
+                int index = indexOf(cast(CTabItem)menuItem.getData(id));
+                this.outer.setSelection(index, true);
+            }
+        });
+    }
+    int x = rect.x;
+    int y = rect.y + rect.height;
+    Point location = getDisplay().map(this, null, x, y);
+    showMenu.setLocation(location.x, location.y);
+    showMenu.setVisible(true);
+}
+/**
+ * Shows the selection.  If the selection is already showing in the receiver,
+ * this method simply returns.  Otherwise, the items are scrolled until
+ * the selection is visible.
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 CTabFolder#showItem(CTabItem)
+ *
+ * @since 2.0
+ */
+public void showSelection () {
+    checkWidget ();
+    if (selectedIndex !is -1) {
+        showItem(getSelection());
+    }
+}
+
+void _setToolTipText (int x, int y) {
+    char[] oldTip = getToolTipText();
+    char[] newTip = _getToolTip(x, y);
+    if (newTip is null || newTip!=oldTip) {
+        setToolTipText(newTip);
+    }
+}
+
+bool updateItems() {
+    return updateItems(selectedIndex);
+}
+
+bool updateItems(int showIndex) {
+    if (!single && !mru && showIndex !is -1) {
+        // make sure selected item will be showing
+        int firstIndex = showIndex;
+        if (priority[0] < showIndex) {
+            int maxWidth = getRightItemEdge() - borderLeft;
+            if (!simple) maxWidth -= curveWidth - 2*curveIndent;
+            int width = 0;
+            int[] widths = new int[items.length];
+            GC gc = new GC(this);
+            for (int i = priority[0]; i <= showIndex; i++) {
+                widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
+                width += widths[i];
+                if (width > maxWidth) break;
+            }
+            if (width > maxWidth) {
+                width = 0;
+                for (int i = showIndex; i >= 0; i--) {
+                    if (widths[i] is 0) widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
+                    width += widths[i];
+                    if (width > maxWidth) break;
+                    firstIndex = i;
+                }
+            } else {
+                firstIndex = priority[0];
+                for (int i = showIndex + 1; i < items.length; i++) {
+                    widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
+                    width += widths[i];
+                    if (width >= maxWidth) break;
+                }
+                if (width < maxWidth) {
+                    for (int i = priority[0] - 1; i >= 0; i--) {
+                        if (widths[i] is 0) widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
+                        width += widths[i];
+                        if (width > maxWidth) break;
+                        firstIndex = i;
+                    }
+                }
+            }
+            gc.dispose();
+        }
+        if (firstIndex !is priority[0]) {
+            int index = 0;
+            for (int i = firstIndex; i < items.length; i++) {
+                priority[index++] = i;
+            }
+            for (int i = 0; i < firstIndex; i++) {
+                priority[index++] = i;
+            }
+        }
+    }
+
+    bool oldShowChevron = showChevron;
+    bool changed = setItemSize();
+    changed |= setItemLocation();
+    setButtonBounds();
+    changed |= showChevron !is oldShowChevron;
+    if (changed && getToolTipText() !is null) {
+        Point pt = getDisplay().getCursorLocation();
+        pt = toControl(pt);
+        _setToolTipText(pt.x, pt.y);
+    }
+    return changed;
+}
+bool updateTabHeight(bool force){
+    int style = getStyle();
+    if (fixedTabHeight is 0 && (style & DWT.FLAT) !is 0 && (style & DWT.BORDER) is 0) highlight_header = 0;
+    int oldHeight = tabHeight;
+    if (fixedTabHeight !is DWT.DEFAULT) {
+        tabHeight = fixedTabHeight is 0 ? 0 : fixedTabHeight + 1; // +1 for line drawn across top of tab
+    } else {
+        int tempHeight = 0;
+        GC gc = new GC(this);
+        if (items.length is 0) {
+            tempHeight = gc.textExtent("Default", CTabItem.FLAGS).y + CTabItem.TOP_MARGIN + CTabItem.BOTTOM_MARGIN; //$NON-NLS-1$
+        } else {
+            for (int i=0; i < items.length; i++) {
+                tempHeight = Math.max(tempHeight, items[i].preferredHeight(gc));
+            }
+        }
+        gc.dispose();
+        tabHeight =  tempHeight;
+    }
+    if (!force && tabHeight is oldHeight) return false;
+
+    oldSize = null;
+    if (onBottom) {
+        int d = tabHeight - 12;
+        curve = [0,13+d, 0,12+d, 2,12+d, 3,11+d, 5,11+d, 6,10+d, 7,10+d, 9,8+d, 10,8+d,
+                          11,7+d, 11+d,7,
+                          12+d,6, 13+d,6, 15+d,4, 16+d,4, 17+d,3, 19+d,3, 20+d,2, 22+d,2, 23+d,1];
+        curveWidth = 26+d;
+        curveIndent = curveWidth/3;
+    } else {
+        int d = tabHeight - 12;
+        curve = [0,0, 0,1, 2,1, 3,2, 5,2, 6,3, 7,3, 9,5, 10,5,
+                          11,6, 11+d,6+d,
+                          12+d,7+d, 13+d,7+d, 15+d,9+d, 16+d,9+d, 17+d,10+d, 19+d,10+d, 20+d,11+d, 22+d,11+d, 23+d,12+d];
+        curveWidth = 26+d;
+        curveIndent = curveWidth/3;
+
+        //this could be static but since values depend on curve, better to keep in one place
+        topCurveHighlightStart = [
+                0, 2,  1, 2,  2, 2,
+                3, 3,  4, 3,  5, 3,
+                6, 4,  7, 4,
+                8, 5,
+                9, 6, 10, 6];
+
+        //also, by adding in 'd' here we save some math cost when drawing the curve
+        topCurveHighlightEnd = [
+                10+d, 6+d,
+                11+d, 7+d,
+                12+d, 8+d,  13+d, 8+d,
+                14+d, 9+d,
+                15+d, 10+d,  16+d, 10+d,
+                17+d, 11+d,  18+d, 11+d,  19+d, 11+d,
+                20+d, 12+d,  21+d, 12+d,  22+d,  12+d ];
+    }
+    notifyListeners(DWT.Resize, new Event());
+    return true;
+}
+char[] _getToolTip(int x, int y) {
+    if (showMin && minRect.contains(x, y)) return minimized ? DWT.getMessage("SWT_Restore") : DWT.getMessage("SWT_Minimize"); //$NON-NLS-1$ //$NON-NLS-2$
+    if (showMax && maxRect.contains(x, y)) return maximized ? DWT.getMessage("SWT_Restore") : DWT.getMessage("SWT_Maximize"); //$NON-NLS-1$ //$NON-NLS-2$
+    if (showChevron && chevronRect.contains(x, y)) return DWT.getMessage("SWT_ShowList"); //$NON-NLS-1$
+    CTabItem item = getItem(new Point (x, y));
+    if (item is null) return null;
+    if (!item.showing) return null;
+    if ((showClose || item.showClose) && item.closeRect.contains(x, y)) {
+        return DWT.getMessage("SWT_Close"); //$NON-NLS-1$
+    }
+    return item.getToolTipText();
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabFolder2Adapter.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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 dwt.custom.CTabFolder2Adapter;
+import dwt.custom.CTabFolder2Listener;
+import dwt.custom.CTabFolderEvent;
+
+/**
+ * This adapter class provides default implementations for the
+ * methods described by the <code>CTabFolder2Listener</code> interface.
+ * <p>
+ * Classes that wish to deal with <code>CTabFolderEvent</code>s can
+ * extend this class and override only the methods which they are
+ * interested in.
+ * </p>
+ *
+ * @see CTabFolder2Listener
+ * @see CTabFolderEvent
+ *
+ * @since 3.0
+ */
+public class CTabFolder2Adapter : CTabFolder2Listener {
+
+/**
+ * Sent when the user clicks on the close button of an item in the CTabFolder.  The item being closed is specified
+ * in the event.item field. Setting the event.doit field to false will stop  the CTabItem from closing.
+ * When the CTabItem is closed, it is disposed.  The contents of the CTabItem (see CTabItem#setControl) will be
+ * made not visible when the CTabItem is closed.
+ * <p>
+ * The default behaviour is to close the CTabItem.
+ * </p>
+ *
+ * @param event an event indicating the item being closed
+ */
+public void close(CTabFolderEvent event){}
+
+/**
+ * Sent when the user clicks on the minimize button of a CTabFolder.
+ * <p>
+ * The default behaviour is to do nothing.
+ * </p>
+ *
+ * @param event an event containing information about the minimize
+ */
+public void minimize(CTabFolderEvent event){}
+
+/**
+ * Sent when the user clicks on the maximize button of a CTabFolder.
+ * <p>
+ * The default behaviour is to do nothing.
+ * </p>
+ *
+ * @param event an event containing information about the maximize
+ */
+public void maximize(CTabFolderEvent event){}
+
+/**
+ * Sent when the user clicks on the restore button of a CTabFolder.
+ * <p>
+ * The default behaviour is to do nothing.
+ * </p>
+ *
+ * @param event an event containing information about the restore
+ */
+public void restore(CTabFolderEvent event){}
+
+/**
+ * Sent when the user clicks on the chevron button of a CTabFolder.
+ * <p>
+ * The default behaviour is to show a list of items that are not currently
+ * visible and to change the selection based on the item selected from the list.
+ * </p>
+ *
+ * @param event an event containing information about the show list
+ */
+public void showList(CTabFolderEvent event){}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabFolder2Listener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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 dwt.custom.CTabFolder2Listener;
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.CTabFolderEvent;
+
+/**
+ * Classes which implement this interface provide methods
+ * that deal with the events that are generated by the CTabFolder
+ * control.
+ * <p>
+ * After creating an instance of a class that :
+ * this interface it can be added to a CTabFolder using the
+ * <code>addCTabFolder2Listener</code> method and removed using
+ * the <code>removeCTabFolder2Listener</code> method. When
+ * events occurs in a CTabFolder the appropriate method
+ * will be invoked.
+ * </p>
+ *
+ * @see CTabFolder2Adapter
+ * @see CTabFolderEvent
+ *
+ * @since 3.0
+ */
+public interface CTabFolder2Listener : DWTEventListener {
+
+/**
+ * Sent when the user clicks on the close button of an item in the CTabFolder.
+ * The item being closed is specified in the event.item field.
+ * Setting the event.doit field to false will stop the CTabItem from closing.
+ * When the CTabItem is closed, it is disposed.  The contents of the
+ * CTabItem (see CTabItem.setControl) will be made not visible when
+ * the CTabItem is closed.
+ *
+ * @param event an event indicating the item being closed
+ */
+public void close(CTabFolderEvent event);
+
+/**
+ * Sent when the user clicks on the minimize button of a CTabFolder.
+ * The state of the CTabFolder does not change automatically - it
+ * is up to the application to change the state of the CTabFolder
+ * in response to this event using CTabFolder.setMinimized(true).
+ *
+ * @param event an event containing information about the minimize
+ *
+ * @see CTabFolder#getMinimized()
+ * @see CTabFolder#setMinimized(bool)
+ * @see CTabFolder#setMinimizeVisible(bool)
+ */
+public void minimize(CTabFolderEvent event);
+
+/**
+ * Sent when the user clicks on the maximize button of a CTabFolder.
+ * The state of the CTabFolder does not change automatically - it
+ * is up to the application to change the state of the CTabFolder
+ * in response to this event using CTabFolder.setMaximized(true).
+ *
+ * @param event an event containing information about the maximize
+ *
+ * @see CTabFolder#getMaximized()
+ * @see CTabFolder#setMaximized(bool)
+ * @see CTabFolder#setMaximizeVisible(bool)
+ */
+public void maximize(CTabFolderEvent event);
+
+/**
+ * Sent when the user clicks on the restore button of a CTabFolder.
+ * This event is sent either to restore the CTabFolder from the
+ * minimized state or from the maximized state.  To determine
+ * which restore is requested, use CTabFolder.getMinimized() or
+ * CTabFolder.getMaximized() to determine the current state.
+ * The state of the CTabFolder does not change automatically - it
+ * is up to the application to change the state of the CTabFolder
+ * in response to this event using CTabFolder.setMaximized(false)
+ * or CTabFolder.setMinimized(false).
+ *
+ * @param event an event containing information about the restore
+ *
+ * @see CTabFolder#getMinimized()
+ * @see CTabFolder#getMaximized()
+ * @see CTabFolder#setMinimized(bool)
+ * @see CTabFolder#setMinimizeVisible(bool)
+ * @see CTabFolder#setMaximized(bool)
+ * @see CTabFolder#setMaximizeVisible(bool)
+ */
+public void restore(CTabFolderEvent event);
+
+/**
+ * Sent when the user clicks on the chevron button of the CTabFolder.
+ * A chevron appears in the CTabFolder when there are more tabs
+ * than can be displayed at the current widget size.  To select a
+ * tab that is not currently visible, the user clicks on the
+ * chevron and selects a tab item from a list.  By default, the
+ * CTabFolder provides a list of all the items that are not currently
+ * visible, however, the application can provide its own list by setting
+ * the event.doit field to <code>false</code> and displaying a selection list.
+ *
+ * @param event an event containing information about the show list
+ *
+ * @see CTabFolder#setSelection(CTabItem)
+ */
+public void showList(CTabFolderEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabFolderAdapter.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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 dwt.custom.CTabFolderAdapter;
+import dwt.custom.CTabFolderEvent;
+import dwt.custom.CTabFolderListener;
+
+
+public class CTabFolderAdapter : CTabFolderListener {
+    public void itemClosed(CTabFolderEvent event){}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabFolderEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.CTabFolderEvent;
+
+
+
+import dwt.events.TypedEvent;
+import dwt.widgets.Widget;
+
+import tango.util.Convert;
+
+/**
+ *
+ */
+public class CTabFolderEvent : TypedEvent {
+    /**
+     * The tab item for the operation.
+     */
+    public Widget item;
+
+    /**
+     * A flag indicating whether the operation should be allowed.
+     * Setting this field to <code>false</code> will cancel the operation.
+     * Applies to the close and showList events.
+     */
+    public bool doit;
+
+    /**
+     * The widget-relative, x coordinate of the chevron button
+     * at the time of the event.  Applies to the showList event.
+     *
+     * @since 3.0
+     */
+    public int x;
+    /**
+     * The widget-relative, y coordinate of the chevron button
+     * at the time of the event.  Applies to the showList event.
+     *
+     * @since 3.0
+     */
+    public int y;
+    /**
+     * The width of the chevron button at the time of the event.
+     * Applies to the showList event.
+     *
+     * @since 3.0
+     */
+    public int width;
+    /**
+     * The height of the chevron button at the time of the event.
+     * Applies to the showList event.
+     *
+     * @since 3.0
+     */
+    public int height;
+
+    static final long serialVersionUID = 3760566386225066807L;
+
+/**
+ * Constructs a new instance of this class.
+ *
+ * @param w the widget that fired the event
+ */
+this(Widget w) {
+    super(w);
+}
+
+/**
+ * Returns a string containing a concise, human-readable
+ * description of the receiver.
+ *
+ * @return a string representation of the event
+ */
+public override char[] toString() {
+    char[] string = super.toString ();
+    return string[0.. $ - 1] // remove trailing '}'
+        ~ " item=" ~ to!(char[])(item)
+        ~ " doit=" ~ to!(char[])(doit)
+        ~ " x=" ~ to!(char[])(x)
+        ~ " y=" ~ to!(char[])(y)
+        ~ " width=" ~ to!(char[])(width)
+        ~ " height=" ~ to!(char[])(height)
+        ~ "}";
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabFolderLayout.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.CTabFolderLayout;
+
+
+import dwt.DWT;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Layout;
+import dwt.custom.CTabFolder;
+import dwt.custom.CTabItem;
+import dwt.dwthelper.utils;
+
+/**
+ * This class provides the layout for CTabFolder
+ *
+ * @see CTabFolder
+ */
+class CTabFolderLayout : Layout {
+protected override Point computeSize(Composite composite, int wHint, int hHint, bool flushCache) {
+    CTabFolder folder = cast(CTabFolder)composite;
+    CTabItem[] items = folder.items;
+    // preferred width of tab area to show all tabs
+    int tabW = 0;
+    GC gc = new GC(folder);
+    for (int i = 0; i < items.length; i++) {
+        if (folder.single) {
+            tabW = Math.max(tabW, items[i].preferredWidth(gc, true, false));
+        } else {
+            tabW += items[i].preferredWidth(gc, i is folder.selectedIndex, false);
+        }
+    }
+    gc.dispose();
+    tabW += 3;
+    if (folder.showMax) tabW += CTabFolder.BUTTON_SIZE;
+    if (folder.showMin) tabW += CTabFolder.BUTTON_SIZE;
+    if (folder.single) tabW += 3*CTabFolder.BUTTON_SIZE/2; //chevron
+    if (folder.topRight !is null) {
+        Point pt = folder.topRight.computeSize(DWT.DEFAULT, folder.tabHeight, flushCache);
+        tabW += 3 + pt.x;
+    }
+    if (!folder.single && !folder.simple) tabW += folder.curveWidth - 2*folder.curveIndent;
+
+    int controlW = 0;
+    int controlH = 0;
+    // preferred size of controls in tab items
+    for (int i = 0; i < items.length; i++) {
+        Control control = items[i].getControl();
+        if (control !is null && !control.isDisposed()){
+            Point size = control.computeSize (wHint, hHint, flushCache);
+            controlW = Math.max (controlW, size.x);
+            controlH = Math.max (controlH, size.y);
+        }
+    }
+
+    int minWidth = Math.max(tabW, controlW);
+    int minHeight = (folder.minimized) ? 0 : controlH;
+    if (minWidth is 0) minWidth = CTabFolder.DEFAULT_WIDTH;
+    if (minHeight is 0) minHeight = CTabFolder.DEFAULT_HEIGHT;
+
+    if (wHint !is DWT.DEFAULT) minWidth  = wHint;
+    if (hHint !is DWT.DEFAULT) minHeight = hHint;
+
+    return new Point (minWidth, minHeight);
+}
+protected override bool flushCache(Control control) {
+    return true;
+}
+protected override void layout(Composite composite, bool flushCache) {
+    CTabFolder folder = cast(CTabFolder)composite;
+    // resize content
+    if (folder.selectedIndex !is -1) {
+        Control control = folder.items[folder.selectedIndex].getControl();
+        if (control !is null && !control.isDisposed()) {
+            control.setBounds(folder.getClientArea());
+        }
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabFolderListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.CTabFolderListener;
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.CTabFolderEvent;
+
+/**
+ * Classes which implement this interface provide a method
+ * that deals with events generated in the CTabFolder.
+ * <p>
+ * After creating an instance of a class that :
+ * this interface it can be added to a CTabFolder using the
+ * <code>addCTabFolderListener</code> method and removed using
+ * the <code>removeCTabFolderListener</code> method. When a
+ * tab item is closed, the itemClosed method will be invoked.
+ * </p>
+ *
+ * @see CTabFolderEvent
+ */
+public interface CTabFolderListener : DWTEventListener {
+
+/**
+ * Sent when the user clicks on the close button of an item in the CTabFolder.  The item being closed is specified
+ * in the event.item field. Setting the event.doit field to false will stop the CTabItem from closing.
+ * When the CTabItem is closed, it is disposed.  The contents of the CTabItem (see CTabItem.setControl) will be
+ * made not visible when the CTabItem is closed.
+ *
+ * @param event an event indicating the item being closed
+ *
+ * @see CTabItem#setControl
+ */
+public void itemClosed(CTabFolderEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/CTabItem.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,1026 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.CTabItem;
+
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.RGB;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Item;
+import dwt.widgets.Widget;
+import dwt.custom.CTabFolder;
+
+/**
+ * Instances of this class represent a selectable user interface object
+ * that represent a page in a notebook widget.
+ *
+ * <dl>
+ * <dt><b>Styles:</b></dt>
+ * <dd>DWT.CLOSE</dd>
+ * <dt><b>Events:</b></dt>
+ * <dd>(none)</dd>
+ * </dl>
+ * <p>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </p>
+ */
+public class CTabItem : Item {
+    CTabFolder parent;
+    int x,y,width,height = 0;
+    Control control; // the tab page
+
+    char[] toolTipText;
+    char[] shortenedText;
+    int shortenedTextWidth;
+
+    // Appearance
+    Font font;
+    Image disabledImage;
+
+    Rectangle closeRect;
+    int closeImageState = CTabFolder.NONE;
+    bool showClose = false;
+    bool showing = false;
+
+    // internal constants
+    static final int TOP_MARGIN = 2;
+    static final int BOTTOM_MARGIN = 2;
+    static final int LEFT_MARGIN = 4;
+    static final int RIGHT_MARGIN = 4;
+    static final int INTERNAL_SPACING = 4;
+    static final int FLAGS = DWT.DRAW_TRANSPARENT | DWT.DRAW_MNEMONIC;
+    static final char[] ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026"
+
+/**
+ * Constructs a new instance of this class given its parent
+ * (which must be a <code>CTabFolder</code>) and a style value
+ * describing its behavior and appearance. The item is added
+ * to the end of the items maintained by its parent.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>DWT</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>DWT</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 CTabFolder which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT
+ * @see Widget#getStyle()
+ */
+public this (CTabFolder parent, int style) {
+    this(parent, style, parent.getItemCount());
+}
+/**
+ * Constructs a new instance of this class given its parent
+ * (which must be a <code>CTabFolder</code>), a style value
+ * describing its behavior and appearance, and the index
+ * at which to place it in the items maintained by its parent.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>DWT</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>DWT</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 CTabFolder which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ * @param index the zero-relative index to store the receiver in its parent
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the parent (inclusive)</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT
+ * @see Widget#getStyle()
+ */
+public this (CTabFolder parent, int style, int index) {
+    closeRect = new Rectangle(0, 0, 0, 0);
+    super (parent, checkStyle(style));
+    showClose = (style & DWT.CLOSE) !is 0;
+    parent.createItem (this, index);
+}
+static int checkStyle(int style) {
+    return DWT.NONE;
+}
+
+/*
+ * Return whether to use ellipses or just truncate labels
+ */
+bool useEllipses() {
+    return parent.simple;
+}
+
+char[] shortenText(GC gc, char[] text, int width) {
+    return useEllipses()
+        ? shortenText(gc, text, width, ELLIPSIS)
+        : shortenText(gc, text, width, ""); //$NON-NLS-1$
+}
+
+char[] shortenText(GC gc, char[] text, int width, char[] ellipses) {
+    if (gc.textExtent(text, FLAGS).x <= width) return text;
+    int ellipseWidth = gc.textExtent(ellipses, FLAGS).x;
+    int length = text.length;
+    int end = length - 1;
+    while (end > 0) {
+        text = text[ 0 .. end ];
+        int l = gc.textExtent(text, FLAGS).x;
+        if (l + ellipseWidth <= width) {
+            return text ~ ellipses;
+        }
+        end--;
+    }
+    return text[ 0 .. 1 ];
+}
+
+public override void dispose() {
+    if (isDisposed ()) return;
+    //if (!isValidThread ()) error (DWT.ERROR_THREAD_INVALID_ACCESS);
+    parent.destroyItem(this);
+    super.dispose();
+    parent = null;
+    control = null;
+    toolTipText = null;
+    shortenedText = null;
+    font = null;
+}
+void drawClose(GC gc) {
+    if (closeRect.width is 0 || closeRect.height is 0) return;
+    Display display = getDisplay();
+
+    // draw X 9x9
+    int indent = Math.max(1, (CTabFolder.BUTTON_SIZE-9)/2);
+    int x = closeRect.x + indent;
+    int y = closeRect.y + indent;
+    y += parent.onBottom ? -1 : 1;
+
+    Color closeBorder = display.getSystemColor(CTabFolder.BUTTON_BORDER);
+    switch (closeImageState) {
+        case CTabFolder.NORMAL: {
+            int[] shape = [x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y,
+                                     x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9,
+                                     x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9,
+                                     x,y+7, x+2,y+5, x+2,y+4, x,y+2];
+            gc.setBackground(display.getSystemColor(CTabFolder.BUTTON_FILL));
+            gc.fillPolygon(shape);
+            gc.setForeground(closeBorder);
+            gc.drawPolygon(shape);
+            break;
+        }
+        case CTabFolder.HOT: {
+            int[] shape = [x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y,
+                                     x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9,
+                                     x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9,
+                                     x,y+7, x+2,y+5, x+2,y+4, x,y+2];
+            Color fill = new Color(display, CTabFolder.CLOSE_FILL);
+            gc.setBackground(fill);
+            gc.fillPolygon(shape);
+            fill.dispose();
+            gc.setForeground(closeBorder);
+            gc.drawPolygon(shape);
+            break;
+        }
+        case CTabFolder.SELECTED: {
+            int[] shape = [x+1,y+1, x+3,y+1, x+5,y+3, x+6,y+3, x+8,y+1, x+10,y+1,
+                                     x+10,y+3, x+8,y+5, x+8,y+6, x+10,y+8, x+10,y+10,
+                                     x+8,y+10, x+6,y+8, x+5,y+8, x+3,y+10, x+1,y+10,
+                                     x+1,y+8, x+3,y+6, x+3,y+5, x+1,y+3];
+            Color fill = new Color(display, CTabFolder.CLOSE_FILL);
+            gc.setBackground(fill);
+            gc.fillPolygon(shape);
+            fill.dispose();
+            gc.setForeground(closeBorder);
+            gc.drawPolygon(shape);
+            break;
+        }
+        case CTabFolder.NONE: {
+            int[] shape = [x,y, x+10,y, x+10,y+10, x,y+10];
+            if (parent.gradientColors !is null && !parent.gradientVertical) {
+                parent.drawBackground(gc, shape, false);
+            } else {
+                Color defaultBackground = parent.getBackground();
+                Image image = parent.bgImage;
+                Color[] colors = parent.gradientColors;
+                int[] percents = parent.gradientPercents;
+                bool vertical = parent.gradientVertical;
+                parent.drawBackground(gc, shape, x, y, 10, 10, defaultBackground, image, colors, percents, vertical);
+            }
+            break;
+        }
+        default:
+    }
+}
+void drawSelected(GC gc ) {
+    Point size = parent.getSize();
+    int rightEdge = Math.min (x + width, parent.getRightItemEdge());
+
+    //   Draw selection border across all tabs
+    int xx = parent.borderLeft;
+    int yy = parent.onBottom ? size.y - parent.borderBottom - parent.tabHeight - parent.highlight_header : parent.borderTop + parent.tabHeight + 1;
+    int ww = size.x - parent.borderLeft - parent.borderRight;
+    int hh = parent.highlight_header - 1;
+    int[] shape = [xx,yy, xx+ww,yy, xx+ww,yy+hh, xx,yy+hh];
+    if (parent.selectionGradientColors !is null && !parent.selectionGradientVertical) {
+        parent.drawBackground(gc, shape, true);
+    } else {
+        gc.setBackground(parent.selectionBackground);
+        gc.fillRectangle(xx, yy, ww, hh);
+    }
+
+    if (parent.single) {
+        if (!showing) return;
+    } else {
+        // if selected tab scrolled out of view or partially out of view
+        // just draw bottom line
+        if (!showing){
+            int x1 = Math.max(0, parent.borderLeft - 1);
+            int y1 = (parent.onBottom) ? y - 1 : y + height;
+            int x2 = size.x - parent.borderRight;
+            gc.setForeground(CTabFolder.borderColor);
+            gc.drawLine(x1, y1, x2, y1);
+            return;
+        }
+
+        // draw selected tab background and outline
+        shape = null;
+        if (this.parent.onBottom) {
+            int[] left = parent.simple ? CTabFolder.SIMPLE_BOTTOM_LEFT_CORNER : CTabFolder.BOTTOM_LEFT_CORNER;
+            int[] right = parent.simple ? CTabFolder.SIMPLE_BOTTOM_RIGHT_CORNER : parent.curve;
+            if (parent.borderLeft is 0 && parent.indexOf(this) is parent.firstIndex) {
+                left = [x, y+height];
+            }
+            shape = new int[left.length+right.length+8];
+            int index = 0;
+            shape[index++] = x; // first point repeated here because below we reuse shape to draw outline
+            shape[index++] = y - 1;
+            shape[index++] = x;
+            shape[index++] = y - 1;
+            for (int i = 0; i < left.length/2; i++) {
+                shape[index++] = x + left[2*i];
+                shape[index++] = y + height + left[2*i+1] - 1;
+            }
+            for (int i = 0; i < right.length/2; i++) {
+                shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - parent.curveIndent + right[2*i];
+                shape[index++] = parent.simple ? y + height + right[2*i+1] - 1 : y + right[2*i+1] - 2;
+            }
+            shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
+            shape[index++] = y - 1;
+            shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
+            shape[index++] = y - 1;
+        } else {
+            int[] left = parent.simple ? CTabFolder.SIMPLE_TOP_LEFT_CORNER : CTabFolder.TOP_LEFT_CORNER;
+            int[] right = parent.simple ? CTabFolder.SIMPLE_TOP_RIGHT_CORNER : parent.curve;
+            if (parent.borderLeft is 0 && parent.indexOf(this) is parent.firstIndex) {
+                left = [x, y];
+            }
+            shape = new int[left.length+right.length+8];
+            int index = 0;
+            shape[index++] = x; // first point repeated here because below we reuse shape to draw outline
+            shape[index++] = y + height + 1;
+            shape[index++] = x;
+            shape[index++] = y + height + 1;
+            for (int i = 0; i < left.length/2; i++) {
+                shape[index++] = x + left[2*i];
+                shape[index++] = y + left[2*i+1];
+            }
+            for (int i = 0; i < right.length/2; i++) {
+                shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - parent.curveIndent + right[2*i];
+                shape[index++] = y + right[2*i+1];
+            }
+            shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
+            shape[index++] = y + height + 1;
+            shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
+            shape[index++] = y + height + 1;
+        }
+
+        Rectangle clipping = gc.getClipping();
+        Rectangle bounds = getBounds();
+        bounds.height += 1;
+        if (parent.onBottom) bounds.y -= 1;
+        bool tabInPaint = clipping.intersects(bounds);
+
+        if (tabInPaint) {
+            // fill in tab background
+            if (parent.selectionGradientColors !is null && !parent.selectionGradientVertical) {
+                parent.drawBackground(gc, shape, true);
+            } else {
+                Color defaultBackground = parent.selectionBackground;
+                Image image = parent.selectionBgImage;
+                Color[] colors = parent.selectionGradientColors;
+                int[] percents = parent.selectionGradientPercents;
+                bool vertical = parent.selectionGradientVertical;
+                xx = x;
+                yy = parent.onBottom ? y -1 : y + 1;
+                ww = width;
+                hh = height;
+                if (!parent.single && !parent.simple) ww += parent.curveWidth - parent.curveIndent;
+                parent.drawBackground(gc, shape, xx, yy, ww, hh, defaultBackground, image, colors, percents, vertical);
+            }
+        }
+
+        //Highlight MUST be drawn before the outline so that outline can cover it in the right spots (start of swoop)
+        //otherwise the curve looks jagged
+        drawHighlight(gc, rightEdge);
+
+        // draw outline
+        shape[0] = Math.max(0, parent.borderLeft - 1);
+        if (parent.borderLeft is 0 && parent.indexOf(this) is parent.firstIndex) {
+            shape[1] = parent.onBottom ? y + height - 1 : y;
+            shape[5] = shape[3] = shape[1];
+        }
+        shape[shape.length - 2] = size.x - parent.borderRight + 1;
+        for (int i = 0; i < shape.length/2; i++) {
+            if (shape[2*i + 1] is y + height + 1) shape[2*i + 1] -= 1;
+        }
+        RGB inside = parent.selectionBackground.getRGB();
+        if (parent.selectionBgImage !is null ||
+            (parent.selectionGradientColors !is null && parent.selectionGradientColors.length > 1)) {
+            inside = null;
+        }
+        RGB outside = parent.getBackground().getRGB();
+        if (parent.bgImage !is null ||
+            (parent.gradientColors !is null && parent.gradientColors.length > 1)) {
+            outside = null;
+        }
+        parent.antialias(shape, CTabFolder.borderColor.getRGB(), inside, outside, gc);
+        gc.setForeground(CTabFolder.borderColor);
+        gc.drawPolyline(shape);
+
+        if (!tabInPaint) return;
+    }
+
+    // draw Image
+    int xDraw = x + LEFT_MARGIN;
+    if (parent.single && (parent.showClose || showClose)) xDraw += CTabFolder.BUTTON_SIZE;
+    Image image = getImage();
+    if (image !is null) {
+        Rectangle imageBounds = image.getBounds();
+        // only draw image if it won't overlap with close button
+        int maxImageWidth = rightEdge - xDraw - RIGHT_MARGIN;
+        if (!parent.single && closeRect.width > 0) maxImageWidth -= closeRect.width + INTERNAL_SPACING;
+        if (imageBounds.width < maxImageWidth) {
+            int imageX = xDraw;
+            int imageY = y + (height - imageBounds.height) / 2;
+            imageY += parent.onBottom ? -1 : 1;
+            gc.drawImage(image, imageX, imageY);
+            xDraw += imageBounds.width + INTERNAL_SPACING;
+        }
+    }
+
+    // draw Text
+    int textWidth = rightEdge - xDraw - RIGHT_MARGIN;
+    if (!parent.single && closeRect.width > 0) textWidth -= closeRect.width + INTERNAL_SPACING;
+    if (textWidth > 0) {
+        Font gcFont = gc.getFont();
+        gc.setFont(font is null ? parent.getFont() : font);
+
+        if (shortenedText is null || shortenedTextWidth !is textWidth) {
+            shortenedText = shortenText(gc, getText(), textWidth);
+            shortenedTextWidth = textWidth;
+        }
+        Point extent = gc.textExtent(shortenedText, FLAGS);
+        int textY = y + (height - extent.y) / 2;
+        textY += parent.onBottom ? -1 : 1;
+
+        gc.setForeground(parent.selectionForeground);
+        gc.drawText(shortenedText, xDraw, textY, FLAGS);
+        gc.setFont(gcFont);
+
+        // draw a Focus rectangle
+        if (parent.isFocusControl()) {
+            Display display = getDisplay();
+            if (parent.simple || parent.single) {
+                gc.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
+                gc.setForeground(display.getSystemColor(DWT.COLOR_WHITE));
+                gc.drawFocus(xDraw-1, textY-1, extent.x+2, extent.y+2);
+            } else {
+                gc.setForeground(display.getSystemColor(CTabFolder.BUTTON_BORDER));
+                gc.drawLine(xDraw, textY+extent.y+1, xDraw+extent.x+1, textY+extent.y+1);
+            }
+        }
+    }
+    if (parent.showClose || showClose) drawClose(gc);
+}
+
+/*
+ * Draw a highlight effect along the left, top, and right edges of the tab.
+ * Only for curved tabs, on top.
+ * Do not draw if insufficient colors.
+ */
+void drawHighlight(GC gc, int rightEdge) {
+    //only draw for curvy tabs and only draw for top tabs
+    if(parent.simple || this.parent.onBottom)
+        return;
+
+    if(parent.selectionHighlightGradientBegin is null)
+        return;
+
+    Color[] gradients = parent.selectionHighlightGradientColorsCache;
+    if(gradients is null)
+        return;
+    int gradientsSize = gradients.length;
+    if(gradientsSize is 0)
+        return;     //shouldn't happen but just to be tidy
+
+    gc.setForeground(gradients[0]);
+
+    //draw top horizontal line
+    gc.drawLine(
+            CTabFolder.TOP_LEFT_CORNER_HILITE[0] + x + 1, //rely on fact that first pair is top/right of curve
+            1 + y,
+            rightEdge - parent.curveIndent,
+            1 + y);
+
+    int[] leftHighlightCurve = CTabFolder.TOP_LEFT_CORNER_HILITE;
+
+    int d = parent.tabHeight - parent.topCurveHighlightEnd.length /2;
+
+    int lastX = 0;
+    int lastY = 0;
+    int lastColorIndex = 0;
+
+    //draw upper left curve highlight
+    for (int i = 0; i < leftHighlightCurve.length /2; i++) {
+        int rawX = leftHighlightCurve[i * 2];
+        int rawY = leftHighlightCurve[i * 2 + 1];
+        lastX = rawX + x;
+        lastY = rawY + y;
+        lastColorIndex = rawY - 1;
+        gc.setForeground(gradients[lastColorIndex]);
+        gc.drawPoint(lastX, lastY);
+    }
+    //draw left vertical line highlight
+    for(int i = lastColorIndex; i < gradientsSize; i++) {
+        gc.setForeground(gradients[i]);
+        gc.drawPoint(lastX, 1 + lastY++);
+    }
+
+    int rightEdgeOffset = rightEdge - parent.curveIndent;
+
+    //draw right swoop highlight up to diagonal portion
+    for (int i = 0; i < parent.topCurveHighlightStart.length /2; i++) {
+        int rawX = parent.topCurveHighlightStart[i * 2];
+        int rawY = parent.topCurveHighlightStart[i * 2 + 1];
+        lastX = rawX + rightEdgeOffset;
+        lastY = rawY + y;
+        lastColorIndex = rawY - 1;
+        if(lastColorIndex >= gradientsSize)
+            break;  //can happen if tabs are unusually short and cut off the curve
+        gc.setForeground(gradients[lastColorIndex]);
+        gc.drawPoint(lastX, lastY);
+    }
+    //draw right diagonal line highlight
+    for(int i = lastColorIndex; i < lastColorIndex + d; i++) {
+        if(i >= gradientsSize)
+            break;  //can happen if tabs are unusually short and cut off the curve
+        gc.setForeground(gradients[i]);
+        gc.drawPoint(1 + lastX++, 1 + lastY++);
+    }
+
+    //draw right swoop highlight from diagonal portion to end
+    for (int i = 0; i < parent.topCurveHighlightEnd.length /2; i++) {
+        int rawX = parent.topCurveHighlightEnd[i * 2]; //d is already encoded in this value
+        int rawY = parent.topCurveHighlightEnd[i * 2 + 1]; //d already encoded
+        lastX = rawX + rightEdgeOffset;
+        lastY = rawY + y;
+        lastColorIndex = rawY - 1;
+        if(lastColorIndex >= gradientsSize)
+            break;  //can happen if tabs are unusually short and cut off the curve
+        gc.setForeground(gradients[lastColorIndex]);
+        gc.drawPoint(lastX, lastY);
+    }
+}
+
+/*
+ * Draw the unselected border for the receiver on the right.
+ *
+ * @param gc
+ */
+void drawRightUnselectedBorder(GC gc) {
+
+    int[] shape = null;
+    int startX = x + width - 1;
+
+    if (this.parent.onBottom) {
+        int[] right = parent.simple
+            ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
+            : CTabFolder.BOTTOM_RIGHT_CORNER;
+
+        shape = new int[right.length + 2];
+        int index = 0;
+
+        for (int i = 0; i < right.length / 2; i++) {
+            shape[index++] = startX + right[2 * i];
+            shape[index++] = y + height + right[2 * i + 1] - 1;
+        }
+        shape[index++] = startX;
+        shape[index++] = y - 1;
+    } else {
+        int[] right = parent.simple
+            ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
+            : CTabFolder.TOP_RIGHT_CORNER;
+
+        shape = new int[right.length + 2];
+        int index = 0;
+
+        for (int i = 0; i < right.length / 2; i++) {
+            shape[index++] = startX + right[2 * i];
+            shape[index++] = y + right[2 * i + 1];
+        }
+
+        shape[index++] = startX;
+        shape[index++] = y + height;
+
+    }
+
+    drawBorder(gc, shape);
+
+}
+
+/*
+ * Draw the border of the tab
+ *
+ * @param gc
+ * @param shape
+ */
+void drawBorder(GC gc, int[] shape) {
+
+    gc.setForeground(CTabFolder.borderColor);
+    gc.drawPolyline(shape);
+}
+
+/*
+ * Draw the unselected border for the receiver on the left.
+ *
+ * @param gc
+ */
+void drawLeftUnselectedBorder(GC gc) {
+
+    int[] shape = null;
+    if (this.parent.onBottom) {
+        int[] left = parent.simple
+            ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
+            : CTabFolder.BOTTOM_LEFT_CORNER;
+
+        shape = new int[left.length + 2];
+        int index = 0;
+        shape[index++] = x;
+        shape[index++] = y - 1;
+        for (int i = 0; i < left.length / 2; i++) {
+            shape[index++] = x + left[2 * i];
+            shape[index++] = y + height + left[2 * i + 1] - 1;
+        }
+    } else {
+        int[] left = parent.simple
+            ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
+            : CTabFolder.TOP_LEFT_CORNER;
+
+        shape = new int[left.length + 2];
+        int index = 0;
+        shape[index++] = x;
+        shape[index++] = y + height;
+        for (int i = 0; i < left.length / 2; i++) {
+            shape[index++] = x + left[2 * i];
+            shape[index++] = y + left[2 * i + 1];
+        }
+
+    }
+
+    drawBorder(gc, shape);
+}
+
+void drawUnselected(GC gc) {
+    // Do not draw partial items
+    if (!showing) return;
+
+    Rectangle clipping = gc.getClipping();
+    Rectangle bounds = getBounds();
+    if (!clipping.intersects(bounds)) return;
+
+    // draw border
+    int index = parent.indexOf(this);
+
+    if (index > 0 && index < parent.selectedIndex)
+        drawLeftUnselectedBorder(gc);
+    // If it is the last one then draw a line
+    if (index > parent.selectedIndex)
+        drawRightUnselectedBorder(gc);
+
+    // draw Image
+    int xDraw = x + LEFT_MARGIN;
+    Image image = getImage();
+    if (image !is null && parent.showUnselectedImage) {
+        Rectangle imageBounds = image.getBounds();
+        // only draw image if it won't overlap with close button
+        int maxImageWidth = x + width - xDraw - RIGHT_MARGIN;
+        if (parent.showUnselectedClose && (parent.showClose || showClose)) {
+            maxImageWidth -= closeRect.width + INTERNAL_SPACING;
+        }
+        if (imageBounds.width < maxImageWidth) {
+            int imageX = xDraw;
+            int imageHeight = imageBounds.height;
+            int imageY = y + (height - imageHeight) / 2;
+            imageY += parent.onBottom ? -1 : 1;
+            int imageWidth = imageBounds.width * imageHeight / imageBounds.height;
+            gc.drawImage(image,
+                         imageBounds.x, imageBounds.y, imageBounds.width, imageBounds.height,
+                         imageX, imageY, imageWidth, imageHeight);
+            xDraw += imageWidth + INTERNAL_SPACING;
+        }
+    }
+    // draw Text
+    int textWidth = x + width - xDraw - RIGHT_MARGIN;
+    if (parent.showUnselectedClose && (parent.showClose || showClose)) {
+        textWidth -= closeRect.width + INTERNAL_SPACING;
+    }
+    if (textWidth > 0) {
+        Font gcFont = gc.getFont();
+        gc.setFont(font is null ? parent.getFont() : font);
+        if (shortenedText is null || shortenedTextWidth !is textWidth) {
+            shortenedText = shortenText(gc, getText(), textWidth);
+            shortenedTextWidth = textWidth;
+        }
+        Point extent = gc.textExtent(shortenedText, FLAGS);
+        int textY = y + (height - extent.y) / 2;
+        textY += parent.onBottom ? -1 : 1;
+        gc.setForeground(parent.getForeground());
+        gc.drawText(shortenedText, xDraw, textY, FLAGS);
+        gc.setFont(gcFont);
+    }
+    // draw close
+    if (parent.showUnselectedClose && (parent.showClose || showClose)) drawClose(gc);
+}
+/**
+ * Returns a rectangle describing the receiver's size and location
+ * relative to its parent.
+ *
+ * @return the receiver's bounding column rectangle
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 Rectangle getBounds () {
+    //checkWidget();
+    int w = width;
+    if (!parent.simple && !parent.single && parent.indexOf(this) is parent.selectedIndex) w += parent.curveWidth - parent.curveIndent;
+    return new Rectangle(x, y, w, height);
+}
+/**
+* Gets the control that is displayed in the content area of the tab item.
+*
+* @return the control
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 Control getControl () {
+    checkWidget();
+    return control;
+}
+/**
+ * Get the image displayed in the tab if the tab is disabled.
+ *
+ * @return the disabled image or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 the disabled image is not used
+ */
+public Image getDisabledImage(){
+    checkWidget();
+    return disabledImage;
+}
+/**
+ * Returns the font that the receiver will use to paint textual information.
+ *
+ * @return the receiver's font
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public Font getFont() {
+    checkWidget();
+    if (font !is null) return font;
+    return parent.getFont();
+}
+/**
+ * Returns the receiver's parent, which must be a <code>CTabFolder</code>.
+ *
+ * @return the receiver's parent
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 CTabFolder getParent () {
+    //checkWidget();
+    return parent;
+}
+/**
+ * Returns the receiver's tool tip text, or null if it has
+ * not been set.
+ *
+ * @return the receiver's tool tip text
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] getToolTipText () {
+    checkWidget();
+    if (toolTipText is null && shortenedText !is null) {
+        char[] text = getText();
+        if (shortenedText!=text) return text;
+    }
+    return toolTipText;
+}
+/**
+* Returns <code>true</code> if the item will be rendered in the visible area of the CTabFolder. Returns false otherwise.
+*
+*  @return <code>true</code> if the item will be rendered in the visible area of the CTabFolder. Returns false otherwise.
+*
+*  @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+*/
+public bool isShowing () {
+    checkWidget();
+    return showing;
+}
+void onPaint(GC gc, bool isSelected) {
+    if (width is 0 || height is 0) return;
+    if (isSelected) {
+        drawSelected(gc);
+    } else {
+        drawUnselected(gc);
+    }
+}
+int preferredHeight(GC gc) {
+    Image image = getImage();
+    int h = (image is null) ? 0 : image.getBounds().height;
+    char[] text = getText();
+    if (font is null) {
+        h = Math.max(h, gc.textExtent(text, FLAGS).y);
+    } else {
+        Font gcFont = gc.getFont();
+        gc.setFont(font);
+        h = Math.max(h, gc.textExtent(text, FLAGS).y);
+        gc.setFont(gcFont);
+    }
+    return h + TOP_MARGIN + BOTTOM_MARGIN;
+}
+int preferredWidth(GC gc, bool isSelected, bool minimum) {
+    // NOTE: preferred width does not include the "dead space" caused
+    // by the curve.
+    if (isDisposed()) return 0;
+    int w = 0;
+    Image image = getImage();
+    if (image !is null && (isSelected || parent.showUnselectedImage)) {
+        w += image.getBounds().width;
+    }
+    char[] text = null;
+    if (minimum) {
+        int minChars = parent.minChars;
+        text = minChars is 0 ? null : getText();
+        if (text !is null && text.length > minChars) {
+            if (useEllipses()) {
+                int end = minChars < ELLIPSIS.length + 1 ? minChars : minChars - ELLIPSIS.length;
+                text = text[ 0 .. end ];
+                if (minChars > ELLIPSIS.length + 1) text ~= ELLIPSIS;
+            } else {
+                int end = minChars;
+                text = text[ 0 .. end ];
+            }
+        }
+    } else {
+        text = getText();
+    }
+    if (text !is null) {
+        if (w > 0) w += INTERNAL_SPACING;
+        if (font is null) {
+            w += gc.textExtent(text, FLAGS).x;
+        } else {
+            Font gcFont = gc.getFont();
+            gc.setFont(font);
+            w += gc.textExtent(text, FLAGS).x;
+            gc.setFont(gcFont);
+        }
+    }
+    if (parent.showClose || showClose) {
+        if (isSelected || parent.showUnselectedClose) {
+            if (w > 0) w += INTERNAL_SPACING;
+            w += CTabFolder.BUTTON_SIZE;
+        }
+    }
+    return w + LEFT_MARGIN + RIGHT_MARGIN;
+}
+/**
+ * Sets the control that is used to fill the client area of
+ * the tab folder when the user selects the tab item.
+ *
+ * @param control the new control (or null)
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the control has been disposed</li>
+ *    <li>ERROR_INVALID_PARENT - if the control is not in the same widget tree</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setControl (Control control) {
+    checkWidget();
+    if (control !is null) {
+        if (control.isDisposed()) DWT.error (DWT.ERROR_INVALID_ARGUMENT);
+        if (control.getParent() !is parent) DWT.error (DWT.ERROR_INVALID_PARENT);
+    }
+    if (this.control !is null && !this.control.isDisposed()) {
+        this.control.setVisible(false);
+    }
+    this.control = control;
+    if (this.control !is null) {
+        int index = parent.indexOf (this);
+        if (index is parent.getSelectionIndex ()){
+            this.control.setBounds(parent.getClientArea ());
+            this.control.setVisible(true);
+        } else {
+            this.control.setVisible(false);
+        }
+    }
+}
+/**
+ * Sets the image that is displayed if the tab item is disabled.
+ * Null will clear the image.
+ *
+ * @param image the image to be displayed when the item is disabled or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 This image is not used
+ */
+public void setDisabledImage (Image image) {
+    checkWidget();
+    if (image !is null && image.isDisposed ()) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    this.disabledImage = image;
+}
+/**
+ * Sets the font that the receiver will use to paint textual information
+ * for this item to the font specified by the argument, or to the default font
+ * for that kind of control if the argument is null.
+ *
+ * @param font the new font (or null)
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setFont (Font font){
+    checkWidget();
+    if (font !is null && font.isDisposed ()) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (font is null && this.font is null) return;
+    if (font !is null && font==this.font) return;
+    this.font = font;
+    if (!parent.updateTabHeight(false)) {
+        parent.updateItems();
+        parent.redrawTabs();
+    }
+}
+public override void setImage (Image image) {
+    checkWidget();
+    if (image !is null && image.isDisposed ()) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    Image oldImage = getImage();
+    if (image is null && oldImage is null) return;
+    if (image !is null && image==oldImage) return;
+    super.setImage(image);
+    if (!parent.updateTabHeight(false)) {
+        // If image is the same size as before,
+        // redraw only the image
+        if (oldImage !is null && image !is null) {
+            Rectangle oldBounds = oldImage.getBounds();
+            Rectangle bounds = image.getBounds();
+            if (bounds.width is oldBounds.width && bounds.height is oldBounds.height) {
+                if (showing) {
+                    bool selected = parent.indexOf(this) is parent.selectedIndex;
+                    if (selected || parent.showUnselectedImage) {
+                        int imageX = x + LEFT_MARGIN, maxImageWidth;
+                        if (selected) {
+                            if (parent.single && (parent.showClose || showClose)) imageX += CTabFolder.BUTTON_SIZE;
+                            int rightEdge = Math.min (x + width, parent.getRightItemEdge());
+                            maxImageWidth = rightEdge - imageX - RIGHT_MARGIN;
+                            if (!parent.single && closeRect.width > 0) maxImageWidth -= closeRect.width + INTERNAL_SPACING;
+                        } else {
+                            maxImageWidth = x + width - imageX - RIGHT_MARGIN;
+                            if (parent.showUnselectedClose && (parent.showClose || showClose)) {
+                                maxImageWidth -= closeRect.width + INTERNAL_SPACING;
+                            }
+                        }
+                        if (bounds.width < maxImageWidth) {
+                            int imageY = y + (height - bounds.height) / 2 + (parent.onBottom ? -1 : 1);
+                            parent.redraw(imageX, imageY, bounds.width, bounds.height, false);
+                        }
+                    }
+                }
+                return;
+            }
+        }
+        parent.updateItems();
+        parent.redrawTabs();
+    }
+}
+public override void setText (char[] string) {
+    checkWidget();
+    if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    if (string==getText()) return;
+    super.setText(string);
+    shortenedText = null;
+    shortenedTextWidth = 0;
+    if (!parent.updateTabHeight(false)) {
+        parent.updateItems();
+        parent.redrawTabs();
+    }
+}
+/**
+ * Sets the receiver's tool tip text to the argument, which
+ * may be null indicating that no tool tip text should be shown.
+ *
+ * @param string the new tool tip text (or null)
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setToolTipText (char[] string) {
+    checkWidget();
+    toolTipText = string;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ControlEditor.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,261 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.ControlEditor;
+
+
+
+import dwt.DWT;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.ScrollBar;
+import dwt.dwthelper.utils;
+
+/**
+*
+* A ControlEditor is a manager for a Control that appears above a composite and tracks with the
+* moving and resizing of that composite.  It can be used to display one control above
+* another control.  This could be used when editing a control that does not have editing
+* capabilities by using a text editor or for launching a dialog by placing a button
+* above a control.
+*
+* <p> Here is an example of using a ControlEditor:
+*
+* <code><pre>
+* Canvas canvas = new Canvas(shell, DWT.BORDER);
+* canvas.setBounds(10, 10, 300, 300);
+* Color color = new Color(null, 255, 0, 0);
+* canvas.setBackground(color);
+* ControlEditor editor = new ControlEditor (canvas);
+* // The editor will be a button in the bottom right corner of the canvas.
+* // When selected, it will launch a Color dialog that will change the background
+* // of the canvas.
+* Button button = new Button(canvas, DWT.PUSH);
+* button.setText("Select Color...");
+* button.addSelectionListener (new SelectionAdapter() {
+*   public void widgetSelected(SelectionEvent e) {
+*       ColorDialog dialog = new ColorDialog(shell);
+*       dialog.open();
+*       RGB rgb = dialog.getRGB();
+*       if (rgb !is null) {
+*           if (color !is null) color.dispose();
+*           color = new Color(null, rgb);
+*           canvas.setBackground(color);
+*       }
+*
+*   }
+* });
+*
+* editor.horizontalAlignment = DWT.RIGHT;
+* editor.verticalAlignment = DWT.BOTTOM;
+* editor.grabHorizontal = false;
+* editor.grabVertical = false;
+* Point size = button.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+* editor.minimumWidth = size.x;
+* editor.minimumHeight = size.y;
+* editor.setEditor (button);
+* </pre></code>
+*/
+public class ControlEditor {
+
+    /**
+    * Specifies how the editor should be aligned relative to the control.  Allowed values
+    * are DWT.LEFT, DWT.RIGHT and DWT.CENTER.  The default value is DWT.CENTER.
+    */
+    public int horizontalAlignment = DWT.CENTER;
+
+    /**
+    * Specifies whether the editor should be sized to use the entire width of the control.
+    * True means resize the editor to the same width as the cell.  False means do not adjust
+    * the width of the editor.  The default value is false.
+    */
+    public bool grabHorizontal = false;
+
+    /**
+    * Specifies the minimum width the editor can have.  This is used in association with
+    * a true value of grabHorizontal.  If the cell becomes smaller than the minimumWidth, the
+    * editor will not made smaller than the minimum width value.  The default value is 0.
+    */
+    public int minimumWidth = 0;
+
+    /**
+    * Specifies how the editor should be aligned relative to the control.  Allowed values
+    * are DWT.TOP, DWT.BOTTOM and DWT.CENTER.  The default value is DWT.CENTER.
+    */
+    public int verticalAlignment = DWT.CENTER;
+
+    /**
+    * Specifies whether the editor should be sized to use the entire height of the control.
+    * True means resize the editor to the same height as the underlying control.  False means do not adjust
+    * the height of the editor. The default value is false.
+    */
+    public bool grabVertical = false;
+
+    /**
+    * Specifies the minimum height the editor can have.  This is used in association with
+    * a true value of grabVertical.  If the control becomes smaller than the minimumHeight, the
+    * editor will not made smaller than the minimum height value.  The default value is 0.
+    */
+    public int minimumHeight = 0;
+
+    Composite parent;
+    Control editor;
+    private bool hadFocus;
+    private Listener controlListener;
+    private Listener scrollbarListener;
+
+    private final static int [] EVENTS = [DWT.KeyDown, DWT.KeyUp, DWT.MouseDown, DWT.MouseUp, DWT.Resize];
+/**
+* Creates a ControlEditor for the specified Composite.
+*
+* @param parent the Composite above which this editor will be displayed
+*
+*/
+public this (Composite parent) {
+    this.parent = parent;
+
+    controlListener = new class() Listener {
+        public void handleEvent(Event e) {
+            layout ();
+        }
+    };
+    for (int i=0; i<EVENTS.length; i++) {
+        parent.addListener (EVENTS [i], controlListener);
+    }
+
+    scrollbarListener = new class() Listener {
+        public void handleEvent(Event e) {
+            scroll (e);
+        }
+    };
+    ScrollBar hBar = parent.getHorizontalBar ();
+    if (hBar !is null) hBar.addListener (DWT.Selection, scrollbarListener);
+    ScrollBar vBar = parent.getVerticalBar ();
+    if (vBar !is null) vBar.addListener (DWT.Selection, scrollbarListener);
+}
+Rectangle computeBounds () {
+    Rectangle clientArea = parent.getClientArea();
+    Rectangle editorRect = new Rectangle(clientArea.x, clientArea.y, minimumWidth, minimumHeight);
+
+    if (grabHorizontal)
+        editorRect.width = Math.max(clientArea.width, minimumWidth);
+
+    if (grabVertical)
+        editorRect.height = Math.max(clientArea.height, minimumHeight);
+
+    switch (horizontalAlignment) {
+        case DWT.RIGHT:
+            editorRect.x += clientArea.width - editorRect.width;
+            break;
+        case DWT.LEFT:
+            // do nothing - clientArea.x is the right answer
+            break;
+        default:
+            // default is CENTER
+            editorRect.x += (clientArea.width - editorRect.width)/2;
+    }
+
+    switch (verticalAlignment) {
+        case DWT.BOTTOM:
+            editorRect.y += clientArea.height - editorRect.height;
+            break;
+        case DWT.TOP:
+            // do nothing - clientArea.y is the right answer
+            break;
+        default :
+            // default is CENTER
+            editorRect.y += (clientArea.height - editorRect.height)/2;
+    }
+
+
+    return editorRect;
+
+}
+/**
+ * Removes all associations between the Editor and the underlying composite.  The
+ * composite and the editor Control are <b>not</b> disposed.
+ */
+public void dispose () {
+    if (parent !is null && !parent.isDisposed()) {
+        for (int i=0; i<EVENTS.length; i++) {
+            parent.removeListener (EVENTS [i], controlListener);
+        }
+        ScrollBar hBar = parent.getHorizontalBar ();
+        if (hBar !is null) hBar.removeListener (DWT.Selection, scrollbarListener);
+        ScrollBar vBar = parent.getVerticalBar ();
+        if (vBar !is null) vBar.removeListener (DWT.Selection, scrollbarListener);
+    }
+
+    parent = null;
+    editor = null;
+    hadFocus = false;
+    controlListener = null;
+    scrollbarListener = null;
+}
+/**
+* Returns the Control that is displayed above the composite being edited.
+*
+* @return the Control that is displayed above the composite being edited
+*/
+public Control getEditor () {
+    return editor;
+}
+/**
+ * Lays out the control within the underlying composite.  This
+ * method should be called after changing one or more fields to
+ * force the Editor to resize.
+ *
+ * @since 2.1
+ */
+public void layout () {
+    if (editor is null || editor.isDisposed()) return;
+    if (editor.getVisible ()) {
+        hadFocus = editor.isFocusControl();
+    } // this doesn't work because
+      // resizing the column takes the focus away
+      // before we get here
+    editor.setBounds (computeBounds ());
+    if (hadFocus) {
+        if (editor is null || editor.isDisposed()) return;
+        editor.setFocus ();
+    }
+}
+void scroll (Event e) {
+    if (editor is null || editor.isDisposed()) return;
+    layout();
+}
+/**
+* Specify the Control that is to be displayed.
+*
+* <p>Note: The Control provided as the editor <b>must</b> be created with its parent
+* being the Composite specified in the ControlEditor constructor.
+*
+* @param editor the Control that is displayed above the composite being edited
+*/
+public void setEditor (Control editor) {
+
+    if (editor is null) {
+        // this is the case where the caller is setting the editor to be blank
+        // set all the values accordingly
+        this.editor = null;
+        return;
+    }
+
+    this.editor = editor;
+    layout();
+    if (this.editor is null || this.editor.isDisposed()) return;
+    editor.setVisible(true);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/DefaultContent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,893 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.DefaultContent;
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.internal.Compatibility;
+import dwt.widgets.TypedListener;
+import dwt.custom.StyledTextContent;
+import dwt.custom.TextChangeListener;
+import dwt.custom.StyledTextEvent;
+import dwt.custom.StyledTextListener;
+import dwt.custom.StyledText;
+
+static import tango.io.FileConst;
+static import tango.text.Text;
+import dwt.dwthelper.utils;
+
+alias tango.text.Text.Text!(char) StringBuffer;
+
+class DefaultContent : StyledTextContent {
+    private final static char[] LineDelimiter = tango.io.FileConst.FileConst.NewlineString;
+
+    StyledTextListener[] textListeners; // stores text listeners for event sending
+    char[] textStore; // stores the actual text
+    int gapStart = -1;  // the character position start of the gap
+    int gapEnd = -1;    // the character position after the end of the gap
+    int gapLine = -1;   // the line on which the gap exists, the gap will always be associated with one line
+    int highWatermark = 300;
+    int lowWatermark = 50;
+
+    int[][] lines; // array of character positions and lengths representing the lines of text
+    int lineCount_ = 0;  // the number of lines of text
+    int expandExp = 1;  // the expansion exponent, used to increase the lines array exponentially
+    int replaceExpandExp = 1;   // the expansion exponent, used to increase the lines array exponentially
+
+/**
+ * Creates a new DefaultContent and initializes it.  A <code>StyledTextContent</> will always have
+ * at least one empty line.
+ */
+this() {
+    lines = new int[][]( 50, 2 );
+    setText("");
+}
+/**
+ * Adds a line to the end of the line indexes array.  Increases the size of the array if necessary.
+ * <code>lineCount</code> is updated to reflect the new entry.
+ * <p>
+ *
+ * @param start the start of the line
+ * @param length the length of the line
+ */
+void addLineIndex(int start, int length) {
+    int size = lines.length;
+    if (lineCount_ is size) {
+        // expand the lines by powers of 2
+        int[][] newLines = new int[][]( size+Compatibility.pow2(expandExp), 2 );
+        System.arraycopy(lines, 0, newLines, 0, size);
+        lines = newLines;
+        expandExp++;
+    }
+    int[] range = [start, length];
+    lines[lineCount_] = range;
+    lineCount_++;
+}
+/**
+ * Adds a line index to the end of <code>linesArray</code>.  Increases the
+ * size of the array if necessary and returns a new array.
+ * <p>
+ *
+ * @param start the start of the line
+ * @param length the length of the line
+ * @param linesArray the array to which to add the line index
+ * @param count the position at which to add the line
+ * @return a new array of line indexes
+ */
+int[][] addLineIndex(int start, int length, int[][] linesArray, int count) {
+    int size = linesArray.length;
+    int[][] newLines = linesArray;
+    if (count is size) {
+        newLines = new int[][]( size+Compatibility.pow2(replaceExpandExp), 2 );
+        replaceExpandExp++;
+        System.arraycopy(linesArray, 0, newLines, 0, size);
+    }
+    int[] range = [start, length];
+    newLines[count] = range;
+    return newLines;
+}
+/**
+ * Adds a <code>TextChangeListener</code> listening for
+ * <code>TextChangingEvent</code> and <code>TextChangedEvent</code>. A
+ * <code>TextChangingEvent</code> is sent before changes to the text occur.
+ * A <code>TextChangedEvent</code> is sent after changes to the text
+ * occurred.
+ * <p>
+ *
+ * @param listener the listener
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addTextChangeListener(TextChangeListener listener) {
+    if (listener is null) error(DWT.ERROR_NULL_ARGUMENT);
+    StyledTextListener typedListener = new StyledTextListener(listener);
+    textListeners ~= typedListener;
+}
+/**
+ * Adjusts the gap to accommodate a text change that is occurring.
+ * <p>
+ *
+ * @param position the position at which a change is occurring
+ * @param sizeHint the size of the change
+ * @param line the line where the gap will go
+ */
+void adjustGap(int position, int sizeHint, int line) {
+    if (position is gapStart) {
+        // text is being inserted at the gap position
+        int size = (gapEnd - gapStart) - sizeHint;
+        if (lowWatermark <= size && size <= highWatermark)
+            return;
+    } else if ((position + sizeHint is gapStart) && (sizeHint < 0)) {
+        // text is being deleted at the gap position
+        int size = (gapEnd - gapStart) - sizeHint;
+        if (lowWatermark <= size && size <= highWatermark)
+            return;
+    }
+    moveAndResizeGap(position, sizeHint, line);
+}
+/**
+ * Calculates the indexes of each line in the text store.  Assumes no gap exists.
+ * Optimized to do less checking.
+ */
+void indexLines(){
+    int start = 0;
+    lineCount_ = 0;
+    int textLength = textStore.length;
+    int i;
+    for (i = start; i < textLength; i++) {
+        char ch = textStore[i];
+        if (ch is DWT.CR) {
+            // see if the next character is a LF
+            if (i + 1 < textLength) {
+                ch = textStore[i+1];
+                if (ch is DWT.LF) {
+                    i++;
+                }
+            }
+            addLineIndex(start, i - start + 1);
+            start = i + 1;
+        } else if (ch is DWT.LF) {
+            addLineIndex(start, i - start + 1);
+            start = i + 1;
+        }
+    }
+    addLineIndex(start, i - start);
+}
+/**
+ * Returns whether or not the given character is a line delimiter.  Both CR and LF
+ * are valid line delimiters.
+ * <p>
+ *
+ * @param ch the character to test
+ * @return true if ch is a delimiter, false otherwise
+ */
+bool isDelimiter(char ch) {
+    if (ch is DWT.CR) return true;
+    if (ch is DWT.LF) return true;
+    return false;
+}
+/**
+ * Determine whether or not the replace operation is valid.  DefaultContent will not allow
+ * the /r/n line delimiter to be split or partially deleted.
+ * <p>
+ *
+ * @param start start offset of text to replace
+ * @param replaceLength start offset of text to replace
+ * @param newText start offset of text to replace
+ * @return a bool specifying whether or not the replace operation is valid
+ */
+protected bool isValidReplace(int start, int replaceLength, char[] newText){
+    if (replaceLength is 0) {
+        // inserting text, see if the \r\n line delimiter is being split
+        if (start is 0) return true;
+        if (start is getCharCount()) return true;
+        char before = getTextRange(start - 1, 1)[0];
+        if (before is '\r') {
+            char after = getTextRange(start, 1)[0];
+            if (after is '\n') return false;
+        }
+    } else {
+        // deleting text, see if part of a \r\n line delimiter is being deleted
+        char startChar = getTextRange(start, 1)[0];
+        if (startChar is '\n') {
+            // see if char before delete position is \r
+            if (start !is 0) {
+                char before = getTextRange(start - 1, 1)[0];
+                if (before is '\r') return false;
+            }
+        }
+        char endChar = getTextRange(start + replaceLength - 1, 1)[0];
+        if (endChar is '\r') {
+            // see if char after delete position is \n
+            if (start + replaceLength !is getCharCount()) {
+                char after = getTextRange(start + replaceLength, 1)[0];
+                if (after is '\n') return false;
+            }
+        }
+    }
+    return true;
+}
+/**
+ * Calculates the indexes of each line of text in the given range.
+ * <p>
+ *
+ * @param offset the logical start offset of the text lineate
+ * @param length the length of the text to lineate, includes gap
+ * @param numLines the number of lines to initially allocate for the line index array,
+ *  passed in for efficiency (the exact number of lines may be known)
+ * @return a line indexes array where each line is identified by a start offset and
+ *  a length
+ */
+int[][] indexLines(int offset, int length, int numLines){
+    int[][] indexedLines = new int[][]( numLines, 2 );
+    int start = 0;
+    int lineCount_ = 0;
+    int i;
+    replaceExpandExp = 1;
+    for (i = start; i < length; i++) {
+        int location = i + offset;
+        if ((location >= gapStart) && (location < gapEnd)) {
+            // ignore the gap
+        } else {
+            char ch = textStore[location];
+            if (ch is DWT.CR) {
+                // see if the next character is a LF
+                if (location+1 < textStore.length) {
+                    ch = textStore[location+1];
+                    if (ch is DWT.LF) {
+                        i++;
+                    }
+                }
+                indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount_);
+                lineCount_++;
+                start = i + 1;
+            } else if (ch is DWT.LF) {
+                indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount_);
+                lineCount_++;
+                start = i + 1;
+            }
+        }
+    }
+    int[][] newLines = new int[][]( lineCount_+1, 2 );
+    System.arraycopy(indexedLines, 0, newLines, 0, lineCount_);
+    int[] range = [start, i - start];
+    newLines[lineCount_] = range;
+    return newLines;
+}
+/**
+ * Inserts text.
+ * <p>
+ *
+ * @param position the position at which to insert the text
+ * @param text the text to insert
+ */
+void insert(int position, char[] text) {
+    if (text.length is 0) return;
+
+    int startLine = getLineAtOffset(position);
+    int change = text.length;
+    bool endInsert = position is getCharCount();
+    adjustGap(position, change, startLine);
+
+    // during an insert the gap will be adjusted to start at
+    // position and it will be associated with startline, the
+    // inserted text will be placed in the gap
+    int startLineOffset = getOffsetAtLine(startLine);
+    // at this point, startLineLength will include the start line
+    // and all of the newly inserted text
+    int startLineLength = getPhysicalLine(startLine).length;
+
+    if (change > 0) {
+        // shrink gap
+        gapStart += (change);
+        for (int i = 0; i < text.length; i++) {
+            textStore[position + i]= text[i];
+        }
+    }
+
+    // figure out the number of new lines that have been inserted
+    int [][] newLines = indexLines(startLineOffset, startLineLength, 10);
+    // only insert an empty line if it is the last line in the text
+    int numNewLines = newLines.length - 1;
+    if (newLines[numNewLines][1] is 0) {
+        // last inserted line is a new line
+        if (endInsert) {
+            // insert happening at end of the text, leave numNewLines as
+            // is since the last new line will not be concatenated with another
+            // line
+            numNewLines += 1;
+        } else {
+            numNewLines -= 1;
+        }
+    }
+
+    // make room for the new lines
+    expandLinesBy(numNewLines);
+    // shift down the lines after the replace line
+    for (int i = lineCount_ - 1; i > startLine; i--) {
+        lines[i + numNewLines]=lines[i];
+    }
+    // insert the new lines
+    for (int i = 0; i < numNewLines; i++) {
+        newLines[i][0] += startLineOffset;
+        lines[startLine + i]=newLines[i];
+    }
+    // update the last inserted line
+    if (numNewLines < newLines.length) {
+        newLines[numNewLines][0] += startLineOffset;
+        lines[startLine + numNewLines] = newLines[numNewLines];
+    }
+
+    lineCount_ += numNewLines;
+    gapLine = getLineAtPhysicalOffset(gapStart);
+}
+/**
+ * Moves the gap and adjusts its size in anticipation of a text change.
+ * The gap is resized to actual size + the specified size and moved to the given
+ * position.
+ * <p>
+ *
+ * @param position the position at which a change is occurring
+ * @param size the size of the change
+ * @param newGapLine the line where the gap should be put
+ */
+void moveAndResizeGap(int position, int size, int newGapLine) {
+    char[] content = null;
+    int oldSize = gapEnd - gapStart;
+    int newSize;
+    if (size > 0) {
+        newSize = highWatermark + size;
+    } else {
+        newSize = lowWatermark - size;
+    }
+    // remove the old gap from the lines information
+    if (gapExists()) {
+        // adjust the line length
+        lines[gapLine][1] = lines[gapLine][1] - oldSize;
+        // adjust the offsets of the lines after the gapLine
+        for (int i = gapLine + 1; i < lineCount_; i++) {
+            lines[i][0] = lines[i][0] - oldSize;
+        }
+    }
+
+    if (newSize < 0) {
+        if (oldSize > 0) {
+            // removing the gap
+            content = new char[textStore.length - oldSize];
+            System.arraycopy(textStore, 0, content, 0, gapStart);
+            System.arraycopy(textStore, gapEnd, content, gapStart, content.length - gapStart);
+            textStore = content;
+        }
+        gapStart = gapEnd = position;
+        return;
+    }
+    content = new char[textStore.length + (newSize - oldSize)];
+    int newGapStart = position;
+    int newGapEnd = newGapStart + newSize;
+    if (oldSize is 0) {
+        System.arraycopy(textStore, 0, content, 0, newGapStart);
+        System.arraycopy(textStore, newGapStart, content, newGapEnd, content.length - newGapEnd);
+    } else if (newGapStart < gapStart) {
+        int delta = gapStart - newGapStart;
+        System.arraycopy(textStore, 0, content, 0, newGapStart);
+        System.arraycopy(textStore, newGapStart, content, newGapEnd, delta);
+        System.arraycopy(textStore, gapEnd, content, newGapEnd + delta, textStore.length - gapEnd);
+    } else {
+        int delta = newGapStart - gapStart;
+        System.arraycopy(textStore, 0, content, 0, gapStart);
+        System.arraycopy(textStore, gapEnd, content, gapStart, delta);
+        System.arraycopy(textStore, gapEnd + delta, content, newGapEnd, content.length - newGapEnd);
+    }
+    textStore = content;
+    gapStart = newGapStart;
+    gapEnd = newGapEnd;
+
+    // add the new gap to the lines information
+    if (gapExists()) {
+        gapLine = newGapLine;
+        // adjust the line length
+        int gapLength = gapEnd - gapStart;
+        lines[gapLine][1] = lines[gapLine][1] + (gapLength);
+        // adjust the offsets of the lines after the gapLine
+        for (int i = gapLine + 1; i < lineCount_; i++) {
+            lines[i][0] = lines[i][0] + gapLength;
+        }
+    }
+}
+/**
+ * Returns the number of lines that are in the specified text.
+ * <p>
+ *
+ * @param startOffset the start of the text to lineate
+ * @param length the length of the text to lineate
+ * @return number of lines
+ */
+int lineCount(int startOffset, int length){
+    if (length is 0) {
+        return 0;
+    }
+    int lineCount_ = 0;
+    int count = 0;
+    int i = startOffset;
+    if (i >= gapStart) {
+        i += gapEnd - gapStart;
+    }
+    while (count < length) {
+        if ((i >= gapStart) && (i < gapEnd)) {
+            // ignore the gap
+        } else {
+            char ch = textStore[i];
+            if (ch is DWT.CR) {
+                // see if the next character is a LF
+                if (i + 1 < textStore.length) {
+                    ch = textStore[i+1];
+                    if (ch is DWT.LF) {
+                        i++;
+                        count++;
+                    }
+                }
+                lineCount_++;
+            } else if (ch is DWT.LF) {
+                lineCount_++;
+            }
+            count++;
+        }
+        i++;
+    }
+    return lineCount_;
+}
+/**
+ * Returns the number of lines that are in the specified text.
+ * <p>
+ *
+ * @param text the text to lineate
+ * @return number of lines in the text
+ */
+int lineCount(char[] text){
+    int lineCount_ = 0;
+    int length = text.length;
+    for (int i = 0; i < length; i++) {
+        char ch = text[i];
+        if (ch is DWT.CR) {
+            if (i + 1 < length && text[i + 1] is DWT.LF) {
+                i++;
+            }
+            lineCount_++;
+        } else if (ch is DWT.LF) {
+            lineCount_++;
+        }
+    }
+    return lineCount_;
+}
+/**
+ * @return the logical length of the text store
+ */
+public int getCharCount() {
+    int length = gapEnd - gapStart;
+    return (textStore.length - length);
+}
+/**
+ * Returns the line at <code>index</code> without delimiters.
+ * <p>
+ *
+ * @param index the index of the line to return
+ * @return the logical line text (i.e., without the gap)
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when index is out of range</li>
+ * </ul>
+ */
+public char[] getLine(int index) {
+    if ((index >= lineCount_) || (index < 0)) error(DWT.ERROR_INVALID_ARGUMENT);
+    int start = lines[index][0];
+    int length_ = lines[index][1];
+    int end = start + length_ - 1;
+    if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
+        // line is before or after the gap
+        while ((length_ - 1 >= 0) && isDelimiter(textStore[start+length_-1])) {
+            length_--;
+        }
+        return textStore[ start .. start + length_].dup;
+    } else {
+        // gap is in the specified range, strip out the gap
+        StringBuffer buf = new StringBuffer();
+        int gapLength = gapEnd - gapStart;
+        buf.append(textStore[ start .. gapStart ] );
+        buf.append(textStore[ gapEnd .. gapEnd + length_ - gapLength - (gapStart - start) ]);
+        length_ = buf.length;
+        while ((length_ - 1 >=0) && isDelimiter(buf.slice[length_ - 1])) {
+            length_--;
+        }
+        return buf.toString()[ 0 .. length_ ].dup;
+    }
+}
+/**
+ * Returns the line delimiter that should be used by the StyledText
+ * widget when inserting new lines.  This delimiter may be different than the
+ * delimiter that is used by the <code>StyledTextContent</code> interface.
+ * <p>
+ *
+ * @return the platform line delimiter as specified in the line.separator
+ *  system property.
+ */
+public char[] getLineDelimiter() {
+    return LineDelimiter;
+}
+/**
+ * Returns the line at the given index with delimiters.
+ * <p>
+ * @param index the index of the line to return
+ * @return the logical line text (i.e., without the gap) with delimiters
+ */
+char[] getFullLine(int index) {
+    int start = lines[index][0];
+    int length_ = lines[index][1];
+    int end = start + length_ - 1;
+    if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
+        // line is before or after the gap
+        return textStore[ start .. start + length_ ].dup;
+    } else {
+        // gap is in the specified range, strip out the gap
+        StringBuffer buffer = new StringBuffer();
+        int gapLength = gapEnd - gapStart;
+        buffer.append(textStore[ start .. gapStart ]);
+        buffer.append(textStore[ gapEnd .. gapEnd + length_ - gapLength - (gapStart - start) ]);
+        return buffer.toString().dup;
+    }
+}
+/**
+ * Returns the physical line at the given index (i.e., with delimiters and the gap).
+ * <p>
+ *
+ * @param index the line index
+ * @return the physical line
+ */
+char[] getPhysicalLine(int index) {
+    int start = lines[index][0];
+    int length_ = lines[index][1];
+    return getPhysicalText(start, length_);
+}
+/**
+ * @return the number of lines in the text store
+ */
+public int getLineCount(){
+    return lineCount_;
+}
+/**
+ * Returns the line at the given offset.
+ * <p>
+ *
+ * @param charPosition logical character offset (i.e., does not include gap)
+ * @return the line index
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT when charPosition is out of range</li>
+ * </ul>
+ */
+public int getLineAtOffset(int charPosition){
+    if ((charPosition > getCharCount()) || (charPosition < 0)) error(DWT.ERROR_INVALID_ARGUMENT);
+    int position;
+    if (charPosition < gapStart) {
+        // position is before the gap
+        position = charPosition;
+    } else {
+        // position includes the gap
+        position = charPosition + (gapEnd - gapStart);
+    }
+
+    // if last line and the line is not empty you can ask for
+    // a position that doesn't exist (the one to the right of the
+    // last character) - for inserting
+    if (lineCount_ > 0) {
+        int lastLine = lineCount_ - 1;
+        if (position is lines[lastLine][0] + lines[lastLine][1])
+            return lastLine;
+    }
+
+    int high = lineCount_;
+    int low = -1;
+    int index = lineCount_;
+    while (high - low > 1) {
+        index = (high + low) / 2;
+        int lineStart = lines[index][0];
+        int lineEnd = lineStart + lines[index][1] - 1;
+        if (position <= lineStart) {
+            high = index;
+        } else if (position <= lineEnd) {
+            high = index;
+            break;
+        } else {
+            low = index;
+        }
+    }
+    return high;
+}
+/**
+ * Returns the line index at the given physical offset.
+ * <p>
+ *
+ * @param position physical character offset (i.e., includes gap)
+ * @return the line index
+ */
+int getLineAtPhysicalOffset(int position){
+    int high = lineCount_;
+    int low = -1;
+    int index = lineCount_;
+    while (high - low > 1) {
+        index = (high + low) / 2;
+        int lineStart = lines[index][0];
+        int lineEnd = lineStart + lines[index][1] - 1;
+        if (position <= lineStart) {
+            high = index;
+        } else if (position <= lineEnd) {
+            high = index;
+            break;
+        } else {
+            low = index;
+        }
+    }
+    return high;
+}
+/**
+ * Returns the logical offset of the given line.
+ * <p>
+ *
+ * @param lineIndex index of line
+ * @return the logical starting offset of the line.  When there are not any lines,
+ *  getOffsetAtLine(0) is a valid call that should answer 0.
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when lineIndex is out of range</li>
+ * </ul>
+ */
+public int getOffsetAtLine(int lineIndex) {
+    if (lineIndex is 0) return 0;
+    if ((lineIndex >= lineCount_) || (lineIndex < 0)) error(DWT.ERROR_INVALID_ARGUMENT);
+    int start = lines[lineIndex][0];
+    if (start > gapEnd) {
+        return start - (gapEnd - gapStart);
+    } else {
+        return start;
+    }
+}
+/**
+ * Increases the line indexes array to accommodate more lines.
+ * <p>
+ *
+ * @param numLines the number to increase the array by
+ */
+void expandLinesBy(int numLines) {
+    int size = lines.length;
+    if (size - lineCount_ >= numLines) {
+        return;
+    }
+    int[][] newLines = new int[][]( size+Math.max(10, numLines), 2 );
+    System.arraycopy(lines, 0, newLines, 0, size);
+    lines = newLines;
+}
+/**
+ * Reports an DWT error.
+ * <p>
+ *
+ * @param code the error code
+ */
+void error (int code) {
+    DWT.error(code);
+}
+/**
+ * Returns whether or not a gap exists in the text store.
+ * <p>
+ *
+ * @return true if gap exists, false otherwise
+ */
+bool gapExists() {
+    return gapStart !is gapEnd;
+}
+/**
+ * Returns a string representing the continuous content of
+ * the text store.
+ * <p>
+ *
+ * @param start the physical start offset of the text to return
+ * @param length the physical length of the text to return
+ * @return the text
+ */
+char[] getPhysicalText(int start, int length_) {
+    return textStore[ start .. start + length_ ].dup;
+}
+/**
+ * Returns a string representing the logical content of
+ * the text store (i.e., gap stripped out).
+ * <p>
+ *
+ * @param start the logical start offset of the text to return
+ * @param length the logical length of the text to return
+ * @return the text
+ */
+public char[] getTextRange(int start, int length_) {
+    if (textStore is null)
+        return "";
+    if (length_ is 0)
+        return "";
+    int end= start + length_;
+    if (!gapExists() || (end < gapStart))
+        return textStore[ start .. start + length_].dup;
+    if (gapStart < start) {
+        int gapLength= gapEnd - gapStart;
+        return textStore[ start + gapLength .. start + gapLength + length_ ].dup;
+    }
+    StringBuffer buf = new StringBuffer();
+    buf.append(textStore[ start .. start + gapStart - start ] );
+    buf.append(textStore[ gapEnd .. gapEnd + end - gapStart ] );
+    return buf.toString().dup;
+}
+/**
+ * Removes the specified <code>TextChangeListener</code>.
+ * <p>
+ *
+ * @param listener the listener
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeTextChangeListener(TextChangeListener listener){
+    if (listener is null) error(DWT.ERROR_NULL_ARGUMENT);
+    for (int i = 0; i < textListeners.length; i++) {
+        TypedListener typedListener = cast(TypedListener) textListeners[i];
+        if (typedListener.getEventListener () is listener) {
+            textListeners = textListeners[ 0 .. i ] ~ textListeners[ i+1 .. $ ];
+            break;
+        }
+    }
+}
+/**
+ * Replaces the text with <code>newText</code> starting at position <code>start</code>
+ * for a length of <code>replaceLength</code>.  Notifies the appropriate listeners.
+ * <p>
+ *
+ * When sending the TextChangingEvent, <code>newLineCount</code> is the number of
+ * lines that are going to be inserted and <code>replaceLineCount</code> is
+ * the number of lines that are going to be deleted, based on the change
+ * that occurs visually.  For example:
+ * <ul>
+ * <li>(replaceText,newText) is> (replaceLineCount,newLineCount)
+ * <li>("","\n") is> (0,1)
+ * <li>("\n\n","a") is> (2,0)
+ * </ul>
+ * </p>
+ *
+ * @param start start offset of text to replace
+ * @param replaceLength start offset of text to replace
+ * @param newText start offset of text to replace
+ *
+ * @exception DWTException <ul>
+ *   <li>ERROR_INVALID_ARGUMENT when the text change results in a multi byte
+ *      line delimiter being split or partially deleted.  Splitting a line
+ *      delimiter by inserting text between the CR and LF characters of the
+ *      \r\n delimiter or deleting part of this line delimiter is not supported</li>
+ * </ul>
+ */
+public void replaceTextRange(int start, int replaceLength, char[] newText){
+    // check for invalid replace operations
+    if (!isValidReplace(start, replaceLength, newText)) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+
+    // inform listeners
+    StyledTextEvent event = new StyledTextEvent(this);
+    event.type = StyledText.TextChanging;
+    event.start = start;
+    event.replaceLineCount = lineCount(start, replaceLength);
+    event.text = newText;
+    event.newLineCount = lineCount(newText);
+    event.replaceCharCount = replaceLength;
+    event.newCharCount = newText.length;
+    sendTextEvent(event);
+
+    // first delete the text to be replaced
+    delete_(start, replaceLength, event.replaceLineCount + 1);
+    // then insert the new text
+    insert(start, newText);
+    // inform listeners
+    event = new StyledTextEvent(this);
+    event.type = StyledText.TextChanged;
+    sendTextEvent(event);
+}
+/**
+ * Sends the text listeners the TextChanged event.
+ */
+void sendTextEvent(StyledTextEvent event) {
+    for (int i = 0; i < textListeners.length; i++) {
+        (cast(StyledTextListener)textListeners[i]).handleEvent(event);
+    }
+}
+/**
+ * Sets the content to text and removes the gap since there are no sensible predictions
+ * about where the next change will occur.
+ * <p>
+ *
+ * @param text the text
+ */
+public void setText (char[] text){
+    textStore = text.dup;
+    gapStart = -1;
+    gapEnd = -1;
+    expandExp = 1;
+    indexLines();
+    StyledTextEvent event = new StyledTextEvent(this);
+    event.type = StyledText.TextSet;
+    event.text = "";
+    sendTextEvent(event);
+}
+/**
+ * Deletes text.
+ * <p>
+ * @param position the position at which the text to delete starts
+ * @param length the length of the text to delete
+ * @param numLines the number of lines that are being deleted
+ */
+void delete_(int position, int length_, int numLines) {
+    if (length_ is 0) return;
+
+    int startLine = getLineAtOffset(position);
+    int startLineOffset = getOffsetAtLine(startLine);
+    int endLine = getLineAtOffset(position + length_);
+
+    char[] endText = "";
+    bool splittingDelimiter = false;
+    if (position + length_ < getCharCount()) {
+        endText = getTextRange(position + length_ - 1, 2);
+        if ((endText[0] is DWT.CR) && (endText[1] is DWT.LF)) {
+            splittingDelimiter = true;
+        }
+    }
+
+    adjustGap(position + length_, -length_, startLine);
+    int [][] oldLines = indexLines(position, length_ + (gapEnd - gapStart), numLines);
+
+    // enlarge the gap - the gap can be enlarged either to the
+    // right or left
+    if (position + length_ is gapStart) {
+        gapStart -= length_;
+    } else {
+        gapEnd += length_;
+    }
+
+    // figure out the length of the new concatenated line, do so by
+    // finding the first line delimiter after position
+    int j = position;
+    bool eol = false;
+    while (j < textStore.length && !eol) {
+        if (j < gapStart || j >= gapEnd) {
+            char ch = textStore[j];
+            if (isDelimiter(ch)) {
+                if (j + 1 < textStore.length) {
+                    if (ch is DWT.CR && (textStore[j+1] is DWT.LF)) {
+                        j++;
+                    }
+                }
+                eol = true;
+            }
+        }
+        j++;
+    }
+    // update the line where the deletion started
+    lines[startLine][1] = (position - startLineOffset) + (j - position);
+    // figure out the number of lines that have been deleted
+    int numOldLines = oldLines.length - 1;
+    if (splittingDelimiter) numOldLines -= 1;
+    // shift up the lines after the last deleted line, no need to update
+    // the offset or length of the lines
+    for (int i = endLine + 1; i < lineCount_; i++) {
+        lines[i - numOldLines] = lines[i];
+    }
+    lineCount_ -= numOldLines;
+    gapLine = getLineAtPhysicalOffset(gapStart);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ExtendedModifyEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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 dwt.custom.ExtendedModifyEvent;
+
+import dwt.events.TypedEvent;
+import dwt.custom.StyledTextEvent;
+
+/**
+ * This event is sent after a text change occurs.
+ */
+public final class ExtendedModifyEvent : TypedEvent {
+    /** start offset of the new text */
+    public int start;
+    /** length of the new text */
+    public int length;
+    /** replaced text or empty string if no text was replaced */
+    public char[] replacedText;
+
+    static final long serialVersionUID = 3258696507027830832L;
+
+public this(StyledTextEvent e) {
+    super(cast(Object)e);
+    start = e.start;
+    length = e.end - e.start;
+    replacedText = e.text;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ExtendedModifyListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.ExtendedModifyListener;
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.ExtendedModifyEvent;
+
+public interface ExtendedModifyListener : DWTEventListener {
+/**
+ * This method is called after a text change occurs.
+ * <p>
+ * The following event fields are used:<ul>
+ * <li>event.start the start offset of the new text (input)</li>
+ * <li>event.length the length of the new text (input)</li>
+ * <li>event.replacedText the replaced text (input)</li>
+ * </ul>
+ *
+ * @param event the given event
+ * @see ExtendedModifyEvent
+ */
+public void modifyText(ExtendedModifyEvent event);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/LineBackgroundEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.LineBackgroundEvent;
+
+
+import dwt.events.TypedEvent;
+import dwt.graphics.Color;
+import dwt.custom.StyledTextEvent;
+
+/**
+ * This event is sent when a line is about to be drawn.
+ */
+public class LineBackgroundEvent : TypedEvent {
+
+    /**
+     * line start offset
+     */
+    public int lineOffset;
+
+    /**
+     * line text
+     */
+    public char[] lineText;
+
+    /**
+     * line background color
+     */
+    public Color lineBackground;
+
+    static final long serialVersionUID = 3978711687853324342L;
+
+public this(StyledTextEvent e) {
+    super(cast(Object)e);
+    lineOffset = e.detail;
+    lineText = e.text;
+}
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/LineBackgroundListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.LineBackgroundListener;
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.LineBackgroundEvent;
+
+public interface LineBackgroundListener : DWTEventListener {
+
+/**
+ * This method is called when a line is about to be drawn in order to get its
+ * background color.
+ * <p>
+ * The following event fields are used:<ul>
+ * <li>event.lineOffset line start offset (input)</li>
+ * <li>event.lineText line text (input)</li>
+ * <li>event.lineBackground line background color (output)</li>
+ * </ul>
+ *
+ * @param event the given event
+ * @see LineBackgroundEvent
+ */
+public void lineGetBackground(LineBackgroundEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/LineStyleEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.LineStyleEvent;
+
+import dwt.events.TypedEvent;
+import dwt.custom.StyleRange;
+import dwt.custom.Bullet;
+import dwt.custom.StyledTextEvent;
+
+/**
+ * This event is sent when a line is about to be drawn.
+ */
+public class LineStyleEvent : TypedEvent {
+
+    /**
+     * line start offset (input)
+     */
+    public int lineOffset;
+
+    /**
+     * line text (input)
+     */
+    public char[] lineText;
+
+    /**
+     * line ranges (output)
+     *
+     * @since 3.2
+     */
+    public int[] ranges;
+
+    /**
+     * line styles (output)
+     *
+     * 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>ranges</code> can be used to share
+     * styles and reduce memory usage.
+     */
+    public StyleRange[] styles;
+
+    /**
+     * line alignment (input, output)
+     *
+     * @since 3.2
+     */
+    public int alignment;
+
+    /**
+     * line indent (input, output)
+     *
+     * @since 3.2
+     */
+    public int indent;
+
+    /**
+     * line justification (input, output)
+     *
+     * @since 3.2
+     */
+    public bool justify;
+
+    /**
+     * line bullet (output)
+     * @since 3.2
+     */
+    public Bullet bullet;
+
+    /**
+     * line bullet index (output)
+     * @since 3.2
+     */
+    public int bulletIndex;
+
+    static final long serialVersionUID = 3906081274027192884L;
+
+public this(StyledTextEvent e) {
+    super(cast(Object)e);
+    lineOffset = e.detail;
+    lineText = e.text;
+    alignment = e.alignment;
+    justify = e.justify;
+    indent = e.indent;
+    bullet = e.bullet;
+    bulletIndex = e.bulletIndex;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/LineStyleListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.LineStyleListener;
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.LineStyleEvent;
+
+public interface LineStyleListener : DWTEventListener {
+/**
+ * This method is called when a line is about to be drawn in order to get the
+ * line's style information.
+ * <p>
+ * The following event fields are used:<ul>
+ * <li>event.lineOffset line start offset (input)</li>
+ * <li>event.lineText line text (input)</li>
+ * <li>event.styles array of StyleRanges, need to be in order (output)</li>
+ * </ul>
+ *
+ * @param event the given event
+ * @see LineStyleEvent
+ */
+public void lineGetStyle(LineStyleEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/MovementEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.MovementEvent;
+
+import dwt.events.TypedEvent;
+import dwt.custom.StyledTextEvent;
+
+/**
+ * This event is sent when a new offset is required based on the current
+ * offset and a movement type.
+ *
+ * @since 3.3
+ */
+public class MovementEvent : TypedEvent {
+
+    /**
+     * line start offset (input)
+     */
+    public int lineOffset;
+
+    /**
+     * line text (input)
+     */
+    public char[] lineText;
+
+    /**
+     * the current offset (input)
+     */
+    public int offset;
+
+    /**
+     * the new offset  (input, output)
+     */
+    public int newOffset;
+
+    /**
+     * the movement type (input)
+     *
+     * @see dwt.DWT#MOVEMENT_WORD
+     * @see dwt.DWT#MOVEMENT_WORD_END
+     * @see dwt.DWT#MOVEMENT_WORD_START
+     * @see dwt.DWT#MOVEMENT_CHAR
+     * @see dwt.DWT#MOVEMENT_CLUSTER
+     */
+    public int movement;
+
+    static final long serialVersionUID = 3978765487853324342L;
+
+public this(StyledTextEvent e) {
+    super(cast(Object)e);
+    lineOffset = e.detail;
+    lineText = e.text;
+    movement = e.count;
+    offset = e.start;
+    newOffset = e.end;
+}
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/MovementListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.MovementListener;
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.MovementEvent;
+
+/**
+ * This listener is invoked when a new offset is required based on the current
+ * offset and a movement type.
+ *
+ * @see dwt.DWT#MOVEMENT_WORD
+ * @see dwt.DWT#MOVEMENT_WORD_END
+ * @see dwt.DWT#MOVEMENT_WORD_START
+ * @see dwt.DWT#MOVEMENT_CHAR
+ * @see dwt.DWT#MOVEMENT_CLUSTER
+ *
+ * @since 3.3
+ */
+public interface MovementListener : DWTEventListener {
+/**
+ * This method is called when a new offset is required based on the current
+ * offset and a movement type.
+ *
+ * <p>
+ * The following event fields are used:<ul>
+ * <li>event.lineOffset line start offset (input)</li>
+ * <li>event.lineText line text (input)</li>
+ * <li>event.movement the movement type (input)</li>
+ * <li>event.offset the current offset (input)</li>
+ * <li>event.newOffset the new offset (input, output)</li>
+ * </ul>
+ *
+ * @param event the event
+ *
+ * @see MovementEvent
+ * @see StyledText#addWordMovementListener(MovementListener)
+ */
+public void getNextOffset (MovementEvent event);
+/**
+ * This method is called when a new offset is required based on the current
+ * offset and a movement type.
+ *
+ * <p>
+ * The following event fields are used:<ul>
+ * <li>event.lineOffset line start offset (input)</li>
+ * <li>event.lineText line text (input)</li>
+ * <li>event.movement the movement type (input)</li>
+ * <li>event.offset the current offset (input)</li>
+ * <li>event.newOffset the new offset (input, output)</li>
+ * </ul>
+ *
+ * @param event the event
+ *
+ * @see MovementEvent
+ * @see StyledText#addWordMovementListener(MovementListener)
+ */
+public void getPreviousOffset (MovementEvent event);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/PaintObjectEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.PaintObjectEvent;
+
+
+import dwt.events.TypedEvent;
+import dwt.graphics.GC;
+import dwt.custom.StyleRange;
+import dwt.custom.Bullet;
+import dwt.custom.StyledTextEvent;
+
+/**
+ * This event is sent when an object needs to be drawn.
+ *
+ * @since 3.2
+ */
+public class PaintObjectEvent : TypedEvent {
+
+    /**
+     * the GC
+     */
+    public GC gc;
+
+    /**
+     * the x location
+     */
+    public int x;
+
+    /**
+     * the y location
+     */
+    public int y;
+
+    /**
+     * the line ascent
+     */
+    public int ascent;
+
+    /**
+     * the line descent
+     */
+    public int descent;
+
+    /**
+     * the StyleRange
+     */
+    public StyleRange style;
+
+    /**
+     * the Bullet
+     */
+    public Bullet bullet;
+
+    /**
+     * the bullet index
+     */
+    public int bulletIndex;
+
+    static final long serialVersionUID = 3906081274027192855L;
+
+public this(StyledTextEvent e) {
+    super(cast(Object)e);
+    gc = e.gc;
+    x = e.x;
+    y = e.y;
+    ascent = e.ascent;
+    descent = e.descent;
+    style = e.style;
+    bullet = e.bullet;
+    bulletIndex = e.bulletIndex;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/PaintObjectListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.PaintObjectListener;
+
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.PaintObjectEvent;
+
+/**
+ * This listener is invoked when an object needs to be drawn.
+ *
+ * @since 3.2
+ */
+public interface PaintObjectListener : DWTEventListener {
+/**
+ * This method is called when an object needs to be drawn.
+ *
+ * <p>
+ * The following event fields are used:<ul>
+ * <li>event.x the x location (input)</li>
+ * <li>event.y the y location (input)</li>
+ * <li>event.ascent the line ascent (input)</li>
+ * <li>event.descent the line descent (input)</li>
+ * <li>event.gc the gc (input)</li>
+ * <li>event.style the style (input)</li>
+ * </ul>
+ *
+ * @param event the event
+ *
+ * @see PaintObjectEvent
+ * @see StyledText#addPaintObjectListener(PaintObjectListener)
+ */
+public void paintObject(PaintObjectEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/PopupList.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,286 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.PopupList;
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.events.ControlEvent;
+import dwt.events.ControlListener;
+import dwt.events.KeyEvent;
+import dwt.events.KeyListener;
+import dwt.events.MouseEvent;
+import dwt.events.MouseListener;
+import dwt.graphics.Font;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.List;
+import dwt.widgets.Listener;
+import dwt.widgets.Shell;
+/**
+* A PopupList is a list of selectable items that appears in its own shell positioned above
+* its parent shell.  It is used for selecting items when editing a Table cell (similar to the
+* list that appears when you open a Combo box).
+*
+* The list will be positioned so that it does not run off the screen and the largest number of items
+* are visible.  It may appear above the current cursor location or below it depending how close you
+* are to the edge of the screen.
+*/
+public class PopupList {
+    Shell  shell;
+    List   list;
+    int    minimumWidth;
+/**
+* Creates a PopupList above the specified shell.
+*
+* @param parent a Shell control which will be the parent of the new instance (cannot be null)
+*/
+public this(Shell parent) {
+    this (parent, 0);
+}
+/**
+* Creates a PopupList above the specified shell.
+*
+* @param parent a widget which will be the parent of the new instance (cannot be null)
+* @param style the style of widget to construct
+*
+* @since 3.0
+*/
+public this(Shell parent, int style) {
+    shell = new Shell(parent, checkStyle(style));
+
+    list = new List(shell, DWT.SINGLE | DWT.V_SCROLL);
+
+    // close dialog if user selects outside of the shell
+    shell.addListener(DWT.Deactivate, new class() Listener {
+        public void handleEvent(Event e){
+            shell.setVisible (false);
+        }
+    });
+
+    // resize shell when list resizes
+    shell.addControlListener(new class() ControlListener {
+        public void controlMoved(ControlEvent e){}
+        public void controlResized(ControlEvent e){
+            Rectangle shellSize = shell.getClientArea();
+            list.setSize(shellSize.width, shellSize.height);
+        }
+    });
+
+    // return list selection on Mouse Up or Carriage Return
+    list.addMouseListener(new class() MouseListener {
+        public void mouseDoubleClick(MouseEvent e){}
+        public void mouseDown(MouseEvent e){}
+        public void mouseUp(MouseEvent e){
+            shell.setVisible (false);
+        }
+    });
+    list.addKeyListener(new class() KeyListener {
+        public void keyReleased(KeyEvent e){}
+        public void keyPressed(KeyEvent e){
+            if (e.character is '\r'){
+                shell.setVisible (false);
+            }
+        }
+    });
+
+}
+private static int checkStyle (int style) {
+    int mask = DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
+    return style & mask;
+}
+/**
+* Gets the widget font.
+* <p>
+* @return the widget font
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 Font getFont () {
+    return list.getFont();
+}
+/**
+* Gets the items.
+* <p>
+* This operation will fail if the items cannot
+* be queried from the OS.
+*
+* @return the items in the widget
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 char[][] getItems () {
+    return list.getItems();
+}
+/**
+* Gets the minimum width of the list.
+*
+* @return the minimum width of the list
+*/
+public int getMinimumWidth () {
+    return minimumWidth;
+}
+/**
+* Launches the Popup List, waits for an item to be selected and then closes the PopupList.
+*
+* @param rect the initial size and location of the PopupList; the dialog will be
+*        positioned so that it does not run off the screen and the largest number of items are visible
+*
+* @return the text of the selected item or null if no item is selected
+*/
+public char[] open (Rectangle rect) {
+
+    Point listSize = list.computeSize (rect.width, DWT.DEFAULT, false);
+    Rectangle screenSize = shell.getDisplay().getBounds();
+
+    // Position the dialog so that it does not run off the screen and the largest number of items are visible
+    int spaceBelow = screenSize.height - (rect.y + rect.height) - 30;
+    int spaceAbove = rect.y - 30;
+
+    int y = 0;
+    if (spaceAbove > spaceBelow && listSize.y > spaceBelow) {
+        // place popup list above table cell
+        if (listSize.y > spaceAbove){
+            listSize.y = spaceAbove;
+        } else {
+            listSize.y += 2;
+        }
+        y = rect.y - listSize.y;
+
+    } else {
+        // place popup list below table cell
+        if (listSize.y > spaceBelow){
+            listSize.y = spaceBelow;
+        } else {
+            listSize.y += 2;
+        }
+        y = rect.y + rect.height;
+    }
+
+    // Make dialog as wide as the cell
+    listSize.x = rect.width;
+    // dialog width should not be less than minimumWidth
+    if (listSize.x < minimumWidth)
+        listSize.x = minimumWidth;
+
+    // Align right side of dialog with right side of cell
+    int x = rect.x + rect.width - listSize.x;
+
+    shell.setBounds(x, y, listSize.x, listSize.y);
+
+    shell.open();
+    list.setFocus();
+
+    Display display = shell.getDisplay();
+    while (!shell.isDisposed () && shell.isVisible ()) {
+        if (!display.readAndDispatch()) display.sleep();
+    }
+
+    char[] result = null;
+    if (!shell.isDisposed ()) {
+        char[] [] strings = list.getSelection ();
+        shell.dispose();
+        if (strings.length !is 0) result = strings [0];
+    }
+    return result;
+}
+/**
+* Selects an item with text that starts with specified char[].
+* <p>
+* If the item is not currently selected, it is selected.
+* If the item at an index is selected, it remains selected.
+* If the string is not matched, it is ignored.
+*
+* @param string the text of the item
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 select(char[] string) {
+    char[][] items = list.getItems();
+
+    // find the first entry in the list that starts with the
+    // specified string
+    if (string !is null){
+        for (int i = 0; i < items.length; i++) {
+            if ( tango.text.Util.locatePattern( items[i], string) is 0 ){
+                int index = list.indexOf(items[i]);
+                list.select(index);
+                break;
+            }
+        }
+    }
+}
+/**
+* Sets the widget font.
+* <p>
+* When new font is null, the font reverts
+* to the default system font for the widget.
+*
+* @param font the new font (or null)
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 setFont (Font font) {
+    list.setFont(font);
+}
+/**
+* Sets all items.
+* <p>
+* The previous selection is cleared.
+* The previous items are deleted.
+* The new items are added.
+* The top index is set to 0.
+*
+* @param strings the array of items
+*
+* This operation will fail when an item is null
+* or could not be added in the OS.
+*
+* @exception IllegalArgumentException <ul>
+*    <li>ERROR_NULL_ARGUMENT - if the items array is null</li>
+*    <li>ERROR_INVALID_ARGUMENT - if an item in the items array is null</li>
+* </ul>
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 setItems (char[][] strings) {
+    list.setItems(strings);
+}
+/**
+* Sets the minimum width of the list.
+*
+* @param width the minimum width of the list
+*/
+public void setMinimumWidth (int width) {
+    if (width < 0)
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+
+    minimumWidth = width;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ST.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.ST;
+
+
+/**
+ * This class provides access to the public constants provided by <code>StyledText</code>.
+ */
+public class ST {
+
+    /*
+     *  Navigation Key Actions. Key bindings for the actions are set
+     *  by the StyledText widget.
+     */
+    public static const int LINE_UP = 16777217;             // binding = DWT.ARROW_UP
+    public static const int LINE_DOWN = 16777218;       // binding = DWT.ARROW_DOWN
+    public static const int LINE_START = 16777223;      // binding = DWT.HOME
+    public static const int LINE_END = 16777224;        // binding = DWT.END
+    public static const int COLUMN_PREVIOUS = 16777219;     // binding = DWT.ARROW_LEFT
+    public static const int COLUMN_NEXT = 16777220;         // binding = DWT.ARROW_RIGHT
+    public static const int PAGE_UP = 16777221;             // binding = DWT.PAGE_UP
+    public static const int PAGE_DOWN = 16777222;       // binding = DWT.PAGE_DOWN
+    public static const int WORD_PREVIOUS = 17039363;   // binding = DWT.MOD1 + DWT.ARROW_LEFT
+    public static const int WORD_NEXT = 17039364;       // binding = DWT.MOD1 + DWT.ARROW_RIGHT
+    public static const int TEXT_START = 17039367;      // binding = DWT.MOD1 + DWT.HOME
+    public static const int TEXT_END = 17039368;        // binding = DWT.MOD1 + DWT.END
+    public static const int WINDOW_START = 17039365;    // binding = DWT.MOD1 + DWT.PAGE_UP
+    public static const int WINDOW_END = 17039366;      // binding = DWT.MOD1 + DWT.PAGE_DOWN
+
+    /*
+     * Selection Key Actions
+     */
+    public static const int SELECT_ALL = 262209;                // binding = DWT.MOD1 + 'A'
+    public static const int SELECT_LINE_UP = 16908289;          // binding = DWT.MOD2 + DWT.ARROW_UP
+    public static const int SELECT_LINE_DOWN = 16908290;        // binding = DWT.MOD2 + DWT.ARROW_DOWN
+    public static const int SELECT_LINE_START = 16908295;       // binding = DWT.MOD2 + DWT.HOME
+    public static const int SELECT_LINE_END = 16908296;             // binding = DWT.MOD2 + DWT.END
+    public static const int SELECT_COLUMN_PREVIOUS = 16908291;  // binding = DWT.MOD2 + DWT.ARROW_LEFT
+    public static const int SELECT_COLUMN_NEXT = 16908292;      // binding = DWT.MOD2 + DWT.ARROW_RIGHT
+    public static const int SELECT_PAGE_UP = 16908293;          // binding = DWT.MOD2 + DWT.PAGE_UP
+    public static const int SELECT_PAGE_DOWN = 16908294;        // binding = DWT.MOD2 + DWT.PAGE_DOWN
+    public static const int SELECT_WORD_PREVIOUS = 17170435;        // binding = DWT.MOD1 + DWT.MOD2 + DWT.ARROW_LEFT
+    public static const int SELECT_WORD_NEXT = 17170436;        // binding = DWT.MOD1 + DWT.MOD2 + DWT.ARROW_RIGHT
+    public static const int SELECT_TEXT_START = 17170439;       // binding = DWT.MOD1 + DWT.MOD2 + DWT.HOME
+    public static const int SELECT_TEXT_END = 17170440;             // binding = DWT.MOD1 + DWT.MOD2 + DWT.END
+    public static const int SELECT_WINDOW_START = 17170437;         // binding = DWT.MOD1 + DWT.MOD2 + DWT.PAGE_UP
+    public static const int SELECT_WINDOW_END = 17170438;       // binding = DWT.MOD1 + DWT.MOD2 + DWT.PAGE_DOWN
+
+    /*
+     *  Modification Key Actions
+     */
+    public static const int CUT = 131199;           // binding = DWT.MOD2 + DWT.DEL
+    public static const int COPY = 17039369;        // binding = DWT.MOD1 + DWT.INSERT;
+    public static const int PASTE = 16908297;       // binding = DWT.MOD2 + DWT.INSERT ;
+    public static const int DELETE_PREVIOUS = '\b';     // binding = DWT.BS;
+    public static const int DELETE_NEXT = 0x7F;         // binding = DWT.DEL;
+    public static const int DELETE_WORD_PREVIOUS = 262152;  // binding = DWT.BS | DWT.MOD1;
+    public static const int DELETE_WORD_NEXT = 262271;  // binding = DWT.DEL | DWT.MOD1;
+
+    /*
+     * Miscellaneous Key Actions
+     */
+    public static const int TOGGLE_OVERWRITE = 16777225; // binding = DWT.INSERT;
+
+    /**
+     *  Bullet style dot.
+     *
+     *  @see Bullet
+     *
+     *  @since 3.2
+     */
+    public static const int BULLET_DOT = 1 << 0;
+
+    /**
+     *  Bullet style number.
+     *
+     *  @see Bullet
+     *
+     *  @since 3.2
+     */
+    public static const int BULLET_NUMBER = 1 << 1;
+
+    /**
+     *  Bullet style lower case letter.
+     *
+     *  @see Bullet
+     *
+     *  @since 3.2
+     */
+    public static const int BULLET_LETTER_LOWER = 1 << 2;
+
+    /**
+     *  Bullet style upper case letter.
+     *
+     *  @see Bullet
+     *
+     *  @since 3.2
+     */
+    public static const int BULLET_LETTER_UPPER = 1 << 3;
+
+    /**
+     *  Bullet style text.
+     *
+     *  @see Bullet
+     *
+     *  @since 3.2
+     */
+    public static const int BULLET_TEXT = 1 << 4;
+
+    /**
+     *  Bullet style custom draw.
+     *
+     *  @see StyledText#addPaintObjectListener(PaintObjectListener)
+     *  @see StyledText#removePaintObjectListener(PaintObjectListener)
+     *  @see Bullet
+     *
+     *  @since 3.2
+     */
+    public static const int BULLET_CUSTOM = 1 << 5;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ScrolledComposite.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,595 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.ScrolledComposite;
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.widgets.ScrollBar;
+import dwt.custom.ScrolledCompositeLayout;
+
+/**
+ * A ScrolledComposite provides scrollbars and will scroll its content when the user
+ * uses the scrollbars.
+ *
+ *
+ * <p>There are two ways to use the ScrolledComposite:
+ *
+ * <p>
+ * 1) Set the size of the control that is being scrolled and the ScrolledComposite
+ * will show scrollbars when the contained control can not be fully seen.
+ *
+ * 2) The second way imitates the way a browser would work.  Set the minimum size of
+ * the control and the ScrolledComposite will show scroll bars if the visible area is
+ * less than the minimum size of the control and it will expand the size of the control
+ * if the visible area is greater than the minimum size.  This requires invoking
+ * both setMinWidth(), setMinHeight() and setExpandHorizontal(), setExpandVertical().
+ *
+ * <code><pre>
+ * public static void main (String [] args) {
+ *      Display display = new Display ();
+ *      Color red = display.getSystemColor(DWT.COLOR_RED);
+ *      Color blue = display.getSystemColor(DWT.COLOR_BLUE);
+ *      Shell shell = new Shell (display);
+ *      shell.setLayout(new FillLayout());
+ *
+ *      // set the size of the scrolled content - method 1
+ *      final ScrolledComposite sc1 = new ScrolledComposite(shell, DWT.H_SCROLL | DWT.V_SCROLL | DWT.BORDER);
+ *      final Composite c1 = new Composite(sc1, DWT.NONE);
+ *      sc1.setContent(c1);
+ *      c1.setBackground(red);
+ *      GridLayout layout = new GridLayout();
+ *      layout.numColumns = 4;
+ *      c1.setLayout(layout);
+ *      Button b1 = new Button (c1, DWT.PUSH);
+ *      b1.setText("first button");
+ *      c1.setSize(c1.computeSize(DWT.DEFAULT, DWT.DEFAULT));
+ *
+ *      // set the minimum width and height of the scrolled content - method 2
+ *      final ScrolledComposite sc2 = new ScrolledComposite(shell, DWT.H_SCROLL | DWT.V_SCROLL | DWT.BORDER);
+ *      sc2.setExpandHorizontal(true);
+ *      sc2.setExpandVertical(true);
+ *      final Composite c2 = new Composite(sc2, DWT.NONE);
+ *      sc2.setContent(c2);
+ *      c2.setBackground(blue);
+ *      layout = new GridLayout();
+ *      layout.numColumns = 4;
+ *      c2.setLayout(layout);
+ *      Button b2 = new Button (c2, DWT.PUSH);
+ *      b2.setText("first button");
+ *      sc2.setMinSize(c2.computeSize(DWT.DEFAULT, DWT.DEFAULT));
+ *
+ *      Button add = new Button (shell, DWT.PUSH);
+ *      add.setText("add children");
+ *      final int[] index = new int[]{0};
+ *      add.addListener(DWT.Selection, new Listener() {
+ *          public void handleEvent(Event e) {
+ *              index[0]++;
+ *              Button button = new Button(c1, DWT.PUSH);
+ *              button.setText("button "+index[0]);
+ *              // reset size of content so children can be seen - method 1
+ *              c1.setSize(c1.computeSize(DWT.DEFAULT, DWT.DEFAULT));
+ *              c1.layout();
+ *
+ *              button = new Button(c2, DWT.PUSH);
+ *              button.setText("button "+index[0]);
+ *              // reset the minimum width and height so children can be seen - method 2
+ *              sc2.setMinSize(c2.computeSize(DWT.DEFAULT, DWT.DEFAULT));
+ *              c2.layout();
+ *          }
+ *      });
+ *
+ *      shell.open ();
+ *      while (!shell.isDisposed ()) {
+ *          if (!display.readAndDispatch ()) display.sleep ();
+ *      }
+ *      display.dispose ();
+ * }
+ * </pre></code>
+ *
+ * <dl>
+ * <dt><b>Styles:</b><dd>H_SCROLL, V_SCROLL
+ * </dl>
+ */
+public class ScrolledComposite : Composite {
+
+    Control content;
+    Listener contentListener;
+
+    int minHeight = 0;
+    int minWidth = 0;
+    bool expandHorizontal = false;
+    bool expandVertical = false;
+    bool alwaysShowScroll = false;
+
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#H_SCROLL
+ * @see DWT#V_SCROLL
+ * @see #getStyle()
+ */
+public this(Composite parent, int style) {
+    super(parent, checkStyle(style));
+    super.setLayout(new ScrolledCompositeLayout());
+    ScrollBar hBar = getHorizontalBar ();
+    if (hBar !is null) {
+        hBar.setVisible(false);
+        hBar.addListener (DWT.Selection, new class() Listener  {
+            public void handleEvent (Event e) {
+                hScroll();
+            }
+        });
+    }
+
+    ScrollBar vBar = getVerticalBar ();
+    if (vBar !is null) {
+        vBar.setVisible(false);
+        vBar.addListener (DWT.Selection, new class() Listener {
+            public void handleEvent (Event e) {
+                vScroll();
+            }
+        });
+    }
+
+    contentListener = new class() Listener {
+        public void handleEvent(Event e) {
+            if (e.type !is DWT.Resize) return;
+            layout(false);
+        }
+    };
+}
+
+static int checkStyle (int style) {
+    int mask = DWT.H_SCROLL | DWT.V_SCROLL | DWT.BORDER | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
+    return style & mask;
+}
+
+/**
+ * Returns the Always Show Scrollbars flag.  True if the scrollbars are
+ * always shown even if they are not required.  False if the scrollbars are only
+ * visible when some part of the composite needs to be scrolled to be seen.
+ * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
+ * horizontal and vertical directions.
+ *
+ * @return the Always Show Scrollbars flag value
+ */
+public bool getAlwaysShowScrollBars() {
+    //checkWidget();
+    return alwaysShowScroll;
+}
+
+/**
+ * Returns <code>true</code> if the content control
+ * will be expanded to fill available horizontal space.
+ *
+ * @return the receiver's horizontal expansion state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 bool getExpandHorizontal() {
+    checkWidget();
+    return expandHorizontal;
+}
+
+/**
+ * Returns <code>true</code> if the content control
+ * will be expanded to fill available vertical space.
+ *
+ * @return the receiver's vertical expansion state
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 bool getExpandVertical() {
+    checkWidget();
+    return expandVertical;
+}
+
+/**
+ * Returns the minimum width of the content control.
+ *
+ * @return the minimum width
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getMinWidth() {
+    checkWidget();
+    return minWidth;
+}
+
+/**
+ * Returns the minimum height of the content control.
+ *
+ * @return the minimum height
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getMinHeight() {
+    checkWidget();
+    return minHeight;
+}
+
+/**
+ * Get the content that is being scrolled.
+ *
+ * @return the control displayed in the content area
+ */
+public Control getContent() {
+    //checkWidget();
+    return content;
+}
+
+void hScroll() {
+    if (content is null) return;
+    Point location = content.getLocation ();
+    ScrollBar hBar = getHorizontalBar ();
+    int hSelection = hBar.getSelection ();
+    content.setLocation (-hSelection, location.y);
+}
+bool needHScroll(Rectangle contentRect, bool vVisible) {
+    ScrollBar hBar = getHorizontalBar();
+    if (hBar is null) return false;
+
+    Rectangle hostRect = getBounds();
+    int border = getBorderWidth();
+    hostRect.width -= 2*border;
+    ScrollBar vBar = getVerticalBar();
+    if (vVisible && vBar !is null) hostRect.width -= vBar.getSize().x;
+
+    if (!expandHorizontal && contentRect.width > hostRect.width) return true;
+    if (expandHorizontal && minWidth > hostRect.width) return true;
+    return false;
+}
+
+bool needVScroll(Rectangle contentRect, bool hVisible) {
+    ScrollBar vBar = getVerticalBar();
+    if (vBar is null) return false;
+
+    Rectangle hostRect = getBounds();
+    int border = getBorderWidth();
+    hostRect.height -= 2*border;
+    ScrollBar hBar = getHorizontalBar();
+    if (hVisible && hBar !is null) hostRect.height -= hBar.getSize().y;
+
+    if (!expandVertical && contentRect.height > hostRect.height) return true;
+    if (expandVertical && minHeight > hostRect.height) return true;
+    return false;
+}
+
+/**
+ * Return the point in the content that currently appears in the top left
+ * corner of the scrolled composite.
+ *
+ * @return the point in the content that currently appears in the top left
+ * corner of the scrolled composite.  If no content has been set, this returns
+ * (0, 0).
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 Point getOrigin() {
+    checkWidget();
+    if (content is null) return new Point(0, 0);
+    Point location = content.getLocation();
+    return new Point(-location.x, -location.y);
+}
+/**
+ * Scrolls the content so that the specified point in the content is in the top
+ * left corner.  If no content has been set, nothing will occur.
+ *
+ * Negative values will be ignored.  Values greater than the maximum scroll
+ * distance will result in scrolling to the end of the scrollbar.
+ *
+ * @param origin the point on the content to appear in the top left corner
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ *    <li>ERROR_INVALID_ARGUMENT - value of origin is outside of content
+ * </ul>
+ * @since 2.0
+ */
+public void setOrigin(Point origin) {
+    setOrigin(origin.x, origin.y);
+}
+/**
+ * Scrolls the content so that the specified point in the content is in the top
+ * left corner.  If no content has been set, nothing will occur.
+ *
+ * Negative values will be ignored.  Values greater than the maximum scroll
+ * distance will result in scrolling to the end of the scrollbar.
+ *
+ * @param x the x coordinate of the content to appear in the top left corner
+ *
+ * @param y the y coordinate of the content to appear in the top left corner
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setOrigin(int x, int y) {
+    checkWidget();
+    if (content is null) return;
+    ScrollBar hBar = getHorizontalBar ();
+    if (hBar !is null) {
+        hBar.setSelection(x);
+        x = -hBar.getSelection ();
+    } else {
+        x = 0;
+    }
+    ScrollBar vBar = getVerticalBar ();
+    if (vBar !is null) {
+        vBar.setSelection(y);
+        y = -vBar.getSelection ();
+    } else {
+        y = 0;
+    }
+    content.setLocation(x, y);
+}
+/**
+ * Set the Always Show Scrollbars flag.  True if the scrollbars are
+ * always shown even if they are not required.  False if the scrollbars are only
+ * visible when some part of the composite needs to be scrolled to be seen.
+ * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
+ * horizontal and vertical directions.
+ *
+ * @param show true to show the scrollbars even when not required, false to show scrollbars only when required
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setAlwaysShowScrollBars(bool show) {
+    checkWidget();
+    if (show is alwaysShowScroll) return;
+    alwaysShowScroll = show;
+    ScrollBar hBar = getHorizontalBar ();
+    if (hBar !is null && alwaysShowScroll) hBar.setVisible(true);
+    ScrollBar vBar = getVerticalBar ();
+    if (vBar !is null && alwaysShowScroll) vBar.setVisible(true);
+    layout(false);
+}
+
+/**
+ * Set the content that will be scrolled.
+ *
+ * @param content the control to be displayed in the content area
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setContent(Control content) {
+    checkWidget();
+    if (this.content !is null && !this.content.isDisposed()) {
+        this.content.removeListener(DWT.Resize, contentListener);
+        this.content.setBounds(new Rectangle(-200, -200, 0, 0));
+    }
+
+    this.content = content;
+    ScrollBar vBar = getVerticalBar ();
+    ScrollBar hBar = getHorizontalBar ();
+    if (this.content !is null) {
+        if (vBar !is null) {
+            vBar.setMaximum (0);
+            vBar.setThumb (0);
+            vBar.setSelection(0);
+        }
+        if (hBar !is null) {
+            hBar.setMaximum (0);
+            hBar.setThumb (0);
+            hBar.setSelection(0);
+        }
+        content.setLocation(0, 0);
+        layout(false);
+        this.content.addListener(DWT.Resize, contentListener);
+    } else {
+        if (hBar !is null) hBar.setVisible(alwaysShowScroll);
+        if (vBar !is null) vBar.setVisible(alwaysShowScroll);
+    }
+}
+/**
+ * Configure the ScrolledComposite to resize the content object to be as wide as the
+ * ScrolledComposite when the width of the ScrolledComposite is greater than the
+ * minimum width specified in setMinWidth.  If the ScrolledComposite is less than the
+ * minimum width, the content will not be resized and instead the horizontal scroll bar will be
+ * used to view the entire width.
+ * If expand is false, this behaviour is turned off.  By default, this behaviour is turned off.
+ *
+ * @param expand true to expand the content control to fill available horizontal space
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setExpandHorizontal(bool expand) {
+    checkWidget();
+    if (expand is expandHorizontal) return;
+    expandHorizontal = expand;
+    layout(false);
+}
+/**
+ * Configure the ScrolledComposite to resize the content object to be as tall as the
+ * ScrolledComposite when the height of the ScrolledComposite is greater than the
+ * minimum height specified in setMinHeight.  If the ScrolledComposite is less than the
+ * minimum height, the content will not be resized and instead the vertical scroll bar will be
+ * used to view the entire height.
+ * If expand is false, this behaviour is turned off.  By default, this behaviour is turned off.
+ *
+ * @param expand true to expand the content control to fill available vertical space
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setExpandVertical(bool expand) {
+    checkWidget();
+    if (expand is expandVertical) return;
+    expandVertical = expand;
+    layout(false);
+}
+/**
+ * Sets the layout which is associated with the receiver to be
+ * the argument which may be null.
+ * <p>
+ * Note: No Layout can be set on this Control because it already
+ * manages the size and position of its children.
+ * </p>
+ *
+ * @param layout the receiver's new layout or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setLayout (Layout layout) {
+    checkWidget();
+    return;
+}
+/**
+ * Specify the minimum height at which the ScrolledComposite will begin scrolling the
+ * content with the vertical scroll bar.  This value is only relevant if
+ * setExpandVertical(true) has been set.
+ *
+ * @param height the minimum height or 0 for default height
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setMinHeight(int height) {
+    setMinSize(minWidth, height);
+}
+/**
+ * Specify the minimum width and height at which the ScrolledComposite will begin scrolling the
+ * content with the horizontal scroll bar.  This value is only relevant if
+ * setExpandHorizontal(true) and setExpandVertical(true) have been set.
+ *
+ * @param size the minimum size or null for the default size
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setMinSize(Point size) {
+    if (size is null) {
+        setMinSize(0, 0);
+    } else {
+        setMinSize(size.x, size.y);
+    }
+}
+/**
+ * Specify the minimum width and height at which the ScrolledComposite will begin scrolling the
+ * content with the horizontal scroll bar.  This value is only relevant if
+ * setExpandHorizontal(true) and setExpandVertical(true) have been set.
+ *
+ * @param width the minimum width or 0 for default width
+ * @param height the minimum height or 0 for default height
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setMinSize(int width, int height) {
+    checkWidget();
+    if (width is minWidth && height is minHeight) return;
+    minWidth = Math.max(0, width);
+    minHeight = Math.max(0, height);
+    layout(false);
+}
+/**
+ * Specify the minimum width at which the ScrolledComposite will begin scrolling the
+ * content with the horizontal scroll bar.  This value is only relevant if
+ * setExpandHorizontal(true) has been set.
+ *
+ * @param width the minimum width or 0 for default width
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setMinWidth(int width) {
+    setMinSize(width, minHeight);
+}
+
+void vScroll() {
+    if (content is null) return;
+    Point location = content.getLocation ();
+    ScrollBar vBar = getVerticalBar ();
+    int vSelection = vBar.getSelection ();
+    content.setLocation (location.x, -vSelection);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ScrolledCompositeLayout.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.ScrolledCompositeLayout;
+
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Layout;
+import dwt.widgets.ScrollBar;
+import dwt.custom.ScrolledComposite;
+
+import Math = tango.math.Math;
+
+/**
+ * This class provides the layout for ScrolledComposite
+ *
+ * @see ScrolledComposite
+ */
+class ScrolledCompositeLayout : Layout {
+
+    bool inLayout = false;
+    static final int DEFAULT_WIDTH  = 64;
+    static final int DEFAULT_HEIGHT = 64;
+
+protected override Point computeSize(Composite composite, int wHint, int hHint, bool flushCache) {
+    ScrolledComposite sc = cast(ScrolledComposite)composite;
+    Point size = new Point(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+    if (sc.content !is null) {
+        Point preferredSize = sc.content.computeSize(wHint, hHint, flushCache);
+        Point currentSize = sc.content.getSize();
+        size.x = sc.getExpandHorizontal() ? preferredSize.x : currentSize.x;
+        size.y = sc.getExpandVertical() ? preferredSize.y : currentSize.y;
+    }
+    size.x = Math.max(size.x, sc.minWidth);
+    size.y = Math.max(size.y, sc.minHeight);
+    if (wHint !is DWT.DEFAULT) size.x = wHint;
+    if (hHint !is DWT.DEFAULT) size.y = hHint;
+    return size;
+}
+
+protected override bool flushCache(Control control) {
+    return true;
+}
+
+protected override void layout(Composite composite, bool flushCache) {
+    if (inLayout) return;
+    ScrolledComposite sc = cast(ScrolledComposite)composite;
+    if (sc.content is null) return;
+    ScrollBar hBar = sc.getHorizontalBar();
+    ScrollBar vBar = sc.getVerticalBar();
+    if (hBar !is null) {
+        if (hBar.getSize().y >= sc.getSize().y) {
+            return;
+        }
+    }
+    if (vBar !is null) {
+        if (vBar.getSize().x >= sc.getSize().x) {
+            return;
+        }
+    }
+    inLayout = true;
+    Rectangle contentRect = sc.content.getBounds();
+    if (!sc.alwaysShowScroll) {
+        bool hVisible = sc.needHScroll(contentRect, false);
+        bool vVisible = sc.needVScroll(contentRect, hVisible);
+        if (!hVisible && vVisible) hVisible = sc.needHScroll(contentRect, vVisible);
+        if (hBar !is null) hBar.setVisible(hVisible);
+        if (vBar !is null) vBar.setVisible(vVisible);
+    }
+    Rectangle hostRect = sc.getClientArea();
+    if (sc.expandHorizontal) {
+        contentRect.width = Math.max(sc.minWidth, hostRect.width);
+    }
+    if (sc.expandVertical) {
+        contentRect.height = Math.max(sc.minHeight, hostRect.height);
+    }
+
+    if (hBar !is null) {
+        hBar.setMaximum (contentRect.width);
+        hBar.setThumb (Math.min (contentRect.width, hostRect.width));
+        int hPage = contentRect.width - hostRect.width;
+        int hSelection = hBar.getSelection ();
+        if (hSelection >= hPage) {
+            if (hPage <= 0) {
+                hSelection = 0;
+                hBar.setSelection(0);
+            }
+            contentRect.x = -hSelection;
+        }
+    }
+
+    if (vBar !is null) {
+        vBar.setMaximum (contentRect.height);
+        vBar.setThumb (Math.min (contentRect.height, hostRect.height));
+        int vPage = contentRect.height - hostRect.height;
+        int vSelection = vBar.getSelection ();
+        if (vSelection >= vPage) {
+            if (vPage <= 0) {
+                vSelection = 0;
+                vBar.setSelection(0);
+            }
+            contentRect.y = -vSelection;
+        }
+    }
+
+    sc.content.setBounds (contentRect);
+    inLayout = false;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StackLayout.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.StackLayout;
+
+
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Layout;
+
+import tango.util.Convert;
+static import tango.text.Util;
+import dwt.dwthelper.utils;
+
+/**
+ * This Layout stacks all the controls one on top of the other and resizes all controls
+ * to have the same size and location.
+ * The control specified in topControl is visible and all other controls are not visible.
+ * Users must set the topControl value to flip between the visible items and then call
+ * layout() on the composite which has the StackLayout.
+ *
+ * <p> Here is an example which places ten buttons in a stack layout and
+ * flips between them:
+ *
+ * <pre><code>
+ *  public static void main(String[] args) {
+ *      Display display = new Display();
+ *      Shell shell = new Shell(display);
+ *      shell.setLayout(new GridLayout());
+ *
+ *      final Composite parent = new Composite(shell, DWT.NONE);
+ *      parent.setLayoutData(new GridData(GridData.FILL_BOTH));
+ *      final StackLayout layout = new StackLayout();
+ *      parent.setLayout(layout);
+ *      final Button[] bArray = new Button[10];
+ *      for (int i = 0; i &lt; 10; i++) {
+ *          bArray[i] = new Button(parent, DWT.PUSH);
+ *          bArray[i].setText("Button "+i);
+ *      }
+ *      layout.topControl = bArray[0];
+ *
+ *      Button b = new Button(shell, DWT.PUSH);
+ *      b.setText("Show Next Button");
+ *      final int[] index = new int[1];
+ *      b.addListener(DWT.Selection, new Listener(){
+ *          public void handleEvent(Event e) {
+ *              index[0] = (index[0] + 1) % 10;
+ *              layout.topControl = bArray[index[0]];
+ *              parent.layout();
+ *          }
+ *      });
+ *
+ *      shell.open();
+ *      while (shell !is null && !shell.isDisposed()) {
+ *          if (!display.readAndDispatch())
+ *              display.sleep();
+ *      }
+ *  }
+ * </code></pre>
+ */
+
+public class StackLayout : Layout {
+
+    /**
+     * marginWidth specifies the number of pixels of horizontal margin
+     * that will be placed along the left and right edges of the layout.
+     *
+     * The default value is 0.
+     */
+    public int marginWidth = 0;
+    /**
+     * marginHeight specifies the number of pixels of vertical margin
+     * that will be placed along the top and bottom edges of the layout.
+     *
+     * The default value is 0.
+     */
+    public int marginHeight = 0;
+
+    /**
+     * topControl the Control that is displayed at the top of the stack.
+     * All other controls that are children of the parent composite will not be visible.
+     */
+    public Control topControl;
+
+protected override Point computeSize(Composite composite, int wHint, int hHint, bool flushCache) {
+    Control children[] = composite.getChildren();
+    int maxWidth = 0;
+    int maxHeight = 0;
+    for (int i = 0; i < children.length; i++) {
+        Point size = children[i].computeSize(wHint, hHint, flushCache);
+        maxWidth = Math.max(size.x, maxWidth);
+        maxHeight = Math.max(size.y, maxHeight);
+    }
+    int width = maxWidth + 2 * marginWidth;
+    int height = maxHeight + 2 * marginHeight;
+    if (wHint !is DWT.DEFAULT) width = wHint;
+    if (hHint !is DWT.DEFAULT) height = hHint;
+    return new Point(width, height);
+}
+
+protected override bool flushCache(Control control) {
+    return true;
+}
+
+protected override void layout(Composite composite, bool flushCache) {
+    Control children[] = composite.getChildren();
+    Rectangle rect = composite.getClientArea();
+    rect.x += marginWidth;
+    rect.y += marginHeight;
+    rect.width -= 2 * marginWidth;
+    rect.height -= 2 * marginHeight;
+    for (int i = 0; i < children.length; i++) {
+        children[i].setBounds(rect);
+        children[i].setVisible(children[i] is topControl);
+
+    }
+}
+
+char[] getName () {
+    char[] string = this.classinfo.name;
+    int index = tango.text.Util.locatePrior( string ,'.');
+    if (index is string.length ) return string;
+    return string[ index + 1 .. $ ];
+}
+
+/**
+ * Returns a string containing a concise, human-readable
+ * description of the receiver.
+ *
+ * @return a string representation of the layout
+ */
+public override char[] toString () {
+    char[] string = getName ()~" {";
+    if (marginWidth !is 0) string ~= "marginWidth="~to!(char[])(marginWidth)~" ";
+    if (marginHeight !is 0) string ~= "marginHeight="~to!(char[])(marginHeight)~" ";
+    if (topControl !is null) string ~= "topControl="~to!(char[])(topControl)~" ";
+    string = tango.text.Util.trim(string);
+    string ~= "}";
+    return string;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyleRange.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.StyleRange;
+
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.TextStyle;
+import dwt.internal.CloneableCompatibility;
+import dwt.custom.StyleRange;
+import dwt.custom.TextChangedEvent;
+import dwt.custom.TextChangingEvent;
+
+static import tango.text.Text;
+alias tango.text.Text.Text!(char) StringBuffer;
+
+public class StyleRange : TextStyle, CloneableCompatibility {
+
+    /**
+     * the start offset of the range, zero-based from the document start
+     */
+    public int start;
+
+    /**
+     * the length of the range
+     */
+    public int length;
+
+    /**
+     * the font style of the range. It may be a combination of
+     * DWT.NORMAL, DWT.ITALIC or DWT.BOLD
+     *
+     * Note: the font style is not used if the <code>font</code> attribute
+     * is set
+     */
+    public int fontStyle = DWT.NORMAL;
+
+/**
+ * Create a new style range with no styles
+ *
+ * @since 3.2
+ */
+public this() {
+    super(null, null, null);
+}
+
+/**
+ * Create a new style range.
+ *
+ * @param start start offset of the style
+ * @param length length of the style
+ * @param foreground foreground color of the style, null if none
+ * @param background background color of the style, null if none
+ */
+public this(int start, int length, Color foreground, Color background) {
+    super(null, foreground, background);
+    this.start = start;
+    this.length = length;
+}
+
+/**
+ * Create a new style range.
+ *
+ * @param start start offset of the style
+ * @param length length of the style
+ * @param foreground foreground color of the style, null if none
+ * @param background background color of the style, null if none
+ * @param fontStyle font style of the style, may be DWT.NORMAL, DWT.ITALIC or DWT.BOLD
+ */
+public this(int start, int length, Color foreground, Color background, int fontStyle) {
+    this(start, length, foreground, background);
+    this.fontStyle = fontStyle;
+}
+
+/**
+ * Compares the argument to the receiver, and returns true
+ * if they represent the <em>same</em> object using a class
+ * specific comparison.
+ *
+ * @param object the object to compare with this object
+ * @return <code>true</code> if the object is the same as this object and <code>false</code> otherwise
+ *
+ * @see #hashCode()
+ */
+public override int opEquals(Object object) {
+    if (object is this) return true;
+    if (auto style = cast(StyleRange) object ) {
+        if (start !is style.start) return false;
+        if (length !is style.length) return false;
+        return similarTo(style);
+    }
+    return false;
+}
+
+/**
+ * Returns an integer hash code for the receiver. Any two
+ * objects that return <code>true</code> when passed to
+ * <code>equals</code> must return the same value for this
+ * method.
+ *
+ * @return the receiver's hash
+ *
+ * @see #equals(Object)
+ */
+public override hash_t toHash() {
+    return super.toHash() ^ fontStyle;
+}
+bool isVariableHeight() {
+    return font !is null || metrics !is null || rise !is 0;
+}
+/**
+ * Returns whether or not the receiver is unstyled (i.e., does not have any
+ * style attributes specified).
+ *
+ * @return true if the receiver is unstyled, false otherwise.
+ */
+public bool isUnstyled() {
+    if (font !is null) return false;
+    if (rise !is 0) return false;
+    if (metrics !is null) return false;
+    if (foreground !is null) return false;
+    if (background !is null) return false;
+    if (fontStyle !is DWT.NORMAL) return false;
+    if (underline) return false;
+    if (strikeout) return false;
+    return true;
+}
+
+/**
+ * Compares the specified object to this StyleRange and answer if the two
+ * are similar. The object must be an instance of StyleRange and have the
+ * same field values for except for start and length.
+ *
+ * @param style the object to compare with this object
+ * @return true if the objects are similar, false otherwise
+ */
+public bool similarTo(StyleRange style) {
+    if (!super.opEquals(style)) return false;
+    if (fontStyle !is style.fontStyle) return false;
+    return true;
+}
+
+/**
+ * Returns a new StyleRange with the same values as this StyleRange.
+ *
+ * @return a shallow copy of this StyleRange
+ */
+public /+override+/ Object clone() {
+    return new StyleRange( start, length, foreground, background, fontStyle );
+}
+
+/**
+ * Returns a string containing a concise, human-readable
+ * description of the receiver.
+ *
+ * @return a string representation of the StyleRange
+ */
+public override char[] toString() {
+    StringBuffer buffer = new StringBuffer();
+    buffer.append("StyleRange {");
+    buffer.append(start);
+    buffer.append(", ");
+    buffer.append(length);
+    buffer.append(", fontStyle=");
+    switch (fontStyle) {
+        case DWT.BOLD:
+            buffer.append("bold");
+            break;
+        case DWT.ITALIC:
+            buffer.append("italic");
+            break;
+        case DWT.BOLD | DWT.ITALIC:
+            buffer.append("bold-italic");
+            break;
+        default:
+            buffer.append("normal");
+    }
+    char[] str = super.toString();
+    int index = tango.text.Util.locate( str, '{');
+    if( index is str.length ) index = -1;
+    str = str[ index + 1 .. $ ];
+    if (str.length > 1) buffer.append(", ");
+    buffer.append(str);
+    return buffer.toString();
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyledText.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,8316 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.StyledText;
+
+
+import dwt.DWT;
+import dwt.DWTError;
+import dwt.DWTException;
+import dwt.accessibility.ACC;
+import dwt.accessibility.Accessible;
+import dwt.accessibility.AccessibleAdapter;
+import dwt.accessibility.AccessibleControlAdapter;
+import dwt.accessibility.AccessibleControlEvent;
+import dwt.accessibility.AccessibleEvent;
+import dwt.accessibility.AccessibleTextAdapter;
+import dwt.accessibility.AccessibleTextEvent;
+import dwt.dnd.Clipboard;
+import dwt.dnd.DND;
+import dwt.dnd.RTFTransfer;
+import dwt.dnd.TextTransfer;
+import dwt.dnd.Transfer;
+import dwt.events.ModifyListener;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.events.VerifyListener;
+import dwt.graphics.Color;
+import dwt.graphics.Cursor;
+import dwt.graphics.Font;
+import dwt.graphics.FontData;
+import dwt.graphics.FontMetrics;
+import dwt.graphics.GC;
+import dwt.graphics.GlyphMetrics;
+import dwt.graphics.Image;
+import dwt.graphics.Device;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.graphics.Resource;
+import dwt.graphics.TextLayout;
+import dwt.internal.BidiUtil;
+import dwt.internal.Compatibility;
+import dwt.printing.Printer;
+import dwt.printing.PrinterData;
+import dwt.widgets.Canvas;
+import dwt.widgets.Caret;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Label;
+import dwt.widgets.Listener;
+import dwt.widgets.ScrollBar;
+import dwt.widgets.TypedListener;
+import dwt.custom.StyledTextContent;
+import dwt.custom.TextChangeListener;
+import dwt.custom.StyledTextRenderer;
+import dwt.custom.StyledTextPrintOptions;
+import dwt.custom.ExtendedModifyListener;
+import dwt.custom.BidiSegmentListener;
+import dwt.custom.LineBackgroundListener;
+import dwt.custom.LineStyleListener;
+import dwt.custom.PaintObjectListener;
+import dwt.custom.VerifyKeyListener;
+import dwt.custom.MovementListener;
+import dwt.custom.Bullet;
+import dwt.custom.StyledTextEvent;
+import dwt.custom.StyleRange;
+import dwt.custom.TextChangedEvent;
+import dwt.custom.TextChangingEvent;
+import dwt.custom.DefaultContent;
+import dwt.custom.StyledTextDropTargetEffect;
+import dwt.custom.StyledTextListener;
+import dwt.custom.ST;
+import dwt.dwthelper.Runnable;
+
+static import tango.text.Text;
+static import tango.text.Util;
+static import tango.io.FileConst;
+static import tango.text.convert.Utf;
+import tango.util.Convert;
+import dwt.dwthelper.utils;
+
+alias tango.text.Text.Text!(char) StringBuffer;
+
+/**
+ * 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>
+ */
+public class StyledText : Canvas {
+
+    alias Canvas.computeSize computeSize;
+
+    static const char TAB = '\t';
+    static const char[] PlatformLineDelimiter = tango.io.FileConst.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 = DWT.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 = DWT.NULL;
+    Caret defaultCaret = null;
+    bool updateCaretDirection = true;
+    bool fixedLineHeight;
+    bool dragDetect_ = true;
+
+    int alignment;
+    bool justify;
+    int indent;
+    int lineSpacing;
+
+    const static bool IS_CARBON, IS_GTK, IS_MOTIF;
+    static this(){
+        char[] platform = DWT.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() & DWT.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++) {
+                char[] 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 = DWT.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(), DWT.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 ? DWT.RIGHT_TO_LEFT : DWT.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() & (DWT.RIGHT_TO_LEFT | DWT.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 count = endLine - startLine + 1;
+            StringBuffer buffer = new StringBuffer("0");
+            while ((count /= 10) > 0) buffer.append("0");
+            printLayout.setText(buffer.toString());
+            int numberingWidth = printLayout.getBounds().width + 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(paintX, paintY, width, height);
+                    printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
+                    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(paintX, clientArea.y, width, layoutHeight - height);
+                        printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
+                        paintY += layoutHeight;
+                    }
+                    gc.setClipping(cast(Rectangle)null);
+                }
+            }
+            printerRenderer.disposeTextLayout(layout);
+        }
+        if (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) {
+        char[] text = header ? printOptions.header : printOptions.footer;
+        if (text is null) return;
+        int lastSegmentIndex = 0;
+        for (int i = 0; i < 3; i++) {
+            int segmentIndex = tango.text.Util.locatePattern( text, StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
+            char[] segment;
+            if (segmentIndex is text.length ) {
+                segment = text[ lastSegmentIndex .. $ ].dup;
+                printDecorationSegment(segment, i, page, header, layout);
+                break;
+            } else {
+                segment = text[ lastSegmentIndex .. segmentIndex ].dup;
+                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(char[] segment, int alignment, int page, bool header, TextLayout layout) {
+        int pageIndex = tango.text.Util.locatePattern( segment, StyledTextPrintOptions.PAGE_TAG );
+        if (pageIndex !is segment.length ) {
+            int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length;
+            StringBuffer buffer = new StringBuffer(segment[ 0 .. pageIndex ]);
+            buffer.append (page);
+            buffer.append (segment[ 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.getDescent());
+            printLayout.setDescent(metrics.getDescent());
+            printLayout.setText( to!(char[])(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() {
+        char[] 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 char[] Win95 = "windows 95";
+//         const char[] Win98 = "windows 98";
+//         const char[] WinME = "windows me";
+//         const char[] WinNT = "windows nt";
+//         char[] osName = System.getProperty("os.name").toLowerCase();
+//         char[] 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(char[] string, int start, int end) {
+        wchar[] wstring = tango.text.convert.Utf.toString16( string[ start .. end ] );
+        start = 0;
+        end = wstring.length;
+        for (int index = start; index < end; index++) {
+            wchar ch = wstring[index];
+            if (ch > 0xFF && WriteUnicode) {
+                // write the sub string from the last escaped character
+                // to the current one. Fixes bug 21698.
+                if (index > start) {
+                    write(tango.text.convert.Utf.toString(wstring[start .. index ]));
+                }
+                write("\\u");
+                write( to!(char[])( cast(short)ch ));
+                write(' ');                     // control word delimiter
+                start = index + 1;
+            } 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(tango.text.convert.Utf.toString(wstring[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(tango.text.convert.Utf.toString(wstring[ 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: char[] cpg = System.getProperty("file.encoding").toLowerCase();
+        char[] 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 DWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public override void writeLine(char[] line, int lineOffset) {
+        if (isClosed()) {
+            DWT.error(DWT.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 DWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public override void writeLineDelimiter(char[] lineDelimiter) {
+        if (isClosed()) {
+            DWT.error(DWT.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(char[] 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 DWT.LEFT: write("\\ql"); break;
+            case DWT.CENTER: write("\\qc"); break;
+            case DWT.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 & DWT.BOLD) !is 0) {
+                    write("\\b");
+                }
+                if ((style.fontStyle & DWT.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 & DWT.BOLD) !is 0) {
+                    write("\\b0");
+                }
+                if ((style.fontStyle & DWT.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 char[] toString() {
+        return buffer.toString();
+    }
+    /**
+     * Appends the given string to the data.
+     */
+    void write(char[] 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(char[] string, int offset) {
+        if (offset < 0 || offset > buffer.length()) {
+            return;
+        }
+        buffer.select( offset );
+        buffer.prepend( 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 DWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public void writeLine(char[] line, int lineOffset) {
+        if (isClosed_) {
+            DWT.error(DWT.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[lineIndex .. copyEnd]);
+        }
+    }
+    /**
+     * Appends the specified line delimiter to the data.
+     *
+     * @param lineDelimiter line delimiter to write
+     * @exception DWTException <ul>
+     *   <li>ERROR_IO when the writer is closed.</li>
+     * </ul>
+     */
+    public void writeLineDelimiter(char[] lineDelimiter) {
+        if (isClosed_) {
+            DWT.error(DWT.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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#FULL_SELECTION
+ * @see DWT#MULTI
+ * @see DWT#READ_ONLY
+ * @see DWT#SINGLE
+ * @see DWT#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() & DWT.MIRRORED) !is 0;
+    fixedLineHeight = true;
+    if ((style & DWT.READ_ONLY) !is 0) {
+        setEditable(false);
+    }
+    leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
+    if ((style & DWT.SINGLE) !is 0 && (style & DWT.BORDER) !is 0) {
+        leftMargin = topMargin = rightMargin = bottomMargin = 2;
+    }
+    alignment = style & (DWT.LEFT | DWT.RIGHT | DWT.CENTER);
+    if (alignment is 0) alignment = DWT.LEFT;
+    clipboard = new Clipboard(display);
+    installDefaultContent();
+    renderer = new StyledTextRenderer(getDisplay(), this);
+    renderer.setContent(content);
+    renderer.setFont(getFont(), tabLength);
+    defaultCaret = new Caret(this, DWT.NULL);
+    if ((style & DWT.WRAP) !is 0) {
+        setWordWrap(true);
+    }
+    if (isBidiCaret()) {
+        createCaretBitmaps();
+        Runnable runnable = new class() Runnable {
+            public void run() {
+                int direction = BidiUtil.getKeyboardLanguage() is BidiUtil.KEYBOARD_BIDI ? DWT.RIGHT : DWT.LEFT;
+                if (direction is caretDirection) return;
+                if (getCaret() !is defaultCaret) return;
+                Point newCaretPos = getPointAtOffset(caretOffset);
+                setCaretLocation(newCaretPos, direction);
+            }
+        };
+        BidiUtil.addLanguageListener(handle, runnable);
+    }
+    setCaret(defaultCaret);
+    calculateScrollBars();
+    createKeyBindings();
+    setCursor(display.getSystemCursor(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    addListener(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    addListener(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    addListener(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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,char[])
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void append(char[] string) {
+    checkWidget();
+    if (string is null) {
+        DWT.error(DWT.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 & DWT.SINGLE) !is 0) {
+        style &= ~(DWT.H_SCROLL | DWT.V_SCROLL | DWT.WRAP | DWT.MULTI);
+    } else {
+        style |= DWT.MULTI;
+        if ((style & DWT.WRAP) !is 0) {
+            style &= ~DWT.H_SCROLL;
+        }
+    }
+    style |= DWT.NO_REDRAW_RESIZE | DWT.DOUBLE_BUFFERED | DWT.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() & DWT.SINGLE) !is 0 ? 1 : content.getLineCount();
+    int width = 0;
+    int height = 0;
+    if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) {
+        Display display = getDisplay();
+        int maxHeight = display.getClientArea().height;
+        for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
+            TextLayout layout = renderer.getTextLayout(lineIndex);
+            if (wordWrap) layout.setWidth(wHint is 0 ? 1 : wHint);
+            Rectangle rect = layout.getBounds();
+            height += rect.height;
+            width = Math.max(width, rect.width);
+            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 DWT.DEFAULT) width = wHint;
+    if (hHint !is DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 (DWTError 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWT.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 DWT.SINGLE style.
+ */
+char[] getModelDelimitedText(char[] 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);
+    char[] delimiter = getLineDelimiter();
+    while (i < length) {
+        if (crIndex !is -1) {
+            crIndex = tango.text.Util.locate( text, DWT.CR, i);
+            if( crIndex is text.length ) crIndex = -1;
+        }
+        if (lfIndex !is -1) {
+            lfIndex = tango.text.Util.locate( text, DWT.LF, i);
+            if( lfIndex is text.length ) lfIndex = -1;
+        }
+        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[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[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[i .. $]);
+    }
+    return convertedText.toString();
+}
+bool checkDragDetect(Event event) {
+    if (!isListening(DWT.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);
+    if (offset > selection.x && offset < selection.y) {
+        return dragDetect(event);
+    }
+    return false;
+}
+/**
+ * Creates default key bindings.
+ */
+void createKeyBindings() {
+    int nextKey = isMirrored() ? DWT.ARROW_LEFT : DWT.ARROW_RIGHT;
+    int previousKey = isMirrored() ? DWT.ARROW_RIGHT : DWT.ARROW_LEFT;
+
+    // Navigation
+    setKeyBinding(DWT.ARROW_UP, ST.LINE_UP);
+    setKeyBinding(DWT.ARROW_DOWN, ST.LINE_DOWN);
+    if (IS_CARBON) {
+        setKeyBinding(previousKey | DWT.MOD1, ST.LINE_START);
+        setKeyBinding(nextKey | DWT.MOD1, ST.LINE_END);
+        setKeyBinding(DWT.HOME, ST.TEXT_START);
+        setKeyBinding(DWT.END, ST.TEXT_END);
+        setKeyBinding(DWT.ARROW_UP | DWT.MOD1, ST.TEXT_START);
+        setKeyBinding(DWT.ARROW_DOWN | DWT.MOD1, ST.TEXT_END);
+        setKeyBinding(nextKey | DWT.MOD3, ST.WORD_NEXT);
+        setKeyBinding(previousKey | DWT.MOD3, ST.WORD_PREVIOUS);
+    } else {
+        setKeyBinding(DWT.HOME, ST.LINE_START);
+        setKeyBinding(DWT.END, ST.LINE_END);
+        setKeyBinding(DWT.HOME | DWT.MOD1, ST.TEXT_START);
+        setKeyBinding(DWT.END | DWT.MOD1, ST.TEXT_END);
+        setKeyBinding(nextKey | DWT.MOD1, ST.WORD_NEXT);
+        setKeyBinding(previousKey | DWT.MOD1, ST.WORD_PREVIOUS);
+    }
+    setKeyBinding(DWT.PAGE_UP, ST.PAGE_UP);
+    setKeyBinding(DWT.PAGE_DOWN, ST.PAGE_DOWN);
+    setKeyBinding(DWT.PAGE_UP | DWT.MOD1, ST.WINDOW_START);
+    setKeyBinding(DWT.PAGE_DOWN | DWT.MOD1, ST.WINDOW_END);
+    setKeyBinding(nextKey, ST.COLUMN_NEXT);
+    setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
+
+    // Selection
+    setKeyBinding(DWT.ARROW_UP | DWT.MOD2, ST.SELECT_LINE_UP);
+    setKeyBinding(DWT.ARROW_DOWN | DWT.MOD2, ST.SELECT_LINE_DOWN);
+    if (IS_CARBON) {
+        setKeyBinding(previousKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_LINE_START);
+        setKeyBinding(nextKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_LINE_END);
+        setKeyBinding(DWT.HOME | DWT.MOD2, ST.SELECT_TEXT_START);
+        setKeyBinding(DWT.END | DWT.MOD2, ST.SELECT_TEXT_END);
+        setKeyBinding(DWT.ARROW_UP | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_START);
+        setKeyBinding(DWT.ARROW_DOWN | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_END);
+        setKeyBinding(nextKey | DWT.MOD2 | DWT.MOD3, ST.SELECT_WORD_NEXT);
+        setKeyBinding(previousKey | DWT.MOD2 | DWT.MOD3, ST.SELECT_WORD_PREVIOUS);
+    } else  {
+        setKeyBinding(DWT.HOME | DWT.MOD2, ST.SELECT_LINE_START);
+        setKeyBinding(DWT.END | DWT.MOD2, ST.SELECT_LINE_END);
+        setKeyBinding(DWT.HOME | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_START);
+        setKeyBinding(DWT.END | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_END);
+        setKeyBinding(nextKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_WORD_NEXT);
+        setKeyBinding(previousKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_WORD_PREVIOUS);
+    }
+    setKeyBinding(DWT.PAGE_UP | DWT.MOD2, ST.SELECT_PAGE_UP);
+    setKeyBinding(DWT.PAGE_DOWN | DWT.MOD2, ST.SELECT_PAGE_DOWN);
+    setKeyBinding(DWT.PAGE_UP | DWT.MOD1 | DWT.MOD2, ST.SELECT_WINDOW_START);
+    setKeyBinding(DWT.PAGE_DOWN | DWT.MOD1 | DWT.MOD2, ST.SELECT_WINDOW_END);
+    setKeyBinding(nextKey | DWT.MOD2, ST.SELECT_COLUMN_NEXT);
+    setKeyBinding(previousKey | DWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
+
+    // Modification
+    // Cut, Copy, Paste
+    setKeyBinding('X' | DWT.MOD1, ST.CUT);
+    setKeyBinding('C' | DWT.MOD1, ST.COPY);
+    setKeyBinding('V' | DWT.MOD1, ST.PASTE);
+    if (IS_CARBON) {
+        setKeyBinding(DWT.DEL | DWT.MOD2, ST.DELETE_NEXT);
+        setKeyBinding(DWT.BS | DWT.MOD3, ST.DELETE_WORD_PREVIOUS);
+        setKeyBinding(DWT.DEL | DWT.MOD3, ST.DELETE_WORD_NEXT);
+    } else {
+        // Cut, Copy, Paste Wordstar style
+        setKeyBinding(DWT.DEL | DWT.MOD2, ST.CUT);
+        setKeyBinding(DWT.INSERT | DWT.MOD1, ST.COPY);
+        setKeyBinding(DWT.INSERT | DWT.MOD2, ST.PASTE);
+    }
+    setKeyBinding(DWT.BS | DWT.MOD2, ST.DELETE_PREVIOUS);
+    setKeyBinding(DWT.BS, ST.DELETE_PREVIOUS);
+    setKeyBinding(DWT.DEL, ST.DELETE_NEXT);
+    setKeyBinding(DWT.BS | DWT.MOD1, ST.DELETE_WORD_PREVIOUS);
+    setKeyBinding(DWT.DEL | DWT.MOD1, ST.DELETE_WORD_NEXT);
+
+    // Miscellaneous
+    setKeyBinding(DWT.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==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(DWT.COLOR_BLACK));
+    gc.fillRectangle(0, 0, caretWidth, lineHeight);
+    gc.setForeground(display.getSystemColor(DWT.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==defaultCaret.getImage()) {
+            defaultCaret.setImage(null);
+        }
+        rightCaretBitmap.dispose();
+    }
+    rightCaretBitmap = new Image(display, caretWidth, lineHeight);
+    gc = new GC (rightCaretBitmap);
+    gc.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
+    gc.fillRectangle(0, 0, caretWidth, lineHeight);
+    gc.setForeground(display.getSystemColor(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 (DWTError 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(DWT.DOWN, event.y - clientAreaHeight);
+    } else if (event.y < 0) {
+        doAutoScroll(DWT.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 DWT.UP, DWT.DOWN, DWT.COLUMN_NEXT, DWT.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 DWT.UP) {
+        timer = new class(disp) Runnable {
+            Display display;
+            this( Display d ){ this.display = d; }
+            public void run() {
+                if (autoScrollDirection is DWT.UP) {
+                    doSelectionPageUp(autoScrollDistance);
+                    display.timerExec(V_SCROLL_RATE, this);
+                }
+            }
+        };
+        autoScrollDirection = direction;
+        display.timerExec(V_SCROLL_RATE, timer);
+    } else if (direction is DWT.DOWN) {
+        timer = new class(disp) Runnable {
+            Display display;
+            this( Display d ){ this.display = d; }
+            public void run() {
+                if (autoScrollDirection is DWT.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) {
+            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, DWT.MOVEMENT_CHAR);
+            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(char key) {
+    if (textLimit > 0 &&
+        content.getCharCount() - (selection.y - selection.x) >= textLimit) {
+        return;
+    }
+    Event event = new Event();
+    event.start = selection.x;
+    event.end = selection.y;
+    // replace a CR line break with the widget line break
+    // CR does not make sense on Windows since most (all?) applications
+    // don't recognize CR as a line break.
+    if (key is DWT.CR || key is DWT.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);
+        char[] line = content.getLine(lineIndex);
+        // replace character at caret offset if the caret is not at the
+        // end of the line
+        if (event.end < lineOffset + line.length) {
+            event.end++;
+        }
+        event.text = [key];
+    } else {
+        event.text = [key];
+    }
+    if (event.text !is null) {
+        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, DWT.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, DWT.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 (clickCount > 1) {
+        // double click word select the previous/next word. fixes bug 15610
+        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 : 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 : 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, DWT.MOVEMENT_WORD_START);
+            } else {
+                newCaretOffset = content.getOffsetAtLine(line);
+            }
+        } else {
+            if (wordSelect) {
+                newCaretOffset = getWordNext(newCaretOffset, DWT.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, DWT.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, DWT.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, DWT.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 = DWT.NULL;
+}
+public override Color getBackground() {
+    checkWidget();
+    if (background is null) {
+        return getDisplay().getSystemColor(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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())) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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);
+    char[] line = content.getLine(lineIndex);
+    Rectangle bounds;
+    if (line.length !is 0) {
+        int offsetInLine = offset - content.getOffsetAtLine(lineIndex);
+        TextLayout layout = renderer.getTextLayout(lineIndex);
+        bounds = layout.getBounds(offsetInLine, offsetInLine);
+        renderer.disposeTextLayout(layout);
+    } else {
+        bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
+    }
+    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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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, DWT.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, DWT.MOVEMENT_CLUSTER);
+    offset += lineOffset;
+    renderer.disposeTextLayout(layout);
+    return offset;
+}
+/**
+ * Returns the content implementation that is used for text storage
+ * or null if no user defined content implementation has been set.
+ *
+ * @return content implementation that is used for text storage or null
+ * if no user defined content implementation has been set.
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 : 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWT.NULL if there is no action associated with the key.
+ *
+ * @param key a key code defined in DWT.java or a character.
+ *  Optionally ORd with a state mask.  Preferred state masks are one or more of
+ *  DWT.MOD1, DWT.MOD2, DWT.MOD3, since these masks account for modifier platform
+ *  differences.  However, there may be cases where using the specific state masks
+ *  (i.e., DWT.CTRL, DWT.SHIFT, DWT.ALT, DWT.COMMAND) makes sense.
+ * @return one of the predefined actions defined in ST.java or DWT.NULL
+ *  if there is no action associated with the key.
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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;
+    }
+}
+/**
+ * Gets the number of characters.
+ *
+ * @return number of characters in the widget
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 alignment of the line at the given index.
+ *
+ * @param index the index of the line
+ *
+ * @return the line alignment
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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, char[] line) {
+    return sendLineEvent(LineGetBackground, lineOffset, line);
+}
+/**
+ * Gets the number of text lines.
+ *
+ * @return the number of lines in the widget
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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())) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    return isListening(LineGetStyle) ? false : renderer.getLineJustify(index, justify);
+}
+/**
+ * Returns the line spacing of the widget.
+ *
+ * @return the line spacing
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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, char[] 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.
+ *
+ * @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.
+ *
+ * @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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 2.0
+ */
+public int getOffsetAtLine(int lineIndex) {
+    checkWidget();
+    if (lineIndex < 0 ||
+        (lineIndex > 0 && lineIndex >= content.getLineCount())) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    }
+    // is y above first line or is x before first column?
+    if (point.y + getVerticalScrollOffset() < 0 || point.x + horizontalScrollOffset < 0) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    int bottomIndex = getLineIndex(clientAreaHeight);
+    int height = getLinePixel(bottomIndex) + renderer.getLineHeight(bottomIndex);
+    if (point.y > height) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    int lineIndex = getLineIndex(point.y);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    TextLayout layout = renderer.getTextLayout(lineIndex);
+    int[] trailing = new int[1];
+    int x = point.x + horizontalScrollOffset - leftMargin ;
+    int y = point.y - getLinePixel(lineIndex);
+    int offsetInLine = layout.getOffset(x, y, trailing);
+    char[] line = content.getLine(lineIndex);
+    if (offsetInLine !is line.length - 1) {
+        offsetInLine = Math.min(line.length, offsetInLine + trailing[0]);
+    }
+    Rectangle rect = layout.getLineBounds(layout.getLineIndex(offsetInLine));
+    renderer.disposeTextLayout(layout);
+    if (x > rect.x + rect.width) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    return lineOffset + offsetInLine;
+}
+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[] trailing = new int[1];
+    int offsetInLine = layout.getOffset(x, y, trailing);
+    caretAlignment = OFFSET_LEADING;
+    if (trailing[0] !is 0) {
+        int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
+        //TODO handle bidi text
+        int lineStart = layout.getLineOffsets()[lineInParagraph];
+        if (offsetInLine + trailing[0] is lineStart) {
+            offsetInLine += trailing[0];
+            caretAlignment = PREVIOUS_OFFSET_TRAILING;
+        } else {
+            char[] 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);
+}
+/**
+ * Returns the orientation of the receiver.
+ *
+ * @return the orientation style
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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() ? DWT.RIGHT_TO_LEFT : DWT.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.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.
+ */
+char[] getPlatformDelimitedText(TextWriter writer) {
+    int end = writer.getStart() + writer.getCharCount();
+    int startLine = content.getLineAtOffset(writer.getStart());
+    int endLine = content.getLineAtOffset(end);
+    char[] 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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(DWT.COLOR_LIST_SELECTION);
+    }
+    return selectionBackground;
+}
+/**
+ * Gets the number of selected characters.
+ *
+ * @return the number of selected characters.
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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(DWT.COLOR_LIST_SELECTION_TEXT);
+    }
+    return selectionForeground;
+}
+/**
+ * Returns the selected text.
+ *
+ * @return selected text, or an empty String if there is no selection.
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] getSelectionText() {
+    checkWidget();
+    return content.getTextRange(selection.x, selection.y - selection.x);
+}
+public override int getStyle() {
+    int style = super.getStyle();
+    style &= ~(DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT | DWT.MIRRORED);
+    if (isMirrored()) {
+        style |= DWT.RIGHT_TO_LEFT | DWT.MIRRORED;
+    } else {
+        style |= DWT.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, char[] 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) {
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+        }
+        for (int i = 1; i < segmentCount; i++) {
+            if (event.segments[i] <= event.segments[i - 1] || event.segments[i] > lineLength) {
+                DWT.error(DWT.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(char[] 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] 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 char[]
+ * @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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] getText(int start, int end) {
+    checkWidget();
+    int contentLength = getCharCount();
+    if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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);
+        if (layout.getText().length > 0) {
+            if (i is lineStart && i is lineEnd) {
+                rect = layout.getBounds(start - lineOffset, end - lineOffset);
+            } else if (i is lineStart) {
+                char[] line = content.getLine(i);
+                rect = layout.getBounds(start - lineOffset, line.length);
+            } 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 char[] getTextRange(int start, int length) {
+    checkWidget();
+    int contentLength = getCharCount();
+    int end = start + length;
+    if (start > end || start < 0 || end > contentLength) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWT.DEFAULT;
+    if (!updateCaretDirection && caretDirection !is DWT.NULL) return caretDirection;
+    updateCaretDirection = false;
+    int caretLine = getCaretLine();
+    int lineOffset = content.getOffsetAtLine(caretLine);
+    char[] line = content.getLine(caretLine);
+    int offset = caretOffset - lineOffset;
+    int lineLength = line.length;
+    if (lineLength is 0) return isMirrored() ? DWT.RIGHT : DWT.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() ? DWT.RIGHT : DWT.LEFT;
+    }
+    TextLayout layout = renderer.getTextLayout(caretLine);
+    int level = layout.getLevel(offset);
+    renderer.disposeTextLayout(layout);
+    return ((level & 1) !is 0) ? DWT.RIGHT : DWT.LEFT;
+}
+/*
+ * Returns the index of the line the caret is on.
+ */
+int getCaretLine() {
+    return content.getLineAtOffset(caretOffset);
+}
+int getWrapWidth () {
+    if (wordWrap && !isSingleLine()) {
+        int width = clientAreaWidth - leftMargin - rightMargin;
+        return width > 0 ? width : 1;
+    }
+    return -1;
+}
+int getWordNext (int offset, int movement) {
+    int newOffset, lineOffset;
+    char[] 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;
+    char[] 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) {
+            char[] 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);
+    char[] 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) {
+            point = layout.getLocation(offsetInLine - 1, 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 {
+                        point = layout.getLocation(offsetInLine - 1, 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,char[])
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when string is null</li>
+ * </ul>
+ */
+public void insert(char[] string) {
+    checkWidget();
+    if (string is null) {
+        DWT.error(DWT.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 DWT.Dispose: handleDispose(event); break;
+                case DWT.KeyDown: handleKeyDown(event); break;
+                case DWT.KeyUp: handleKeyUp(event); break;
+                case DWT.MouseDown: handleMouseDown(event); break;
+                case DWT.MouseUp: handleMouseUp(event); break;
+                case DWT.MouseMove: handleMouseMove(event); break;
+                case DWT.Paint: handlePaint(event); break;
+                case DWT.Resize: handleResize(event); break;
+                case DWT.Traverse: handleTraverse(event); break;
+                default:
+            }
+        }
+    };
+    addListener(DWT.Dispose, listener);
+    addListener(DWT.KeyDown, listener);
+    addListener(DWT.KeyUp, listener);
+    addListener(DWT.MouseDown, listener);
+    addListener(DWT.MouseUp, listener);
+    addListener(DWT.MouseMove, listener);
+    addListener(DWT.Paint, listener);
+    addListener(DWT.Resize, listener);
+    addListener(DWT.Traverse, listener);
+    if (verticalBar !is null) {
+        verticalBar.addListener(DWT.Selection, new class() Listener {
+            public void handleEvent(Event event) {
+                handleVerticalScroll(event);
+            }
+        });
+    }
+    if (horizontalBar !is null) {
+        horizontalBar.addListener(DWT.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);
+    }
+}
+/**
+ * Frees resources.
+ */
+void handleDispose(Event event) {
+    removeListener(DWT.Dispose, listener);
+    notifyListeners(DWT.Dispose, event);
+    event.type = DWT.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(handle);
+    }
+    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 DWT.NULL) {
+            // see if we have a control character
+            if ((event.stateMask & DWT.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 DWT.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 ^ DWT.COMMAND) is 0 ||
+                    (event.stateMask ^ (DWT.COMMAND | DWT.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 ^ DWT.CTRL) is 0 ||
+                    (event.stateMask ^ (DWT.CTRL | DWT.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 ^ DWT.ALT) is 0 ||
+                    (event.stateMask ^ DWT.CTRL) is 0 ||
+                    (event.stateMask ^ (DWT.ALT | DWT.SHIFT)) is 0 ||
+                    (event.stateMask ^ (DWT.CTRL | DWT.SHIFT)) is 0;
+        }
+        // -ignore anything below SPACE except for line delimiter keys and tab.
+        // -ignore DEL
+        if (!ignore && event.character > 31 && event.character !is DWT.DEL ||
+            event.character is DWT.CR || event.character is DWT.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 (DWTError 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);
+        char[] 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 & DWT.MOD4) !is 0)) {
+        return;
+    }
+    clickCount = event.count;
+    if (clickCount is 1) {
+        bool select = (event.stateMask & DWT.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, DWT.MOVEMENT_WORD_START));
+                end = Math.min(content.getCharCount(), getWordNext(start, DWT.MOVEMENT_WORD_END));
+            } else {
+                start = lineOffset;
+                end = lineEnd;
+            }
+            selection.x = selection.y = start;
+            selectionAnchor = -1;
+            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 (DWTError 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 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();
+            if (wordWrap) setCaretLocation();
+        } 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 DWT.TRAVERSE_ESCAPE:
+        case DWT.TRAVERSE_PAGE_NEXT:
+        case DWT.TRAVERSE_PAGE_PREVIOUS:
+            event.doit = true;
+            break;
+        case DWT.TRAVERSE_RETURN:
+        case DWT.TRAVERSE_TAB_NEXT:
+        case DWT.TRAVERSE_TAB_PREVIOUS:
+            if ((getStyle() & DWT.SINGLE) !is 0) {
+                event.doit = true;
+            } else {
+                if (!editable || (event.stateMask & DWT.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) {
+            char[] 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) {
+            char[] shortcut = null;
+            Label label = getAssociatedLabel ();
+            if (label !is null) {
+                char[] 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(DWT.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;
+}
+char[] stripMnemonic (char[] 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[0 .. index-1] ~ string[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 (char[] 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 CharacterToLower(getFirstCodepoint( 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 DWT provides this
+ */
+bool isBidi() {
+    return IS_GTK || 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() & DWT.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(DWT.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 DWT.SINGLE style and the
+ * clipboard text contains more than one line, only the first line without
+ * line delimiters is  inserted in the widget.
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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();
+    char[] text = (cast(ArrayWrapperString) getClipboardContent(DND.CLIPBOARD)).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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(LineGetSegments, listener);
+}
+/**
+ * Removes the specified extended modify listener.
+ *
+ * @param extendedModifyListener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(ExtendedModify, extendedModifyListener);
+}
+/**
+ * Removes the specified line background listener.
+ *
+ * @param listener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(LineGetBackground, listener);
+}
+/**
+ * Removes the specified line style listener.
+ *
+ * @param listener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(LineGetStyle, listener);
+}
+/**
+ * Removes the specified modify listener.
+ *
+ * @param modifyListener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Modify, modifyListener);
+}
+/**
+ * Removes the specified listener.
+ *
+ * @param listener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Selection, listener);
+}
+/**
+ * Removes the specified verify listener.
+ *
+ * @param verifyListener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Verify, verifyListener);
+}
+/**
+ * Removes the specified key verify listener.
+ *
+ * @param listener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    removeListener(VerifyKey, listener);
+}
+/**
+ * Removes the specified word movement listener.
+ *
+ * @param listener the listener
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
+ *   <li>ERROR_NULL_ARGUMENT when ranges is null</li>
+ * </ul>
+ *
+ * @since 2.0
+ *
+ * @see #setStyleRanges(int, int, int[], StyleRange[])
+ */
+public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (ranges is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    setStyleRanges(start, length, null, ranges, false);
+}
+/**
+ * Replaces the given text range with new text.
+ * If the widget has the DWT.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 DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *   <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
+ *   <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
+ *      Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li>
+ *   <li>ERROR_NULL_ARGUMENT when string is null</li>
+ * </ul>
+ */
+public void replaceTextRange(int start, int length, char[] text) {
+    checkWidget();
+    if (text is null) {
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    }
+    int contentLength = getCharCount();
+    int end = start + length;
+    if (start > end || start < 0 || end > contentLength) {
+        DWT.error(DWT.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;
+    int oldColumnX = columnX;
+    setCaretLocation();
+    columnX = oldColumnX;
+    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();
+    }
+    int oldColumnX = columnX;
+    setCaretLocation();
+    columnX = oldColumnX;
+    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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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, char[] 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(DWT.Modify, event);
+}
+/**
+ * Sends the specified selection event.
+ */
+void sendSelectionEvent() {
+    getAccessible().textSelectionChanged();
+    Event event = new Event();
+    event.x = selection.x;
+    event.y = selection.y;
+    notifyListeners(DWT.Selection, event);
+}
+int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, char[] 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)) {
+                    DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+                }
+            }
+        }
+        return offset;
+    }
+    return newOffset;
+}
+/**
+ * Sets the alignment of the widget. The argument should be one of <code>DWT.LEFT</code>,
+ * <code>DWT.CENTER</code> or <code>DWT.RIGHT</code>. The alignment applies for all lines.
+ *
+ * @param alignment the new alignment
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 &= (DWT.LEFT | DWT.RIGHT | DWT.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.redraw();
+}
+/**
+ * Sets the receiver's caret.  Set the caret's height and location.
+ *
+ * </p>
+ * @param caret the new caret for the receiver
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 = DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 = DWT.DEFAULT;
+            }
+        }
+        int imageDirection = direction;
+        if (isMirrored()) {
+            if (imageDirection is DWT.LEFT) {
+                imageDirection = DWT.RIGHT;
+            } else if (imageDirection is DWT.RIGHT) {
+                imageDirection = DWT.LEFT;
+            }
+        }
+        if (isDefaultCaret && imageDirection is DWT.RIGHT) {
+            location.x -= (caret.getSize().x - 1);
+        }
+        if (isDefaultCaret) {
+            caret.setBounds(location.x, location.y, 0, caretHeight);
+        } else {
+            caret.setLocation(location);
+        }
+        getAccessible().textCaretMoved(getCaretOffset());
+        if (direction !is caretDirection) {
+            caretDirection = direction;
+            if (isDefaultCaret) {
+                if (imageDirection is DWT.DEFAULT) {
+                    defaultCaret.setImage(null);
+                } else if (imageDirection is DWT.LEFT) {
+                    defaultCaret.setImage(leftCaretBitmap);
+                } else if (imageDirection is DWT.RIGHT) {
+                    defaultCaret.setImage(rightCaretBitmap);
+                }
+            }
+            if (caretDirection is DWT.LEFT) {
+                BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
+            } else if (caretDirection is DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            }
+            caretOffset = offset;
+        }
+        // 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 DWTError, see Clipboard.setContents
+ * @see dwt.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);
+    char[] 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);
+        char[] 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 = DWT.NULL;
+    setCaretLocation();
+    super.redraw();
+}
+/**
+ * @see dwt.widgets.Control#setForeground
+ */
+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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWT.java or a character.
+ *  Optionally ORd with a state mask.  Preferred state masks are one or more of
+ *  DWT.MOD1, DWT.MOD2, DWT.MOD3, since these masks account for modifier platform
+ *  differences.  However, there may be cases where using the specific state masks
+ *  (i.e., DWT.CTRL, DWT.SHIFT, DWT.ALT, DWT.COMMAND) makes sense.
+ * @param action one of the predefined actions defined in ST.java.
+ *  Use DWT.NULL to remove a key binding.
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 & DWT.MODIFIER_MASK;
+    char keyChar = cast(char)(key & DWT.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 DWT.NULL) {
+            keyActionMap.remove(newKey);
+        } else {
+            keyActionMap[newKey] = action;
+        }
+        ch = CharacterToLower(keyChar);
+        newKey = ch | modifierValue;
+        if (action is DWT.NULL) {
+            keyActionMap.remove(newKey);
+        } else {
+            keyActionMap[newKey] = action;
+        }
+    } else {
+        if (action is DWT.NULL) {
+            keyActionMap.remove(key);
+        } else {
+            keyActionMap[key]=action;
+        }
+    }
+}
+/**
+ * Sets the alignment of the specified lines. The argument should be one of <code>DWT.LEFT</code>,
+ * <code>DWT.CENTER</code> or <code>DWT.RIGHT</code>.
+ * <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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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>DWT.LEFT_TO_RIGHT</code> or <code>DWT.RIGHT_TO_LEFT</code>.
+ *
+ * @param orientation new orientation style
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 & (DWT.RIGHT_TO_LEFT | DWT.LEFT_TO_RIGHT)) is 0) {
+        return;
+    }
+    if ((orientation & DWT.RIGHT_TO_LEFT) !is 0 && (orientation & DWT.LEFT_TO_RIGHT) !is 0) {
+        return;
+    }
+    if ((orientation & DWT.RIGHT_TO_LEFT) !is 0 && isMirrored()) {
+        return;
+    }
+    if ((orientation & DWT.LEFT_TO_RIGHT) !is 0 && !isMirrored()) {
+        return;
+    }
+    if (!BidiUtil.setOrientation(handle, orientation)) {
+        return;
+    }
+    isMirrored_ = (orientation & DWT.RIGHT_TO_LEFT) !is 0;
+    caretDirection = DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error (DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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()) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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;
+    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;
+        }
+        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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.ERROR_INVALID_RANGE);
+    }
+    if (styles !is null) {
+        if (end > charCount) {
+            DWT.error(DWT.ERROR_INVALID_RANGE);
+        }
+        if (ranges !is null) {
+            if (ranges.length !is styles.length << 1) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+        }
+        int lastOffset = 0;
+        bool variableHeight = false;
+        for (int i = 0; i < styles.length; i ++) {
+            if (styles[i] is null) DWT.error(DWT.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) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+            if (lastOffset > rangeStart) DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li>
+ *    <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
+ * </ul>
+ *
+ * @see #setStyleRanges(int[], StyleRange[])
+ */
+public void setStyleRanges(StyleRange[] ranges) {
+    checkWidget();
+    if (isListening(LineGetStyle)) return;
+    if (ranges is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    setStyleRanges(0, 0, null, ranges, true);
+}
+/**
+ * Sets the tab width.
+ *
+ * @param tabs tab width measured in characters.
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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);
+    if (caretOffset > 0) {
+        caretOffset = 0;
+        showCaret();
+        clearSelection(false);
+    }
+    resetCache(0, content.getLineCount());
+    super.redraw();
+}
+/**
+ * Sets the widget content.
+ * If the widget has the DWT.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 DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when string is null</li>
+ * </ul>
+ */
+public void setText(char[] text) {
+    checkWidget();
+    if (text is null) {
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    }
+    Event event = new Event();
+    event.start = 0;
+    event.end = getCharCount();
+    event.text = text;
+    event.doit = true;
+    notifyListeners(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) {
+        DWT.error(DWT.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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 DWT.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() & DWT.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();
+}
+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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+        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();
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyledTextContent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,208 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.StyledTextContent;
+
+import dwt.custom.TextChangeListener;
+/**
+ * Clients may implement the StyledTextContent interface to provide a
+ * custom store for the StyledText widget content. The StyledText widget
+ * interacts with its StyledTextContent in order to access and update
+ * the text that is being displayed and edited in the widget.
+ * A custom content implementation can be set in the widget using the
+ * StyledText.setContent API.
+ */
+public interface StyledTextContent {
+
+/**
+ * Called by StyledText to add itself as an Observer to content changes.
+ * See TextChangeListener for a description of the listener methods that
+ * are called when text changes occur.
+ * <p>
+ *
+ * @param listener the listener
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addTextChangeListener(TextChangeListener listener);
+
+/**
+ * Return the number of characters in the content.
+ * <p>
+ *
+ * @return the number of characters in the content.
+ */
+public int getCharCount();
+
+/**
+ * Return the line at the given line index without delimiters.
+ * <p>
+ *
+ * @param lineIndex index of the line to return. Does not include
+ *  delimiters of preceding lines. Index 0 is the first line of the
+ *  content.
+ * @return the line text without delimiters
+ */
+public char[] getLine(int lineIndex);
+
+/**
+ * Return the line index at the given character offset.
+ * <p>
+ *
+ * @param offset offset of the line to return. The first character of the
+ *  document is at offset 0.  An offset of getLength() is valid and should
+ *  answer the number of lines.
+ * @return the line index. The first line is at index 0.  If the character
+ *  at offset is a delimiter character, answer the line index of the line
+ *  that is delimited.
+ *  For example, if text = "\r\n\r\n", and delimiter = "\r\n", then:
+ * <ul>
+ * <li>getLineAtOffset(0) is 0
+ * <li>getLineAtOffset(1) is 0
+ * <li>getLineAtOffset(2) is 1
+ * <li>getLineAtOffset(3) is 1
+ * <li>getLineAtOffset(4) is 2
+ * </ul>
+ */
+public int getLineAtOffset(int offset);
+
+/**
+ * Return the number of lines.  Should answer 1 when no text is specified.
+ * The  StyledText widget relies on this behavior for drawing the cursor.
+ * <p>
+ *
+ * @return the number of lines.  For example:
+ * <ul>
+ * <li> text value is> getLineCount
+ * <li> null is> 1
+ * <li> "" is> 1
+ * <li> "a\n" is> 2
+ * <li> "\n\n" is> 3
+ * </ul>
+ */
+public int getLineCount();
+
+/**
+ * Return the line delimiter that should be used by the StyledText
+ * widget when inserting new lines. New lines entered using key strokes
+ * and paste operations use this line delimiter.
+ * Implementors may use System.getProperty("line.separator") to return
+ * the platform line delimiter.
+ * <p>
+ *
+ * @return the line delimiter that should be used by the StyledText widget
+ *  when inserting new lines.
+ */
+public char[] getLineDelimiter();
+
+/**
+ * Return the character offset of the first character of the given line.
+ * <p>
+ * <b>NOTE:</b> When there is no text (i.e., no lines), getOffsetAtLine(0)
+ * is a valid call that should return 0.
+ * </p>
+ *
+ * @param lineIndex index of the line. The first line is at index 0.
+ * @return offset offset of the first character of the line. The first
+ *  character of the document is at offset 0.  The return value should
+ *  include line delimiters.
+ *  For example, if text = "\r\ntest\r\n" and delimiter = "\r\n", then:
+ * <ul>
+ * <li>getOffsetAtLine(0) is 0
+ * <li>getOffsetAtLine(1) is 2
+ * <li>getOffsetAtLine(2) is 8
+ * </ul>
+ */
+public int getOffsetAtLine(int lineIndex);
+
+/**
+ * Returns a string representing the content at the given range.
+ * <p>
+ *
+ * @param start the start offset of the text to return. Offset 0 is the
+ *  first character of the document.
+ * @param length the length of the text to return
+ * @return the text at the given range
+ */
+public char[] getTextRange(int start, int length);
+
+/**
+ * Remove the specified text changed listener.
+ * <p>
+ *
+ * @param listener the listener
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeTextChangeListener(TextChangeListener listener);
+
+/**
+ * Replace the text with "newText" starting at position "start"
+ * for a length of "replaceLength".
+ * <p>
+ * Implementors have to notify the TextChangeListeners that were added
+ * using <code>addTextChangeListener</code> before and after the content
+ * is changed. A <code>TextChangingEvent</code> has to be sent to the
+ * textChanging method before the content is changed and a
+ * <code>TextChangedEvent</code> has to be sent to the textChanged method
+ * after the content has changed.
+ * The text change that occurs after the <code>TextChangingEvent</code>
+ * has been sent has to be consistent with the data provided in the
+ * <code>TextChangingEvent</code>.
+ * This data will be cached by the widget and will be used when the
+ * <code>TextChangedEvent</code> is received.
+ * <p>
+ * The <code>TextChangingEvent</code> should be set as follows:
+ * <ul>
+ * <li>event.start = start of the replaced text
+ * <li>event.newText = text that is going to be inserted or empty String
+ *  if no text will be inserted
+ * <li>event.replaceCharCount = length of text that is going to be replaced
+ * <li>event.newCharCount = length of text that is going to be inserted
+ * <li>event.replaceLineCount = number of lines that are going to be replaced
+ * <li>event.newLineCount = number of new lines that are going to be inserted
+ * </ul>
+ * <b>NOTE:</b> newLineCount is the number of inserted lines and replaceLineCount
+ * is the number of deleted lines based on the change that occurs visually.
+ * For example:
+ * <ul>
+ * <li>(replaceText, newText) is> (replaceLineCount, newLineCount)
+ * <li>("", "\n") is> (0, 1)
+ * <li>("\n\n", "a") is> (2, 0)
+ * <li>("a", "\n\n") is> (0, 2)
+ * <li>("\n", "") is> (1, 0)
+ * </ul>
+ * </p>
+ *
+ * @param start start offset of text to replace, none of the offsets include
+ *  delimiters of preceding lines, offset 0 is the first character of the
+ *  document
+ * @param replaceLength length of text to replace
+ * @param text text to replace
+ * @see TextChangeListener
+ */
+public void replaceTextRange(int start, int replaceLength, char[] text);
+
+/**
+ * Set text to "text".
+ * Implementors have to send a <code>TextChangedEvent</code> to the
+ * textSet method of the TextChangeListeners that were added using
+ * <code>addTextChangeListener</code>.
+ * <p>
+ *
+ * @param text the new text
+ * @see TextChangeListener
+ */
+public void setText(char[] text);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyledTextDropTargetEffect.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,308 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.StyledTextDropTargetEffect;
+
+
+import dwt.DWT;
+import dwt.dnd.DND;
+import dwt.dnd.DropTargetAdapter;
+import dwt.dnd.DropTargetEffect;
+import dwt.dnd.DropTargetEvent;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.custom.StyledText;
+import dwt.custom.StyledTextContent;
+
+static import tango.core.Exception;
+import Math = tango.math.Math;
+import dwt.dwthelper.utils;
+
+/**
+ * This adapter class provides a default drag under effect (eg. select and scroll)
+ * when a drag occurs over a <code>Table</code>.
+ *
+ * <p>Classes that wish to provide their own drag under effect for a <code>StyledText</code>
+ * can extend this class, override the <code>StyledTextDropTargetEffect.dragOver</code>
+ * method and override any other applicable methods in <code>StyledTextDropTargetEffect</code> to
+ * display their own drag under effect.</p>
+ *
+ * Subclasses that override any methods of this class should call the corresponding
+ * <code>super</code> method to get the default drag under effect implementation.
+ *
+ * <p>The feedback value is either one of the FEEDBACK constants defined in
+ * class <code>DND</code> which is applicable to instances of this class,
+ * or it must be built by <em>bitwise OR</em>'ing together
+ * (that is, using the <code>int</code> "|" operator) two or more
+ * of those <code>DND</code> effect constants.
+ * </p>
+ * <p>
+ * <dl>
+ * <dt><b>Feedback:</b></dt>
+ * <dd>FEEDBACK_SELECT, FEEDBACK_SCROLL</dd>
+ * </dl>
+ * </p>
+ *
+ * @see DropTargetAdapter
+ * @see DropTargetEvent
+ *
+ * @since 3.3
+ */
+public class StyledTextDropTargetEffect : DropTargetEffect {
+    static final int CARET_WIDTH = 2;
+    static final int SCROLL_HYSTERESIS = 100; // milli seconds
+    static final int SCROLL_TOLERANCE = 20; // pixels
+
+    int currentOffset = -1;
+    long scrollBeginTime;
+    int scrollX = -1, scrollY = -1;
+    Listener paintListener;
+
+    /**
+     * Creates a new <code>StyledTextDropTargetEffect</code> to handle the drag under effect on the specified
+     * <code>StyledText</code>.
+     *
+     * @param styledText the <code>StyledText</code> over which the user positions the cursor to drop the data
+     */
+    public this(StyledText styledText) {
+        super(styledText);
+        paintListener = new class() Listener {
+            public void handleEvent (Event event) {
+                if (currentOffset !is -1) {
+                    StyledText text = cast(StyledText) getControl();
+                    Point position = text.getLocationAtOffset(currentOffset);
+                    int height = text.getLineHeight(currentOffset);
+                    event.gc.setBackground(event.display.getSystemColor (DWT.COLOR_BLACK));
+                    event.gc.fillRectangle(position.x, position.y, CARET_WIDTH, height);
+                }
+            }
+        };
+    }
+
+    /**
+     * This implementation of <code>dragEnter</code> provides a default drag under effect
+     * for the feedback specified in <code>event.feedback</code>.
+     *
+     * For additional information see <code>DropTargetAdapter.dragEnter</code>.
+     *
+     * Subclasses that override this method should call <code>super.dragEnter(event)</code>
+     * to get the default drag under effect implementation.
+     *
+     * @param event  the information associated with the drag start event
+     *
+     * @see DropTargetAdapter
+     * @see DropTargetEvent
+     */
+    public override void dragEnter(DropTargetEvent event) {
+        currentOffset = -1;
+        scrollBeginTime = 0;
+        scrollX = -1;
+        scrollY = -1;
+        getControl().removeListener(DWT.Paint, paintListener);
+        getControl().addListener (DWT.Paint, paintListener);
+    }
+
+    /**
+     * This implementation of <code>dragLeave</code> provides a default drag under effect
+     * for the feedback specified in <code>event.feedback</code>.
+     *
+     * For additional information see <code>DropTargetAdapter.dragLeave</code>.
+     *
+     * Subclasses that override this method should call <code>super.dragLeave(event)</code>
+     * to get the default drag under effect implementation.
+     *
+     * @param event  the information associated with the drag leave event
+     *
+     * @see DropTargetAdapter
+     * @see DropTargetEvent
+     */
+    public override void dragLeave(DropTargetEvent event) {
+        StyledText text = cast(StyledText) getControl();
+        if (currentOffset !is -1) {
+            refreshCaret(text, currentOffset, -1);
+        }
+        text.removeListener(DWT.Paint, paintListener);
+        scrollBeginTime = 0;
+        scrollX = -1;
+        scrollY = -1;
+    }
+
+    /**
+     * This implementation of <code>dragOver</code> provides a default drag under effect
+     * for the feedback specified in <code>event.feedback</code>.
+     *
+     * For additional information see <code>DropTargetAdapter.dragOver</code>.
+     *
+     * Subclasses that override this method should call <code>super.dragOver(event)</code>
+     * to get the default drag under effect implementation.
+     *
+     * @param event  the information associated with the drag over event
+     *
+     * @see DropTargetAdapter
+     * @see DropTargetEvent
+     * @see DND#FEEDBACK_SELECT
+     * @see DND#FEEDBACK_SCROLL
+     */
+    public override void dragOver(DropTargetEvent event) {
+        int effect = event.feedback;
+        StyledText text = cast(StyledText) getControl();
+
+        Point pt = text.getDisplay().map(null, text, event.x, event.y);
+        if ((effect & DND.FEEDBACK_SCROLL) is 0) {
+            scrollBeginTime = 0;
+            scrollX = scrollY = -1;
+        } else {
+            if (text.getCharCount() is 0) {
+                scrollBeginTime = 0;
+                scrollX = scrollY = -1;
+            } else {
+                if (scrollX !is -1 && scrollY !is -1 && scrollBeginTime !is 0 &&
+                    (pt.x >= scrollX && pt.x <= (scrollX + SCROLL_TOLERANCE) ||
+                     pt.y >= scrollY && pt.y <= (scrollY + SCROLL_TOLERANCE))) {
+                    if (System.currentTimeMillis() >= scrollBeginTime) {
+                        Rectangle area = text.getClientArea();
+                        Rectangle bounds = text.getTextBounds(0, 0);
+                        int charWidth = bounds.width;
+                        int scrollAmount = 10*charWidth;
+                        if (pt.x < area.x + 3*charWidth) {
+                            int leftPixel = text.getHorizontalPixel();
+                            text.setHorizontalPixel(leftPixel - scrollAmount);
+                            if (text.getHorizontalPixel() !is leftPixel) {
+                                text.redraw();
+                            }
+                        }
+                        if (pt.x > area.width - 3*charWidth) {
+                            int leftPixel = text.getHorizontalPixel();
+                            text.setHorizontalPixel(leftPixel + scrollAmount);
+                            if (text.getHorizontalPixel() !is leftPixel) {
+                                text.redraw();
+                            }
+                        }
+                        int lineHeight = bounds.height;
+                        if (pt.y < area.y + lineHeight) {
+                            int topPixel = text.getTopPixel();
+                            text.setTopPixel(topPixel - lineHeight);
+                            if (text.getTopPixel() !is topPixel) {
+                                text.redraw();
+                            }
+                        }
+                        if (pt.y > area.height - lineHeight) {
+                            int topPixel = text.getTopPixel();
+                            text.setTopPixel(topPixel + lineHeight);
+                            if (text.getTopPixel() !is topPixel) {
+                                text.redraw();
+                            }
+                        }
+                        scrollBeginTime = 0;
+                        scrollX = scrollY = -1;
+                    }
+                } else {
+                    scrollBeginTime = System.currentTimeMillis() + SCROLL_HYSTERESIS;
+                    scrollX = pt.x;
+                    scrollY = pt.y;
+                }
+            }
+        }
+
+        if ((effect & DND.FEEDBACK_SELECT) !is 0) {
+            StyledTextContent content = text.getContent();
+            int newOffset = -1;
+            try {
+                newOffset = text.getOffsetAtLocation(pt);
+            } catch ( tango.core.Exception.IllegalArgumentException ex1) {
+                int maxOffset = content.getCharCount();
+                Point maxLocation = text.getLocationAtOffset(maxOffset);
+                if (pt.y >= maxLocation.y) {
+                    try {
+                        newOffset = text.getOffsetAtLocation(new Point(pt.x, maxLocation.y));
+                    } catch (tango.core.Exception.IllegalArgumentException ex2) {
+                        newOffset = maxOffset;
+                    }
+                } else {
+                    try {
+                        int startOffset = text.getOffsetAtLocation(new Point(0, pt.y));
+                        int endOffset = maxOffset;
+                        int line = content.getLineAtOffset(startOffset);
+                        int lineCount = content.getLineCount();
+                        if (line + 1 < lineCount) {
+                            endOffset = content.getOffsetAtLine(line + 1)  - 1;
+                        }
+                        int lineHeight = text.getLineHeight(startOffset);
+                        for (int i = endOffset; i >= startOffset; i--) {
+                            Point p = text.getLocationAtOffset(i);
+                            if (p.x < pt.x && p.y < pt.y && p.y + lineHeight > pt.y) {
+                                newOffset = i;
+                                break;
+                            }
+                        }
+                    } catch (tango.core.Exception.IllegalArgumentException ex2) {
+                        newOffset = -1;
+                    }
+                }
+            }
+            if (newOffset !is -1 && newOffset !is currentOffset) {
+                // check if offset is line delimiter
+                // see StyledText.isLineDelimiter()
+                int line = content.getLineAtOffset(newOffset);
+                int lineOffset = content.getOffsetAtLine(line);
+                int offsetInLine = newOffset - 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.
+                if (offsetInLine > content.getLine(line).length) {
+                    newOffset = Math.max(0, newOffset - 1);
+                }
+                refreshCaret(text, currentOffset, newOffset);
+                currentOffset = newOffset;
+            }
+        }
+    }
+
+    void refreshCaret(StyledText text, int oldOffset, int newOffset) {
+        if (oldOffset !is newOffset) {
+            if (oldOffset !is -1) {
+                Point oldPos = text.getLocationAtOffset(oldOffset);
+                int oldHeight = text.getLineHeight(oldOffset);
+                text.redraw (oldPos.x, oldPos.y, CARET_WIDTH, oldHeight, false);
+            }
+            if (newOffset !is -1) {
+                Point newPos = text.getLocationAtOffset(newOffset);
+                int newHeight = text.getLineHeight(newOffset);
+                text.redraw (newPos.x, newPos.y, CARET_WIDTH, newHeight, false);
+            }
+        }
+    }
+
+    /**
+     * This implementation of <code>dropAccept</code> provides a default drag under effect
+     * for the feedback specified in <code>event.feedback</code>.
+     *
+     * For additional information see <code>DropTargetAdapter.dropAccept</code>.
+     *
+     * Subclasses that override this method should call <code>super.dropAccept(event)</code>
+     * to get the default drag under effect implementation.
+     *
+     * @param event  the information associated with the drop accept event
+     *
+     * @see DropTargetAdapter
+     * @see DropTargetEvent
+     */
+    public override void dropAccept(DropTargetEvent event) {
+        if (currentOffset !is -1) {
+            StyledText text = cast(StyledText) getControl();
+            text.setSelection(currentOffset);
+            currentOffset = -1;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyledTextEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.StyledTextEvent;
+
+
+import dwt.graphics.Color;
+import dwt.widgets.Event;
+import dwt.custom.StyleRange;
+import dwt.custom.Bullet;
+import dwt.custom.StyledTextContent;
+
+/**
+ *
+ */
+class StyledTextEvent : Event {
+    // used by LineStyleEvent
+    int[] ranges;
+    StyleRange[] styles;
+    int alignment;
+    int indent;
+    bool justify;
+    Bullet bullet;
+    int bulletIndex;
+    // used by LineBackgroundEvent
+    Color lineBackground;
+    // used by BidiSegmentEvent
+    int[] segments;
+    // used by TextChangedEvent
+    int replaceCharCount;
+    int newCharCount;
+    int replaceLineCount;
+    int newLineCount;
+    // used by PaintObjectEvent
+    int x;
+    int y;
+    int ascent;
+    int descent;
+    StyleRange style;
+
+this (StyledTextContent content) {
+    data = cast(Object)content;
+}
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyledTextListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.StyledTextListener;
+
+
+import dwt.events.VerifyEvent;
+import dwt.internal.DWTEventListener;
+import dwt.widgets.Event;
+import dwt.widgets.TypedListener;
+import dwt.custom.StyledText;
+import dwt.custom.ExtendedModifyEvent;
+import dwt.custom.ExtendedModifyListener;
+import dwt.custom.StyledTextEvent;
+import dwt.custom.LineBackgroundEvent;
+import dwt.custom.BidiSegmentEvent;
+import dwt.custom.LineStyleEvent;
+import dwt.custom.PaintObjectEvent;
+import dwt.custom.MovementEvent;
+import dwt.custom.TextChangedEvent;
+import dwt.custom.TextChangingEvent;
+import dwt.custom.LineBackgroundListener;
+import dwt.custom.BidiSegmentListener;
+import dwt.custom.LineStyleListener;
+import dwt.custom.PaintObjectListener;
+import dwt.custom.VerifyKeyListener;
+import dwt.custom.StyledTextContent;
+import dwt.custom.TextChangeListener;
+import dwt.custom.MovementListener;
+
+
+class StyledTextListener : TypedListener {
+/**
+ */
+this(DWTEventListener listener) {
+    super(listener);
+}
+/**
+ * Process StyledText events by invoking the event's handler.
+ */
+public override void handleEvent(Event e) {
+
+    switch (e.type) {
+        case StyledText.ExtendedModify:
+            ExtendedModifyEvent extendedModifyEvent = new ExtendedModifyEvent(cast(StyledTextEvent) e);
+            (cast(ExtendedModifyListener) eventListener).modifyText(extendedModifyEvent);
+            break;
+        case StyledText.LineGetBackground:
+            LineBackgroundEvent lineBgEvent = new LineBackgroundEvent(cast(StyledTextEvent) e);
+            (cast(LineBackgroundListener) eventListener).lineGetBackground(lineBgEvent);
+            (cast(StyledTextEvent) e).lineBackground = lineBgEvent.lineBackground;
+            break;
+        case StyledText.LineGetSegments:
+            BidiSegmentEvent segmentEvent = new BidiSegmentEvent(cast(StyledTextEvent) e);
+            (cast(BidiSegmentListener) eventListener).lineGetSegments(segmentEvent);
+            (cast(StyledTextEvent) e).segments = segmentEvent.segments;
+            break;
+        case StyledText.LineGetStyle:
+            LineStyleEvent lineStyleEvent = new LineStyleEvent(cast(StyledTextEvent) e);
+            (cast(LineStyleListener) eventListener).lineGetStyle(lineStyleEvent);
+            (cast(StyledTextEvent) e).ranges = lineStyleEvent.ranges;
+            (cast(StyledTextEvent) e).styles = lineStyleEvent.styles;
+            (cast(StyledTextEvent) e).alignment = lineStyleEvent.alignment;
+            (cast(StyledTextEvent) e).indent = lineStyleEvent.indent;
+            (cast(StyledTextEvent) e).justify = lineStyleEvent.justify;
+            (cast(StyledTextEvent) e).bullet = lineStyleEvent.bullet;
+            (cast(StyledTextEvent) e).bulletIndex = lineStyleEvent.bulletIndex;
+            break;
+        case StyledText.PaintObject:
+            PaintObjectEvent paintObjectEvent = new PaintObjectEvent(cast(StyledTextEvent) e);
+            (cast(PaintObjectListener) eventListener).paintObject(paintObjectEvent);
+            break;
+        case StyledText.VerifyKey:
+            VerifyEvent verifyEvent = new VerifyEvent(e);
+            (cast(VerifyKeyListener) eventListener).verifyKey(verifyEvent);
+            e.doit = verifyEvent.doit;
+            break;
+        case StyledText.TextChanged: {
+            TextChangedEvent textChangedEvent = new TextChangedEvent(cast(StyledTextContent) e.data);
+            (cast(TextChangeListener) eventListener).textChanged(textChangedEvent);
+            break;
+        }
+        case StyledText.TextChanging:
+            TextChangingEvent textChangingEvent = new TextChangingEvent(cast(StyledTextContent) e.data, cast(StyledTextEvent) e);
+            (cast(TextChangeListener) eventListener).textChanging(textChangingEvent);
+            break;
+        case StyledText.TextSet: {
+            TextChangedEvent textChangedEvent = new TextChangedEvent(cast(StyledTextContent) e.data);
+            (cast(TextChangeListener) eventListener).textSet(textChangedEvent);
+            break;
+        }
+        case StyledText.WordNext: {
+            MovementEvent wordBoundaryEvent = new MovementEvent(cast(StyledTextEvent) e);
+            (cast(MovementListener) eventListener).getNextOffset(wordBoundaryEvent);
+            (cast(StyledTextEvent) e).end = wordBoundaryEvent.newOffset;
+            break;
+        }
+        case StyledText.WordPrevious: {
+            MovementEvent wordBoundaryEvent = new MovementEvent(cast(StyledTextEvent) e);
+            (cast(MovementListener) eventListener).getPreviousOffset(wordBoundaryEvent);
+            (cast(StyledTextEvent) e).end = wordBoundaryEvent.newOffset;
+            break;
+        }
+        default:
+    }
+}
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyledTextPrintOptions.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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 dwt.custom.StyledTextPrintOptions;
+
+/**
+ * Use StyledTextPrintOptions to specify printing options for the
+ * StyledText.print(Printer, StyledTextPrintOptions) API.
+ * <p>
+ * The following example prints a right aligned page number in the footer,
+ * sets the job name to "Example" and prints line background colors but no other
+ * formatting:
+ * </p>
+ * <pre>
+ * StyledTextPrintOptions options = new StyledTextPrintOptions();
+ * options.footer = "\t\t&lt;page&gt;";
+ * options.jobName = "Example";
+ * options.printLineBackground = true;
+ *
+ * Runnable runnable = styledText.print(new Printer(), options);
+ * runnable.run();
+ * </pre>
+ * @since 2.1
+ */
+public class StyledTextPrintOptions {
+    /**
+     * Page number placeholder constant for use in <code>header</code>
+     * and <code>footer</code>. Value is <code>&lt;page&gt;</code>
+     */
+    public static final char[] PAGE_TAG = "<page>";
+    /**
+     * Separator constant for use in <code>header</code> and
+     * <code>footer</code>. Value is <code>\t</code>
+     */
+    public static final char[] SEPARATOR = "\t";
+    /**
+     * Formatted text to print in the header of each page.
+     * <p>"left '\t' center '\t' right"</p>
+     * <p>left, center, right = &lt;page&gt; | #CDATA</p>
+     * <p>Header and footer are defined as three separate regions for arbitrary
+     * text or the page number placeholder &lt;page&gt;
+     * (<code>StyledTextPrintOptions.PAGE_TAG</code>). The three regions are
+     * left aligned, centered and right aligned. They are separated by a tab
+     * character (<code>StyledTextPrintOptions.SEPARATOR</code>).
+     */
+    public char[] header = null;
+    /**
+     * Formatted text to print in the footer of each page.
+     * <p>"left '\t' center '\t' right"</p>
+     * <p>left, center, right = &lt;page&gt; | #CDATA</p>
+     * <p>Header and footer are defined as three separate regions for arbitrary
+     * text or the page number placeholder &lt;page&gt;
+     * (<code>StyledTextPrintOptions.PAGE_TAG</code>). The three regions are
+     * left aligned, centered and right aligned. They are separated by a tab
+     * character (<code>StyledTextPrintOptions.SEPARATOR</code>).
+     */
+    public char[] footer = null;
+    /**
+     * Name of the print job.
+     */
+    public char[] jobName = null;
+
+    /**
+     * Print the text foreground color. Default value is <code>false</code>.
+     */
+    public bool printTextForeground = false;
+    /**
+     * Print the text background color. Default value is <code>false</code>.
+     */
+    public bool printTextBackground = false;
+    /**
+     * Print the font styles. Default value is <code>false</code>.
+     */
+    public bool printTextFontStyle = false;
+    /**
+     * Print the line background color. Default value is <code>false</code>.
+     */
+    public bool printLineBackground = false;
+
+    /**
+     * Print line numbers. Default value is <code>false</code>.
+     *
+     * @since 3.3
+     */
+    public bool printLineNumbers = false;
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/StyledTextRenderer.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,1491 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.StyledTextRenderer;
+
+
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.Device;
+import dwt.graphics.Font;
+import dwt.graphics.FontData;
+import dwt.graphics.FontMetrics;
+import dwt.graphics.GC;
+import dwt.graphics.GlyphMetrics;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.graphics.TextLayout;
+import dwt.graphics.TextStyle;
+import dwt.widgets.Display;
+import dwt.widgets.ScrollBar;
+import dwt.custom.StyledText;
+import dwt.custom.Bullet;
+import dwt.custom.StyleRange;
+import dwt.custom.StyledText;
+import dwt.custom.StyledTextContent;
+import dwt.custom.TextChangingEvent;
+import dwt.custom.ST;
+import dwt.custom.StyledTextEvent;
+
+import dwt.dwthelper.Runnable;
+
+static import tango.text.Text;
+static import tango.text.Util;
+static import tango.io.FileConst;
+static import tango.text.convert.Utf;
+import tango.util.Convert;
+import dwt.dwthelper.utils;
+
+/**
+ * A StyledTextRenderer renders the content of a StyledText widget.
+ * This class can be used to render to the display or to a printer.
+ */
+class StyledTextRenderer {
+    Device device;
+    StyledText styledText;
+    StyledTextContent content;
+
+    /* Font info */
+    Font regularFont, boldFont, italicFont, boldItalicFont;
+    int tabWidth;
+    int ascent, descent;
+    int averageCharWidth;
+
+    /* Line data */
+    int topIndex = -1;
+    TextLayout[] layouts;
+    int lineCount;
+    int[] lineWidth;
+    int[] lineHeight;
+    LineInfo[] lines;
+    int maxWidth;
+    int maxWidthLineIndex;
+    bool idleRunning;
+
+    /* Bullet */
+    Bullet[] bullets;
+    int[] bulletsIndices;
+    int[] redrawLines;
+
+    /* Style data */
+    int[] ranges;
+    int styleCount;
+    StyleRange[] styles;
+    StyleRange[] stylesSet;
+    int stylesSetCount = 0;
+    final static int BULLET_MARGIN = 8;
+
+    final static bool COMPACT_STYLES = true;
+    final static bool MERGE_STYLES = true;
+
+    final static int GROW = 32;
+    final static int IDLE_TIME = 50;
+    final static int CACHE_SIZE = 128;
+
+    final static int BACKGROUND = 1 << 0;
+    final static int ALIGNMENT = 1 << 1;
+    final static int INDENT = 1 << 2;
+    final static int JUSTIFY = 1 << 3;
+    final static int SEGMENTS = 1 << 5;
+
+    static class LineInfo {
+        int flags;
+        Color background;
+        int alignment;
+        int indent;
+        bool justify;
+        int[] segments;
+
+        public this() {
+        }
+        public this(LineInfo info) {
+            if (info !is null) {
+                flags = info.flags;
+                background = info.background;
+                alignment = info.alignment;
+                indent = info.indent;
+                justify = info.justify;
+                segments = info.segments;
+            }
+        }
+    }
+
+this(Device device, StyledText styledText) {
+    this.device = device;
+    this.styledText = styledText;
+}
+int addMerge(int[] mergeRanges, StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) {
+    int rangeCount = styleCount << 1;
+    StyleRange endStyle = null;
+    int endStart = 0, endLength = 0;
+    if (modifyEnd < rangeCount) {
+        endStyle = styles[modifyEnd >> 1];
+        endStart = ranges[modifyEnd];
+        endLength = ranges[modifyEnd + 1];
+    }
+    int grow = mergeCount - (modifyEnd - modifyStart);
+    if (rangeCount + grow >= ranges.length) {
+        int[] tmpRanges = new int[ranges.length + grow + (GROW << 1)];
+        System.arraycopy(ranges, 0, tmpRanges, 0, modifyStart);
+        StyleRange[] tmpStyles = new StyleRange[styles.length + (grow >> 1) + GROW];
+        System.arraycopy(styles, 0, tmpStyles, 0, modifyStart >> 1);
+        if (rangeCount > modifyEnd) {
+            System.arraycopy(ranges, modifyEnd, tmpRanges, modifyStart + mergeCount, rangeCount - modifyEnd);
+            System.arraycopy(styles, modifyEnd >> 1, tmpStyles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1));
+        }
+        ranges = tmpRanges;
+        styles = tmpStyles;
+    } else {
+        if (rangeCount > modifyEnd) {
+            System.arraycopy(ranges, modifyEnd, ranges, modifyStart + mergeCount, rangeCount - modifyEnd);
+            System.arraycopy(styles, modifyEnd >> 1, styles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1));
+        }
+    }
+    if (MERGE_STYLES) {
+        int j = modifyStart;
+        for (int i = 0; i < mergeCount; i += 2) {
+            if (j > 0 && ranges[j - 2] + ranges[j - 1] is mergeRanges[i] && mergeStyles[i >> 1].similarTo(styles[(j - 2) >> 1])) {
+                ranges[j - 1] += mergeRanges[i + 1];
+            } else {
+                styles[j >> 1] = mergeStyles[i >> 1];
+                ranges[j++] = mergeRanges[i];
+                ranges[j++] = mergeRanges[i + 1];
+            }
+        }
+        if (endStyle !is null && ranges[j - 2] + ranges[j - 1] is endStart && endStyle.similarTo(styles[(j - 2) >> 1])) {
+            ranges[j - 1] += endLength;
+            modifyEnd += 2;
+            mergeCount += 2;
+        }
+        if (rangeCount > modifyEnd) {
+            System.arraycopy(ranges, modifyStart + mergeCount, ranges, j, rangeCount - modifyEnd);
+            System.arraycopy(styles, (modifyStart + mergeCount) >> 1, styles, j >> 1, styleCount - (modifyEnd >> 1));
+        }
+        grow = (j - modifyStart) - (modifyEnd - modifyStart);
+    } else {
+        System.arraycopy(mergeRanges, 0, ranges, modifyStart, mergeCount);
+        System.arraycopy(mergeStyles, 0, styles, modifyStart >> 1, mergeCount >> 1);
+    }
+    styleCount += grow >> 1;
+    return grow;
+}
+int addMerge(StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) {
+    int grow = mergeCount - (modifyEnd - modifyStart);
+    StyleRange endStyle = null;
+    if (modifyEnd < styleCount) endStyle = styles[modifyEnd];
+    if (styleCount + grow >= styles.length) {
+        StyleRange[] tmpStyles = new StyleRange[styles.length + grow + GROW];
+        System.arraycopy(styles, 0, tmpStyles, 0, modifyStart);
+        if (styleCount > modifyEnd) {
+            System.arraycopy(styles, modifyEnd, tmpStyles, modifyStart + mergeCount, styleCount - modifyEnd);
+        }
+        styles = tmpStyles;
+    } else {
+        if (styleCount > modifyEnd) {
+            System.arraycopy(styles, modifyEnd, styles, modifyStart + mergeCount, styleCount - modifyEnd);
+        }
+    }
+    if (MERGE_STYLES) {
+        int j = modifyStart;
+        for (int i = 0; i < mergeCount; i++) {
+            StyleRange newStyle = mergeStyles[i], style;
+            if (j > 0 && (style = styles[j - 1]).start + style.length is newStyle.start && newStyle.similarTo(style)) {
+                style.length += newStyle.length;
+            } else {
+                styles[j++] = newStyle;
+            }
+        }
+        StyleRange style = styles[j - 1];
+        if (endStyle !is null && style.start + style.length is endStyle.start && endStyle.similarTo(style)) {
+            style.length += endStyle.length;
+            modifyEnd++;
+            mergeCount++;
+        }
+        if (styleCount > modifyEnd) {
+            System.arraycopy(styles, modifyStart + mergeCount, styles, j, styleCount - modifyEnd);
+        }
+        grow = (j - modifyStart) - (modifyEnd - modifyStart);
+    } else {
+        System.arraycopy(mergeStyles, 0, styles, modifyStart, mergeCount);
+    }
+    styleCount += grow;
+    return grow;
+}
+void calculate(int startLine, int lineCount) {
+    int endLine = startLine + lineCount;
+    if (startLine < 0 || endLine > lineWidth.length) {
+        return;
+    }
+    int hTrim = styledText.leftMargin + styledText.rightMargin + styledText.getCaretWidth();
+    for (int i = startLine; i < endLine; i++) {
+        if (lineWidth[i] is -1 || lineHeight[i] is -1) {
+            TextLayout layout = getTextLayout(i);
+            Rectangle rect = layout.getBounds();
+            lineWidth[i] = rect.width + hTrim;
+            lineHeight[i] = rect.height;
+            disposeTextLayout(layout);
+        }
+        if (lineWidth[i] > maxWidth) {
+            maxWidth = lineWidth[i];
+            maxWidthLineIndex = i;
+        }
+    }
+}
+void calculateClientArea () {
+    int index = styledText.getTopIndex();
+    int lineCount = content.getLineCount();
+    int height = styledText.getClientArea().height;
+    int y = 0;
+    while (height > y && lineCount > index) {
+        calculate(index, 1);
+        y += lineHeight[index++];
+    }
+}
+void calculateIdle () {
+    if (idleRunning) return;
+    Runnable runnable = new class() Runnable {
+        public void run() {
+            if (styledText is null) return;
+            int i;
+            long start = System.currentTimeMillis();
+            for (i = 0; i < lineCount; i++) {
+                if (lineHeight[i] is -1 || lineWidth[i] is -1) {
+                    calculate(i, 1);
+                    if (System.currentTimeMillis() - start > IDLE_TIME) break;
+                }
+            }
+            if (i < lineCount) {
+                Display display = styledText.getDisplay();
+                display.asyncExec(this);
+            } else {
+                idleRunning = false;
+                styledText.setScrollBars(true);
+                ScrollBar bar = styledText.getVerticalBar();
+                if (bar !is null) {
+                    bar.setSelection(styledText.getVerticalScrollOffset());
+                }
+            }
+        }
+    };
+    Display display = styledText.getDisplay();
+    display.asyncExec(runnable);
+    idleRunning = true;
+}
+void clearLineBackground(int startLine, int count) {
+    if (lines is null) return;
+    for (int i = startLine; i < startLine + count; i++) {
+        LineInfo info = lines[i];
+        if (info !is null) {
+            info.flags &= ~BACKGROUND;
+            info.background = null;
+            if (info.flags is 0) lines[i] = null;
+        }
+    }
+}
+void clearLineStyle(int startLine, int count) {
+    if (lines is null) return;
+    for (int i = startLine; i < startLine + count; i++) {
+        LineInfo info = lines[i];
+        if (info !is null) {
+            info.flags &= ~(ALIGNMENT | INDENT | JUSTIFY);
+            if (info.flags is 0) lines[i] = null;
+        }
+    }
+}
+void copyInto(StyledTextRenderer renderer) {
+    if (ranges !is null) {
+        int[] newRanges = renderer.ranges = new int[styleCount << 1];
+        System.arraycopy(ranges, 0, newRanges, 0, newRanges.length);
+    }
+    if (styles !is null) {
+        StyleRange[] newStyles = renderer.styles = new StyleRange[styleCount];
+        for (int i = 0; i < newStyles.length; i++) {
+            newStyles[i] = cast(StyleRange)styles[i].clone();
+        }
+        renderer.styleCount = styleCount;
+    }
+    if (lines !is null) {
+        LineInfo[] newLines = renderer.lines = new LineInfo[lineCount];
+        for (int i = 0; i < newLines.length; i++) {
+            newLines[i] = new LineInfo(lines[i]);
+        }
+        renderer.lineCount = lineCount;
+    }
+}
+void dispose() {
+    if (boldFont !is null) boldFont.dispose();
+    if (italicFont !is null) italicFont.dispose();
+    if (boldItalicFont !is null) boldItalicFont.dispose();
+    boldFont = italicFont = boldItalicFont = null;
+    reset();
+    content = null;
+    device = null;
+    styledText = null;
+}
+void disposeTextLayout (TextLayout layout) {
+    if (layouts !is null) {
+        for (int i = 0; i < layouts.length; i++) {
+            if (layouts[i] is layout) return;
+        }
+    }
+    layout.dispose();
+}
+void drawBullet(Bullet bullet, GC gc, int paintX, int paintY, int index, int lineAscent, int lineDescent) {
+    StyleRange style = bullet.style;
+    GlyphMetrics metrics = style.metrics;
+    Color color = style.foreground;
+    if (color !is null) gc.setForeground(color);
+    if ((bullet.type & ST.BULLET_DOT) !is 0 && StyledText.IS_MOTIF) {
+        int size = Math.max(4, (lineAscent + lineDescent) / 4);
+        if ((size & 1) is 0) size++;
+        if (color is null) {
+            Display display = styledText.getDisplay();
+            color = display.getSystemColor(DWT.COLOR_BLACK);
+        }
+        gc.setBackground(color);
+        int x = paintX + Math.max(0, metrics.width - size - BULLET_MARGIN);
+        gc.fillArc(x, paintY + size, size + 1, size + 1, 0, 360);
+        return;
+    }
+    Font font = style.font;
+    if (font !is null) gc.setFont(font);
+    char[] string = "";
+    int type = bullet.type & (ST.BULLET_DOT|ST.BULLET_NUMBER|ST.BULLET_LETTER_LOWER|ST.BULLET_LETTER_UPPER);
+    switch (type) {
+        case ST.BULLET_DOT: string = "\u2022"; break;
+        case ST.BULLET_NUMBER: string = to!(char[])(index); break;
+        case ST.BULLET_LETTER_LOWER: string = [cast(char) (index % 26 + 97)]; break;
+        case ST.BULLET_LETTER_UPPER: string = [cast(char) (index % 26 + 65)]; break;
+        default:
+    }
+    if ((bullet.type & ST.BULLET_TEXT) !is 0) string ~= bullet.text;
+    Display display = styledText.getDisplay();
+    TextLayout layout = new TextLayout(display);
+    layout.setText(string);
+    layout.setAscent(lineAscent);
+    layout.setDescent(lineDescent);
+    style = cast(StyleRange)style.clone();
+    style.metrics = null;
+    if (style.font is null) style.font = getFont(style.fontStyle);
+    layout.setStyle(style, 0, string.length);
+    int x = paintX + Math.max(0, metrics.width - layout.getBounds().width - BULLET_MARGIN);
+    layout.draw(gc, x, paintY);
+    layout.dispose();
+}
+int drawLine(int lineIndex, int paintX, int paintY, GC gc, Color widgetBackground, Color widgetForeground) {
+    TextLayout layout = getTextLayout(lineIndex);
+    char[] line = content.getLine(lineIndex);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    int lineLength = line.length;
+    Point selection = styledText.getSelection();
+    int selectionStart = selection.x - lineOffset;
+    int selectionEnd = selection.y - lineOffset;
+    Rectangle client = styledText.getClientArea();
+    Color lineBackground = getLineBackground(lineIndex, widgetBackground);
+    StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
+    if (event !is null && event.lineBackground !is null) lineBackground = event.lineBackground;
+
+    int height = layout.getBounds().height;
+    gc.setBackground(lineBackground);
+    styledText.drawBackground(gc, client.x, paintY, client.width, height);
+
+    gc.setForeground(widgetForeground);
+    gc.setBackground(lineBackground);
+    if (selectionStart is selectionEnd || (selectionEnd <= 0 && selectionStart > lineLength - 1)) {
+        layout.draw(gc, paintX, paintY);
+    } else {
+        int start = Math.max(0, selectionStart);
+        int end = Math.min(lineLength, selectionEnd);
+        Color selectionFg = styledText.getSelectionForeground();
+        Color selectionBg = styledText.getSelectionBackground();
+        int flags;
+        if ((styledText.getStyle() & DWT.FULL_SELECTION) !is 0) {
+            flags = DWT.FULL_SELECTION;
+        } else {
+            flags = DWT.DELIMITER_SELECTION;
+        }
+        if (selectionStart <= lineLength && lineLength < selectionEnd ) {
+            flags |= DWT.LAST_LINE_SELECTION;
+        }
+        layout.draw(gc, paintX, paintY, start, end - 1, selectionFg, selectionBg, flags);
+    }
+
+    // draw objects
+    Bullet bullet = null;
+    int bulletIndex = -1;
+    if (bullets !is null) {
+        if (bulletsIndices !is null) {
+            int index = lineIndex - topIndex;
+            if (0 <= index && index < CACHE_SIZE) {
+                bullet = bullets[index];
+                bulletIndex = bulletsIndices[index];
+            }
+        } else {
+            for (int i = 0; i < bullets.length; i++) {
+                bullet = bullets[i];
+                bulletIndex = bullet.indexOf(lineIndex);
+                if (bulletIndex !is -1) break;
+            }
+        }
+    }
+    if (bulletIndex !is -1 && bullet !is null) {
+        FontMetrics metrics = layout.getLineMetrics(0);
+        int lineAscent = metrics.getAscent() + metrics.getLeading();
+        if (bullet.type is ST.BULLET_CUSTOM) {
+            bullet.style.start = lineOffset;
+            styledText.paintObject(gc, paintX, paintY, lineAscent, metrics.getDescent(), bullet.style, bullet, bulletIndex);
+        } else {
+            drawBullet(bullet, gc, paintX, paintY, bulletIndex, lineAscent, metrics.getDescent());
+        }
+    }
+    TextStyle[] styles = layout.getStyles();
+    int[] ranges = null;
+    for (int i = 0; i < styles.length; i++) {
+        if (styles[i].metrics !is null) {
+            if (ranges is null) ranges = layout.getRanges();
+            int start = ranges[i << 1];
+            int length = ranges[(i << 1) + 1] - start;
+            Point point = layout.getLocation(start, false);
+            FontMetrics metrics = layout.getLineMetrics(layout.getLineIndex(start));
+            StyleRange style = cast(StyleRange)(cast(StyleRange)styles[i]).clone();
+            style.start = start + lineOffset;
+            style.length = length;
+            int lineAscent = metrics.getAscent() + metrics.getLeading();
+            styledText.paintObject(gc, point.x + paintX, point.y + paintY, lineAscent, metrics.getDescent(), style, null, 0);
+        }
+    }
+    disposeTextLayout(layout);
+    return height;
+}
+int getBaseline() {
+    return ascent;
+}
+Font getFont(int style) {
+    switch (style) {
+        case DWT.BOLD:
+            if (boldFont !is null) return boldFont;
+            return boldFont = new Font(device, getFontData(style));
+        case DWT.ITALIC:
+            if (italicFont !is null) return italicFont;
+            return italicFont = new Font(device, getFontData(style));
+        case DWT.BOLD | DWT.ITALIC:
+            if (boldItalicFont !is null) return boldItalicFont;
+            return boldItalicFont = new Font(device, getFontData(style));
+        default:
+            return regularFont;
+    }
+}
+FontData[] getFontData(int style) {
+    FontData[] fontDatas = regularFont.getFontData();
+    for (int i = 0; i < fontDatas.length; i++) {
+        fontDatas[i].setStyle(style);
+    }
+    return fontDatas;
+}
+int getHeight () {
+    int defaultLineHeight = getLineHeight();
+    if (styledText.isFixedLineHeight()) {
+        return lineCount * defaultLineHeight;
+    }
+    int totalHeight = 0;
+    int width = styledText.getWrapWidth();
+    for (int i = 0; i < lineCount; i++) {
+        int height = lineHeight[i];
+        if (height is -1) {
+            if (width > 0) {
+                int length = content.getLine(i).length;
+                height = ((length * averageCharWidth / width) + 1) * defaultLineHeight;
+            } else {
+                height = defaultLineHeight;
+            }
+        }
+        totalHeight += height;
+    }
+    return totalHeight + styledText.topMargin + styledText.bottomMargin;
+}
+int getLineAlignment(int index, int defaultAlignment) {
+    if (lines is null) return defaultAlignment;
+    LineInfo info = lines[index];
+    if (info !is null && (info.flags & ALIGNMENT) !is 0) {
+        return info.alignment;
+    }
+    return defaultAlignment;
+}
+Color getLineBackground(int index, Color defaultBackground) {
+    if (lines is null) return defaultBackground;
+    LineInfo info = lines[index];
+    if (info !is null && (info.flags & BACKGROUND) !is 0) {
+        return info.background;
+    }
+    return defaultBackground;
+}
+Bullet getLineBullet (int index, Bullet defaultBullet) {
+    if (bullets is null) return defaultBullet;
+    if (bulletsIndices !is null) return defaultBullet;
+    for (int i = 0; i < bullets.length; i++) {
+        Bullet bullet = bullets[i];
+        if (bullet.indexOf(index) !is -1) return bullet;
+    }
+    return defaultBullet;
+}
+int getLineHeight() {
+    return ascent + descent;
+}
+int getLineHeight(int lineIndex) {
+    if (lineHeight[lineIndex] is -1) {
+        calculate(lineIndex, 1);
+    }
+    return lineHeight[lineIndex];
+}
+int getLineIndent(int index, int defaultIndent) {
+    if (lines is null) return defaultIndent;
+    LineInfo info = lines[index];
+    if (info !is null && (info.flags & INDENT) !is 0) {
+        return info.indent;
+    }
+    return defaultIndent;
+}
+bool getLineJustify(int index, bool defaultJustify) {
+    if (lines is null) return defaultJustify;
+    LineInfo info = lines[index];
+    if (info !is null && (info.flags & JUSTIFY) !is 0) {
+        return info.justify;
+    }
+    return defaultJustify;
+}
+int[] getLineSegments(int index, int[] defaultSegments) {
+    if (lines is null) return defaultSegments;
+    LineInfo info = lines[index];
+    if (info !is null && (info.flags & SEGMENTS) !is 0) {
+        return info.segments;
+    }
+    return defaultSegments;
+}
+int getRangeIndex(int offset, int low, int high) {
+    if (styleCount is 0) return 0;
+    if (ranges !is null)  {
+        while (high - low > 2) {
+            int index = ((high + low) / 2) / 2 * 2;
+            int end = ranges[index] + ranges[index + 1];
+            if (end > offset) {
+                high = index;
+            } else {
+                low = index;
+            }
+        }
+    } else {
+        while (high - low > 1) {
+            int index = ((high + low) / 2);
+            int end = styles[index].start + styles[index].length;
+            if (end > offset) {
+                high = index;
+            } else {
+                low = index;
+            }
+        }
+    }
+    return high;
+}
+int[] getRanges(int start, int length) {
+    int[] newRanges;
+    int end = start + length - 1;
+    if (ranges !is null) {
+        int rangeCount = styleCount << 1;
+        int rangeStart = getRangeIndex(start, -1, rangeCount);
+        if (rangeStart >= rangeCount) return null;
+        if (ranges[rangeStart] > end) return null;
+        int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount) + 1);
+        newRanges = new int[rangeEnd - rangeStart + 2];
+        System.arraycopy(ranges, rangeStart, newRanges, 0, newRanges.length);
+    } else {
+        int rangeStart = getRangeIndex(start, -1, styleCount);
+        if (rangeStart >= styleCount) return null;
+        if (styles[rangeStart].start > end) return null;
+        int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount));
+        newRanges = new int[(rangeEnd - rangeStart + 1) << 1];
+        for (int i = rangeStart, j = 0; i <= rangeEnd; i++, j += 2) {
+            StyleRange style = styles[i];
+            newRanges[j] = style.start;
+            newRanges[j + 1] = style.length;
+        }
+    }
+    if (start > newRanges[0]) {
+        newRanges[1] = newRanges[0] + newRanges[1] - start;
+        newRanges[0] = start;
+    }
+    if (end < newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1] - 1) {
+        newRanges[newRanges.length - 1] = end - newRanges[newRanges.length - 2] + 1;
+    }
+    return newRanges;
+}
+StyleRange[] getStyleRanges(int start, int length, bool includeRanges) {
+    StyleRange[] newStyles;
+    int end = start + length - 1;
+    if (ranges !is null) {
+        int rangeCount = styleCount << 1;
+        int rangeStart = getRangeIndex(start, -1, rangeCount);
+        if (rangeStart >= rangeCount) return null;
+        if (ranges[rangeStart] > end) return null;
+        int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount) + 1);
+        newStyles = new StyleRange[((rangeEnd - rangeStart) >> 1) + 1];
+        if (includeRanges) {
+            for (int i = rangeStart, j = 0; i <= rangeEnd; i += 2, j++) {
+                newStyles[j] = cast(StyleRange)styles[i >> 1].clone();
+                newStyles[j].start = ranges[i];
+                newStyles[j].length = ranges[i + 1];
+            }
+        } else {
+            System.arraycopy(styles, rangeStart >> 1, newStyles, 0, newStyles.length);
+        }
+    } else {
+        int rangeStart = getRangeIndex(start, -1, styleCount);
+        if (rangeStart >= styleCount) return null;
+        if (styles[rangeStart].start > end) return null;
+        int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount));
+        newStyles = new StyleRange[rangeEnd - rangeStart + 1];
+        System.arraycopy(styles, rangeStart, newStyles, 0, newStyles.length);
+    }
+    StyleRange style = newStyles[0];
+    if (start > style.start) {
+        if (!includeRanges || ranges is null) newStyles[0] = style = cast(StyleRange)style.clone();
+        style.length = style.start + style.length - start;
+        style.start = start;
+    }
+    style = newStyles[newStyles.length - 1];
+    if (end < style.start + style.length - 1) {
+        if (!includeRanges || ranges is null) newStyles[newStyles.length - 1] = style = cast(StyleRange)style.clone();
+        style.length = end - style.start + 1;
+    }
+    return newStyles;
+}
+StyleRange getStyleRange(StyleRange style) {
+    if (style.start is 0 && style.length is 0 && style.fontStyle is DWT.NORMAL) return style;
+    StyleRange clone = cast(StyleRange)style.clone();
+    clone.start = clone.length = 0;
+    clone.fontStyle = DWT.NORMAL;
+    if (clone.font is null) clone.font = getFont(style.fontStyle);
+    return clone;
+}
+TextLayout getTextLayout(int lineIndex) {
+    return getTextLayout(lineIndex, styledText.getOrientation(), styledText.getWrapWidth(), styledText.lineSpacing);
+}
+TextLayout getTextLayout(int lineIndex, int orientation, int width, int lineSpacing) {
+    TextLayout layout = null;
+    if (styledText !is null) {
+        int topIndex = styledText.topIndex > 0 ? styledText.topIndex - 1 : 0;
+        if (layouts is null || topIndex !is this.topIndex) {
+            TextLayout[] newLayouts = new TextLayout[CACHE_SIZE];
+            if (layouts !is null) {
+                for (int i = 0; i < layouts.length; i++) {
+                    if (layouts[i] !is null) {
+                        int layoutIndex = (i + this.topIndex) - topIndex;
+                        if (0 <= layoutIndex && layoutIndex < newLayouts.length) {
+                            newLayouts[layoutIndex] = layouts[i];
+                        } else {
+                            layouts[i].dispose();
+                        }
+                    }
+                }
+            }
+            if (bullets !is null && bulletsIndices !is null && topIndex !is this.topIndex) {
+                int delta = topIndex - this.topIndex;
+                if (delta > 0) {
+                    if (delta < bullets.length) {
+                        System.arraycopy(bullets, delta, bullets, 0, bullets.length - delta);
+                        System.arraycopy(bulletsIndices, delta, bulletsIndices, 0, bulletsIndices.length - delta);
+                    }
+                    int startIndex = Math.max(0, bullets.length - delta);
+                    for (int i = startIndex; i < bullets.length; i++) bullets[i] = null;
+                } else {
+                    if (-delta < bullets.length) {
+                        System.arraycopy(bullets, 0, bullets, -delta, bullets.length + delta);
+                        System.arraycopy(bulletsIndices, 0, bulletsIndices, -delta, bulletsIndices.length + delta);
+                    }
+                    int endIndex = Math.min(bullets.length, -delta);
+                    for (int i = 0; i < endIndex; i++) bullets[i] = null;
+                }
+            }
+            this.topIndex = topIndex;
+            layouts = newLayouts;
+        }
+        if (layouts !is null) {
+            int layoutIndex = lineIndex - topIndex;
+            if (0 <= layoutIndex && layoutIndex < layouts.length) {
+                layout = layouts[layoutIndex];
+                if (layout !is null) {
+                    if (lineWidth[lineIndex] !is -1) return layout;
+                } else {
+                    layout = layouts[layoutIndex] = new TextLayout(device);
+                }
+            }
+        }
+    }
+    if (layout is null) layout = new TextLayout(device);
+    char[] line = content.getLine(lineIndex);
+    int lineOffset = content.getOffsetAtLine(lineIndex);
+    int[] segments = null;
+    int indent = 0;
+    int alignment = DWT.LEFT;
+    bool justify = false;
+    Bullet bullet = null;
+    int[] ranges = null;
+    StyleRange[] styles = null;
+    int rangeStart = 0, styleCount = 0;
+    StyledTextEvent event = null;
+    if (styledText !is null) {
+        event = styledText.getLineStyleData(lineOffset, line);
+        segments = styledText.getBidiSegments(lineOffset, line);
+        indent = styledText.indent;
+        alignment = styledText.alignment;
+        justify = styledText.justify;
+    }
+    if (event !is null) {
+        indent = event.indent;
+        alignment = event.alignment;
+        justify = event.justify;
+        bullet = event.bullet;
+        ranges = event.ranges;
+        styles = event.styles;
+        if (styles !is null) {
+            styleCount = styles.length;
+            if (styledText.isFixedLineHeight()) {
+                for (int i = 0; i < styleCount; i++) {
+                    if (styles[i].isVariableHeight()) {
+                        styledText.verticalScrollOffset = -1;
+                        styledText.setVariableLineHeight();
+                        styledText.redraw();
+                        break;
+                    }
+                }
+            }
+        }
+        if (bullets is null || bulletsIndices is null) {
+            bullets = new Bullet[CACHE_SIZE];
+            bulletsIndices = new int[CACHE_SIZE];
+        }
+        int index = lineIndex - topIndex;
+        if (0 <= index && index < CACHE_SIZE) {
+            bullets[index] = bullet;
+            bulletsIndices[index] = event.bulletIndex;
+        }
+    } else {
+        if (lines !is null) {
+            LineInfo info = lines[lineIndex];
+            if (info !is null) {
+                if ((info.flags & INDENT) !is 0) indent = info.indent;
+                if ((info.flags & ALIGNMENT) !is 0) alignment = info.alignment;
+                if ((info.flags & JUSTIFY) !is 0) justify = info.justify;
+                if ((info.flags & SEGMENTS) !is 0) segments = info.segments;
+            }
+        }
+        if (bulletsIndices !is null) {
+            bullets = null;
+            bulletsIndices = null;
+        }
+        if (bullets !is null) {
+            for (int i = 0; i < bullets.length; i++) {
+                if (bullets[i].indexOf(lineIndex) !is -1) {
+                    bullet = bullets[i];
+                    break;
+                }
+            }
+        }
+        ranges = this.ranges;
+        styles = this.styles;
+        styleCount = this.styleCount;
+        if (ranges !is null) {
+            rangeStart = getRangeIndex(lineOffset, -1, styleCount << 1);
+        } else {
+            rangeStart = getRangeIndex(lineOffset, -1, styleCount);
+        }
+    }
+    if (bullet !is null) {
+        StyleRange style = bullet.style;
+        GlyphMetrics metrics = style.metrics;
+        indent += metrics.width;
+    }
+    layout.setFont(regularFont);
+    layout.setAscent(ascent);
+    layout.setDescent(descent);
+    layout.setText(line);
+    layout.setOrientation(orientation);
+    layout.setSegments(segments);
+    layout.setWidth(width);
+    layout.setSpacing(lineSpacing);
+    layout.setTabs([tabWidth]);
+    layout.setIndent(indent);
+    layout.setAlignment(alignment);
+    layout.setJustify(justify);
+
+    int lastOffset = 0;
+    int length = line.length;
+    if (styles !is null) {
+        if (ranges !is null) {
+            int rangeCount = styleCount << 1;
+            for (int i = rangeStart; i < rangeCount; i += 2) {
+                int start, end;
+                if (lineOffset > ranges[i]) {
+                    start = 0;
+                    end = Math.min (length, ranges[i + 1] - lineOffset + ranges[i]);
+                } else {
+                    start = ranges[i] - lineOffset;
+                    end = Math.min(length, start + ranges[i + 1]);
+                }
+                if (start >= length) break;
+                if (lastOffset < start) {
+                    layout.setStyle(null, lastOffset, start - 1);
+                }
+                layout.setStyle(getStyleRange(styles[i >> 1]), start, end);
+                lastOffset = Math.max(lastOffset, end);
+            }
+        } else {
+            for (int i = rangeStart; i < styleCount; i++) {
+                int start, end;
+                if (lineOffset > styles[i].start) {
+                    start = 0;
+                    end = Math.min (length, styles[i].length - lineOffset + styles[i].start);
+                } else {
+                    start = styles[i].start - lineOffset;
+                    end = Math.min(length, start + styles[i].length);
+                }
+                if (start >= length) break;
+                if (lastOffset < start) {
+                    layout.setStyle(null, lastOffset, start - 1);
+                }
+                layout.setStyle(getStyleRange(styles[i]), start, end);
+                lastOffset = Math.max(lastOffset, end);
+            }
+        }
+    }
+    if (lastOffset < length) layout.setStyle(null, lastOffset, length);
+    if (styledText !is null && styledText.isFixedLineHeight()) {
+        int index = -1;
+        int lineCount = layout.getLineCount();
+        int height = getLineHeight();
+        for (int i = 0; i < lineCount; i++) {
+            int lineHeight = layout.getLineBounds(i).height;
+            if (lineHeight > height) {
+                height = lineHeight;
+                index = i;
+            }
+        }
+        if (index !is -1) {
+            FontMetrics metrics = layout.getLineMetrics(index);
+            ascent = metrics.getAscent() + metrics.getLeading();
+            descent = metrics.getDescent();
+            if (layouts !is null) {
+                for (int i = 0; i < layouts.length; i++) {
+                    if (layouts[i] !is null && layouts[i] !is layout) {
+                        layouts[i].setAscent(ascent);
+                        layouts[i].setDescent(descent);
+                    }
+                }
+            }
+            if (styledText.verticalScrollOffset !is 0) {
+                int topIndex = styledText.topIndex;
+                int topIndexY = styledText.topIndexY;
+                int lineHeight = getLineHeight();
+                if (topIndexY >= 0) {
+                    styledText.verticalScrollOffset = (topIndex - 1) * lineHeight + lineHeight - topIndexY;
+                } else {
+                    styledText.verticalScrollOffset = topIndex * lineHeight - topIndexY;
+                }
+            }
+            styledText.calculateScrollBars();
+            if (styledText.isBidiCaret()) styledText.createCaretBitmaps();
+            styledText.caretDirection = DWT.NULL;
+            styledText.setCaretLocation();
+            styledText.redraw();
+        }
+    }
+    return layout;
+}
+int getWidth() {
+    return maxWidth;
+}
+void reset() {
+    if (layouts !is null) {
+        for (int i = 0; i < layouts.length; i++) {
+            TextLayout layout = layouts[i];
+            if (layout !is null) layout.dispose();
+        }
+        layouts = null;
+    }
+    topIndex = -1;
+    stylesSetCount = styleCount = lineCount = 0;
+    ranges = null;
+    styles = null;
+    stylesSet = null;
+    lines = null;
+    lineWidth = null;
+    lineHeight = null;
+    bullets = null;
+    bulletsIndices = null;
+    redrawLines = null;
+}
+void reset(int startLine, int lineCount) {
+    int endLine = startLine + lineCount;
+    if (startLine < 0 || endLine > lineWidth.length) return;
+    for (int i = startLine; i < endLine; i++) {
+        lineWidth[i] = -1;
+        lineHeight[i] = -1;
+    }
+    if (startLine <= maxWidthLineIndex && maxWidthLineIndex < endLine) {
+        maxWidth = 0;
+        maxWidthLineIndex = -1;
+        if (lineCount !is this.lineCount) {
+            for (int i = 0; i < this.lineCount; i++) {
+                if (lineWidth[i] > maxWidth) {
+                    maxWidth = lineWidth[i];
+                    maxWidthLineIndex = i;
+                }
+            }
+        }
+    }
+}
+void setContent(StyledTextContent content) {
+    reset();
+    this.content = content;
+    lineCount = content.getLineCount();
+    lineWidth = new int[lineCount];
+    lineHeight = new int[lineCount];
+    reset(0, lineCount);
+}
+void setFont(Font font, int tabs) {
+    TextLayout layout = new TextLayout(device);
+    layout.setFont(regularFont);
+    if (font !is null) {
+        if (boldFont !is null) boldFont.dispose();
+        if (italicFont !is null) italicFont.dispose();
+        if (boldItalicFont !is null) boldItalicFont.dispose();
+        boldFont = italicFont = boldItalicFont = null;
+        regularFont = font;
+        layout.setText("    ");
+        layout.setFont(font);
+        layout.setStyle(new TextStyle(getFont(DWT.NORMAL), null, null), 0, 0);
+        layout.setStyle(new TextStyle(getFont(DWT.BOLD), null, null), 1, 1);
+        layout.setStyle(new TextStyle(getFont(DWT.ITALIC), null, null), 2, 2);
+        layout.setStyle(new TextStyle(getFont(DWT.BOLD | DWT.ITALIC), null, null), 3, 3);
+        FontMetrics metrics = layout.getLineMetrics(0);
+        ascent = metrics.getAscent() + metrics.getLeading();
+        descent = metrics.getDescent();
+        boldFont.dispose();
+        italicFont.dispose();
+        boldItalicFont.dispose();
+        boldFont = italicFont = boldItalicFont = null;
+    }
+    layout.dispose();
+    layout = new TextLayout(device);
+    layout.setFont(regularFont);
+    StringBuffer tabBuffer = new StringBuffer(tabs);
+    for (int i = 0; i < tabs; i++) {
+        tabBuffer.append(' ');
+    }
+    layout.setText(tabBuffer.toString());
+    tabWidth = layout.getBounds().width;
+    layout.dispose();
+    if (styledText !is null) {
+        GC gc = new GC(styledText);
+        averageCharWidth = gc.getFontMetrics().getAverageCharWidth();
+        gc.dispose();
+    }
+}
+void setLineAlignment(int startLine, int count, int alignment) {
+    if (lines is null) lines = new LineInfo[lineCount];
+    for (int i = startLine; i < startLine + count; i++) {
+        if (lines[i] is null) {
+            lines[i] = new LineInfo();
+        }
+        lines[i].flags |= ALIGNMENT;
+        lines[i].alignment = alignment;
+    }
+}
+void setLineBackground(int startLine, int count, Color background) {
+    if (lines is null) lines = new LineInfo[lineCount];
+    for (int i = startLine; i < startLine + count; i++) {
+        if (lines[i] is null) {
+            lines[i] = new LineInfo();
+        }
+        lines[i].flags |= BACKGROUND;
+        lines[i].background = background;
+    }
+}
+void setLineBullet(int startLine, int count, Bullet bullet) {
+    if (bulletsIndices !is null) {
+        bulletsIndices = null;
+        bullets = null;
+    }
+    if (bullets is null) {
+        if (bullet is null) return;
+        bullets = new Bullet[1];
+        bullets[0] = bullet;
+    }
+    int index = 0;
+    while (index < bullets.length) {
+        if (bullet is bullets[index]) break;
+        index++;
+    }
+    if (bullet !is null) {
+        if (index is bullets.length) {
+            Bullet[] newBulletsList = new Bullet[bullets.length + 1];
+            System.arraycopy(bullets, 0, newBulletsList, 0, bullets.length);
+            newBulletsList[index] = bullet;
+            bullets = newBulletsList;
+        }
+        bullet.addIndices(startLine, count);
+    } else {
+        updateBullets(startLine, count, 0, false);
+        styledText.redrawLinesBullet(redrawLines);
+        redrawLines = null;
+    }
+}
+void setLineIndent(int startLine, int count, int indent) {
+    if (lines is null) lines = new LineInfo[lineCount];
+    for (int i = startLine; i < startLine + count; i++) {
+        if (lines[i] is null) {
+            lines[i] = new LineInfo();
+        }
+        lines[i].flags |= INDENT;
+        lines[i].indent = indent;
+    }
+}
+void setLineJustify(int startLine, int count, bool justify) {
+    if (lines is null) lines = new LineInfo[lineCount];
+    for (int i = startLine; i < startLine + count; i++) {
+        if (lines[i] is null) {
+            lines[i] = new LineInfo();
+        }
+        lines[i].flags |= JUSTIFY;
+        lines[i].justify = justify;
+    }
+}
+void setLineSegments(int startLine, int count, int[] segments) {
+    if (lines is null) lines = new LineInfo[lineCount];
+    for (int i = startLine; i < startLine + count; i++) {
+        if (lines[i] is null) {
+            lines[i] = new LineInfo();
+        }
+        lines[i].flags |= SEGMENTS;
+        lines[i].segments = segments;
+    }
+}
+void setStyleRanges (int[] newRanges, StyleRange[] newStyles) {
+    if (newStyles is null) {
+        stylesSetCount = styleCount = 0;
+        ranges = null;
+        styles = null;
+        stylesSet = null;
+        return;
+    }
+    if (newRanges is null && COMPACT_STYLES) {
+        newRanges = new int[newStyles.length << 1];
+        StyleRange[] tmpStyles = new StyleRange[newStyles.length];
+        if (stylesSet is null) stylesSet = new StyleRange[4];
+        for (int i = 0, j = 0; i < newStyles.length; i++) {
+            StyleRange newStyle = newStyles[i];
+            newRanges[j++] = newStyle.start;
+            newRanges[j++] = newStyle.length;
+            int index = 0;
+            while (index < stylesSetCount) {
+                if (stylesSet[index].similarTo(newStyle)) break;
+                index++;
+            }
+            if (index is stylesSetCount) {
+                if (stylesSetCount is stylesSet.length) {
+                    StyleRange[] tmpStylesSet = new StyleRange[stylesSetCount + 4];
+                    System.arraycopy(stylesSet, 0, tmpStylesSet, 0, stylesSetCount);
+                    stylesSet = tmpStylesSet;
+                }
+                stylesSet[stylesSetCount++] = newStyle;
+            }
+            tmpStyles[i] = stylesSet[index];
+        }
+        newStyles = tmpStyles;
+    }
+
+    if (styleCount is 0) {
+        if (newRanges !is null) {
+            ranges = new int[newRanges.length];
+            System.arraycopy(newRanges, 0, ranges, 0, ranges.length);
+        }
+        styles = new StyleRange[newStyles.length];
+        System.arraycopy(newStyles, 0, styles, 0, styles.length);
+        styleCount = newStyles.length;
+        return;
+    }
+    if (newRanges !is null && ranges is null) {
+        ranges = new int[styles.length << 1];
+        for (int i = 0, j = 0; i < styleCount; i++) {
+            ranges[j++] = styles[i].start;
+            ranges[j++] = styles[i].length;
+        }
+    }
+    if (newRanges is null && ranges !is null) {
+        newRanges = new int[newStyles.length << 1];
+        for (int i = 0, j = 0; i < newStyles.length; i++) {
+            newRanges[j++] = newStyles[i].start;
+            newRanges[j++] = newStyles[i].length;
+        }
+    }
+    if (ranges !is null) {
+        int rangeCount = styleCount << 1;
+        int start = newRanges[0];
+        int modifyStart = getRangeIndex(start, -1, rangeCount), modifyEnd;
+        bool insert = modifyStart is rangeCount;
+        if (!insert) {
+            int end = newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1];
+            modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount);
+            insert = modifyStart is modifyEnd && ranges[modifyStart] >= end;
+        }
+        if (insert) {
+            addMerge(newRanges, newStyles, newRanges.length, modifyStart, modifyStart);
+            return;
+        }
+        modifyEnd = modifyStart;
+        int[] mergeRanges = new int[6];
+        StyleRange[] mergeStyles = new StyleRange[3];
+        for (int i = 0; i < newRanges.length; i += 2) {
+            int newStart = newRanges[i];
+            int newEnd = newStart + newRanges[i + 1];
+            if (newStart is newEnd) continue;
+            int modifyLast = 0, mergeCount = 0;
+            while (modifyEnd < rangeCount) {
+                if (newStart >= ranges[modifyStart] + ranges[modifyStart + 1]) modifyStart += 2;
+                if (ranges[modifyEnd] + ranges[modifyEnd + 1] > newEnd) break;
+                modifyEnd += 2;
+            }
+            if (ranges[modifyStart] < newStart && newStart < ranges[modifyStart] + ranges[modifyStart + 1]) {
+                mergeStyles[mergeCount >> 1] = styles[modifyStart >> 1];
+                mergeRanges[mergeCount] = ranges[modifyStart];
+                mergeRanges[mergeCount + 1] = newStart - ranges[modifyStart];
+                mergeCount += 2;
+            }
+            mergeStyles[mergeCount >> 1] = newStyles[i >> 1];
+            mergeRanges[mergeCount] = newStart;
+            mergeRanges[mergeCount + 1] = newRanges[i + 1];
+            mergeCount += 2;
+            if (modifyEnd < rangeCount && ranges[modifyEnd] < newEnd && newEnd < ranges[modifyEnd] + ranges[modifyEnd + 1]) {
+                mergeStyles[mergeCount >> 1] = styles[modifyEnd >> 1];
+                mergeRanges[mergeCount] = newEnd;
+                mergeRanges[mergeCount + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - newEnd;
+                mergeCount += 2;
+                modifyLast = 2;
+            }
+            int grow = addMerge(mergeRanges, mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast);
+            rangeCount += grow;
+            modifyStart = modifyEnd += grow;
+        }
+    } else {
+        int start = newStyles[0].start;
+        int modifyStart = getRangeIndex(start, -1, styleCount), modifyEnd;
+        bool insert = modifyStart is styleCount;
+        if (!insert) {
+            int end = newStyles[newStyles.length - 1].start + newStyles[newStyles.length - 1].length;
+            modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount);
+            insert = modifyStart is modifyEnd && styles[modifyStart].start >= end;
+        }
+        if (insert) {
+            addMerge(newStyles, newStyles.length, modifyStart, modifyStart);
+            return;
+        }
+        modifyEnd = modifyStart;
+        StyleRange[] mergeStyles = new StyleRange[3];
+        for (int i = 0; i < newStyles.length; i++) {
+            StyleRange newStyle = newStyles[i], style;
+            int newStart = newStyle.start;
+            int newEnd = newStart + newStyle.length;
+            if (newStart is newEnd) continue;
+            int modifyLast = 0, mergeCount = 0;
+            while (modifyEnd < styleCount) {
+                if (newStart >= styles[modifyStart].start + styles[modifyStart].length) modifyStart++;
+                if (styles[modifyEnd].start + styles[modifyEnd].length > newEnd) break;
+                modifyEnd++;
+            }
+            style = styles[modifyStart];
+            if (style.start < newStart && newStart < style.start + style.length) {
+                style = mergeStyles[mergeCount++] = cast(StyleRange)style.clone();
+                style.length = newStart - style.start;
+            }
+            mergeStyles[mergeCount++] = newStyle;
+            if (modifyEnd < styleCount) {
+                style = styles[modifyEnd];
+                if (style.start < newEnd && newEnd < style.start + style.length) {
+                    style = mergeStyles[mergeCount++] = cast(StyleRange)style.clone();
+                    style.length += style.start - newEnd;
+                    style.start = newEnd;
+                    modifyLast = 1;
+                }
+            }
+            int grow = addMerge(mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast);
+            modifyStart = modifyEnd += grow;
+        }
+    }
+}
+void textChanging(TextChangingEvent event) {
+    int start = event.start;
+    int newCharCount = event.newCharCount, replaceCharCount = event.replaceCharCount;
+    int newLineCount = event.newLineCount, replaceLineCount = event.replaceLineCount;
+
+    updateRanges(start, replaceCharCount, newCharCount);
+
+    int startLine = content.getLineAtOffset(start);
+    if (replaceCharCount is content.getCharCount()) lines = null;
+    if (replaceLineCount is lineCount) {
+        lineCount = newLineCount;
+        lineWidth = new int[lineCount];
+        lineHeight = new int[lineCount];
+        reset(0, lineCount);
+    } else {
+        int delta = newLineCount - replaceLineCount;
+        if (lineCount + delta > lineWidth.length) {
+            int[] newWidths = new int[lineCount + delta + GROW];
+            System.arraycopy(lineWidth, 0, newWidths, 0, lineCount);
+            lineWidth = newWidths;
+            int[] newHeights = new int[lineCount + delta + GROW];
+            System.arraycopy(lineHeight, 0, newHeights, 0, lineCount);
+            lineHeight = newHeights;
+        }
+        if (lines !is null) {
+            if (lineCount + delta > lines.length) {
+                LineInfo[] newLines = new LineInfo[lineCount + delta + GROW];
+                System.arraycopy(lines, 0, newLines, 0, lineCount);
+                lines = newLines;
+            }
+        }
+        int startIndex = startLine + replaceLineCount + 1;
+        int endIndex = startLine + newLineCount + 1;
+        System.arraycopy(lineWidth, startIndex, lineWidth, endIndex, lineCount - startIndex);
+        System.arraycopy(lineHeight, startIndex, lineHeight, endIndex, lineCount - startIndex);
+        for (int i = startLine; i < endIndex; i++) {
+            lineWidth[i] = lineHeight[i] = -1;
+        }
+        for (int i = lineCount + delta; i < lineCount; i++) {
+            lineWidth[i] = lineHeight[i] = -1;
+        }
+        if (layouts !is null) {
+            int layoutStartLine = startLine - topIndex;
+            int layoutEndLine = layoutStartLine + replaceLineCount + 1;
+            for (int i = layoutStartLine; i < layoutEndLine; i++) {
+                if (0 <= i && i < layouts.length) {
+                    if (layouts[i] !is null) layouts[i].dispose();
+                    layouts[i] = null;
+                    if (bullets !is null && bulletsIndices !is null) bullets[i] = null;
+                }
+            }
+            if (delta > 0) {
+                for (int i = layouts.length - 1; i >= layoutEndLine; i--) {
+                    if (0 <= i && i < layouts.length) {
+                        endIndex = i + delta;
+                        if (0 <= endIndex && endIndex < layouts.length) {
+                            layouts[endIndex] = layouts[i];
+                            layouts[i] = null;
+                            if (bullets !is null && bulletsIndices !is null) {
+                                bullets[endIndex] = bullets[i];
+                                bulletsIndices[endIndex] = bulletsIndices[i];
+                                bullets[i] = null;
+                            }
+                        } else {
+                            if (layouts[i] !is null) layouts[i].dispose();
+                            layouts[i] = null;
+                            if (bullets !is null && bulletsIndices !is null) bullets[i] = null;
+                        }
+                    }
+                }
+            } else if (delta < 0) {
+                for (int i = layoutEndLine; i < layouts.length; i++) {
+                    if (0 <= i && i < layouts.length) {
+                        endIndex = i + delta;
+                        if (0 <= endIndex && endIndex < layouts.length) {
+                            layouts[endIndex] = layouts[i];
+                            layouts[i] = null;
+                            if (bullets !is null && bulletsIndices !is null) {
+                                bullets[endIndex] = bullets[i];
+                                bulletsIndices[endIndex] = bulletsIndices[i];
+                                bullets[i] = null;
+                            }
+                        } else {
+                            if (layouts[i] !is null) layouts[i].dispose();
+                            layouts[i] = null;
+                            if (bullets !is null && bulletsIndices !is null) bullets[i] = null;
+                        }
+                    }
+                }
+            }
+        }
+        if (replaceLineCount !is 0 || newLineCount !is 0) {
+            int startLineOffset = content.getOffsetAtLine(startLine);
+            if (startLineOffset !is start) startLine++;
+            updateBullets(startLine, replaceLineCount, newLineCount, true);
+            if (lines !is null) {
+                startIndex = startLine + replaceLineCount;
+                endIndex = startLine + newLineCount;
+                System.arraycopy(lines, startIndex, lines, endIndex, lineCount - startIndex);
+                for (int i = startLine; i < endIndex; i++) {
+                    lines[i] = null;
+                }
+                for (int i = lineCount + delta; i < lineCount; i++) {
+                    lines[i] = null;
+                }
+            }
+        }
+        lineCount += delta;
+        if (maxWidthLineIndex !is -1 && startLine <= maxWidthLineIndex && maxWidthLineIndex <= startLine + replaceLineCount) {
+            maxWidth = 0;
+            maxWidthLineIndex = -1;
+            for (int i = 0; i < lineCount; i++) {
+                if (lineWidth[i] > maxWidth) {
+                    maxWidth = lineWidth[i];
+                    maxWidthLineIndex = i;
+                }
+            }
+        }
+    }
+}
+void updateBullets(int startLine, int replaceLineCount, int newLineCount, bool update) {
+    if (bullets is null) return;
+    if (bulletsIndices !is null) return;
+    for (int i = 0; i < bullets.length; i++) {
+        Bullet bullet = bullets[i];
+        int[] lines = bullet.removeIndices(startLine, replaceLineCount, newLineCount, update);
+        if (lines !is null) {
+            if (redrawLines is null) {
+                redrawLines = lines;
+            } else {
+                int[] newRedrawBullets = new int[redrawLines.length + lines.length];
+                System.arraycopy(redrawLines, 0, newRedrawBullets, 0, redrawLines.length);
+                System.arraycopy(lines, 0, newRedrawBullets, redrawLines.length, lines.length);
+                redrawLines = newRedrawBullets;
+            }
+        }
+    }
+    int removed = 0;
+    for (int i = 0; i < bullets.length; i++) {
+        if (bullets[i].size() is 0) removed++;
+    }
+    if (removed > 0) {
+        if (removed is bullets.length) {
+            bullets = null;
+        } else {
+            Bullet[] newBulletsList = new Bullet[bullets.length - removed];
+            for (int i = 0, j = 0; i < bullets.length; i++) {
+                Bullet bullet = bullets[i];
+                if (bullet.size() > 0) newBulletsList[j++] = bullet;
+            }
+            bullets = newBulletsList;
+        }
+    }
+}
+void updateRanges(int start, int replaceCharCount, int newCharCount) {
+    if (styleCount is 0 || (replaceCharCount is 0 && newCharCount is 0)) return;
+    if (ranges !is null) {
+        int rangeCount = styleCount << 1;
+        int modifyStart = getRangeIndex(start, -1, rangeCount);
+        if (modifyStart is rangeCount) return;
+        int end = start + replaceCharCount;
+        int modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount);
+        int offset = newCharCount - replaceCharCount;
+        if (modifyStart is modifyEnd && ranges[modifyStart] < start && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) {
+            if (newCharCount is 0) {
+                ranges[modifyStart + 1] -= replaceCharCount;
+                modifyEnd += 2;
+            } else {
+                if (rangeCount + 2 > ranges.length) {
+                    int[] newRanges = new int[ranges.length + (GROW << 1)];
+                    System.arraycopy(ranges, 0, newRanges, 0, rangeCount);
+                    ranges = newRanges;
+                    StyleRange[] newStyles = new StyleRange[styles.length + GROW];
+                    System.arraycopy(styles, 0, newStyles, 0, styleCount);
+                    styles = newStyles;
+                }
+                System.arraycopy(ranges, modifyStart + 2, ranges, modifyStart + 4, rangeCount - (modifyStart + 2));
+                System.arraycopy(styles, (modifyStart + 2) >> 1, styles, (modifyStart + 4) >> 1, styleCount - ((modifyStart + 2) >> 1));
+                ranges[modifyStart + 3] = ranges[modifyStart] + ranges[modifyStart + 1] - end;
+                ranges[modifyStart + 2] = start + newCharCount;
+                ranges[modifyStart + 1] = start - ranges[modifyStart];
+                styles[(modifyStart >> 1) + 1] = styles[modifyStart >> 1];
+                rangeCount += 2;
+                styleCount++;
+                modifyEnd += 4;
+            }
+            if (offset !is 0) {
+                for (int i = modifyEnd; i < rangeCount; i += 2) {
+                    ranges[i] += offset;
+                }
+            }
+        } else {
+            if (ranges[modifyStart] < start && start < ranges[modifyStart] + ranges[modifyStart + 1]) {
+                ranges[modifyStart + 1] = start - ranges[modifyStart];
+                modifyStart += 2;
+            }
+            if (modifyEnd < rangeCount && ranges[modifyEnd] < end && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) {
+                ranges[modifyEnd + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - end;
+                ranges[modifyEnd] = end;
+            }
+            if (offset !is 0) {
+                for (int i = modifyEnd; i < rangeCount; i += 2) {
+                    ranges[i] += offset;
+                }
+            }
+            System.arraycopy(ranges, modifyEnd, ranges, modifyStart, rangeCount - modifyEnd);
+            System.arraycopy(styles, modifyEnd >> 1, styles, modifyStart >> 1, styleCount - (modifyEnd >> 1));
+            styleCount -= (modifyEnd - modifyStart) >> 1;
+        }
+    } else {
+        int modifyStart = getRangeIndex(start, -1, styleCount);
+        if (modifyStart is styleCount) return;
+        int end = start + replaceCharCount;
+        int modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount);
+        int offset = newCharCount - replaceCharCount;
+        if (modifyStart is modifyEnd && styles[modifyStart].start < start && end < styles[modifyEnd].start + styles[modifyEnd].length) {
+            if (newCharCount is 0) {
+                styles[modifyStart].length -= replaceCharCount;
+                modifyEnd++;
+            } else {
+                if (styleCount + 1 > styles.length) {
+                    StyleRange[] newStyles = new StyleRange[styles.length + GROW];
+                    System.arraycopy(styles, 0, newStyles, 0, styleCount);
+                    styles = newStyles;
+                }
+                System.arraycopy(styles, modifyStart + 1, styles, modifyStart + 2, styleCount - (modifyStart + 1));
+                styles[modifyStart + 1] = cast(StyleRange)styles[modifyStart].clone();
+                styles[modifyStart + 1].length = styles[modifyStart].start + styles[modifyStart].length - end;
+                styles[modifyStart + 1].start = start + newCharCount;
+                styles[modifyStart].length = start - styles[modifyStart].start;
+                styleCount++;
+                modifyEnd += 2;
+            }
+            if (offset !is 0) {
+                for (int i = modifyEnd; i < styleCount; i++) {
+                    styles[i].start += offset;
+                }
+            }
+        } else {
+            if (styles[modifyStart].start < start && start < styles[modifyStart].start + styles[modifyStart].length) {
+                styles[modifyStart].length = start - styles[modifyStart].start;
+                modifyStart++;
+            }
+            if (modifyEnd < styleCount && styles[modifyEnd].start < end && end < styles[modifyEnd].start + styles[modifyEnd].length) {
+                styles[modifyEnd].length = styles[modifyEnd].start + styles[modifyEnd].length - end;
+                styles[modifyEnd].start = end;
+            }
+            if (offset !is 0) {
+                for (int i = modifyEnd; i < styleCount; i++) {
+                    styles[i].start += offset;
+                }
+            }
+            System.arraycopy(styles, modifyEnd, styles, modifyStart, styleCount - modifyEnd);
+            styleCount -= modifyEnd - modifyStart;
+        }
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TableCursor.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,710 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.TableCursor;
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Canvas;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.ScrollBar;
+import dwt.widgets.Table;
+import dwt.widgets.TableColumn;
+import dwt.widgets.TableItem;
+import dwt.widgets.TypedListener;
+import dwt.widgets.Widget;
+import dwt.dwthelper.utils;
+
+/**
+ * A TableCursor provides a way for the user to navigate around a Table
+ * using the keyboard.  It also provides a mechanism for selecting an
+ * individual cell in a table.
+ *
+ * <p> Here is an example of using a TableCursor to navigate to a cell and then edit it.
+ *
+ * <code><pre>
+ *  public static void main(String[] args) {
+ *      Display display = new Display();
+ *      Shell shell = new Shell(display);
+ *      shell.setLayout(new GridLayout());
+ *
+ *      // create a a table with 3 columns and fill with data
+ *      final Table table = new Table(shell, DWT.BORDER | DWT.MULTI | DWT.FULL_SELECTION);
+ *      table.setLayoutData(new GridData(GridData.FILL_BOTH));
+ *      TableColumn column1 = new TableColumn(table, DWT.NONE);
+ *      TableColumn column2 = new TableColumn(table, DWT.NONE);
+ *      TableColumn column3 = new TableColumn(table, DWT.NONE);
+ *      for (int i = 0; i &lt; 100; i++) {
+ *          TableItem item = new TableItem(table, DWT.NONE);
+ *          item.setText(new String[] { "cell "+i+" 0", "cell "+i+" 1", "cell "+i+" 2"});
+ *      }
+ *      column1.pack();
+ *      column2.pack();
+ *      column3.pack();
+ *
+ *      // create a TableCursor to navigate around the table
+ *      final TableCursor cursor = new TableCursor(table, DWT.NONE);
+ *      // create an editor to edit the cell when the user hits "ENTER"
+ *      // while over a cell in the table
+ *      final ControlEditor editor = new ControlEditor(cursor);
+ *      editor.grabHorizontal = true;
+ *      editor.grabVertical = true;
+ *
+ *      cursor.addSelectionListener(new SelectionAdapter() {
+ *          // when the TableEditor is over a cell, select the corresponding row in
+ *          // the table
+ *          public void widgetSelected(SelectionEvent e) {
+ *              table.setSelection(new TableItem[] {cursor.getRow()});
+ *          }
+ *          // when the user hits "ENTER" in the TableCursor, pop up a text editor so that
+ *          // they can change the text of the cell
+ *          public void widgetDefaultSelected(SelectionEvent e){
+ *              final Text text = new Text(cursor, DWT.NONE);
+ *              TableItem row = cursor.getRow();
+ *              int column = cursor.getColumn();
+ *              text.setText(row.getText(column));
+ *              text.addKeyListener(new KeyAdapter() {
+ *                  public void keyPressed(KeyEvent e) {
+ *                      // close the text editor and copy the data over
+ *                      // when the user hits "ENTER"
+ *                      if (e.character is DWT.CR) {
+ *                          TableItem row = cursor.getRow();
+ *                          int column = cursor.getColumn();
+ *                          row.setText(column, text.getText());
+ *                          text.dispose();
+ *                      }
+ *                      // close the text editor when the user hits "ESC"
+ *                      if (e.character is DWT.ESC) {
+ *                          text.dispose();
+ *                      }
+ *                  }
+ *              });
+ *              editor.setEditor(text);
+ *              text.setFocus();
+ *          }
+ *      });
+ *      // Hide the TableCursor when the user hits the "MOD1" or "MOD2" key.
+ *      // This allows the user to select multiple items in the table.
+ *      cursor.addKeyListener(new KeyAdapter() {
+ *          public void keyPressed(KeyEvent e) {
+ *              if (e.keyCode is DWT.MOD1 ||
+ *                  e.keyCode is DWT.MOD2 ||
+ *                  (e.stateMask & DWT.MOD1) !is 0 ||
+ *                  (e.stateMask & DWT.MOD2) !is 0) {
+ *                  cursor.setVisible(false);
+ *              }
+ *          }
+ *      });
+ *      // Show the TableCursor when the user releases the "MOD2" or "MOD1" key.
+ *      // This signals the end of the multiple selection task.
+ *      table.addKeyListener(new KeyAdapter() {
+ *          public void keyReleased(KeyEvent e) {
+ *              if (e.keyCode is DWT.MOD1 && (e.stateMask & DWT.MOD2) !is 0) return;
+ *              if (e.keyCode is DWT.MOD2 && (e.stateMask & DWT.MOD1) !is 0) return;
+ *              if (e.keyCode !is DWT.MOD1 && (e.stateMask & DWT.MOD1) !is 0) return;
+ *              if (e.keyCode !is DWT.MOD2 && (e.stateMask & DWT.MOD2) !is 0) return;
+ *
+ *              TableItem[] selection = table.getSelection();
+ *              TableItem row = (selection.length is 0) ? table.getItem(table.getTopIndex()) : selection[0];
+ *              table.showItem(row);
+ *              cursor.setSelection(row, 0);
+ *              cursor.setVisible(true);
+ *              cursor.setFocus();
+ *          }
+ *      });
+ *
+ *      shell.open();
+ *      while (!shell.isDisposed()) {
+ *          if (!display.readAndDispatch())
+ *              display.sleep();
+ *      }
+ *      display.dispose();
+ *  }
+ * </pre></code>
+ *
+ * <dl>
+ * <dt><b>Styles:</b></dt>
+ * <dd>BORDER</dd>
+ * <dt><b>Events:</b></dt>
+ * <dd>Selection, DefaultSelection</dd>
+ * </dl>
+ *
+ * @since 2.0
+ *
+ */
+public class TableCursor : Canvas {
+
+    alias Canvas.dispose dispose;
+
+    Table table;
+    TableItem row = null;
+    TableColumn column = null;
+    Listener tableListener, resizeListener, disposeItemListener, disposeColumnListener;
+
+    // By default, invert the list selection colors
+    static final int BACKGROUND = DWT.COLOR_LIST_SELECTION_TEXT;
+    static final int FOREGROUND = DWT.COLOR_LIST_SELECTION;
+
+/**
+ * Constructs a new instance of this class given its parent
+ * table and a style value describing its behavior and appearance.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>DWT</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>DWT</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 Table control which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
+ * </ul>
+ *
+ * @see DWT#BORDER
+ * @see Widget#checkSubclass()
+ * @see Widget#getStyle()
+ */
+public this(Table parent, int style) {
+    super(parent, style);
+    table = parent;
+    setBackground(null);
+    setForeground(null);
+
+    Listener listener = new class() Listener {
+        public void handleEvent(Event event) {
+            switch (event.type) {
+                case DWT.Dispose :
+                    dispose(event);
+                    break;
+                case DWT.FocusIn :
+                case DWT.FocusOut :
+                    redraw();
+                    break;
+                case DWT.KeyDown :
+                    keyDown(event);
+                    break;
+                case DWT.Paint :
+                    paint(event);
+                    break;
+                case DWT.Traverse : {
+                    event.doit = true;
+                    switch (event.detail) {
+                        case DWT.TRAVERSE_ARROW_NEXT :
+                        case DWT.TRAVERSE_ARROW_PREVIOUS :
+                        case DWT.TRAVERSE_RETURN :
+                            event.doit = false;
+                            break;
+                        default:
+                    }
+                    break;
+                }
+                default:
+            }
+        }
+    };
+    int[] events = [DWT.Dispose, DWT.FocusIn, DWT.FocusOut, DWT.KeyDown, DWT.Paint, DWT.Traverse];
+    for (int i = 0; i < events.length; i++) {
+        addListener(events[i], listener);
+    }
+
+    tableListener = new class() Listener {
+        public void handleEvent(Event event) {
+            switch (event.type) {
+                case DWT.MouseDown :
+                    tableMouseDown(event);
+                    break;
+                case DWT.FocusIn :
+                    tableFocusIn(event);
+                    break;
+                default:
+            }
+        }
+    };
+    table.addListener(DWT.FocusIn, tableListener);
+    table.addListener(DWT.MouseDown, tableListener);
+
+    disposeItemListener = new class() Listener {
+        public void handleEvent(Event event) {
+            row = null;
+            column = null;
+            _resize();
+        }
+    };
+    disposeColumnListener = new class() Listener {
+        public void handleEvent(Event event) {
+            row = null;
+            column = null;
+            _resize();
+        }
+    };
+    resizeListener = new class() Listener {
+        public void handleEvent(Event event) {
+            _resize();
+        }
+    };
+    ScrollBar hBar = table.getHorizontalBar();
+    if (hBar !is null) {
+        hBar.addListener(DWT.Selection, resizeListener);
+    }
+    ScrollBar vBar = table.getVerticalBar();
+    if (vBar !is null) {
+        vBar.addListener(DWT.Selection, resizeListener);
+    }
+}
+
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when the user changes the receiver's selection, by sending
+ * it one of the messages defined in the <code>SelectionListener</code>
+ * interface.
+ * <p>
+ * When <code>widgetSelected</code> is called, the item field of the event object is valid.
+ * If the receiver has <code>DWT.CHECK</code> style set and the check selection changes,
+ * the event object detail field contains the value <code>DWT.CHECK</code>.
+ * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked.
+ * </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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 SelectionEvent
+ * @see #removeSelectionListener(SelectionListener)
+ *
+ */
+public void addSelectionListener(SelectionListener listener) {
+    checkWidget();
+    if (listener is null)
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    TypedListener typedListener = new TypedListener(listener);
+    addListener(DWT.Selection, typedListener);
+    addListener(DWT.DefaultSelection, typedListener);
+}
+
+void dispose(Event event) {
+    table.removeListener(DWT.FocusIn, tableListener);
+    table.removeListener(DWT.MouseDown, tableListener);
+    if (column !is null) {
+        column.removeListener(DWT.Dispose, disposeColumnListener);
+        column.removeListener(DWT.Move, resizeListener);
+        column.removeListener(DWT.Resize, resizeListener);
+        column = null;
+    }
+    if (row !is null) {
+        row.removeListener(DWT.Dispose, disposeItemListener);
+        row = null;
+    }
+    ScrollBar hBar = table.getHorizontalBar();
+    if (hBar !is null) {
+        hBar.removeListener(DWT.Selection, resizeListener);
+    }
+    ScrollBar vBar = table.getVerticalBar();
+    if (vBar !is null) {
+        vBar.removeListener(DWT.Selection, resizeListener);
+    }
+}
+
+void keyDown(Event event) {
+    if (row is null) return;
+    switch (event.character) {
+        case DWT.CR :
+            notifyListeners(DWT.DefaultSelection, new Event());
+            return;
+        default:
+    }
+    int rowIndex = table.indexOf(row);
+    int columnIndex = column is null ? 0 : table.indexOf(column);
+    switch (event.keyCode) {
+        case DWT.ARROW_UP :
+            setRowColumn(Math.max(0, rowIndex - 1), columnIndex, true);
+            break;
+        case DWT.ARROW_DOWN :
+            setRowColumn(Math.min(rowIndex + 1, table.getItemCount() - 1), columnIndex, true);
+            break;
+        case DWT.ARROW_LEFT :
+        case DWT.ARROW_RIGHT :
+            {
+                int columnCount = table.getColumnCount();
+                if (columnCount is 0) break;
+                int[] order = table.getColumnOrder();
+                int index = 0;
+                while (index < order.length) {
+                    if (order[index] is columnIndex) break;
+                    index++;
+                }
+                if (index is order.length) index = 0;
+                int leadKey = (getStyle() & DWT.RIGHT_TO_LEFT) !is 0 ? DWT.ARROW_RIGHT : DWT.ARROW_LEFT;
+                if (event.keyCode is leadKey) {
+                   setRowColumn(rowIndex, order[Math.max(0, index - 1)], true);
+                } else {
+                   setRowColumn(rowIndex, order[Math.min(columnCount - 1, index + 1)], true);
+                }
+                break;
+            }
+        case DWT.HOME :
+            setRowColumn(0, columnIndex, true);
+            break;
+        case DWT.END :
+            {
+                int i = table.getItemCount() - 1;
+                setRowColumn(i, columnIndex, true);
+                break;
+            }
+        case DWT.PAGE_UP :
+            {
+                int index = table.getTopIndex();
+                if (index is rowIndex) {
+                    Rectangle rect = table.getClientArea();
+                    TableItem item = table.getItem(index);
+                    Rectangle itemRect = item.getBounds(0);
+                    rect.height -= itemRect.y;
+                    int height = table.getItemHeight();
+                    int page = Math.max(1, rect.height / height);
+                    index = Math.max(0, index - page + 1);
+                }
+                setRowColumn(index, columnIndex, true);
+                break;
+            }
+        case DWT.PAGE_DOWN :
+            {
+                int index = table.getTopIndex();
+                Rectangle rect = table.getClientArea();
+                TableItem item = table.getItem(index);
+                Rectangle itemRect = item.getBounds(0);
+                rect.height -= itemRect.y;
+                int height = table.getItemHeight();
+                int page = Math.max(1, rect.height / height);
+                int end = table.getItemCount() - 1;
+                index = Math.min(end, index + page - 1);
+                if (index is rowIndex) {
+                    index = Math.min(end, index + page - 1);
+                }
+                setRowColumn(index, columnIndex, true);
+                break;
+            }
+        default:
+    }
+}
+
+void paint(Event event) {
+    if (row is null) return;
+    int columnIndex = column is null ? 0 : table.indexOf(column);
+    GC gc = event.gc;
+    Display display = getDisplay();
+    gc.setBackground(getBackground());
+    gc.setForeground(getForeground());
+    gc.fillRectangle(event.x, event.y, event.width, event.height);
+    int x = 0;
+    Point size = getSize();
+    Image image = row.getImage(columnIndex);
+    if (image !is null) {
+        Rectangle imageSize = image.getBounds();
+        int imageY = (size.y - imageSize.height) / 2;
+        gc.drawImage(image, x, imageY);
+        x += imageSize.width;
+    }
+    char[] text = row.getText(columnIndex);
+    if (text.length > 0) {
+        Rectangle bounds = row.getBounds(columnIndex);
+        Point extent = gc.stringExtent(text);
+        // Temporary code - need a better way to determine table trim
+        char[] platform = DWT.getPlatform();
+        if ("win32"==platform) { //$NON-NLS-1$
+            if (table.getColumnCount() is 0 || columnIndex is 0) {
+                x += 2;
+            } else {
+                int alignmnent = column.getAlignment();
+                switch (alignmnent) {
+                    case DWT.LEFT:
+                        x += 6;
+                        break;
+                    case DWT.RIGHT:
+                        x = bounds.width - extent.x - 6;
+                        break;
+                    case DWT.CENTER:
+                        x += (bounds.width - x - extent.x) / 2;
+                        break;
+                    default:
+                }
+            }
+        }  else {
+            if (table.getColumnCount() is 0) {
+                x += 5;
+            } else {
+                int alignmnent = column.getAlignment();
+                switch (alignmnent) {
+                    case DWT.LEFT:
+                        x += 5;
+                        break;
+                    case DWT.RIGHT:
+                        x = bounds.width- extent.x - 2;
+                        break;
+                    case DWT.CENTER:
+                        x += (bounds.width - x - extent.x) / 2 + 2;
+                        break;
+                    default:
+                }
+            }
+        }
+        int textY = (size.y - extent.y) / 2;
+        gc.drawString(text, x, textY);
+    }
+    if (isFocusControl()) {
+        gc.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
+        gc.setForeground(display.getSystemColor(DWT.COLOR_WHITE));
+        gc.drawFocus(0, 0, size.x, size.y);
+    }
+}
+
+void tableFocusIn(Event event) {
+    if (isDisposed())
+        return;
+    if (isVisible())
+        setFocus();
+}
+
+void tableMouseDown(Event event) {
+    if (isDisposed() || !isVisible()) return;
+    Point pt = new Point(event.x, event.y);
+    int lineWidth = table.getLinesVisible() ? table.getGridLineWidth() : 0;
+    TableItem item = table.getItem(pt);
+    if ((table.getStyle() & DWT.FULL_SELECTION) !is 0) {
+        if (item is null) return;
+    } else {
+        int start = item !is null ? table.indexOf(item) : table.getTopIndex();
+        int end = table.getItemCount();
+        Rectangle clientRect = table.getClientArea();
+        for (int i = start; i < end; i++) {
+            TableItem nextItem = table.getItem(i);
+            Rectangle rect = nextItem.getBounds(0);
+            if (pt.y >= rect.y && pt.y < rect.y + rect.height + lineWidth) {
+                item = nextItem;
+                break;
+            }
+            if (rect.y > clientRect.y + clientRect.height)  return;
+        }
+        if (item is null) return;
+    }
+    TableColumn newColumn = null;
+    int columnCount = table.getColumnCount();
+    if (columnCount > 0) {
+        for (int i = 0; i < columnCount; i++) {
+            Rectangle rect = item.getBounds(i);
+            rect.width += lineWidth;
+            rect.height += lineWidth;
+            if (rect.contains(pt)) {
+                newColumn = table.getColumn(i);
+                break;
+            }
+        }
+        if (newColumn is null) {
+            newColumn = table.getColumn(0);
+        }
+    }
+    setRowColumn(item, newColumn, true);
+    setFocus();
+    return;
+}
+void setRowColumn(int row, int column, bool notify) {
+    TableItem item = row is -1 ? null : table.getItem(row);
+    TableColumn col = column is -1 || table.getColumnCount() is 0 ? null : table.getColumn(column);
+    setRowColumn(item, col, notify);
+}
+void setRowColumn(TableItem row, TableColumn column, bool notify) {
+    if (this.row is row && this.column is column) {
+        return;
+    }
+    if (this.row !is null && this.row !is row) {
+        this.row.removeListener(DWT.Dispose, disposeItemListener);
+        this.row = null;
+    }
+    if (this.column !is null && this.column !is column) {
+        this.column.removeListener(DWT.Dispose, disposeColumnListener);
+        this.column.removeListener(DWT.Move, resizeListener);
+        this.column.removeListener(DWT.Resize, resizeListener);
+        this.column = null;
+    }
+    if (row !is null) {
+        if (this.row !is row) {
+            this.row = row;
+            row.addListener(DWT.Dispose, disposeItemListener);
+            table.showItem(row);
+        }
+        if (this.column !is column && column !is null) {
+            this.column = column;
+            column.addListener(DWT.Dispose, disposeColumnListener);
+            column.addListener(DWT.Move, resizeListener);
+            column.addListener(DWT.Resize, resizeListener);
+            table.showColumn(column);
+        }
+        int columnIndex = column is null ? 0 : table.indexOf(column);
+        setBounds(row.getBounds(columnIndex));
+        redraw();
+        if (notify) {
+            notifyListeners(DWT.Selection, new Event());
+        }
+    }
+}
+
+public override void setVisible(bool visible) {
+    checkWidget();
+    if (visible) _resize();
+    super.setVisible(visible);
+}
+
+/**
+ * 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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(SelectionListener)
+ *
+ * @since 3.0
+ */
+public void removeSelectionListener(SelectionListener listener) {
+    checkWidget();
+    if (listener is null) {
+        DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    }
+    removeListener(DWT.Selection, listener);
+    removeListener(DWT.DefaultSelection, listener);
+}
+
+void _resize() {
+    if (row is null) {
+        setBounds(-200, -200, 0, 0);
+    } else {
+        int columnIndex = column is null ? 0 : table.indexOf(column);
+        setBounds(row.getBounds(columnIndex));
+    }
+}
+/**
+ * Returns the column over which the TableCursor is positioned.
+ *
+ * @return the column for the current position
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getColumn() {
+    checkWidget();
+    return column is null ? 0 : table.indexOf(column);
+}
+/**
+ * Returns the row over which the TableCursor is positioned.
+ *
+ * @return the item for the current position
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TableItem getRow() {
+    checkWidget();
+    return row;
+}
+public override void setBackground (Color color) {
+    if (color is null) color = getDisplay().getSystemColor(BACKGROUND);
+    super.setBackground(color);
+    redraw();
+}
+public override void setForeground (Color color) {
+    if (color is null) color = getDisplay().getSystemColor(FOREGROUND);
+    super.setForeground(color);
+    redraw();
+}
+/**
+ * Positions the TableCursor over the cell at the given row and column in the parent table.
+ *
+ * @param row the index of the row for the cell to select
+ * @param column the index of column for the cell to select
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setSelection(int row, int column) {
+    checkWidget();
+    int columnCount = table.getColumnCount();
+    int maxColumnIndex =  columnCount is 0 ? 0 : columnCount - 1;
+    if (row < 0
+        || row >= table.getItemCount()
+        || column < 0
+        || column > maxColumnIndex)
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    setRowColumn(row, column, false);
+}
+/**
+ * Positions the TableCursor over the cell at the given row and column in the parent table.
+ *
+ * @param row the TableItem of the row for the cell to select
+ * @param column the index of column for the cell to select
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setSelection(TableItem row, int column) {
+    checkWidget();
+    int columnCount = table.getColumnCount();
+    int maxColumnIndex =  columnCount is 0 ? 0 : columnCount - 1;
+    if (row is null
+        || row.isDisposed()
+        || column < 0
+        || column > maxColumnIndex)
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    setRowColumn(table.indexOf(row), column, false);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TableEditor.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.TableEditor;
+
+
+
+import dwt.DWT;
+import dwt.events.ControlEvent;
+import dwt.events.ControlListener;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Table;
+import dwt.widgets.TableColumn;
+import dwt.widgets.TableItem;
+import dwt.custom.ControlEditor;
+import dwt.dwthelper.Runnable;
+import dwt.dwthelper.utils;
+
+/**
+*
+* A TableEditor is a manager for a Control that appears above a cell in a Table and tracks with the
+* moving and resizing of that cell.  It can be used to display a text widget above a cell
+* in a Table so that the user can edit the contents of that cell.  It can also be used to display
+* a button that can launch a dialog for modifying the contents of the associated cell.
+*
+* <p> Here is an example of using a TableEditor:
+* <code><pre>
+*   final Table table = new Table(shell, DWT.FULL_SELECTION | DWT.HIDE_SELECTION);
+*   TableColumn column1 = new TableColumn(table, DWT.NONE);
+*   TableColumn column2 = new TableColumn(table, DWT.NONE);
+*   for (int i = 0; i &lt; 10; i++) {
+*       TableItem item = new TableItem(table, DWT.NONE);
+*       item.setText(new char[][] {"item " + i, "edit this value"});
+*   }
+*   column1.pack();
+*   column2.pack();
+*
+*   final TableEditor editor = new TableEditor(table);
+*   //The editor must have the same size as the cell and must
+*   //not be any smaller than 50 pixels.
+*   editor.horizontalAlignment = DWT.LEFT;
+*   editor.grabHorizontal = true;
+*   editor.minimumWidth = 50;
+*   // editing the second column
+*   final int EDITABLECOLUMN = 1;
+*
+*   table.addSelectionListener(new SelectionAdapter() {
+*       public void widgetSelected(SelectionEvent e) {
+*           // Clean up any previous editor control
+*           Control oldEditor = editor.getEditor();
+*           if (oldEditor !is null) oldEditor.dispose();
+*
+*           // Identify the selected row
+*           TableItem item = (TableItem)e.item;
+*           if (item is null) return;
+*
+*           // The control that will be the editor must be a child of the Table
+*           Text newEditor = new Text(table, DWT.NONE);
+*           newEditor.setText(item.getText(EDITABLECOLUMN));
+*           newEditor.addModifyListener(new ModifyListener() {
+*               public void modifyText(ModifyEvent e) {
+*                   Text text = (Text)editor.getEditor();
+*                   editor.getItem().setText(EDITABLECOLUMN, text.getText());
+*               }
+*           });
+*           newEditor.selectAll();
+*           newEditor.setFocus();
+*           editor.setEditor(newEditor, item, EDITABLECOLUMN);
+*       }
+*   });
+* </pre></code>
+*/
+public class TableEditor : ControlEditor {
+    Table table;
+    TableItem item;
+    int column = -1;
+    ControlListener columnListener;
+    Runnable timer;
+    static final int TIMEOUT = 1500;
+/**
+* Creates a TableEditor for the specified Table.
+*
+* @param table the Table Control above which this editor will be displayed
+*
+*/
+public this (Table table) {
+    super(table);
+    this.table = table;
+
+    columnListener = new class() ControlListener {
+        public void controlMoved(ControlEvent e){
+            layout ();
+        }
+        public void controlResized(ControlEvent e){
+            layout ();
+        }
+    };
+    timer = new class() Runnable {
+        public void run() {
+            layout ();
+        }
+    };
+
+    // To be consistent with older versions of DWT, grabVertical defaults to true
+    grabVertical = true;
+}
+override Rectangle computeBounds () {
+    if (item is null || column is -1 || item.isDisposed()) return new Rectangle(0, 0, 0, 0);
+    Rectangle cell = item.getBounds(column);
+    Rectangle rect = item.getImageBounds(column);
+    cell.x = rect.x + rect.width;
+    cell.width -= rect.width;
+    Rectangle area = table.getClientArea();
+    if (cell.x < area.x + area.width) {
+        if (cell.x + cell.width > area.x + area.width) {
+            cell.width = area.x + area.width - cell.x;
+        }
+    }
+    Rectangle editorRect = new Rectangle(cell.x, cell.y, minimumWidth, minimumHeight);
+
+    if (grabHorizontal) {
+        editorRect.width = Math.max(cell.width, minimumWidth);
+    }
+
+    if (grabVertical) {
+        editorRect.height = Math.max(cell.height, minimumHeight);
+    }
+
+    if (horizontalAlignment is DWT.RIGHT) {
+        editorRect.x += cell.width - editorRect.width;
+    } else if (horizontalAlignment is DWT.LEFT) {
+        // do nothing - cell.x is the right answer
+    } else { // default is CENTER
+        editorRect.x += (cell.width - editorRect.width)/2;
+    }
+
+    if (verticalAlignment is DWT.BOTTOM) {
+        editorRect.y += cell.height - editorRect.height;
+    } else if (verticalAlignment is DWT.TOP) {
+        // do nothing - cell.y is the right answer
+    } else { // default is CENTER
+        editorRect.y += (cell.height - editorRect.height)/2;
+    }
+    return editorRect;
+}
+/**
+ * Removes all associations between the TableEditor and the cell in the table.  The
+ * Table and the editor Control are <b>not</b> disposed.
+ */
+public override void dispose () {
+    if (table !is null && !table.isDisposed()) {
+        if (this.column > -1 && this.column < table.getColumnCount()){
+            TableColumn tableColumn = table.getColumn(this.column);
+            tableColumn.removeControlListener(columnListener);
+        }
+    }
+    columnListener = null;
+    table = null;
+    item = null;
+    column = -1;
+    timer = null;
+    super.dispose();
+}
+/**
+* Returns the zero based index of the column of the cell being tracked by this editor.
+*
+* @return the zero based index of the column of the cell being tracked by this editor
+*/
+public int getColumn () {
+    return column;
+}
+/**
+* Returns the TableItem for the row of the cell being tracked by this editor.
+*
+* @return the TableItem for the row of the cell being tracked by this editor
+*/
+public TableItem getItem () {
+    return item;
+}
+void resize () {
+    layout();
+    /*
+     * On some platforms, the table scrolls when an item that
+     * is partially visible at the bottom of the table is
+     * selected.  Ensure that the correct row is edited by
+     * laying out one more time in a timerExec().
+     */
+    if (table !is null) {
+        Display display = table.getDisplay();
+        display.timerExec(-1, timer);
+        display.timerExec(TIMEOUT, timer);
+    }
+}
+/**
+* Sets the zero based index of the column of the cell being tracked by this editor.
+*
+* @param column the zero based index of the column of the cell being tracked by this editor
+*/
+public void setColumn(int column) {
+    int columnCount = table.getColumnCount();
+    // Separately handle the case where the table has no TableColumns.
+    // In this situation, there is a single default column.
+    if (columnCount is 0) {
+        this.column = (column is 0) ? 0 : -1;
+        resize();
+        return;
+    }
+    if (this.column > -1 && this.column < columnCount){
+        TableColumn tableColumn = table.getColumn(this.column);
+        tableColumn.removeControlListener(columnListener);
+        this.column = -1;
+    }
+
+    if (column < 0  || column >= table.getColumnCount()) return;
+
+    this.column = column;
+    TableColumn tableColumn = table.getColumn(this.column);
+    tableColumn.addControlListener(columnListener);
+    resize();
+}
+public void setItem (TableItem item) {
+    this.item = item;
+    resize();
+}
+public override void setEditor (Control editor) {
+    super.setEditor(editor);
+    resize();
+}
+/**
+* Specify the Control that is to be displayed and the cell in the table that it is to be positioned above.
+*
+* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Table control
+* specified in the TableEditor constructor.
+*
+* @param editor the Control that is displayed above the cell being edited
+* @param item the TableItem for the row of the cell being tracked by this editor
+* @param column the zero based index of the column of the cell being tracked by this editor
+*/
+public void setEditor (Control editor, TableItem item, int column) {
+    setItem(item);
+    setColumn(column);
+    setEditor(editor);
+}
+public override void layout () {
+    if (table is null || table.isDisposed()) return;
+    if (item is null || item.isDisposed()) return;
+    int columnCount = table.getColumnCount();
+    if (columnCount is 0 && column !is 0) return;
+    if (columnCount > 0 && (column < 0 || column >= columnCount)) return;
+    super.layout();
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TableTree.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,843 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.TableTree;
+
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.events.TreeListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.ImageData;
+import dwt.graphics.PaletteData;
+import dwt.graphics.Point;
+import dwt.graphics.RGB;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.Menu;
+import dwt.widgets.Table;
+import dwt.widgets.TableItem;
+import dwt.widgets.TypedListener;
+import dwt.custom.TableTreeItem;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A TableTree is a selectable user interface object
+ * that displays a hierarchy of items, and issues
+ * notification when an item is selected.
+ * A TableTree may be single or multi select.
+ * <p>
+ * The item children that may be added to instances of this class
+ * must be of type <code>TableTreeItem</code>.
+ * </p><p>
+ * Note that although this class is a subclass of <code>Composite</code>,
+ * it does not make sense to add <code>Control</code> children to it,
+ * or set a layout on it.
+ * </p><p>
+ * <dl>
+ *  <dt><b>Styles:</b> <dd> SINGLE, MULTI, CHECK, FULL_SELECTION
+ *  <dt><b>Events:</b> <dd> Selection, DefaultSelection, Collapse, Expand
+ * </dl>
+ * <p>
+ * Note: Only one of the styles SINGLE, and MULTI may be specified.
+ * </p>
+ *
+ * @deprecated As of 3.1 use Tree, TreeItem and TreeColumn
+ */
+public class TableTree : Composite {
+
+    alias Composite.computeSize computeSize;
+
+    Table table;
+    TableTreeItem[] items;
+    Image plusImage, minusImage, sizeImage;
+
+    /*
+    * TableTreeItems are not treated as children but rather as items.
+    * When the TableTree is disposed, all children are disposed because
+    * TableTree inherits this behaviour from Composite.  The items
+    * must be disposed separately.  Because TableTree is not part of
+    * the org.eclipse.swt.widgets module, the method releaseWidget can
+    * not be overridden (this is how items are disposed of in Table and Tree).
+    * Instead, the items are disposed of in response to the dispose event on the
+    * TableTree.  The "inDispose" flag is used to distinguish between disposing
+    * one TableTreeItem (e.g. when removing an entry from the TableTree) and
+    * disposing the entire TableTree.
+    */
+    bool inDispose = false;
+
+    static final TableTreeItem[] EMPTY_ITEMS;
+    static final char[][] EMPTY_TEXTS;
+    static final Image[] EMPTY_IMAGES;
+    static final char[] ITEMID = "TableTreeItemID"; //$NON-NLS-1$
+
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#SINGLE
+ * @see DWT#MULTI
+ * @see DWT#CHECK
+ * @see DWT#FULL_SELECTION
+ * @see #getStyle
+ */
+public this(Composite parent, int style) {
+    super(parent, checkStyle (style));
+    items = EMPTY_ITEMS;
+    table = new Table(this, style);
+    Listener tableListener = new class() Listener {
+        public void handleEvent(Event e) {
+            switch (e.type) {
+            case DWT.MouseDown: onMouseDown(e); break;
+            case DWT.Selection: onSelection(e); break;
+            case DWT.DefaultSelection: onSelection(e); break;
+            case DWT.KeyDown: onKeyDown(e); break;
+            default:
+            }
+        }
+    };
+    int[] tableEvents = [DWT.MouseDown,
+                                   DWT.Selection,
+                                   DWT.DefaultSelection,
+                                   DWT.KeyDown];
+    for (int i = 0; i < tableEvents.length; i++) {
+        table.addListener(tableEvents[i], tableListener);
+    }
+
+    Listener listener = new class() Listener {
+        public void handleEvent(Event e) {
+            switch (e.type) {
+            case DWT.Dispose: onDispose(e); break;
+            case DWT.Resize:  onResize(e); break;
+            case DWT.FocusIn: onFocusIn(e); break;
+            default:
+            }
+        }
+    };
+    int[] events = [DWT.Dispose,
+                              DWT.Resize,
+                              DWT.FocusIn];
+    for (int i = 0; i < events.length; i++) {
+        addListener(events[i], listener);
+    }
+}
+
+int addItem(TableTreeItem item, int index) {
+    if (index < 0 || index > items.length) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    TableTreeItem[] newItems = new TableTreeItem[items.length + 1];
+    System.arraycopy(items, 0, newItems, 0, index);
+    newItems[index] = item;
+    System.arraycopy(items, index, newItems, index + 1, items.length - index);
+    items = newItems;
+
+    /* Return the index in the table where this table should be inserted */
+    if (index is items.length - 1 )
+        return table.getItemCount();
+    else
+        return table.indexOf(items[index+1].tableItem);
+}
+
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when the user changes the receiver's selection, by sending
+ * it one of the messages defined in the <code>SelectionListener</code>
+ * interface.
+ * <p>
+ * When <code>widgetSelected</code> is called, the item field of the event object is valid.
+ * If the receiver has <code>DWT.CHECK</code> style set and the check selection changes,
+ * the event object detail field contains the value <code>DWT.CHECK</code>.
+ * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked.
+ * The item field of the event object is valid for default selection, but the detail field is not used.
+ * </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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    TypedListener typedListener = new TypedListener (listener);
+    addListener (DWT.Selection,typedListener);
+    addListener (DWT.DefaultSelection,typedListener);
+}
+
+/**
+ * Adds the listener to the collection of listeners who will
+ * be notified when an item in the receiver is expanded or collapsed
+ * by sending it one of the messages defined in the <code>TreeListener</code>
+ * interface.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TreeListener
+ * @see #removeTreeListener
+ */
+public void addTreeListener(TreeListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    TypedListener typedListener = new TypedListener (listener);
+    addListener (DWT.Expand, typedListener);
+    addListener (DWT.Collapse, typedListener);
+}
+private static int checkStyle (int style) {
+    int mask = DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
+    style = style & mask;
+    return style;
+}
+public override Point computeSize (int wHint, int hHint, bool changed) {
+    checkWidget();
+    return table.computeSize (wHint, hHint, changed);
+}
+public override Rectangle computeTrim (int x, int y, int width, int height) {
+    checkWidget();
+    return table.computeTrim(x, y, width, height);
+}
+
+/**
+ * Deselects all items.
+ * <p>
+ * If an item is selected, it is deselected.
+ * If an item is not selected, it remains unselected.
+ *
+ * @exception DWTException <ul>
+ *  <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ *  <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * </ul>
+ */
+public void deselectAll () {
+    checkWidget();
+    table.deselectAll();
+}
+
+/* Expand upward from the specified leaf item. */
+void expandItem (TableTreeItem item) {
+    if (item is null) return;
+    expandItem(item.parentItem);
+    if (!item.getVisible()) item.setVisible(true);
+    if ( !item.expanded && item.items.length > 0) {
+        item.setExpanded(true);
+        Event event = new Event();
+        event.item = item;
+        notifyListeners(DWT.Expand, event);
+    }
+}
+public override Color getBackground () {
+    // This method must be overridden otherwise, in a TableTree in which the first
+    // item has no sub items, a grey (Widget background colour) square will appear in
+    // the first column of the first item.
+    // It is not possible in the constructor to set the background of the TableTree
+    // to be the same as the background of the Table because this interferes with
+    // the TableTree adapting to changes in the System color settings.
+    return table.getBackground();
+}
+public override Rectangle getClientArea () {
+    return table.getClientArea();
+}
+public override Color getForeground () {
+    return table.getForeground();
+}
+public override Font getFont () {
+    return table.getFont();
+}
+/**
+ * Gets the number of items.
+ * <p>
+ * @return the number of items in the widget
+ */
+public int getItemCount () {
+    //checkWidget();
+    return items.length;
+}
+
+/**
+ * Gets the height of one item.
+ * <p>
+ * This operation will fail if the height of
+ * one item could not be queried from the OS.
+ *
+ * @return the height of one item in the widget
+ *
+ * @exception DWTException <ul>
+ *  <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ *  <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * </ul>
+ */
+public int getItemHeight () {
+    checkWidget();
+    return table.getItemHeight();
+}
+
+/**
+ * Gets the items.
+ * <p>
+ * @return the items in the widget
+ */
+public TableTreeItem [] getItems () {
+    //checkWidget();
+    TableTreeItem[] newItems = new TableTreeItem[items.length];
+    System.arraycopy(items, 0, newItems, 0, items.length);
+    return newItems;
+}
+
+/**
+ * Gets the selected items.
+ * <p>
+ * This operation will fail if the selected
+ * items cannot be queried from the OS.
+ *
+ * @return the selected items in the widget
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public TableTreeItem [] getSelection () {
+    checkWidget();
+    TableItem[] selection = table.getSelection();
+    TableTreeItem [] result = new TableTreeItem[selection.length];
+    for (int i = 0; i < selection.length; i++){
+        result[i] = cast(TableTreeItem) selection[i].getData(ITEMID);
+    }
+    return result;
+}
+
+/**
+ * Gets the number of selected items.
+ * <p>
+ * This operation will fail if the number of selected
+ * items cannot be queried from the OS.
+ *
+ * @return the number of selected items in the widget
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public int getSelectionCount () {
+    checkWidget();
+    return table.getSelectionCount();
+}
+
+public override int getStyle () {
+    checkWidget();
+    return table.getStyle();
+}
+
+/**
+ * Returns the underlying Table control.
+ *
+ * @return the underlying Table control
+ */
+public Table getTable () {
+    //checkWidget();
+    return table;
+}
+
+void createImages () {
+
+    int itemHeight = sizeImage.getBounds().height;
+    // Calculate border around image.
+    // At least 9 pixels are needed to draw the image
+    // Leave at least a 6 pixel border.
+    int indent = Math.min(6, (itemHeight - 9) / 2);
+    indent = Math.max(0, indent);
+    int size = Math.max (10, itemHeight - 2 * indent);
+    size = ((size + 1) / 2) * 2; // size must be an even number
+    int midpoint = indent + size / 2;
+
+    Color foreground = getForeground();
+    Color plusMinus = getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW);
+    Color background = getBackground();
+
+    /* Plus image */
+    PaletteData palette = new PaletteData( [ foreground.getRGB(), background.getRGB(), plusMinus.getRGB()]);
+    ImageData imageData = new ImageData(itemHeight, itemHeight, 4, palette);
+    imageData.transparentPixel = 1;
+    plusImage = new Image(getDisplay(), imageData);
+    GC gc = new GC(plusImage);
+    gc.setBackground(background);
+    gc.fillRectangle(0, 0, itemHeight, itemHeight);
+    gc.setForeground(plusMinus);
+    gc.drawRectangle(indent, indent, size, size);
+    gc.setForeground(foreground);
+    gc.drawLine(midpoint, indent + 2, midpoint, indent + size - 2);
+    gc.drawLine(indent + 2, midpoint, indent + size - 2, midpoint);
+    gc.dispose();
+
+    /* Minus image */
+    palette = new PaletteData([foreground.getRGB(), background.getRGB(), plusMinus.getRGB()]);
+    imageData = new ImageData(itemHeight, itemHeight, 4, palette);
+    imageData.transparentPixel = 1;
+    minusImage = new Image(getDisplay(), imageData);
+    gc = new GC(minusImage);
+    gc.setBackground(background);
+    gc.fillRectangle(0, 0, itemHeight, itemHeight);
+    gc.setForeground(plusMinus);
+    gc.drawRectangle(indent, indent, size, size);
+    gc.setForeground(foreground);
+    gc.drawLine(indent + 2, midpoint, indent + size - 2, midpoint);
+    gc.dispose();
+}
+
+Image getPlusImage() {
+    if (plusImage is null) createImages();
+    return plusImage;
+}
+
+Image getMinusImage() {
+    if (minusImage is null) createImages();
+    return minusImage;
+}
+
+/**
+ * Gets the index of an item.
+ *
+ * <p>The widget is searched starting at 0 until an
+ * item is found that is equal to the search item.
+ * If no item is found, -1 is returned.  Indexing
+ * is zero based.  This index is relative to the parent only.
+ *
+ * @param item the search item
+ * @return the index of the item or -1
+ */
+public int indexOf (TableTreeItem item) {
+    //checkWidget();
+    for (int i = 0; i < items.length; i++) {
+        if (item is items[i]) return i;
+    }
+    return -1;
+}
+
+void onDispose(Event e) {
+    /*
+     * Usually when an item is disposed, destroyItem will change the size of the items array
+     * and dispose of the underlying table items.
+     * Since the whole table tree is being disposed, this is not necessary.  For speed
+     * the inDispose flag is used to skip over this part of the item dispose.
+     */
+    inDispose = true;
+    for (int i = 0; i < items.length; i++) {
+        items[i].dispose();
+    }
+    inDispose = false;
+    if (plusImage !is null) plusImage.dispose();
+    if (minusImage !is null) minusImage.dispose();
+    if (sizeImage !is null) sizeImage.dispose();
+    plusImage = minusImage = sizeImage = null;
+}
+
+void onResize(Event e) {
+    Point size = getSize();
+    table.setBounds(0, 0, size.x, size.y);
+}
+
+void onSelection(Event e) {
+    Event event = new Event();
+    TableItem tableItem = cast(TableItem)e.item;
+    TableTreeItem item = getItem(tableItem);
+    event.item = item;
+
+    if (e.type is DWT.Selection && e.detail is DWT.CHECK && item !is null) {
+        event.detail = DWT.CHECK;
+        item.checked = tableItem.getChecked();
+    }
+    notifyListeners(e.type, event);
+}
+/**
+ * Returns the item at the given, zero-relative index in the
+ * receiver. Throws an exception if the index is out of range.
+ *
+ * @param index the index of the item to return
+ * @return the item at the given index
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TableTreeItem getItem (int index) {
+    checkWidget();
+    int count = items.length;
+    if (!(0 <= index && index < count)) DWT.error (DWT.ERROR_INVALID_RANGE);
+    return items [index];
+}
+
+/**
+ * Returns the item at the given point in the receiver
+ * or null if no such item exists. The point is in the
+ * coordinate system of the receiver.
+ *
+ * @param point the point used to locate the item
+ * @return the item at the given point
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TableTreeItem getItem(Point point) {
+    checkWidget();
+    TableItem item = table.getItem(point);
+    if (item is null) return null;
+    return getItem(item);
+
+}
+TableTreeItem getItem(TableItem tableItem) {
+    if (tableItem is null) return null;
+    for (int i = 0; i < items.length; i++) {
+            TableTreeItem item = items[i].getItem(tableItem);
+            if (item !is null) return item;
+    }
+    return null;
+}
+void onFocusIn (Event e) {
+    table.setFocus();
+}
+
+void onKeyDown (Event e) {
+    TableTreeItem[] selection = getSelection();
+    if (selection.length is 0) return;
+    TableTreeItem item = selection[0];
+    int type = 0;
+    if (e.keyCode is DWT.ARROW_RIGHT || e.keyCode is DWT.ARROW_LEFT) {
+        int trailKey = (getStyle() & DWT.MIRRORED) !is 0 ? DWT.ARROW_LEFT : DWT.ARROW_RIGHT;
+        if (e.keyCode is trailKey) {
+            if (item.getItemCount() is 0) return;
+            if (item.getExpanded()) {
+                TableTreeItem newSelection = item.getItems()[0];
+                table.setSelection([newSelection.tableItem]);
+                showItem(newSelection);
+                type = DWT.Selection;
+            } else {
+                item.setExpanded(true);
+                type = DWT.Expand;
+            }
+        } else {
+            if (item.getExpanded()) {
+                item.setExpanded(false);
+                type = DWT.Collapse;
+            } else {
+                TableTreeItem parent = item.getParentItem();
+                if (parent !is null) {
+                    int index = parent.indexOf(item);
+                    if (index !is 0) return;
+                    table.setSelection([parent.tableItem]);
+                    type = DWT.Selection;
+                }
+            }
+        }
+    }
+    if (e.character is '*') {
+        item.expandAll(true);
+    }
+    if (e.character is '-') {
+        if (item.getExpanded()) {
+            item.setExpanded(false);
+            type = DWT.Collapse;
+        }
+    }
+    if (e.character is '+') {
+        if (item.getItemCount() > 0 && !item.getExpanded()) {
+            item.setExpanded(true);
+            type = DWT.Expand;
+        }
+    }
+    if (type is 0) return;
+    Event event = new Event();
+    event.item = item;
+    notifyListeners(type, event);
+}
+void onMouseDown(Event event) {
+    /* If user clicked on the [+] or [-], expand or collapse the tree. */
+    TableItem[] items = table.getItems();
+    for (int i = 0; i < items.length; i++) {
+        Rectangle rect = items[i].getImageBounds(0);
+        if (rect.contains(event.x, event.y)) {
+            TableTreeItem item = cast(TableTreeItem) items[i].getData(ITEMID);
+            event = new Event();
+            event.item = item;
+            item.setExpanded(!item.getExpanded());
+            if (item.getExpanded()) {
+                notifyListeners(DWT.Expand, event);
+            } else {
+                notifyListeners(DWT.Collapse, event);
+            }
+            return;
+        }
+    }
+}
+
+/**
+ * Removes all items.
+ * <p>
+ * This operation will fail when an item
+ * could not be removed in the OS.
+ *
+ * @exception DWTException <ul>
+ *  <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ *  <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * </ul>
+ */
+public void removeAll () {
+    checkWidget();
+    setRedraw(false);
+    for (int i = items.length - 1; i >= 0; i--) {
+        items[i].dispose();
+    }
+    items = EMPTY_ITEMS;
+    setRedraw(true);
+}
+
+void removeItem(TableTreeItem item) {
+    int index = 0;
+    while (index < items.length && items[index] !is item) index++;
+    if (index is items.length) return;
+    TableTreeItem[] newItems = new TableTreeItem[items.length - 1];
+    System.arraycopy(items, 0, newItems, 0, index);
+    System.arraycopy(items, index + 1, newItems, index, items.length - index - 1);
+    items = newItems;
+}
+
+/**
+ * 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Selection, listener);
+    removeListener(DWT.DefaultSelection, listener);
+}
+
+/**
+ * Removes the listener from the collection of listeners who will
+ * be notified when items in the receiver are expanded or collapsed.
+ *
+ * @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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TreeListener
+ * @see #addTreeListener
+ */
+public void removeTreeListener (TreeListener listener) {
+    checkWidget();
+    if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    removeListener(DWT.Expand, listener);
+    removeListener(DWT.Collapse, listener);
+}
+
+/**
+ * Selects all of the items in the receiver.
+ * <p>
+ * If the receiver is single-select, do nothing.
+ *
+ * @exception DWTException <ul>
+ *  <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ *  <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * </ul>
+ */
+public void selectAll () {
+    checkWidget();
+    table.selectAll();
+}
+public override void setBackground (Color color) {
+    super.setBackground(color);
+    table.setBackground(color);
+    if (sizeImage !is null) {
+        GC gc = new GC (sizeImage);
+        gc.setBackground(getBackground());
+        Rectangle size = sizeImage.getBounds();
+        gc.fillRectangle(size);
+        gc.dispose();
+    }
+}
+public override void setEnabled (bool enabled) {
+    super.setEnabled(enabled);
+    table.setEnabled(enabled);
+}
+public override void setFont (Font font) {
+    super.setFont(font);
+    table.setFont(font);
+}
+public override void setForeground (Color color) {
+    super.setForeground(color);
+    table.setForeground(color);
+}
+public override void setMenu (Menu menu) {
+    super.setMenu(menu);
+    table.setMenu(menu);
+}
+
+/**
+ * Sets the receiver's selection to be the given array of items.
+ * The current selection is cleared before the new items are selected.
+ * <p>
+ * Items that are not in the receiver are ignored.
+ * If the receiver is single-select and multiple items are specified,
+ * then all items are ignored.
+ *
+ * @param items the array of items
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the array of items is null</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if one of the item has been disposed</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TableTree#deselectAll()
+ */
+public void setSelection (TableTreeItem[] items) {
+    checkWidget ();
+    if (items is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    int length = items.length;
+    if (length is 0 || ((table.getStyle() & DWT.SINGLE) !is 0 && length > 1)) {
+        deselectAll();
+        return;
+    }
+    TableItem[] tableItems = new TableItem[length];
+    for (int i = 0; i < length; i++) {
+        if (items[i] is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+        if (!items[i].getVisible()) expandItem (items[i]);
+        tableItems[i] = items[i].tableItem;
+    }
+    table.setSelection(tableItems);
+}
+public override void setToolTipText (char[] string) {
+    super.setToolTipText(string);
+    table.setToolTipText(string);
+}
+
+/**
+ * Shows the item.  If the item is already showing in the receiver,
+ * this method simply returns.  Otherwise, the items are scrolled
+ * and expanded until the item is visible.
+ *
+ * @param item the item to be shown
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
+ *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TableTree#showSelection()
+ */
+public void showItem (TableTreeItem item) {
+    checkWidget();
+    if (item is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    if (!item.getVisible()) expandItem (item);
+    TableItem tableItem = item.tableItem;
+    table.showItem(tableItem);
+}
+
+/**
+ * Shows the selection.
+ * <p>
+ * If there is no selection or the selection
+ * is already visible, this method does nothing.
+ * If the selection is scrolled out of view,
+ * the top index of the widget is changed such
+ * that selection becomes visible.
+ *
+ * @exception DWTException <ul>
+ *  <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ *  <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * </ul>
+ */
+public void showSelection () {
+    checkWidget();
+    table.showSelection();
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TableTreeEditor.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.TableTreeEditor;
+
+
+
+import dwt.DWT;
+import dwt.events.ControlEvent;
+import dwt.events.ControlListener;
+import dwt.events.TreeEvent;
+import dwt.events.TreeListener;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Control;
+import dwt.widgets.Table;
+import dwt.widgets.TableColumn;
+import dwt.custom.ControlEditor;
+import dwt.custom.TableTree;
+import dwt.custom.TableTreeItem;
+
+import dwt.dwthelper.Runnable;
+import dwt.dwthelper.utils;
+
+/**
+*
+* A TableTreeEditor is a manager for a Control that appears above a cell in a TableTree
+* and tracks with the moving and resizing of that cell.  It can be used to display a
+* text widget above a cell in a TableTree so that the user can edit the contents of
+* that cell.  It can also be used to display a button that can launch a dialog for
+* modifying the contents of the associated cell.
+*
+* <p> Here is an example of using a TableTreeEditor:
+* <code><pre>
+*   final TableTree tableTree = new TableTree(shell, DWT.FULL_SELECTION | DWT.HIDE_SELECTION);
+*   final Table table = tableTree.getTable();
+*   TableColumn column1 = new TableColumn(table, DWT.NONE);
+*   TableColumn column2 = new TableColumn(table, DWT.NONE);
+*   for (int i = 0; i &lt; 10; i++) {
+*       TableTreeItem item = new TableTreeItem(tableTree, DWT.NONE);
+*       item.setText(0, "item " + i);
+*       item.setText(1, "edit this value");
+*       for (int j = 0; j &lt; 3; j++) {
+*           TableTreeItem subitem = new TableTreeItem(item, DWT.NONE);
+*           subitem.setText(0, "subitem " + i + " " + j);
+*           subitem.setText(1, "edit this value");
+*       }
+*   }
+*   column1.setWidth(100);
+*   column2.pack();
+*
+*   final TableTreeEditor editor = new TableTreeEditor(tableTree);
+*   //The editor must have the same size as the cell and must
+*   //not be any smaller than 50 pixels.
+*   editor.horizontalAlignment = DWT.LEFT;
+*   editor.grabHorizontal = true;
+*   editor.minimumWidth = 50;
+*   // editing the second column
+*   final int EDITABLECOLUMN = 1;
+*
+*   tableTree.addSelectionListener(new SelectionAdapter() {
+*       public void widgetSelected(SelectionEvent e) {
+*           // Clean up any previous editor control
+*           Control oldEditor = editor.getEditor();
+*           if (oldEditor !is null) oldEditor.dispose();
+*
+*           // Identify the selected row
+*           TableTreeItem item = (TableTreeItem)e.item;
+*           if (item is null) return;
+*
+*           // The control that will be the editor must be a child of the Table
+*           Text newEditor = new Text(table, DWT.NONE);
+*           newEditor.setText(item.getText(EDITABLECOLUMN));
+*           newEditor.addModifyListener(new ModifyListener() {
+*               public void modifyText(ModifyEvent e) {
+*                   Text text = (Text)editor.getEditor();
+*                   editor.getItem().setText(EDITABLECOLUMN, text.getText());
+*               }
+*           });
+*           newEditor.selectAll();
+*           newEditor.setFocus();
+*           editor.setEditor(newEditor, item, EDITABLECOLUMN);
+*       }
+*   });
+* </pre></code>
+*
+* @deprecated As of 3.1 use TreeEditor with Tree, TreeItem and TreeColumn
+*/
+public class TableTreeEditor : ControlEditor {
+
+    alias ControlEditor.setEditor setEditor;
+
+    TableTree tableTree;
+    TableTreeItem item;
+    int column = -1;
+    ControlListener columnListener;
+    TreeListener treeListener;
+/**
+* Creates a TableTreeEditor for the specified TableTree.
+*
+* @param tableTree the TableTree Control above which this editor will be displayed
+*
+*/
+public this (TableTree tableTree) {
+    super(tableTree.getTable());
+    this.tableTree = tableTree;
+
+    treeListener = new class() TreeListener  {
+        Runnable runnable;
+        this() {
+            runnable = new class() Runnable {
+                public void run() {
+                    if (editor is null || editor.isDisposed()) return;
+                    if (this.outer.outer.tableTree.isDisposed()) return;
+                    layout();
+                    editor.setVisible(true);
+                }
+            };
+        }
+        public void treeCollapsed(TreeEvent e) {
+            if (editor is null || editor.isDisposed ()) return;
+            editor.setVisible(false);
+            e.display.asyncExec(runnable);
+        }
+        public void treeExpanded(TreeEvent e) {
+            if (editor is null || editor.isDisposed ()) return;
+            editor.setVisible(false);
+            e.display.asyncExec(runnable);
+        }
+    };
+    tableTree.addTreeListener(treeListener);
+
+    columnListener = new class() ControlListener {
+        public void controlMoved(ControlEvent e){
+            layout ();
+        }
+        public void controlResized(ControlEvent e){
+            layout ();
+        }
+    };
+
+    // To be consistent with older versions of DWT, grabVertical defaults to true
+    grabVertical = true;
+}
+
+override Rectangle computeBounds () {
+    if (item is null || column is -1 || item.isDisposed() || item.tableItem is null) return new Rectangle(0, 0, 0, 0);
+    Rectangle cell = item.getBounds(column);
+    Rectangle area = tableTree.getClientArea();
+    if (cell.x < area.x + area.width) {
+        if (cell.x + cell.width > area.x + area.width) {
+            cell.width = area.x + area.width - cell.x;
+        }
+    }
+    Rectangle editorRect = new Rectangle(cell.x, cell.y, minimumWidth, minimumHeight);
+
+    if (grabHorizontal) {
+        editorRect.width = Math.max(cell.width, minimumWidth);
+    }
+
+    if (grabVertical) {
+        editorRect.height = Math.max(cell.height, minimumHeight);
+    }
+
+    if (horizontalAlignment is DWT.RIGHT) {
+        editorRect.x += cell.width - editorRect.width;
+    } else if (horizontalAlignment is DWT.LEFT) {
+        // do nothing - cell.x is the right answer
+    } else { // default is CENTER
+        editorRect.x += (cell.width - editorRect.width)/2;
+    }
+
+    if (verticalAlignment is DWT.BOTTOM) {
+        editorRect.y += cell.height - editorRect.height;
+    } else if (verticalAlignment is DWT.TOP) {
+        // do nothing - cell.y is the right answer
+    } else { // default is CENTER
+        editorRect.y += (cell.height - editorRect.height)/2;
+    }
+    return editorRect;
+}
+/**
+ * Removes all associations between the TableTreeEditor and the cell in the table tree.  The
+ * TableTree and the editor Control are <b>not</b> disposed.
+ */
+public override void dispose () {
+    if (tableTree !is null && !tableTree.isDisposed()) {
+        Table table = tableTree.getTable();
+        if (table !is null && !table.isDisposed()) {
+            if (this.column > -1 && this.column < table.getColumnCount()){
+                TableColumn tableColumn = table.getColumn(this.column);
+                tableColumn.removeControlListener(columnListener);
+            }
+        }
+        if (treeListener !is null) tableTree.removeTreeListener(treeListener);
+    }
+    treeListener = null;
+    columnListener = null;
+    tableTree = null;
+    item = null;
+    column = -1;
+    super.dispose();
+}
+/**
+* Returns the zero based index of the column of the cell being tracked by this editor.
+*
+* @return the zero based index of the column of the cell being tracked by this editor
+*/
+public int getColumn () {
+    return column;
+}
+/**
+* Returns the TableTreeItem for the row of the cell being tracked by this editor.
+*
+* @return the TableTreeItem for the row of the cell being tracked by this editor
+*/
+public TableTreeItem getItem () {
+    return item;
+}
+public void setColumn(int column) {
+    Table table = tableTree.getTable();
+    int columnCount = table.getColumnCount();
+    // Separately handle the case where the table has no TableColumns.
+    // In this situation, there is a single default column.
+    if (columnCount is 0) {
+        this.column = (column is 0) ? 0 : -1;
+        layout();
+        return;
+    }
+    if (this.column > -1 && this.column < columnCount){
+        TableColumn tableColumn = table.getColumn(this.column);
+        tableColumn.removeControlListener(columnListener);
+        this.column = -1;
+    }
+
+    if (column < 0  || column >= table.getColumnCount()) return;
+
+    this.column = column;
+    TableColumn tableColumn = table.getColumn(this.column);
+    tableColumn.addControlListener(columnListener);
+    layout();
+}
+public void setItem (TableTreeItem item) {
+    this.item = item;
+    layout();
+}
+
+/**
+* Specify the Control that is to be displayed and the cell in the table that it is to be positioned above.
+*
+* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Table control
+* specified in the TableEditor constructor.
+*
+* @param editor the Control that is displayed above the cell being edited
+* @param item the TableItem for the row of the cell being tracked by this editor
+* @param column the zero based index of the column of the cell being tracked by this editor
+*/
+alias ControlEditor.setEditor setEditor;
+public void setEditor (Control editor, TableTreeItem item, int column) {
+    setItem(item);
+    setColumn(column);
+    setEditor(editor);
+}
+public override void layout () {
+    if (tableTree is null || tableTree.isDisposed()) return;
+    if (item is null || item.isDisposed()) return;
+    Table table = tableTree.getTable();
+    int columnCount = table.getColumnCount();
+    if (columnCount is 0 && column !is 0) return;
+    if (columnCount > 0 && (column < 0 || column >= columnCount)) return;
+    super.layout();
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TableTreeItem.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,896 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.TableTreeItem;
+
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Event;
+import dwt.widgets.Item;
+import dwt.widgets.Table;
+import dwt.widgets.TableItem;
+import dwt.widgets.Widget;
+import dwt.custom.TableTree;
+import dwt.dwthelper.utils;
+
+
+/**
+ * A TableTreeItem is a selectable user interface object
+ * that represents an item in a hierarchy of items in a
+ * TableTree.
+ *
+ * @deprecated As of 3.1 use Tree, TreeItem and TreeColumn
+ */
+public class TableTreeItem : Item {
+    TableItem tableItem;
+    TableTree parent;
+    TableTreeItem parentItem;
+    TableTreeItem [] items;
+    char[][] texts;
+    Image[] images;
+    Color background;
+    Color foreground;
+    Font font;
+    bool expanded;
+    bool checked;
+    bool grayed;
+
+/**
+ * Constructs a new instance of this class given its parent
+ * (which must be a <code>TableTree</code>)
+ * and a style value describing its behavior and appearance.
+ * The item is added to the end of the items maintained by its parent.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>DWT</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>DWT</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 composite control which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT
+ * @see Widget#getStyle()
+ */
+public this(TableTree parent, int style) {
+    this (parent, style, parent.getItemCount());
+}
+
+/**
+ * Constructs a new instance of this class given its parent
+ * (which must be a <code>TableTree</code>,
+ * a style value describing its behavior and appearance, and the index
+ * at which to place it in the items maintained by its parent.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>DWT</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>DWT</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 composite control which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ * @param index the index to store the receiver in its parent
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT
+ * @see Widget#getStyle()
+ */
+public this(TableTree parent, int style, int index) {
+    this (parent, null, style, index);
+}
+
+/**
+ * Constructs a new instance of this class given its parent
+ * (which must be a <code>TableTreeItem</code>)
+ * and a style value describing its behavior and appearance.
+ * The item is added to the end of the items maintained by its parent.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>DWT</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>DWT</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 composite control which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT
+ * @see Widget#getStyle()
+ */
+public this(TableTreeItem parent, int style) {
+    this (parent, style, parent.getItemCount());
+}
+
+/**
+ * Constructs a new instance of this class given its parent
+ * (which must be a <code>TableTreeItem</code>),
+ * a style value describing its behavior and appearance, and the index
+ * at which to place it in the items maintained by its parent.
+ * <p>
+ * The style value is either one of the style constants defined in
+ * class <code>DWT</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>DWT</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 composite control which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ * @param index the index to store the receiver in its parent
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT
+ * @see Widget#getStyle()
+ */
+public this(TableTreeItem parent, int style, int index) {
+    this (parent.getParent(), parent, style, index);
+}
+
+this(TableTree parent, TableTreeItem parentItem, int style, int index) {
+
+    items = TableTree.EMPTY_ITEMS;
+    texts = TableTree.EMPTY_TEXTS;
+    images = TableTree.EMPTY_IMAGES;
+
+    super(parent, style);
+    this.parent = parent;
+    this.parentItem = parentItem;
+    if (parentItem is null) {
+
+        /* Root items are visible immediately */
+        int tableIndex = parent.addItem(this, index);
+        tableItem = new TableItem(parent.getTable(), style, tableIndex);
+        tableItem.setData(TableTree.ITEMID, this);
+        addCheck();
+        /*
+        * Feature in the Table.  The table uses the first image that
+        * is inserted into the table to size the table rows.  If the
+        * user is allowed to insert the first image, this will cause
+        * the +/- images to be scaled.  The fix is to insert a dummy
+        * image to force the size.
+        */
+        if (parent.sizeImage is null) {
+            int itemHeight = parent.getItemHeight();
+            parent.sizeImage = new Image(null, itemHeight, itemHeight);
+            GC gc = new GC (parent.sizeImage);
+            gc.setBackground(parent.getBackground());
+            gc.fillRectangle(0, 0, itemHeight, itemHeight);
+            gc.dispose();
+            tableItem.setImage(0, parent.sizeImage);
+        }
+    } else {
+        parentItem.addItem(this, index);
+    }
+}
+void addCheck() {
+    Table table = parent.getTable();
+    if ((table.getStyle() & DWT.CHECK) is 0) return;
+    tableItem.setChecked(checked);
+    tableItem.setGrayed(grayed);
+}
+void addItem(TableTreeItem item, int index) {
+    if (item is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    if (index < 0 || index > items.length) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+
+    /* Now that item has a sub-node it must indicate that it can be expanded */
+    if (items.length is 0 && index is 0) {
+        if (tableItem !is null) {
+            Image image = expanded ? parent.getMinusImage() : parent.getPlusImage();
+            tableItem.setImage(0, image);
+        }
+    }
+
+    /* Put the item in the items list */
+    TableTreeItem[] newItems = new TableTreeItem[items.length + 1];
+    System.arraycopy(items, 0, newItems, 0, index);
+    newItems[index] = item;
+    System.arraycopy(items, index, newItems, index + 1, items.length - index);
+    items = newItems;
+    if (expanded) item.setVisible(true);
+}
+
+/**
+ * Returns the receiver's background color.
+ *
+ * @return the background color
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 Color getBackground () {
+    checkWidget ();
+    return (background is null) ? parent.getBackground() : background;
+}
+
+/**
+ * Returns a rectangle describing the receiver's size and location
+ * relative to its parent.
+ *
+ * @return the receiver's bounding rectangle
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 Rectangle getBounds (int index) {
+    checkWidget();
+    if (tableItem !is null) {
+        return tableItem.getBounds(index);
+    } else {
+        return new Rectangle(0, 0, 0, 0);
+    }
+}
+/**
+ * Returns <code>true</code> if the receiver is checked,
+ * and false otherwise.  When the parent does not have
+ * the <code>CHECK style, return false.
+ *
+ * @return the checked state of the checkbox
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 getChecked () {
+    checkWidget();
+    if (tableItem is null) return checked;
+    return tableItem.getChecked();
+}
+
+/**
+ * Returns <code>true</code> if the receiver is grayed,
+ * and false otherwise. When the parent does not have
+ * the <code>CHECK</code> style, return false.
+ *
+ * @return the grayed state of the checkbox
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 bool getGrayed () {
+    checkWidget();
+    if (tableItem is null) return grayed;
+    return tableItem.getGrayed();
+}
+
+/**
+ * Returns <code>true</code> if the receiver is expanded,
+ * and false otherwise.
+ * <p>
+ *
+ * @return the expanded state
+ */
+public bool getExpanded () {
+    //checkWidget();
+    return expanded;
+}
+
+/**
+ * Returns the font that the receiver will use to paint textual information for this item.
+ *
+ * @return the receiver's font
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public Font getFont () {
+    checkWidget ();
+    return (font is null) ? parent.getFont() : font;
+}
+/**
+ * Returns the foreground color that the receiver will use to draw.
+ *
+ * @return the receiver's foreground color
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 Color getForeground () {
+    checkWidget ();
+    return (foreground is null) ? parent.getForeground() : foreground;
+}
+/**
+ * Gets the first image.
+ * <p>
+ * The image in column 0 is reserved for the [+] and [-]
+ * images of the tree, therefore getImage(0) will return null.
+ *
+ * @return the image at index 0
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 Image getImage () {
+    checkWidget();
+    return getImage(0);
+}
+
+/**
+ * Gets the image at the specified index.
+ * <p>
+ * Indexing is zero based. The image can be null.
+ * The image in column 0 is reserved for the [+] and [-]
+ * images of the tree, therefore getImage(0) will return null.
+ * Return null if the index is out of range.
+ *
+ * @param index the index of the image
+ * @return the image at the specified index or null
+ */
+public Image getImage (int index) {
+    //checkWidget();
+    if (0 < index && index < images.length) return images[index];
+    return null;
+}
+
+int getIndent() {
+    if (parentItem is null) return 0;
+    return parentItem.getIndent() + 1;
+}
+
+/**
+ * Returns the item at the given, zero-relative index in the
+ * receiver. Throws an exception if the index is out of range.
+ *
+ * @param index the index of the item to return
+ * @return the item at the given index
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 TableTreeItem getItem (int index) {
+    checkWidget();
+    int count = items.length;
+    if (!(0 <= index && index < count)) DWT.error (DWT.ERROR_INVALID_RANGE);
+    return items [index];
+}
+
+/**
+ * Returns the number of items contained in the receiver
+ * that are direct item children of the receiver.
+ *
+ * @return the number of items
+ */
+public int getItemCount () {
+    //checkWidget();
+    return items.length;
+}
+
+/**
+ * Returns an array of <code>TableTreeItem</code>s which are the
+ * direct item children of the receiver.
+ * <p>
+ * Note: This is not the actual structure used by the receiver
+ * to maintain its list of items, so modifying the array will
+ * not affect the receiver.
+ * </p>
+ *
+ * @return the receiver's items
+ */
+public TableTreeItem[] getItems () {
+    //checkWidget();
+    TableTreeItem[] newItems = new TableTreeItem[items.length];
+    System.arraycopy(items, 0, newItems, 0, items.length);
+    return newItems;
+}
+
+TableTreeItem getItem(TableItem tableItem) {
+    if (tableItem is null) return null;
+    if (this.tableItem is tableItem) return this;
+    for (int i = 0; i < items.length; i++) {
+        TableTreeItem item =  items[i].getItem(tableItem);
+            if (item !is null) return item;
+    }
+    return null;
+}
+
+/**
+ * Returns the receiver's parent, which must be a <code>TableTree</code>.
+ *
+ * @return the receiver's parent
+ */
+public TableTree getParent () {
+    //checkWidget();
+    return parent;
+}
+
+/**
+ * Returns the receiver's parent item, which must be a
+ * <code>TableTreeItem</code> or null when the receiver is a
+ * root.
+ *
+ * @return the receiver's parent item
+ */
+public TableTreeItem getParentItem () {
+    //checkWidget();
+    return parentItem;
+}
+public override char[] getText () {
+    checkWidget();
+    return getText(0);
+}
+
+/**
+ * Gets the item text at the specified index.
+ * <p>
+ * Indexing is zero based.
+ *
+ * This operation will fail when the index is out
+ * of range or an item could not be queried from
+ * the OS.
+ *
+ * @param index the index of the item
+ * @return the item text at the specified index, which can be null
+ */
+public char[] getText(int index) {
+    //checkWidget();
+    if (0 <= index && index < texts.length) return texts[index];
+    return null;
+}
+
+bool getVisible () {
+    return tableItem !is null;
+}
+
+/**
+ * Gets the index of the specified item.
+ *
+ * <p>The widget is searched starting at 0 until an
+ * item is found that is equal to the search item.
+ * If no item is found, -1 is returned.  Indexing
+ * is zero based.  This index is relative to the parent only.
+ *
+ * @param item the search item
+ * @return the index of the item or -1 if the item is not found
+ *
+ */
+public int indexOf (TableTreeItem item) {
+    //checkWidget();
+    for (int i = 0; i < items.length; i++) {
+        if (items[i] is item) return i;
+    }
+    return -1;
+}
+
+void expandAll(bool notify) {
+    if (items.length is 0) return;
+    if (!expanded) {
+        setExpanded(true);
+        if (notify) {
+            Event event = new Event();
+            event.item = this;
+            parent.notifyListeners(DWT.Expand, event);
+        }
+    }
+    for (int i = 0; i < items.length; i++) {
+        items[i].expandAll(notify);
+    }
+}
+int expandedIndexOf (TableTreeItem item) {
+    int index = 0;
+    for (int i = 0; i < items.length; i++) {
+        if (items[i] is item) return index;
+        if (items[i].expanded) index += items[i].visibleChildrenCount ();
+        index++;
+    }
+    return -1;
+}
+
+int visibleChildrenCount () {
+    int count = 0;
+    for (int i = 0; i < items.length; i++) {
+        if (items[i].getVisible ()) {
+            count += 1 + items[i].visibleChildrenCount ();
+        }
+    }
+    return count;
+}
+
+public override void dispose () {
+    if (isDisposed()) return;
+    for (int i = items.length - 1; i >= 0; i--) {
+        items[i].dispose();
+    }
+    super.dispose();
+    if (!parent.inDispose) {
+        if (parentItem !is null) {
+            parentItem.removeItem(this);
+        } else {
+            parent.removeItem(this);
+        }
+        if (tableItem !is null) tableItem.dispose();
+    }
+    items = null;
+    parentItem = null;
+    parent = null;
+    images = null;
+    texts = null;
+    tableItem = null;
+    foreground = null;
+    background = null;
+    font = null;
+}
+
+void removeItem(TableTreeItem item) {
+    int index = 0;
+    while (index < items.length && items[index] !is item) index++;
+    if (index is items.length) return;
+    TableTreeItem[] newItems = new TableTreeItem[items.length - 1];
+    System.arraycopy(items, 0, newItems, 0, index);
+    System.arraycopy(items, index + 1, newItems, index, items.length - index - 1);
+    items = newItems;
+    if (items.length is 0) {
+        if (tableItem !is null) tableItem.setImage(0, null);
+    }
+}
+
+/**
+ * Sets the receiver's background color to the color specified
+ * by the argument, or to the default system color for the item
+ * 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 DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setBackground (Color color) {
+    checkWidget ();
+    if (color !is null && color.isDisposed ()) {
+        DWT.error (DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (tableItem !is null) {
+        tableItem.setBackground(color);
+    }
+    background = color;
+}
+
+/**
+ * Sets the checked state of the checkbox for this item.  This state change
+ * only applies if the Table was created with the DWT.CHECK style.
+ *
+ * @param checked the new checked state of the checkbox
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setChecked (bool checked) {
+    checkWidget();
+    Table table = parent.getTable();
+    if ((table.getStyle() & DWT.CHECK) is 0) return;
+    if (tableItem !is null) {
+        tableItem.setChecked(checked);
+    }
+    this.checked = checked;
+}
+
+/**
+ * Sets the grayed state of the checkbox for this item.  This state change
+ * only applies if the Table was created with the DWT.CHECK style.
+ *
+ * @param grayed the new grayed state of the checkbox;
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setGrayed (bool grayed) {
+    checkWidget();
+    Table table = parent.getTable();
+    if ((table.getStyle() & DWT.CHECK) is 0) return;
+    if (tableItem !is null) {
+        tableItem.setGrayed(grayed);
+    }
+    this.grayed = grayed;
+}
+
+/**
+ * Sets the expanded state.
+ * <p>
+ * @param expanded the new expanded state.
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public void setExpanded (bool expanded) {
+    checkWidget();
+    if (items.length is 0) return;
+    if (this.expanded is expanded) return;
+    this.expanded = expanded;
+    if (tableItem is null) return;
+    parent.setRedraw(false);
+    for (int i = 0; i < items.length; i++) {
+        items[i].setVisible(expanded);
+    }
+    Image image = expanded ? parent.getMinusImage() : parent.getPlusImage();
+    tableItem.setImage(0, image);
+    parent.setRedraw(true);
+}
+
+/**
+ * Sets the font that the receiver will use to paint textual information
+ * for this item to the font specified by the argument, or to the default font
+ * for that kind of control if the argument is null.
+ *
+ * @param font the new font (or null)
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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
+ */
+public void setFont (Font font){
+    checkWidget ();
+    if (font !is null && font.isDisposed ()) {
+        DWT.error (DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (tableItem !is null) {
+        tableItem.setFont(font);
+    }
+    this.font = font;
+}
+/**
+ * Sets the receiver's foreground color to the color specified
+ * by the argument, or to the default system color for the item
+ * if the argument is null.
+ *
+ * @param color the new color (or null)
+ *
+ * @since 2.0
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setForeground (Color color) {
+    checkWidget ();
+    if (color !is null && color.isDisposed ()) {
+        DWT.error (DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (tableItem !is null) {
+        tableItem.setForeground(color);
+    }
+    foreground = color;
+}
+
+/**
+ * Sets the image at an index.
+ * <p>
+ * The image can be null.
+ * The image in column 0 is reserved for the [+] and [-]
+ * images of the tree, therefore do nothing if index is 0.
+ *
+ * @param image the new image or null
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public void setImage (int index, Image image) {
+    checkWidget();
+    int columnCount = Math.max(parent.getTable().getColumnCount(), 1);
+    if (index <= 0 || index >= columnCount) return;
+    if (images.length < columnCount) {
+        Image[] newImages = new Image[columnCount];
+        System.arraycopy(images, 0, newImages, 0, images.length);
+        images = newImages;
+    }
+    images[index] = image;
+    if (tableItem !is null) tableItem.setImage(index, image);
+}
+
+/**
+ * Sets the first image.
+ * <p>
+ * The image can be null.
+ * The image in column 0 is reserved for the [+] and [-]
+ * images of the tree, therefore do nothing.
+ *
+ * @param image the new image or null
+ *
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public override void setImage (Image image) {
+    setImage(0, image);
+}
+
+/**
+ * Sets the widget text.
+ * <p>
+ *
+ * The widget text for an item is the label of the
+ * item or the label of the text specified by a column
+ * number.
+ *
+ * @param index the column number
+ * @param text the new text
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the text is null</li>
+ * </ul>
+ * @exception DWTException <ul>
+ *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ *  </ul>
+ */
+public void setText(int index, char[] text) {
+    checkWidget();
+    if (text is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    int columnCount = Math.max(parent.getTable().getColumnCount(), 1);
+    if (index < 0 || index >= columnCount) return;
+    if (texts.length < columnCount) {
+        char[][] newTexts = new char[][columnCount];
+        System.arraycopy(texts, 0, newTexts, 0, texts.length);
+        texts = newTexts;
+    }
+    texts[index] = text;
+    if (tableItem !is null) tableItem.setText(index, text);
+}
+public override void setText (char[] string) {
+    setText(0, string);
+}
+
+void setVisible (bool show) {
+    if (parentItem is null) return; // this is a root and can not be toggled between visible and hidden
+    if (getVisible() is show) return;
+
+    if (show) {
+        if (!parentItem.getVisible()) return; // parentItem must already be visible
+        // create underlying table item and set data in table item to stored data
+        Table table = parent.getTable();
+        int parentIndex = table.indexOf(parentItem.tableItem);
+        int index = parentItem.expandedIndexOf(this) + parentIndex + 1;
+        if (index < 0) return;
+        tableItem = new TableItem(table, getStyle(), index);
+        tableItem.setData(TableTree.ITEMID, this);
+        tableItem.setImageIndent(getIndent());
+        if (background !is null) tableItem.setBackground(background);
+        if (foreground !is null) tableItem.setForeground(foreground);
+        if (font !is null) tableItem.setFont(font);
+        addCheck();
+
+        // restore fields to item
+        // ignore any images in the first column
+        int columnCount = Math.max(table.getColumnCount(), 1);
+        for (int i = 0; i < columnCount; i++) {
+            if (i < texts.length && texts[i] !is null) setText(i, texts[i]);
+            if (i < images.length && images[i] !is null) setImage(i, images[i]);
+        }
+
+        // display the children and the appropriate [+]/[-] symbol as required
+        if (items.length !is 0) {
+            if (expanded) {
+                tableItem.setImage(0, parent.getMinusImage());
+                for (int i = 0, length = items.length; i < length; i++) {
+                    items[i].setVisible(true);
+                }
+            } else {
+                tableItem.setImage(0, parent.getPlusImage());
+            }
+        }
+
+    } else {
+
+        for (int i = 0, length = items.length; i < length; i++) {
+            items[i].setVisible(false);
+        }
+        // remove row from table
+        tableItem.dispose();
+        tableItem = null;
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TextChangeListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 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 dwt.custom.TextChangeListener;
+
+
+import dwt.internal.DWTEventListener;
+import dwt.custom.TextChangingEvent;
+import dwt.custom.TextChangedEvent;
+
+/**
+ * The StyledText widget implements this listener to receive
+ * notifications when changes to the model occur.
+ * It is not intended to be implemented by clients or by
+ * implementors of StyledTextContent.
+ * Clients should listen to the ModifyEvent or ExtendedModifyEvent
+ * that is sent by the StyledText widget to receive text change
+ * notifications.
+ * Implementors of StyledTextContent should call the textChanging
+ * and textChanged methods when text changes occur as described
+ * below. If the entire text is replaced the textSet method
+ * should be called instead.
+ */
+public interface TextChangeListener : DWTEventListener {
+
+/**
+ * This method is called when the content is about to be changed.
+ * Callers also need to call the textChanged method after the
+ * content change has been applied. The widget only updates the
+ * screen properly when it receives both events.
+ *
+ * @param event the text changing event. All event fields need
+ *  to be set by the sender.
+ * @see TextChangingEvent
+ */
+public void textChanging(TextChangingEvent event);
+/**
+ * This method is called when the content has changed.
+ * Callers need to have called the textChanging method prior to
+ * applying the content change and calling this method. The widget
+ * only updates the screen properly when it receives both events.
+ *
+ * @param event the text changed event
+ */
+public void textChanged(TextChangedEvent event);
+/**
+ * This method is called instead of the textChanging/textChanged
+ * combination when the entire old content has been replaced
+ * (e.g., by a call to StyledTextContent.setText()).
+ *
+ * @param event the text changed event
+ */
+public void textSet(TextChangedEvent event);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TextChangedEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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 dwt.custom.TextChangedEvent;
+
+import dwt.events.TypedEvent;
+import dwt.custom.StyledTextContent;
+/**
+ * This event is sent by the StyledTextContent implementor when a change to
+ * the text occurs.
+ */
+public class TextChangedEvent : TypedEvent {
+
+/**
+ * Create the TextChangedEvent to be used by the StyledTextContent implementor.
+ * <p>
+ *
+ * @param source the object that will be sending the TextChangedEvent,
+ *  cannot be null
+ */
+public this(StyledTextContent source) {
+    super(cast(Object)source);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TextChangingEvent.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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 dwt.custom.TextChangingEvent;
+
+
+import dwt.events.TypedEvent;
+import dwt.custom.StyledTextContent;
+import dwt.custom.StyledTextEvent;
+
+/**
+ * This event is sent by the StyledTextContent implementor when a change
+ * to the text is about to occur.
+ */
+public class TextChangingEvent : TypedEvent {
+    /**
+     * Start offset of the text that is going to be replaced
+     */
+    public int start;
+    /**
+     * Text that is going to be inserted or empty string
+     * if no text will be inserted
+     */
+    public char[] newText;
+    /**
+     * Length of text that is going to be replaced
+     */
+    public int replaceCharCount;
+    /**
+     * Length of text that is going to be inserted
+     */
+    public int newCharCount;
+    /**
+     * Number of lines that are going to be replaced
+     */
+    public int replaceLineCount;
+    /**
+     * Number of new lines that are going to be inserted
+     */
+    public int newLineCount;
+
+    static final long serialVersionUID = 3257290210114352439L;
+
+/**
+ * Create the TextChangedEvent to be used by the StyledTextContent implementor.
+ * <p>
+ *
+ * @param source the object that will be sending the new TextChangingEvent,
+ *  cannot be null
+ */
+public this(StyledTextContent source) {
+    super( cast(Object)source);
+}
+this(StyledTextContent source, StyledTextEvent e) {
+    super( cast(Object)source);
+    start = e.start;
+    replaceCharCount = e.replaceCharCount;
+    newCharCount = e.newCharCount;
+    replaceLineCount = e.replaceLineCount;
+    newLineCount = e.newLineCount;
+    newText = e.text;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/TreeEditor.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.TreeEditor;
+
+
+
+import dwt.DWT;
+import dwt.events.ControlEvent;
+import dwt.events.ControlListener;
+import dwt.events.TreeEvent;
+import dwt.events.TreeListener;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Tree;
+import dwt.widgets.TreeColumn;
+import dwt.widgets.TreeItem;
+import dwt.custom.ControlEditor;
+import dwt.dwthelper.Runnable;
+
+import dwt.dwthelper.utils;
+
+/**
+*
+* A TreeEditor is a manager for a Control that appears above a cell in a Tree and tracks with the
+* moving and resizing of that cell.  It can be used to display a text widget above a cell
+* in a Tree so that the user can edit the contents of that cell.  It can also be used to display
+* a button that can launch a dialog for modifying the contents of the associated cell.
+*
+* <p> Here is an example of using a TreeEditor:
+* <code><pre>
+*   final Tree tree = new Tree(shell, DWT.BORDER);
+*   for (int i = 0; i &lt; 3; i++) {
+*       TreeItem item = new TreeItem(tree, DWT.NONE);
+*       item.setText("item " + i);
+*       for (int j = 0; j &lt; 3; j++) {
+*           TreeItem subItem = new TreeItem(item, DWT.NONE);
+*           subItem.setText("item " + i + " " + j);
+*       }
+*   }
+*
+*   final TreeEditor editor = new TreeEditor(tree);
+*   //The editor must have the same size as the cell and must
+*   //not be any smaller than 50 pixels.
+*   editor.horizontalAlignment = DWT.LEFT;
+*   editor.grabHorizontal = true;
+*   editor.minimumWidth = 50;
+*
+*   tree.addSelectionListener(new SelectionAdapter() {
+*       public void widgetSelected(SelectionEvent e) {
+*           // Clean up any previous editor control
+*           Control oldEditor = editor.getEditor();
+*           if (oldEditor !is null) oldEditor.dispose();
+*
+*           // Identify the selected row
+*           TreeItem item = (TreeItem)e.item;
+*           if (item is null) return;
+*
+*           // The control that will be the editor must be a child of the Tree
+*           Text newEditor = new Text(tree, DWT.NONE);
+*           newEditor.setText(item.getText());
+*           newEditor.addModifyListener(new ModifyListener() {
+*               public void modifyText(ModifyEvent e) {
+*                   Text text = (Text)editor.getEditor();
+*                   editor.getItem().setText(text.getText());
+*               }
+*           });
+*           newEditor.selectAll();
+*           newEditor.setFocus();
+*           editor.setEditor(newEditor, item);
+*       }
+*   });
+* </pre></code>
+*/
+
+public class TreeEditor : ControlEditor {
+    Tree tree;
+    TreeItem item;
+    int column = 0;
+    ControlListener columnListener;
+    TreeListener treeListener;
+    Runnable timer;
+    static const int TIMEOUT = 1500;
+
+/**
+* Creates a TreeEditor for the specified Tree.
+*
+* @param tree the Tree Control above which this editor will be displayed
+*
+*/
+public this (Tree tree) {
+    super(tree);
+    this.tree = tree;
+
+    columnListener = new class() ControlListener {
+        public void controlMoved(ControlEvent e){
+            layout();
+        }
+        public void controlResized(ControlEvent e){
+            layout();
+        }
+    };
+    timer = new class() Runnable {
+        public void run() {
+            layout ();
+        }
+    };
+    treeListener = new class() TreeListener {
+        Runnable runnable;
+        this(){
+            runnable = new class() Runnable {
+                public void run() {
+                    if (editor is null || editor.isDisposed()) return;
+                    if (this.outer.outer.tree.isDisposed()) return;
+                    layout();
+                    editor.setVisible(true);
+                }
+            };
+        }
+        public void treeCollapsed(TreeEvent e) {
+            if (editor is null || editor.isDisposed ()) return;
+            editor.setVisible(false);
+            e.display.asyncExec(runnable);
+        }
+        public void treeExpanded(TreeEvent e) {
+            if (editor is null || editor.isDisposed ()) return;
+            editor.setVisible(false);
+            e.display.asyncExec(runnable);
+        }
+    };
+    tree.addTreeListener(treeListener);
+
+    // To be consistent with older versions of DWT, grabVertical defaults to true
+    grabVertical = true;
+}
+
+override Rectangle computeBounds () {
+    if (item is null || column is -1 || item.isDisposed()) return new Rectangle(0, 0, 0, 0);
+    Rectangle cell = item.getBounds(column);
+    Rectangle rect = item.getImageBounds(column);
+    cell.x = rect.x + rect.width;
+    cell.width -= rect.width;
+    Rectangle area = tree.getClientArea();
+    if (cell.x < area.x + area.width) {
+        if (cell.x + cell.width > area.x + area.width) {
+            cell.width = area.x + area.width - cell.x;
+        }
+    }
+    Rectangle editorRect = new Rectangle(cell.x, cell.y, minimumWidth, minimumHeight);
+
+    if (grabHorizontal) {
+        if (tree.getColumnCount() is 0) {
+            // Bounds of tree item only include the text area - stretch out to include
+            // entire client area
+            cell.width = area.x + area.width - cell.x;
+        }
+        editorRect.width = Math.max(cell.width, minimumWidth);
+    }
+
+    if (grabVertical) {
+        editorRect.height = Math.max(cell.height, minimumHeight);
+    }
+
+    if (horizontalAlignment is DWT.RIGHT) {
+        editorRect.x += cell.width - editorRect.width;
+    } else if (horizontalAlignment is DWT.LEFT) {
+        // do nothing - cell.x is the right answer
+    } else { // default is CENTER
+        editorRect.x += (cell.width - editorRect.width)/2;
+    }
+    // don't let the editor overlap with the + / - of the tree
+    editorRect.x = Math.max(cell.x, editorRect.x);
+
+    if (verticalAlignment is DWT.BOTTOM) {
+        editorRect.y += cell.height - editorRect.height;
+    } else if (verticalAlignment is DWT.TOP) {
+        // do nothing - cell.y is the right answer
+    } else { // default is CENTER
+        editorRect.y += (cell.height - editorRect.height)/2;
+    }
+    return editorRect;
+}
+
+/**
+ * Removes all associations between the TreeEditor and the row in the tree.  The
+ * tree and the editor Control are <b>not</b> disposed.
+ */
+public override void dispose () {
+    if (tree !is null && !tree.isDisposed()) {
+        if (this.column > -1 && this.column < tree.getColumnCount()){
+            TreeColumn treeColumn = tree.getColumn(this.column);
+            treeColumn.removeControlListener(columnListener);
+        }
+        if (treeListener !is null) tree.removeTreeListener(treeListener);
+    }
+    columnListener = null;
+    treeListener = null;
+    tree = null;
+    item = null;
+    column = 0;
+    timer = null;
+    super.dispose();
+}
+
+/**
+* Returns the zero based index of the column of the cell being tracked by this editor.
+*
+* @return the zero based index of the column of the cell being tracked by this editor
+*
+* @since 3.1
+*/
+public int getColumn () {
+    return column;
+}
+
+/**
+* Returns the TreeItem for the row of the cell being tracked by this editor.
+*
+* @return the TreeItem for the row of the cell being tracked by this editor
+*/
+public TreeItem getItem () {
+    return item;
+}
+
+void resize () {
+    layout();
+    /*
+     * On some platforms, the table scrolls when an item that
+     * is partially visible at the bottom of the table is
+     * selected.  Ensure that the correct row is edited by
+     * laying out one more time in a timerExec().
+     */
+    if (tree !is null) {
+        Display display = tree.getDisplay();
+        display.timerExec(-1, timer);
+        display.timerExec(TIMEOUT, timer);
+    }
+}
+
+/**
+* Sets the zero based index of the column of the cell being tracked by this editor.
+*
+* @param column the zero based index of the column of the cell being tracked by this editor
+*
+* @since 3.1
+*/
+public void setColumn(int column) {
+    int columnCount = tree.getColumnCount();
+    // Separately handle the case where the tree has no TreeColumns.
+    // In this situation, there is a single default column.
+    if (columnCount is 0) {
+        this.column = (column is 0) ? 0 : -1;
+        resize();
+        return;
+    }
+    if (this.column > -1 && this.column < columnCount){
+        TreeColumn treeColumn = tree.getColumn(this.column);
+        treeColumn.removeControlListener(columnListener);
+        this.column = -1;
+    }
+
+    if (column < 0  || column >= tree.getColumnCount()) return;
+
+    this.column = column;
+    TreeColumn treeColumn = tree.getColumn(this.column);
+    treeColumn.addControlListener(columnListener);
+    resize();
+}
+
+public void setItem (TreeItem item) {
+    this.item = item;
+    resize();
+}
+
+/**
+* Specify the Control that is to be displayed and the cell in the tree that it is to be positioned above.
+*
+* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Tree control
+* specified in the TreeEditor constructor.
+*
+* @param editor the Control that is displayed above the cell being edited
+* @param item the TreeItem for the row of the cell being tracked by this editor
+* @param column the zero based index of the column of the cell being tracked by this editor
+*
+* @since 3.1
+*/
+public void setEditor (Control editor, TreeItem item, int column) {
+    setItem(item);
+    setColumn(column);
+    setEditor(editor);
+}
+public override void setEditor (Control editor) {
+    super.setEditor(editor);
+    resize();
+}
+
+/**
+* Specify the Control that is to be displayed and the cell in the tree that it is to be positioned above.
+*
+* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Tree control
+* specified in the TreeEditor constructor.
+*
+* @param editor the Control that is displayed above the cell being edited
+* @param item the TreeItem for the row of the cell being tracked by this editor
+*/
+public void setEditor (Control editor, TreeItem item) {
+    setItem(item);
+    setEditor(editor);
+}
+
+public override void layout () {
+    if (tree is null || tree.isDisposed()) return;
+    if (item is null || item.isDisposed()) return;
+    int columnCount = tree.getColumnCount();
+    if (columnCount is 0 && column !is 0) return;
+    if (columnCount > 0 && (column < 0 || column >= columnCount)) return;
+    super.layout();
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/VerifyKeyListener.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.VerifyKeyListener;
+
+
+import dwt.events.VerifyEvent;
+import dwt.internal.DWTEventListener;
+
+public interface VerifyKeyListener : DWTEventListener {
+/**
+ * The following event fields are used:<ul>
+ * <li>event.character is the character that was typed (input)</li>
+ * <li>event.keyCode is the key code that was typed (input)</li>
+ * <li>event.stateMask is the state of the keyboard (input)</li>
+ * <li>event.doit is processed or not (output)</li>
+ * </ul>
+ * @param event the verify event
+ * @see VerifyEvent
+ */
+public void verifyKey (VerifyEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ViewForm.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,489 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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 dwt.custom.ViewForm;
+
+
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.RGB;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.custom.ViewFormLayout;
+
+/**
+ * Instances of this class implement a Composite that positions and sizes
+ * children and allows programmatic control of layout and border parameters.
+ * ViewForm is used in the workbench to lay out a view's label/menu/toolbar
+ * local bar.
+ * <p>
+ * Note that although this class is a subclass of <code>Composite</code>,
+ * it does not make sense to set a layout on it.
+ * </p><p>
+ * <dl>
+ * <dt><b>Styles:</b></dt>
+ * <dd>BORDER, FLAT</dd>
+ * <dt><b>Events:</b></dt>
+ * <dd>(None)</dd>
+ * </dl>
+ * <p>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </p>
+ */
+
+public class ViewForm : Composite {
+
+    /**
+     * marginWidth specifies the number of pixels of horizontal margin
+     * that will be placed along the left and right edges of the form.
+     *
+     * The default value is 0.
+     */
+    public int marginWidth = 0;
+    /**
+     * marginHeight specifies the number of pixels of vertical margin
+     * that will be placed along the top and bottom edges of the form.
+     *
+     * The default value is 0.
+     */
+    public int marginHeight = 0;
+    /**
+     * horizontalSpacing specifies the number of pixels between the right
+     * edge of one cell and the left edge of its neighbouring cell to
+     * the right.
+     *
+     * The default value is 1.
+     */
+    public int horizontalSpacing = 1;
+    /**
+     * verticalSpacing specifies the number of pixels between the bottom
+     * edge of one cell and the top edge of its neighbouring cell underneath.
+     *
+     * The default value is 1.
+     */
+    public int verticalSpacing = 1;
+
+    /**
+     * Color of innermost line of drop shadow border.
+     *
+     * NOTE This field is badly named and can not be fixed for backwards compatibility.
+     * It should be capitalized.
+     *
+     * @deprecated
+     */
+    public static RGB borderInsideRGB;
+    /**
+     * Color of middle line of drop shadow border.
+     *
+     * NOTE This field is badly named and can not be fixed for backwards compatibility.
+     * It should be capitalized.
+     *
+     * @deprecated
+     */
+    public static RGB borderMiddleRGB;
+    /**
+     * Color of outermost line of drop shadow border.
+     *
+     * NOTE This field is badly named and can not be fixed for backwards compatibility.
+     * It should be capitalized.
+     *
+     * @deprecated
+     */
+    public static RGB borderOutsideRGB;
+
+    // DWT widgets
+    Control topLeft;
+    Control topCenter;
+    Control topRight;
+    Control content;
+
+    // Configuration and state info
+    bool separateTopCenter = false;
+    bool showBorder = false;
+
+    int separator = -1;
+    int borderTop = 0;
+    int borderBottom = 0;
+    int borderLeft = 0;
+    int borderRight = 0;
+    int highlight = 0;
+    Point oldSize;
+
+    Color selectionBackground;
+
+    static final int OFFSCREEN = -200;
+    static final int BORDER1_COLOR = DWT.COLOR_WIDGET_NORMAL_SHADOW;
+    static final int SELECTION_BACKGROUND = DWT.COLOR_LIST_BACKGROUND;
+
+
+    static this(){
+        borderInsideRGB  = new RGB (132, 130, 132);
+        borderMiddleRGB  = new RGB (143, 141, 138);
+        borderOutsideRGB = new RGB (171, 168, 165);
+    }
+/**
+ * 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>DWT</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>DWT</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 DWTException <ul>
+ *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
+ * </ul>
+ *
+ * @see DWT#BORDER
+ * @see DWT#FLAT
+ * @see #getStyle()
+ */
+public this(Composite parent, int style) {
+    super(parent, checkStyle(style));
+    super.setLayout(new ViewFormLayout());
+
+    setBorderVisible((style & DWT.BORDER) !is 0);
+
+    Listener listener = new class() Listener {
+        public void handleEvent(Event e) {
+            switch (e.type) {
+                case DWT.Dispose: onDispose(); break;
+                case DWT.Paint: onPaint(e.gc); break;
+                case DWT.Resize: onResize(); break;
+                default:
+            }
+        }
+    };
+
+    int[] events = [DWT.Dispose, DWT.Paint, DWT.Resize];
+
+    for (int i = 0; i < events.length; i++) {
+        addListener(events[i], listener);
+    }
+}
+
+static int checkStyle (int style) {
+    int mask = DWT.FLAT | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
+    return style & mask | DWT.NO_REDRAW_RESIZE;
+}
+
+//protected void checkSubclass () {
+//  String name = getClass().getName ();
+//  String validName = ViewForm.class.getName();
+//  if (!validName.equals(name)) {
+//      DWT.error (DWT.ERROR_INVALID_SUBCLASS);
+//  }
+//}
+
+public override Rectangle computeTrim (int x, int y, int width, int height) {
+    checkWidget ();
+    int trimX = x - borderLeft - highlight;
+    int trimY = y - borderTop - highlight;
+    int trimWidth = width + borderLeft + borderRight + 2*highlight;
+    int trimHeight = height + borderTop + borderBottom + 2*highlight;
+    return new Rectangle(trimX, trimY, trimWidth, trimHeight);
+}
+public override Rectangle getClientArea() {
+    checkWidget();
+    Rectangle clientArea = super.getClientArea();
+    clientArea.x += borderLeft;
+    clientArea.y += borderTop;
+    clientArea.width -= borderLeft + borderRight;
+    clientArea.height -= borderTop + borderBottom;
+    return clientArea;
+}
+/**
+* Returns the content area.
+*
+* @return the control in the content area of the pane or null
+*/
+public Control getContent() {
+    //checkWidget();
+    return content;
+}
+/**
+* Returns Control that appears in the top center of the pane.
+* Typically this is a toolbar.
+*
+* @return the control in the top center of the pane or null
+*/
+public Control getTopCenter() {
+    //checkWidget();
+    return topCenter;
+}
+/**
+* Returns the Control that appears in the top left corner of the pane.
+* Typically this is a label such as CLabel.
+*
+* @return the control in the top left corner of the pane or null
+*/
+public Control getTopLeft() {
+    //checkWidget();
+    return topLeft;
+}
+/**
+* Returns the control in the top right corner of the pane.
+* Typically this is a Close button or a composite with a Menu and Close button.
+*
+* @return the control in the top right corner of the pane or null
+*/
+public Control getTopRight() {
+    //checkWidget();
+    return topRight;
+}
+void onDispose() {
+    topLeft = null;
+    topCenter = null;
+    topRight = null;
+    content = null;
+    oldSize = null;
+    selectionBackground = null;
+}
+void onPaint(GC gc) {
+    Color gcForeground = gc.getForeground();
+    Point size = getSize();
+    Color border = getDisplay().getSystemColor(BORDER1_COLOR);
+    if (showBorder) {
+        gc.setForeground(border);
+        gc.drawRectangle(0, 0, size.x - 1, size.y - 1);
+        if (highlight > 0) {
+            int x1 = 1;
+            int y1 = 1;
+            int x2 = size.x - 1;
+            int y2 = size.y - 1;
+            int[] shape = [x1,y1, x2,y1, x2,y2, x1,y2, x1,y1+highlight,
+                               x1+highlight,y1+highlight, x1+highlight,y2-highlight,
+                               x2-highlight,y2-highlight, x2-highlight,y1+highlight, x1,y1+highlight];
+            Color highlightColor = getDisplay().getSystemColor(DWT.COLOR_LIST_SELECTION);
+            gc.setBackground(highlightColor);
+            gc.fillPolygon(shape);
+        }
+    }
+    if (separator > -1) {
+        gc.setForeground(border);
+        gc.drawLine(borderLeft + highlight, separator, size.x - borderLeft - borderRight - highlight, separator);
+    }
+    gc.setForeground(gcForeground);
+}
+void onResize() {
+    Point size = getSize();
+    if (oldSize is null || oldSize.x is 0 || oldSize.y is 0) {
+        redraw();
+    } else {
+        int width = 0;
+        if (oldSize.x < size.x) {
+            width = size.x - oldSize.x + borderRight + highlight;
+        } else if (oldSize.x > size.x) {
+            width = borderRight + highlight;
+        }
+        redraw(size.x - width, 0, width, size.y, false);
+
+        int height = 0;
+        if (oldSize.y < size.y) {
+            height = size.y - oldSize.y + borderBottom + highlight;
+        }
+        if (oldSize.y > size.y) {
+            height = borderBottom + highlight;
+        }
+        redraw(0, size.y - height, size.x, height, false);
+    }
+    oldSize = size;
+}
+/**
+* Sets the content.
+* Setting the content to null will remove it from
+* the pane - however, the creator of the content must dispose of the content.
+*
+* @param content the control to be displayed in the content area or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+*    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+*    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li>
+* </ul>
+*/
+public void setContent(Control content) {
+    checkWidget();
+    if (content !is null && content.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (this.content !is null && !this.content.isDisposed()) {
+        this.content.setBounds(OFFSCREEN, OFFSCREEN, 0, 0);
+    }
+    this.content = content;
+    layout(false);
+}
+/**
+ * Sets the layout which is associated with the receiver to be
+ * the argument which may be null.
+ * <p>
+ * Note: No Layout can be set on this Control because it already
+ * manages the size and position of its children.
+ * </p>
+ *
+ * @param layout the receiver's new layout or null
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_WIDGET_DISPOSED - 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 setLayout (Layout layout) {
+    checkWidget();
+    return;
+}
+void setSelectionBackground (Color color) {
+    checkWidget();
+    if (selectionBackground is color) return;
+    if (color is null) color = getDisplay().getSystemColor(SELECTION_BACKGROUND);
+    selectionBackground = color;
+    redraw();
+}
+/**
+* Set the control that appears in the top center of the pane.
+* Typically this is a toolbar.
+* The topCenter is optional.  Setting the topCenter to null will remove it from
+* the pane - however, the creator of the topCenter must dispose of the topCenter.
+*
+* @param topCenter the control to be displayed in the top center or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+*    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+*    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li>
+* </ul>
+*/
+public void setTopCenter(Control topCenter) {
+    checkWidget();
+    if (topCenter !is null && topCenter.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (this.topCenter !is null && !this.topCenter.isDisposed()) {
+        Point size = this.topCenter.getSize();
+        this.topCenter.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
+    }
+    this.topCenter = topCenter;
+    layout(false);
+}
+/**
+* Set the control that appears in the top left corner of the pane.
+* Typically this is a label such as CLabel.
+* The topLeft is optional.  Setting the top left control to null will remove it from
+* the pane - however, the creator of the control must dispose of the control.
+*
+* @param c the control to be displayed in the top left corner or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+*    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+*    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li>
+* </ul>
+*/
+public void setTopLeft(Control c) {
+    checkWidget();
+    if (c !is null && c.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (this.topLeft !is null && !this.topLeft.isDisposed()) {
+        Point size = this.topLeft.getSize();
+        this.topLeft.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
+    }
+    this.topLeft = c;
+    layout(false);
+}
+/**
+* Set the control that appears in the top right corner of the pane.
+* Typically this is a Close button or a composite with a Menu and Close button.
+* The topRight is optional.  Setting the top right control to null will remove it from
+* the pane - however, the creator of the control must dispose of the control.
+*
+* @param c the control to be displayed in the top right corner or null
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+*    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+*    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li>
+* </ul>
+*/
+public void setTopRight(Control c) {
+    checkWidget();
+    if (c !is null && c.getParent() !is this) {
+        DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    }
+    if (this.topRight !is null && !this.topRight.isDisposed()) {
+        Point size = this.topRight.getSize();
+        this.topRight.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
+    }
+    this.topRight = c;
+    layout(false);
+}
+/**
+* Specify whether the border should be displayed or not.
+*
+* @param show true if the border should be displayed
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 setBorderVisible(bool show) {
+    checkWidget();
+    if (showBorder is show) return;
+
+    showBorder = show;
+    if (showBorder) {
+        borderLeft = borderTop = borderRight = borderBottom = 1;
+        if ((getStyle() & DWT.FLAT)is 0) highlight = 2;
+    } else {
+        borderBottom = borderTop = borderLeft = borderRight = 0;
+        highlight = 0;
+    }
+    layout(false);
+    redraw();
+}
+/**
+* If true, the topCenter will always appear on a separate line by itself, otherwise the
+* topCenter will appear in the top row if there is room and will be moved to the second row if
+* required.
+*
+* @param show true if the topCenter will always appear on a separate line by itself
+*
+* @exception DWTException <ul>
+*    <li>ERROR_WIDGET_DISPOSED - 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 setTopCenterSeparate(bool show) {
+    checkWidget();
+    separateTopCenter = show;
+    layout(false);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/custom/ViewFormLayout.d	Thu Feb 14 19:18:37 2008 +0100
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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 dwt.custom.ViewFormLayout;
+
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Layout;
+import dwt.widgets.Scrollable;
+import dwt.custom.ViewForm;
+import dwt.custom.CLayoutData;
+import dwt.dwthelper.utils;
+
+/**
+ * This class provides the layout for ViewForm
+ *
+ * @see ViewForm
+ */
+class ViewFormLayout : Layout {
+
+protected override Point computeSize(Composite composite, int wHint, int hHint, bool flushCache) {
+    ViewForm form = cast(ViewForm)composite;
+    Control left = form.topLeft;
+    Control center = form.topCenter;
+    Control right = form.topRight;
+    Control content = form.content;
+
+    Point leftSize = new Point(0, 0);
+    if (left !is null) {
+        leftSize = computeChildSize(left, DWT.DEFAULT, DWT.DEFAULT, flushCache);
+    }
+    Point centerSize = new Point(0, 0);
+    if (center !is null) {
+         centerSize = computeChildSize(center, DWT.DEFAULT, DWT.DEFAULT, flushCache);
+    }
+    Point rightSize = new Point(0, 0);
+    if (right !is null) {
+         rightSize = computeChildSize(right, DWT.DEFAULT, DWT.DEFAULT, flushCache);
+    }
+    Point size = new Point(0, 0);
+    // calculate width of title bar
+    if (form.separateTopCenter ||
+        (wHint !is DWT.DEFAULT &&  leftSize.x + centerSize.x + rightSize.x > wHint)) {
+        size.x = leftSize.x + rightSize.x;
+        if (leftSize.x > 0 && rightSize.x > 0) size.x += form.horizontalSpacing;
+        size.x = Math.max(centerSize.x, size.x);
+        size.y = Math.max(leftSize.y, rightSize.y);
+        if (center !is null){
+            size.y += centerSize.y;
+            if (left !is null ||right !is null)size.y += form.verticalSpacing;
+        }
+    } else {
+        size.x = leftSize.x + centerSize.x + rightSize.x;
+        int count = -1;
+        if (leftSize.x > 0) count++;
+        if (centerSize.x > 0) count++;
+        if (rightSize.x > 0) count++;
+        if (count > 0) size.x += count * form.horizontalSpacing;
+        size.y = Math.max(leftSize.y, Math.max(centerSize.y, rightSize.y));
+    }
+
+    if (content !is null) {
+        if (left !is null || right !is null || center !is null) size.y += 1; // allow space for a vertical separator
+        Point contentSize = new Point(0, 0);
+        contentSize = computeChildSize(content, DWT.DEFAULT, DWT.DEFAULT, flushCache);
+        size.x = Math.max (size.x, contentSize.x);
+        size.y += contentSize.y;
+        if (size.y > contentSize.y) size.y += form.verticalSpacing;
+    }
+
+    size.x += 2*form.marginWidth;
+    size.y += 2*form.marginHeight;
+
+    if (wHint !is DWT.DEFAULT) size.x  = wHint;
+    if (hHint !is DWT.DEFAULT) size.y = hHint;
+
+    return size;
+}
+
+Point computeChildSize(Control control, int wHint, int hHint, bool flushCache) {
+    Object data = control.getLayoutData();
+    if (data is null || !( null !is cast(CLayoutData)data )) {
+        data = new CLayoutData();
+        control.setLayoutData(data);
+    }
+    return (cast(CLayoutData)data).computeSize(control, wHint, hHint, flushCache);
+}
+
+int computeTrim(Control c) {
+    if ( auto sa = cast(Scrollable)c) {
+        Rectangle rect = sa.computeTrim (0, 0, 0, 0);
+        return rect.width;
+    }
+    return c.getBorderWidth () * 2;
+}
+
+protected override bool flushCache(Control control) {
+    Object data = control.getLayoutData();
+    if ( auto ld = cast(CLayoutData)data ) ld.flushCache();
+    return true;
+}
+
+protected override void layout(Composite composite, bool flushCache) {
+    ViewForm form = cast(ViewForm)composite;
+    Control left = form.topLeft;
+    Control center = form.topCenter;
+    Control right = form.topRight;
+    Control content = form.content;
+
+    Rectangle rect = composite.getClientArea();
+
+    Point leftSize = new Point(0, 0);
+    if (left !is null && !left.isDisposed()) {
+        leftSize = computeChildSize(left, DWT.DEFAULT, DWT.DEFAULT, flushCache);
+    }
+    Point centerSize = new Point(0, 0);
+    if (center !is null && !center.isDisposed()) {
+         centerSize = computeChildSize(center, DWT.DEFAULT, DWT.DEFAULT, flushCache);
+    }
+    Point rightSize = new Point(0, 0);
+    if (right !is null && !right.isDisposed()) {
+         rightSize = computeChildSize(right, DWT.DEFAULT, DWT.DEFAULT, flushCache);
+    }
+
+    int minTopWidth = leftSize.x + centerSize.x + rightSize.x + 2*form.marginWidth + 2*form.highlight;
+    int count = -1;
+    if (leftSize.x > 0) count++;
+    if (centerSize.x > 0) count++;
+    if (rightSize.x > 0) count++;
+    if (count > 0) minTopWidth += count * form.horizontalSpacing;
+
+    int x = rect.x + rect.width - form.marginWidth - form.highlight;
+    int y = rect.y + form.marginHeight + form.highlight;
+
+    bool top = false;
+    if (form.separateTopCenter || minTopWidth > rect.width) {
+        int topHeight = Math.max(rightSize.y, leftSize.y);
+        if (right !is null && !right.isDisposed()) {
+            top = true;
+            x -= rightSize.x;
+            right.setBounds(x, y, rightSize.x, topHeight);
+            x -= form.horizontalSpacing;
+        }
+        if (left !is null && !left.isDisposed()) {
+            top = true;
+            int trim = computeTrim(left);
+            int leftW = x - rect.x - form.marginWidth - form.highlight - trim;
+            leftSize = computeChildSize(left, leftW, DWT.DEFAULT, false);
+            left.setBounds(rect.x + form.marginWidth + form.highlight, y, leftSize.x, topHeight);
+        }
+        if (top) y += topHeight + form.verticalSpacing;
+        if (center !is null && !center.isDisposed()) {
+            top = true;
+            int trim = computeTrim(center);
+            int w = rect.width - 2*form.marginWidth - 2*form.highlight - trim;
+            centerSize = computeChildSize(center, w, DWT.DEFAULT, false);
+            center.setBounds(rect.x + rect.width - form.marginWidth - form.highlight - centerSize.x, y, centerSize.x, centerSize.y);
+            y += centerSize.y + form.verticalSpacing;
+        }
+    } else {
+        int topHeight = Math.max(rightSize.y, Math.max(centerSize.y, leftSize.y));
+        if (right !is null && !right.isDisposed()) {
+            top = true;
+            x -= rightSize.x;
+            right.setBounds(x, y, rightSize.x, topHeight);
+            x -= form.horizontalSpacing;
+        }
+        if (center !is null && !center.isDisposed()) {
+            top = true;
+            x -= centerSize.x;
+            center.setBounds(x, y, centerSize.x, topHeight);
+            x -= form.horizontalSpacing;
+        }
+        if (left !is null && !left.isDisposed()) {
+            top = true;
+            Rectangle trim = ( null !is cast(Composite)left ) ? (cast(Composite)left).computeTrim(0, 0, 0, 0) : new Rectangle(0, 0, 0, 0);
+            int w = x - rect.x - form.marginWidth - form.highlight - trim.width;
+            int h = topHeight - trim.height;
+            leftSize = computeChildSize(left, w, h, false);
+            left.setBounds(rect.x + form.marginWidth + form.highlight, y, leftSize.x, topHeight);
+        }
+        if (top)y += topHeight + form.verticalSpacing;
+    }
+    int oldSeperator = form.separator;
+    form.separator = -1;
+    if (content !is null && !content.isDisposed()) {
+        if (left !is null || right!is null || center !is null){
+            form.separator = y;
+            y++;
+        }
+         content.setBounds(rect.x + form.marginWidth + form.highlight, y, rect.width - 2 * form.marginWidth - 2*form.highlight, rect.y + rect.height - y - form.marginHeight - form.highlight);
+    }
+    if (oldSeperator !is -1 && form.separator !is -1) {
+        int t = Math.min(form.separator, oldSeperator);
+        int b = Math.max(form.separator, oldSeperator);
+        form.redraw(form.borderLeft, t, form.getSize().x - form.borderLeft - form.borderRight, b - t, false);
+    }
+}
+}
--- a/dwt/internal/BidiUtil.d	Thu Feb 14 19:07:08 2008 +0100
+++ b/dwt/internal/BidiUtil.d	Thu Feb 14 19:18:37 2008 +0100
@@ -654,6 +654,7 @@
             Runnable runnable = languageMap[key];
             if (runnable !is null) runnable.run ();
             break;
+        default:
         }
     auto oldProc = oldProcMap[key];
     return OS.CallWindowProc ( oldProc, hwnd, msg, wParam, lParam);