changeset 75:5d489b9f966c

Fix continue porting
author Frank Benoit <benoit@tionex.de>
date Sat, 24 May 2008 05:11:16 +0200
parents dad2e11b8ae4
children e193036d82c9
files dwtx/jface/viewers/deferred/package.html dwtx/ui/forms/AbstractFormPart.d dwtx/ui/forms/DetailsPart.d dwtx/ui/forms/FormColors.d dwtx/ui/forms/FormDialog.d dwtx/ui/forms/HyperlinkGroup.d dwtx/ui/forms/HyperlinkSettings.d dwtx/ui/forms/IDetailsPage.d dwtx/ui/forms/IDetailsPageProvider.d dwtx/ui/forms/IFormColors.d dwtx/ui/forms/IFormPart.d dwtx/ui/forms/IManagedForm.d dwtx/ui/forms/IMessage.d dwtx/ui/forms/IMessageManager.d dwtx/ui/forms/IMessagePrefixProvider.d dwtx/ui/forms/IPartSelectionListener.d dwtx/ui/forms/ManagedForm.d dwtx/ui/forms/MasterDetailsBlock.d dwtx/ui/forms/SectionPart.d dwtx/ui/forms/events/ExpansionAdapter.d dwtx/ui/forms/events/ExpansionEvent.d dwtx/ui/forms/events/HyperlinkAdapter.d dwtx/ui/forms/events/HyperlinkEvent.d dwtx/ui/forms/events/IExpansionListener.d dwtx/ui/forms/events/IHyperlinkListener.d dwtx/ui/forms/widgets/AbstractHyperlink.d dwtx/ui/forms/widgets/ColumnLayout.d dwtx/ui/forms/widgets/ColumnLayoutData.d dwtx/ui/forms/widgets/ExpandableComposite.d dwtx/ui/forms/widgets/Form.d dwtx/ui/forms/widgets/FormText.d dwtx/ui/forms/widgets/FormToolkit.d dwtx/ui/forms/widgets/Hyperlink.d dwtx/ui/forms/widgets/ILayoutExtension.d dwtx/ui/forms/widgets/ImageHyperlink.d dwtx/ui/forms/widgets/LayoutCache.d dwtx/ui/forms/widgets/LayoutComposite.d dwtx/ui/forms/widgets/ScrolledForm.d dwtx/ui/forms/widgets/ScrolledFormText.d dwtx/ui/forms/widgets/ScrolledPageBook.d dwtx/ui/forms/widgets/Section.d dwtx/ui/forms/widgets/SharedScrolledComposite.d dwtx/ui/forms/widgets/SizeCache.d dwtx/ui/forms/widgets/TableWrapData.d dwtx/ui/forms/widgets/TableWrapLayout.d dwtx/ui/forms/widgets/ToggleHyperlink.d dwtx/ui/forms/widgets/TreeNode.d dwtx/ui/forms/widgets/Twistie.d dwtx/ui/internal/forms/IMessageToolTipManager.d dwtx/ui/internal/forms/MessageManager.d dwtx/ui/internal/forms/Messages.d dwtx/ui/internal/forms/widgets/AggregateHyperlinkSegment.d dwtx/ui/internal/forms/widgets/BreakSegment.d dwtx/ui/internal/forms/widgets/BulletParagraph.d dwtx/ui/internal/forms/widgets/BusyIndicator.d dwtx/ui/internal/forms/widgets/ControlSegment.d dwtx/ui/internal/forms/widgets/FormFonts.d dwtx/ui/internal/forms/widgets/FormHeading.d dwtx/ui/internal/forms/widgets/FormImages.d dwtx/ui/internal/forms/widgets/FormTextModel.d dwtx/ui/internal/forms/widgets/FormUtil.d dwtx/ui/internal/forms/widgets/FormsResources.d dwtx/ui/internal/forms/widgets/IFocusSelectable.d dwtx/ui/internal/forms/widgets/IHyperlinkSegment.d dwtx/ui/internal/forms/widgets/ImageHyperlinkSegment.d dwtx/ui/internal/forms/widgets/ImageSegment.d dwtx/ui/internal/forms/widgets/Locator.d dwtx/ui/internal/forms/widgets/ObjectSegment.d dwtx/ui/internal/forms/widgets/Paragraph.d dwtx/ui/internal/forms/widgets/ParagraphSegment.d dwtx/ui/internal/forms/widgets/PixelConverter.d dwtx/ui/internal/forms/widgets/SWTUtil.d dwtx/ui/internal/forms/widgets/SelectionData.d dwtx/ui/internal/forms/widgets/TextHyperlinkSegment.d dwtx/ui/internal/forms/widgets/TextSegment.d dwtx/ui/internal/forms/widgets/TitleRegion.d dwtx/ui/internal/forms/widgets/WrappedPageBook.d res/dwtx.ui.internal.forms.Messages.properties
diffstat 78 files changed, 20490 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/dwtx/jface/viewers/deferred/package.html	Thu May 22 20:08:10 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-   <meta name="Author" content="IBM">
-   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
-   <title>Package-level Javadoc</title>
-</head>
-<body>
-Provides a framework for viewers that handle deferred contents. 
-<h2>
-Package Specification</h2>
-<p>The deferred viewers are viewers that can handle concurrent updates from a 
-  variety of Threads.<br>
-  &nbsp; 
-</body>
-</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/AbstractFormPart.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.AbstractFormPart;
+
+import dwtx.ui.forms.IFormPart;
+import dwtx.ui.forms.IManagedForm;
+
+import dwt.dwthelper.utils;
+
+/**
+ * AbstractFormPart implements IFormPart interface and can be used as a
+ * convenient base class for concrete form parts. If a method contains
+ * code that must be called, look for instructions to call 'super'
+ * when overriding.
+ *
+ * @see dwtx.ui.forms.widgets.Section
+ * @since 3.0
+ */
+public abstract class AbstractFormPart : IFormPart {
+    private IManagedForm managedForm;
+    private bool dirty = false;
+    private bool stale = true;
+    /**
+     * @see dwtx.ui.forms.IFormPart#initialize(dwtx.ui.forms.IManagedForm)
+     */
+    public void initialize(IManagedForm form) {
+        this.managedForm = form;
+    }
+    /**
+     * Returns the form that manages this part.
+     *
+     * @return the managed form
+     */
+    public IManagedForm getManagedForm() {
+        return managedForm;
+    }
+    /**
+     * Disposes the part. Subclasses should override to release any system
+     * resources.
+     */
+    public void dispose() {
+    }
+    /**
+     * Commits the part. Subclasses should call 'super' when overriding.
+     *
+     * @param onSave
+     *            <code>true</code> if the request to commit has arrived as a
+     *            result of the 'save' action.
+     */
+    public void commit(bool onSave) {
+        dirty = false;
+    }
+    /**
+     * Sets the overall form input. Subclases may elect to override the method
+     * and adjust according to the form input.
+     *
+     * @param input
+     *            the form input object
+     * @return <code>false</code>
+     */
+    public bool setFormInput(Object input) {
+        return false;
+    }
+    /**
+     * Instructs the part to grab keyboard focus.
+     */
+    public void setFocus() {
+    }
+    /**
+     * Refreshes the section after becoming stale (falling behind data in the
+     * model). Subclasses must call 'super' when overriding this method.
+     */
+    public void refresh() {
+        stale = false;
+        // since we have refreshed, any changes we had in the
+        // part are gone and we are not dirty
+        dirty = false;
+    }
+    /**
+     * Marks the part dirty. Subclasses should call this method as a result of
+     * user interaction with the widgets in the section.
+     */
+    public void markDirty() {
+        dirty = true;
+        managedForm.dirtyStateChanged();
+    }
+    /**
+     * Tests whether the part is dirty i.e. its widgets have state that is
+     * newer than the data in the model.
+     *
+     * @return <code>true</code> if the part is dirty, <code>false</code>
+     *         otherwise.
+     */
+    public bool isDirty() {
+        return dirty;
+    }
+    /**
+     * Tests whether the part is stale i.e. its widgets have state that is
+     * older than the data in the model.
+     *
+     * @return <code>true</code> if the part is stale, <code>false</code>
+     *         otherwise.
+     */
+    public bool isStale() {
+        return stale;
+    }
+    /**
+     * Marks the part stale. Subclasses should call this method as a result of
+     * model notification that indicates that the content of the section is no
+     * longer in sync with the model.
+     */
+    public void markStale() {
+        stale = true;
+        managedForm.staleStateChanged();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/DetailsPart.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,338 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.DetailsPart;
+
+import dwtx.ui.forms.IFormPart;
+import dwtx.ui.forms.IPartSelectionListener;
+import dwtx.ui.forms.IManagedForm;
+import dwtx.ui.forms.IDetailsPageProvider;
+import dwtx.ui.forms.IDetailsPage;
+
+import dwt.DWT;
+import dwt.custom.BusyIndicator;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwtx.jface.viewers.ISelection;
+import dwtx.jface.viewers.IStructuredSelection;
+import dwtx.ui.forms.widgets.ScrolledPageBook;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+import tango.util.collection.HashMap;
+
+/**
+ * This managed form part handles the 'details' portion of the
+ * 'master/details' block. It has a page book that manages pages
+ * of details registered for the current selection.
+ * <p>By default, details part accepts any number of pages.
+ * If dynamic page provider is registered, this number may
+ * be excessive. To avoid running out of steam (by creating
+ * a large number of pages with widgets on each), maximum
+ * number of pages can be set to some reasonable value (e.g. 10).
+ * When this number is reached, old pages (those created first)
+ * will be removed and disposed as new ones are added. If
+ * the disposed pages are needed again after that, they
+ * will be created again.
+ *
+ * @since 3.0
+ */
+public final class DetailsPart : IFormPart, IPartSelectionListener {
+    private IManagedForm managedForm;
+    private ScrolledPageBook pageBook;
+    private IFormPart masterPart;
+    private IStructuredSelection currentSelection;
+    private HashMap!(Object,Object) pages;
+    private IDetailsPageProvider pageProvider;
+    private int pageLimit=Integer.MAX_VALUE;
+
+    private static class PageBag {
+        private static int counter;
+        private int ticket;
+        private IDetailsPage page;
+        private bool fixed;
+
+        public this(IDetailsPage page, bool fixed) {
+            this.page= page;
+            this.fixed = fixed;
+            this.ticket = ++counter;
+        }
+        public int getTicket() {
+            return ticket;
+        }
+        public IDetailsPage getPage() {
+            return page;
+        }
+        public void dispose() {
+            page.dispose();
+            page=null;
+        }
+        public bool isDisposed() {
+            return page is null;
+        }
+        public bool isFixed() {
+            return fixed;
+        }
+        public static int getCurrentTicket() {
+            return counter;
+        }
+    }
+/**
+ * Creates a details part by wrapping the provided page book.
+ * @param mform the parent form
+ * @param pageBook the page book to wrap
+ */
+    public this(IManagedForm mform, ScrolledPageBook pageBook) {
+        this.pageBook = pageBook;
+        pages = new HashMap!(Object,Object);
+        initialize(mform);
+    }
+/**
+ * Creates a new details part in the provided form by creating
+ * the page book.
+ * @param mform the parent form
+ * @param parent the composite to create the page book in
+ * @param style the style for the page book
+ */
+    public this(IManagedForm mform, Composite parent, int style) {
+        this(mform, mform.getToolkit().createPageBook(parent, style|DWT.V_SCROLL|DWT.H_SCROLL));
+    }
+/**
+ * Registers the details page to be used for all the objects of
+ * the provided object class.
+ * @param objectClass an object of type 'java.lang.Class' to be used
+ * as a key for the provided page
+ * @param page the page to show for objects of the provided object class
+ */
+    public void registerPage(Object objectClass, IDetailsPage page) {
+        registerPage(objectClass, page, true);
+    }
+
+    private void registerPage(Object objectClass, IDetailsPage page, bool fixed) {
+        pages.add(objectClass, new PageBag(page, fixed));
+        page.initialize(managedForm);
+    }
+/**
+ * Sets the dynamic page provider. The dynamic provider can return
+ * different pages for objects of the same class based on their state.
+ * @param provider the provider to use
+ */
+    public void setPageProvider(IDetailsPageProvider provider) {
+        this.pageProvider = provider;
+    }
+/**
+ * Commits the part by committing the current page.
+ * @param onSave <code>true</code> if commit is requested as a result
+ * of the 'save' action, <code>false</code> otherwise.
+ */
+    public void commit(bool onSave) {
+        IDetailsPage page = getCurrentPage();
+        if (page !is null)
+            page.commit(onSave);
+    }
+/**
+ * Returns the current page visible in the part.
+ * @return the current page
+ */
+    public IDetailsPage getCurrentPage() {
+        Control control = pageBook.getCurrentPage();
+        if (control !is null) {
+            Object data = control.getData();
+            if (null !is cast(IDetailsPage)data )
+                return cast(IDetailsPage) data;
+        }
+        return null;
+    }
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IFormPart#dispose()
+     */
+    public void dispose() {
+        foreach( k, v; pages ){
+            PageBag pageBag = cast(PageBag) v;
+            pageBag.dispose();
+        }
+    }
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IFormPart#initialize(dwtx.ui.forms.IManagedForm)
+     */
+    public void initialize(IManagedForm form) {
+        this.managedForm = form;
+    }
+/**
+ * Tests if the currently visible page is dirty.
+ * @return <code>true</code> if the page is dirty, <code>false</code> otherwise.
+ */
+    public bool isDirty() {
+        IDetailsPage page = getCurrentPage();
+        if (page !is null)
+            return page.isDirty();
+        return false;
+    }
+/**
+ * Tests if the currently visible page is stale and needs refreshing.
+ * @return <code>true</code> if the page is stale, <code>false</code> otherwise.
+ */
+    public bool isStale() {
+        IDetailsPage page = getCurrentPage();
+        if (page !is null)
+            return page.isStale();
+        return false;
+    }
+
+/**
+ * Refreshes the current page.
+ */
+    public void refresh() {
+        IDetailsPage page = getCurrentPage();
+        if (page !is null)
+            page.refresh();
+    }
+/**
+ * Sets the focus to the currently visible page.
+ */
+    public void setFocus() {
+        IDetailsPage page = getCurrentPage();
+        if (page !is null)
+            page.setFocus();
+    }
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IFormPart#setFormInput(java.lang.Object)
+     */
+    public bool setFormInput(Object input) {
+        return false;
+    }
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IPartSelectionListener#selectionChanged(dwtx.ui.forms.IFormPart,
+     *      dwtx.jface.viewers.ISelection)
+     */
+    public void selectionChanged(IFormPart part, ISelection selection) {
+        this.masterPart = part;
+        if (currentSelection !is null) {
+        }
+        if (null !is cast(IStructuredSelection)selection )
+            currentSelection = cast(IStructuredSelection) selection;
+        else
+            currentSelection = null;
+        update();
+    }
+    private void update() {
+        Object key = null;
+        if (currentSelection !is null) {
+            foreach ( obj; currentSelection.iterator() ) {
+                if (key is null)
+                    key = getKey(obj);
+                else if (getKey(obj).opEquals(key) is false) {
+                    key = null;
+                    break;
+                }
+            }
+        }
+        showPage(key);
+    }
+    private Object getKey(Object object) {
+        if (pageProvider !is null) {
+            Object key = pageProvider.getPageKey(object);
+            if (key !is null)
+                return key;
+        }
+        return object.classinfo;
+    }
+    private void showPage( Object key) {
+        checkLimit();
+        final IDetailsPage oldPage = getCurrentPage();
+        if (key !is null) {
+            PageBag pageBag = cast(PageBag)pages.get(key);
+            IDetailsPage page = pageBag !is null?pageBag.getPage():null;
+            if (page is null) {
+                // try to get the page dynamically from the provider
+                if (pageProvider !is null) {
+                    page = pageProvider.getPage(key);
+                    if (page !is null) {
+                        registerPage(key, page, false);
+                    }
+                }
+            }
+            if (page !is null) {
+                BusyIndicator.showWhile(pageBook.getDisplay(), dgRunnable( (IDetailsPage fpage,Object key,IDetailsPage oldPage) {
+                    if (!pageBook.hasPage(key)) {
+                        Composite parent = pageBook.createPage(key);
+                        fpage.createContents(parent);
+                        parent.setData(cast(Object)fpage);
+                    }
+                    //commit the current page
+                    if (oldPage !is null && oldPage.isDirty())
+                        oldPage.commit(false);
+                    //refresh the new page
+                    if (fpage.isStale())
+                        fpage.refresh();
+                    fpage.selectionChanged(masterPart, currentSelection);
+                    pageBook.showPage(key);
+                }, page, key, oldPage));
+                return;
+            }
+        }
+        // If we are switching from an old page to nothing,
+        // don't loose data
+        if (oldPage !is null && oldPage.isDirty())
+            oldPage.commit(false);
+        pageBook.showEmptyPage();
+    }
+    private void checkLimit() {
+        if (pages.size() <= getPageLimit()) return;
+        // overflow
+        int currentTicket = PageBag.getCurrentTicket();
+        int cutoffTicket = currentTicket - getPageLimit();
+        foreach( key, v; cast(HashMap!(Object,Object))pages.dup ){
+            PageBag pageBag = cast(PageBag)v;
+            if (pageBag.getTicket()<=cutoffTicket) {
+                // candidate - see if it is active and not fixed
+                if (!pageBag.isFixed() && !(cast(Object)pageBag.getPage()).opEquals(cast(Object)getCurrentPage())) {
+                    // drop it
+                    pageBag.dispose();
+                    pages.remove(key);
+                    pageBook.removePage(key, false);
+                }
+            }
+        }
+    }
+    /**
+     * Returns the maximum number of pages that should be
+     * maintained in this part. When an attempt is made to
+     * add more pages, old pages are removed and disposed
+     * based on the order of creation (the oldest pages
+     * are removed). The exception is made for the
+     * page that should otherwise be disposed but is
+     * currently active.
+     * @return maximum number of pages for this part
+     */
+    public int getPageLimit() {
+        return pageLimit;
+    }
+    /**
+     * Sets the page limit for this part.
+     * @see #getPageLimit()
+     * @param pageLimit the maximum number of pages that
+     * should be maintained in this part.
+     */
+    public void setPageLimit(int pageLimit) {
+        this.pageLimit = pageLimit;
+        checkLimit();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/FormColors.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,737 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.FormColors;
+
+import dwtx.ui.forms.IFormColors;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.RGB;
+import dwt.widgets.Display;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.HashMap;
+
+/**
+ * Manages colors that will be applied to forms and form widgets. The colors are
+ * chosen to make the widgets look correct in the editor area. If a different
+ * set of colors is needed, subclass this class and override 'initialize' and/or
+ * 'initializeColors'.
+ *
+ * @since 3.0
+ */
+public class FormColors {
+    /**
+     * Key for the form title foreground color.
+     *
+     * @deprecated use <code>IFormColors.TITLE</code>.
+     */
+    public static const String TITLE = IFormColors.TITLE;
+
+    /**
+     * Key for the tree/table border color.
+     *
+     * @deprecated use <code>IFormColors.BORDER</code>
+     */
+    public static const String BORDER = IFormColors.BORDER;
+
+    /**
+     * Key for the section separator color.
+     *
+     * @deprecated use <code>IFormColors.SEPARATOR</code>.
+     */
+    public static const String SEPARATOR = IFormColors.SEPARATOR;
+
+    /**
+     * Key for the section title bar background.
+     *
+     * @deprecated use <code>IFormColors.TB_BG
+     */
+    public static const String TB_BG = IFormColors.TB_BG;
+
+    /**
+     * Key for the section title bar foreground.
+     *
+     * @deprecated use <code>IFormColors.TB_FG</code>
+     */
+    public static const String TB_FG = IFormColors.TB_FG;
+
+    /**
+     * Key for the section title bar gradient.
+     *
+     * @deprecated use <code>IFormColors.TB_GBG</code>
+     */
+    public static const String TB_GBG = IFormColors.TB_GBG;
+
+    /**
+     * Key for the section title bar border.
+     *
+     * @deprecated use <code>IFormColors.TB_BORDER</code>.
+     */
+    public static const String TB_BORDER = IFormColors.TB_BORDER;
+
+    /**
+     * Key for the section toggle color. Since 3.1, this color is used for all
+     * section styles.
+     *
+     * @deprecated use <code>IFormColors.TB_TOGGLE</code>.
+     */
+    public static const String TB_TOGGLE = IFormColors.TB_TOGGLE;
+
+    /**
+     * Key for the section toggle hover color.
+     *
+     * @since 3.1
+     * @deprecated use <code>IFormColors.TB_TOGGLE_HOVER</code>.
+     */
+    public static const String TB_TOGGLE_HOVER = IFormColors.TB_TOGGLE_HOVER;
+
+    protected HashMap!(String,Object) colorRegistry;
+
+    protected Color background;
+
+    protected Color foreground;
+
+    private bool shared;
+
+    protected Display display;
+
+    protected Color border;
+
+    /**
+     * Creates form colors using the provided display.
+     *
+     * @param display
+     *            the display to use
+     */
+    public this(Display display) {
+        colorRegistry = new HashMap!(String,Object);
+        this.display = display;
+        initialize();
+    }
+
+    /**
+     * Returns the display used to create colors.
+     *
+     * @return the display
+     */
+    public Display getDisplay() {
+        return display;
+    }
+
+    /**
+     * Initializes the colors. Subclasses can override this method to change the
+     * way colors are created. Alternatively, only the color table can be
+     * modified by overriding <code>initializeColorTable()</code>.
+     *
+     * @see #initializeColorTable
+     */
+    protected void initialize() {
+        background = display.getSystemColor(DWT.COLOR_LIST_BACKGROUND);
+        foreground = display.getSystemColor(DWT.COLOR_LIST_FOREGROUND);
+        initializeColorTable();
+        updateBorderColor();
+    }
+
+    /**
+     * Allocates colors for the following keys: BORDER, SEPARATOR and
+     * TITLE. Subclasses can override to allocate these colors differently.
+     */
+    protected void initializeColorTable() {
+        createTitleColor();
+        createColor(IFormColors.SEPARATOR, getColor(IFormColors.TITLE).getRGB());
+        RGB black = getSystemColor(DWT.COLOR_BLACK);
+        RGB borderRGB = getSystemColor(DWT.COLOR_TITLE_INACTIVE_BACKGROUND_GRADIENT);
+        createColor(IFormColors.BORDER, blend(borderRGB, black, 80));
+    }
+
+    /**
+     * Allocates colors for the section tool bar (all the keys that start with
+     * TB). Since these colors are only needed when TITLE_BAR style is used with
+     * the Section widget, they are not needed all the time and are allocated on
+     * demand. Consequently, this method will do nothing if the colors have been
+     * already initialized. Call this method prior to using colors with the TB
+     * keys to ensure they are available.
+     */
+    public void initializeSectionToolBarColors() {
+        if (colorRegistry.containsKey(IFormColors.TB_BG))
+            return;
+        createTitleBarGradientColors();
+        createTitleBarOutlineColors();
+        createTwistieColors();
+    }
+
+    /**
+     * Allocates additional colors for the form header, namely background
+     * gradients, bottom separator keylines and DND highlights. Since these
+     * colors are only needed for clients that want to use these particular
+     * style of header rendering, they are not needed all the time and are
+     * allocated on demand. Consequently, this method will do nothing if the
+     * colors have been already initialized. Call this method prior to using
+     * color keys with the H_ prefix to ensure they are available.
+     *
+     * @since 3.3
+     */
+    protected void initializeFormHeaderColors() {
+        if (colorRegistry.containsKey(IFormColors.H_BOTTOM_KEYLINE2))
+            return;
+        createFormHeaderColors();
+    }
+
+    /**
+     * Returns the RGB value of the system color represented by the code
+     * argument, as defined in <code>DWT</code> class.
+     *
+     * @param code
+     *            the system color constant as defined in <code>DWT</code>
+     *            class.
+     * @return the RGB value of the system color
+     */
+    public RGB getSystemColor(int code) {
+        return getDisplay().getSystemColor(code).getRGB();
+    }
+
+    /**
+     * Creates the color for the specified key using the provided RGB object.
+     * The color object will be returned and also put into the registry. When
+     * the class is disposed, the color will be disposed with it.
+     *
+     * @param key
+     *            the unique color key
+     * @param rgb
+     *            the RGB object
+     * @return the allocated color object
+     */
+    public Color createColor(String key, RGB rgb) {
+        return createColor(key, rgb.red, rgb.green, rgb.blue);
+    }
+
+    /**
+     * Creates a color that can be used for areas of the form that is inactive.
+     * These areas can contain images, links, controls and other content but are
+     * considered auxilliary to the main content area.
+     *
+     * <p>
+     * The color should not be disposed because it is managed by this class.
+     *
+     * @return the inactive form color
+     * @since 3.1
+     */
+    public Color getInactiveBackground() {
+        String key = "__ncbg__"; //$NON-NLS-1$
+        Color color = getColor(key);
+        if (color is null) {
+            RGB sel = getSystemColor(DWT.COLOR_LIST_SELECTION);
+            // a blend of 95% white and 5% list selection system color
+            RGB ncbg = blend(sel, getSystemColor(DWT.COLOR_WHITE), 5);
+            color = createColor(key, ncbg);
+        }
+        return color;
+    }
+
+    /**
+     * Creates the color for the specified key using the provided RGB values.
+     * The color object will be returned and also put into the registry. If
+     * there is already another color object under the same key in the registry,
+     * the existing object will be disposed. When the class is disposed, the
+     * color will be disposed with it.
+     *
+     * @param key
+     *            the unique color key
+     * @param r
+     *            red value
+     * @param g
+     *            green value
+     * @param b
+     *            blue value
+     * @return the allocated color object
+     */
+    public Color createColor(String key, int r, int g, int b) {
+        Color c = new Color(display, r, g, b);
+        Color prevC = cast(Color) colorRegistry.get(key);
+        if (prevC !is null)
+            prevC.dispose();
+        colorRegistry.add(key, c);
+        return c;
+    }
+
+    /**
+     * Computes the border color relative to the background. Allocated border
+     * color is designed to work well with white. Otherwise, stanard widget
+     * background color will be used.
+     */
+    protected void updateBorderColor() {
+        if (isWhiteBackground())
+            border = getColor(IFormColors.BORDER);
+        else {
+            border = display.getSystemColor(DWT.COLOR_WIDGET_BACKGROUND);
+            Color bg = getImpliedBackground();
+            if (border.getRed() is bg.getRed()
+                    && border.getGreen() is bg.getGreen()
+                    && border.getBlue() is bg.getBlue())
+                border = display.getSystemColor(DWT.COLOR_WIDGET_DARK_SHADOW);
+        }
+    }
+
+    /**
+     * Sets the background color. All the toolkits that use this class will
+     * share the same background.
+     *
+     * @param bg
+     *            background color
+     */
+    public void setBackground(Color bg) {
+        this.background = bg;
+        updateBorderColor();
+        updateFormHeaderColors();
+    }
+
+    /**
+     * Sets the foreground color. All the toolkits that use this class will
+     * share the same foreground.
+     *
+     * @param fg
+     *            foreground color
+     */
+    public void setForeground(Color fg) {
+        this.foreground = fg;
+    }
+
+    /**
+     * Returns the current background color.
+     *
+     * @return the background color
+     */
+    public Color getBackground() {
+        return background;
+    }
+
+    /**
+     * Returns the current foreground color.
+     *
+     * @return the foreground color
+     */
+    public Color getForeground() {
+        return foreground;
+    }
+
+    /**
+     * Returns the computed border color. Border color depends on the background
+     * and is recomputed whenever the background changes.
+     *
+     * @return the current border color
+     */
+    public Color getBorderColor() {
+        return border;
+    }
+
+    /**
+     * Tests if the background is white. White background has RGB value
+     * 255,255,255.
+     *
+     * @return <samp>true</samp> if background is white, <samp>false</samp>
+     *         otherwise.
+     */
+    public bool isWhiteBackground() {
+        Color bg = getImpliedBackground();
+        return bg.getRed() is 255 && bg.getGreen() is 255
+                && bg.getBlue() is 255;
+    }
+
+    /**
+     * Returns the color object for the provided key or <samp>null </samp> if
+     * not in the registry.
+     *
+     * @param key
+     *            the color key
+     * @return color object if found, or <samp>null </samp> if not.
+     */
+    public Color getColor(String key) {
+        if (key.startsWith(IFormColors.TB_PREFIX))
+            initializeSectionToolBarColors();
+        else if (key.startsWith(IFormColors.H_PREFIX))
+            initializeFormHeaderColors();
+        return cast(Color) colorRegistry.get(key);
+    }
+
+    /**
+     * Disposes all the colors in the registry.
+     */
+    public void dispose() {
+        foreach( k, v; colorRegistry )
+            (cast(Color) v).dispose();
+
+        colorRegistry = null;
+    }
+
+    /**
+     * Marks the colors shared. This prevents toolkits that share this object
+     * from disposing it.
+     */
+    public void markShared() {
+        this.shared = true;
+    }
+
+    /**
+     * Tests if the colors are shared.
+     *
+     * @return <code>true</code> if shared, <code>false</code> otherwise.
+     */
+    public bool isShared() {
+        return shared;
+    }
+
+    /**
+     * Blends c1 and c2 based in the provided ratio.
+     *
+     * @param c1
+     *            first color
+     * @param c2
+     *            second color
+     * @param ratio
+     *            percentage of the first color in the blend (0-100)
+     * @return the RGB value of the blended color
+     * @since 3.1
+     */
+    public static RGB blend(RGB c1, RGB c2, int ratio) {
+        int r = blend(c1.red, c2.red, ratio);
+        int g = blend(c1.green, c2.green, ratio);
+        int b = blend(c1.blue, c2.blue, ratio);
+        return new RGB(r, g, b);
+    }
+
+    /**
+     * Tests the source RGB for range.
+     *
+     * @param rgb
+     *            the tested RGB
+     * @param from
+     *            range start (excluding the value itself)
+     * @param to
+     *            range end (excluding the value itself)
+     * @return <code>true</code> if at least one of the primary colors in the
+     *         source RGB are within the provided range, <code>false</code>
+     *         otherwise.
+     * @since 3.1
+     */
+    public static bool testAnyPrimaryColor(RGB rgb, int from, int to) {
+        if (testPrimaryColor(rgb.red, from, to))
+            return true;
+        if (testPrimaryColor(rgb.green, from, to))
+            return true;
+        if (testPrimaryColor(rgb.blue, from, to))
+            return true;
+        return false;
+    }
+
+    /**
+     * Tests the source RGB for range.
+     *
+     * @param rgb
+     *            the tested RGB
+     * @param from
+     *            range start (excluding the value itself)
+     * @param to
+     *            tange end (excluding the value itself)
+     * @return <code>true</code> if at least two of the primary colors in the
+     *         source RGB are within the provided range, <code>false</code>
+     *         otherwise.
+     * @since 3.1
+     */
+    public static bool testTwoPrimaryColors(RGB rgb, int from, int to) {
+        int total = 0;
+        if (testPrimaryColor(rgb.red, from, to))
+            total++;
+        if (testPrimaryColor(rgb.green, from, to))
+            total++;
+        if (testPrimaryColor(rgb.blue, from, to))
+            total++;
+        return total >= 2;
+    }
+
+    /**
+     * Blends two primary color components based on the provided ratio.
+     *
+     * @param v1
+     *            first component
+     * @param v2
+     *            second component
+     * @param ratio
+     *            percentage of the first component in the blend
+     * @return
+     */
+    private static int blend(int v1, int v2, int ratio) {
+        int b = (ratio * v1 + (100 - ratio) * v2) / 100;
+        return Math.min(255, b);
+    }
+
+    private Color getImpliedBackground() {
+        if (getBackground() !is null)
+            return getBackground();
+        return getDisplay().getSystemColor(DWT.COLOR_WIDGET_BACKGROUND);
+    }
+
+    private static bool testPrimaryColor(int value, int from, int to) {
+        return value > from && value < to;
+    }
+
+    private void createTitleColor() {
+        /*
+         * RGB rgb = getSystemColor(DWT.COLOR_LIST_SELECTION); // test too light
+         * if (testTwoPrimaryColors(rgb, 120, 151)) rgb = blend(rgb, BLACK, 80);
+         * else if (testTwoPrimaryColors(rgb, 150, 256)) rgb = blend(rgb, BLACK,
+         * 50); createColor(TITLE, rgb);
+         */
+        RGB bg = getImpliedBackground().getRGB();
+        RGB listSelection = getSystemColor(DWT.COLOR_LIST_SELECTION);
+        RGB listForeground = getSystemColor(DWT.COLOR_LIST_FOREGROUND);
+        RGB rgb = listSelection;
+
+        // Group 1
+        // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or
+        // between 0 and 120, then use 100% LIST_SELECTION as it is (no
+        // additions)
+        // Examples: XP Default, Win Classic Standard, Win High Con White, Win
+        // Classic Marine
+        if (testTwoPrimaryColors(listSelection, -1, 121))
+            rgb = listSelection;
+        // Group 2
+        // When LIST_BACKGROUND = white (255, 255, 255) or not black, text
+        // colour = LIST_SELECTION @ 100% Opacity + 50% LIST_FOREGROUND over
+        // LIST_BACKGROUND
+        // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or
+        // between 121 and 255, then add 50% LIST_FOREGROUND to LIST_SELECTION
+        // foreground colour
+        // Examples: Win Vista, XP Silver, XP Olive , Win Classic Plum, OSX
+        // Aqua, OSX Graphite, Linux GTK
+        else if (testTwoPrimaryColors(listSelection, 120, 256)
+                || (bg.red is 0 && bg.green is 0 && bg.blue is 0))
+            rgb = blend(listSelection, listForeground, 50);
+        // Group 3
+        // When LIST_BACKGROUND = black (0, 0, 0), text colour = LIST_SELECTION
+        // @ 100% Opacity + 50% LIST_FOREGROUND over LIST_BACKGROUND
+        // Rule: If LIST_BACKGROUND = 0, 0, 0, then add 50% LIST_FOREGROUND to
+        // LIST_SELECTION foreground colour
+        // Examples: Win High Con Black, Win High Con #1, Win High Con #2
+        // (covered in the second part of the OR clause above)
+        createColor(IFormColors.TITLE, rgb);
+    }
+
+    private void createTwistieColors() {
+        RGB rgb = getColor(IFormColors.TITLE).getRGB();
+        RGB white = getSystemColor(DWT.COLOR_WHITE);
+        createColor(TB_TOGGLE, rgb);
+        rgb = blend(rgb, white, 60);
+        createColor(TB_TOGGLE_HOVER, rgb);
+    }
+
+    private void createTitleBarGradientColors() {
+        RGB tbBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND);
+        RGB bg = getImpliedBackground().getRGB();
+
+        // Group 1
+        // Rule: If at least 2 of the RGB values are equal to or between 180 and
+        // 255, then apply specified opacity for Group 1
+        // Examples: Vista, XP Silver, Wn High Con #2
+        // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+        // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+        if (testTwoPrimaryColors(tbBg, 179, 256))
+            tbBg = blend(tbBg, bg, 30);
+
+        // Group 2
+        // Rule: If at least 2 of the RGB values are equal to or between 121 and
+        // 179, then apply specified opacity for Group 2
+        // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+        // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND
+        // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+        else if (testTwoPrimaryColors(tbBg, 120, 180))
+            tbBg = blend(tbBg, bg, 20);
+
+        // Group 3
+        // Rule: Everything else
+        // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+        // Aqua, Wn High Con White, Wn High Con #1
+        // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND
+        // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+        else {
+            tbBg = blend(tbBg, bg, 10);
+        }
+
+        createColor(IFormColors.TB_BG, tbBg);
+
+        // for backward compatibility
+        createColor(TB_GBG, tbBg);
+    }
+
+    private void createTitleBarOutlineColors() {
+        // title bar outline - border color
+        RGB tbBorder = getSystemColor(DWT.COLOR_TITLE_BACKGROUND);
+        RGB bg = getImpliedBackground().getRGB();
+        // Group 1
+        // Rule: If at least 2 of the RGB values are equal to or between 180 and
+        // 255, then apply specified opacity for Group 1
+        // Examples: Vista, XP Silver, Wn High Con #2
+        // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND
+        if (testTwoPrimaryColors(tbBorder, 179, 256))
+            tbBorder = blend(tbBorder, bg, 70);
+
+        // Group 2
+        // Rule: If at least 2 of the RGB values are equal to or between 121 and
+        // 179, then apply specified opacity for Group 2
+        // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+
+        // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND
+        else if (testTwoPrimaryColors(tbBorder, 120, 180))
+            tbBorder = blend(tbBorder, bg, 50);
+
+        // Group 3
+        // Rule: Everything else
+        // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+        // Aqua, Wn High Con White, Wn High Con #1
+
+        // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+        else {
+            tbBorder = blend(tbBorder, bg, 30);
+        }
+        createColor(FormColors.TB_BORDER, tbBorder);
+    }
+
+    private void updateFormHeaderColors() {
+        if (colorRegistry.containsKey(IFormColors.H_GRADIENT_END)) {
+            disposeIfFound(IFormColors.H_GRADIENT_END);
+            disposeIfFound(IFormColors.H_GRADIENT_START);
+            disposeIfFound(IFormColors.H_BOTTOM_KEYLINE1);
+            disposeIfFound(IFormColors.H_BOTTOM_KEYLINE2);
+            disposeIfFound(IFormColors.H_HOVER_LIGHT);
+            disposeIfFound(IFormColors.H_HOVER_FULL);
+            initializeFormHeaderColors();
+        }
+    }
+
+    private void disposeIfFound(String key) {
+        Color color = getColor(key);
+        if (color !is null) {
+            colorRegistry.removeKey(key);
+            color.dispose();
+        }
+    }
+
+    private void createFormHeaderColors() {
+        createFormHeaderGradientColors();
+        createFormHeaderKeylineColors();
+        createFormHeaderDNDColors();
+    }
+
+    private void createFormHeaderGradientColors() {
+        RGB titleBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND);
+        Color bgColor = getImpliedBackground();
+        RGB bg = bgColor.getRGB();
+        RGB bottom, top;
+        // Group 1
+        // Rule: If at least 2 of the RGB values are equal to or between 180 and
+        // 255, then apply specified opacity for Group 1
+        // Examples: Vista, XP Silver, Wn High Con #2
+        // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+        // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+        if (testTwoPrimaryColors(titleBg, 179, 256)) {
+            bottom = blend(titleBg, bg, 30);
+            top = bg;
+        }
+
+        // Group 2
+        // Rule: If at least 2 of the RGB values are equal to or between 121 and
+        // 179, then apply specified opacity for Group 2
+        // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+        // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND
+        // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+        else if (testTwoPrimaryColors(titleBg, 120, 180)) {
+            bottom = blend(titleBg, bg, 20);
+            top = bg;
+        }
+
+        // Group 3
+        // Rule: If at least 2 of the RGB values are equal to or between 0 and
+        // 120, then apply specified opacity for Group 3
+        // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+        // Aqua, Wn High Con White, Wn High Con #1
+        // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND
+        // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+        else {
+            bottom = blend(titleBg, bg, 10);
+            top = bg;
+        }
+        createColor(IFormColors.H_GRADIENT_END, top);
+        createColor(IFormColors.H_GRADIENT_START, bottom);
+    }
+
+    private void createFormHeaderKeylineColors() {
+        RGB titleBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND);
+        Color bgColor = getImpliedBackground();
+        RGB bg = bgColor.getRGB();
+        RGB keyline2;
+        // H_BOTTOM_KEYLINE1
+        createColor(IFormColors.H_BOTTOM_KEYLINE1, new RGB(255, 255, 255));
+
+        // H_BOTTOM_KEYLINE2
+        // Group 1
+        // Rule: If at least 2 of the RGB values are equal to or between 180 and
+        // 255, then apply specified opacity for Group 1
+        // Examples: Vista, XP Silver, Wn High Con #2
+        // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND
+        if (testTwoPrimaryColors(titleBg, 179, 256))
+            keyline2 = blend(titleBg, bg, 70);
+
+        // Group 2
+        // Rule: If at least 2 of the RGB values are equal to or between 121 and
+        // 179, then apply specified opacity for Group 2
+        // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+        // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND
+        else if (testTwoPrimaryColors(titleBg, 120, 180))
+            keyline2 = blend(titleBg, bg, 50);
+
+        // Group 3
+        // Rule: If at least 2 of the RGB values are equal to or between 0 and
+        // 120, then apply specified opacity for Group 3
+        // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+        // Aqua, Wn High Con White, Wn High Con #1
+
+        // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+        else
+            keyline2 = blend(titleBg, bg, 30);
+        // H_BOTTOM_KEYLINE2
+        createColor(IFormColors.H_BOTTOM_KEYLINE2, keyline2);
+    }
+
+    private void createFormHeaderDNDColors() {
+        RGB titleBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND_GRADIENT);
+        Color bgColor = getImpliedBackground();
+        RGB bg = bgColor.getRGB();
+        RGB light, full;
+        // ALL Themes
+        //
+        // Light Highlight
+        // When *near* the 'hot' area
+        // Rule: If near the title in the 'hot' area, show background highlight
+        // TITLE_BACKGROUND_GRADIENT @ 40%
+        light = blend(titleBg, bg, 40);
+        // Full Highlight
+        // When *on* the title area (regions 1 and 2)
+        // Rule: If near the title in the 'hot' area, show background highlight
+        // TITLE_BACKGROUND_GRADIENT @ 60%
+        full = blend(titleBg, bg, 60);
+        // H_DND_LIGHT
+        // H_DND_FULL
+        createColor(IFormColors.H_HOVER_LIGHT, light);
+        createColor(IFormColors.H_HOVER_FULL, full);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/FormDialog.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 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 dwtx.ui.forms.FormDialog;
+
+import dwtx.ui.forms.IManagedForm;
+import dwtx.ui.forms.ManagedForm;
+
+import dwt.DWT;
+import dwt.layout.GridData;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Label;
+import dwt.widgets.Shell;
+import dwtx.jface.dialogs.TrayDialog;
+import dwtx.jface.window.IShellProvider;
+import dwtx.ui.forms.widgets.FormToolkit;
+import dwtx.ui.forms.widgets.ScrolledForm;
+import dwtx.ui.internal.forms.Messages;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A general-purpose dialog that hosts a form. Clients should extend the class
+ * and override <code>createFormContent(IManagedForm)</code> protected method.
+ * <p>
+ * Since forms with wrapped text typically don't have a preferred size, it is
+ * important to set the initial dialog size upon creation:
+ * <p>
+ *
+ * <pre>
+ * MyFormDialog dialog = new MyFormDialog(shell);
+ * dialog.create();
+ * dialog.getShell().setSize(500, 500);
+ * dialog.open();
+ * </pre>
+ *
+ * <p>
+ * Otherwise, the dialog may open very wide.
+ * <p>
+ *
+ * @since 3.3
+ */
+
+public class FormDialog : TrayDialog {
+    private FormToolkit toolkit;
+
+    /**
+     * Creates a new form dialog for a provided parent shell.
+     *
+     * @param shell
+     *            the parent shell
+     */
+    public this(Shell shell) {
+        super(shell);
+        setShellStyle(getShellStyle() | DWT.RESIZE);
+    }
+
+    /**
+     * Creates a new form dialog for a provided parent shell provider.
+     *
+     * @param parentShellProvider
+     *            the parent shell provider
+     */
+    public this(IShellProvider parentShellProvider) {
+        super(parentShellProvider);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.TrayDialog#close()
+     */
+    public bool close() {
+        bool rcode = super.close();
+        toolkit.dispose();
+        return rcode;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.Dialog#createDialogArea(dwt.widgets.Composite)
+     */
+    protected Control createDialogArea(Composite parent) {
+        toolkit = new FormToolkit(parent.getDisplay());
+        ScrolledForm sform = toolkit.createScrolledForm(parent);
+        sform.setLayoutData(new GridData(GridData.FILL_BOTH));
+        ManagedForm mform = new ManagedForm(toolkit, sform);
+        createFormContent(mform);
+        applyDialogFont(sform.getBody());
+        return sform;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.TrayDialog#createButtonBar(dwt.widgets.Composite)
+     */
+    protected Control createButtonBar(Composite parent) {
+        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+        //Composite sep = new Composite(parent, DWT.NULL);
+        //sep.setBackground(parent.getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW));
+        //gd.heightHint = 1;
+        Label sep = new Label(parent, DWT.HORIZONTAL|DWT.SEPARATOR);
+        sep.setLayoutData(gd);
+        Control bar = super.createButtonBar(parent);
+        return bar;
+    }
+
+    /**
+     * Configures the dialog form and creates form content. Clients should
+     * override this method.
+     *
+     * @param mform
+     *            the dialog form
+     */
+    protected void createFormContent(IManagedForm mform) {
+        mform.getForm().setText(Messages.FormDialog_defaultTitle);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/HyperlinkGroup.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.HyperlinkGroup;
+
+import dwtx.ui.forms.HyperlinkSettings;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwtx.ui.forms.events.HyperlinkEvent;
+import dwtx.ui.forms.events.IHyperlinkListener;
+import dwtx.ui.forms.widgets.Hyperlink;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+
+/**
+ * Manages a group of hyperlinks. It tracks activation, updates normal and
+ * active colors and updates underline state depending on the underline
+ * preference. Hyperlink labels are added to the group after creation and are
+ * automatically removed from the group when they are disposed.
+ *
+ * @since 3.0
+ */
+
+public final class HyperlinkGroup : HyperlinkSettings {
+    private ArraySeq!(Object) links;
+    private Hyperlink lastActivated;
+    private Hyperlink lastEntered;
+    private GroupListener listener;
+    private bool isActiveBackgroundSet;
+    private bool isActiveForegroundSet;
+    private bool isBackgroundSet;
+    private bool isForegroundSet;
+
+    private class GroupListener : Listener, IHyperlinkListener {
+
+        private Color previousBackground;
+        private Color previousForeground;
+
+        public void handleEvent(Event e) {
+            switch (e.type) {
+                case DWT.MouseEnter :
+                    onMouseEnter(e);
+                    break;
+                case DWT.MouseExit :
+                    onMouseExit(e);
+                    break;
+                case DWT.MouseDown :
+                    onMouseDown(e);
+                    break;
+                case DWT.Dispose :
+                    unhook(cast(Hyperlink) e.widget);
+                    break;
+            }
+        }
+        private void onMouseEnter(Event e) {
+            Hyperlink link = cast(Hyperlink) e.widget;
+            previousBackground = link.getBackground();
+            previousForeground = link.getForeground();
+            if (isActiveBackgroundSet)
+                link.setBackground(getActiveBackground());
+            if (isActiveForegroundSet)
+                link.setForeground(getActiveForeground());
+            if (getHyperlinkUnderlineMode() is UNDERLINE_HOVER)
+                link.setUnderlined(true);
+            link.setCursor(getHyperlinkCursor());
+        }
+        private void onMouseExit(Event e) {
+            Hyperlink link = cast(Hyperlink) e.widget;
+            if (isActiveBackgroundSet)
+                link.setBackground(previousBackground);
+            if (isActiveForegroundSet)
+                link.setForeground(previousForeground);
+            if (getHyperlinkUnderlineMode() is UNDERLINE_HOVER)
+                link.setUnderlined(false);
+        }
+        public void linkActivated(HyperlinkEvent e) {
+        }
+
+        public void linkEntered(HyperlinkEvent e) {
+            Hyperlink link = cast(Hyperlink) e.widget;
+            if (lastEntered !is null) {
+                linkExited(lastEntered);
+            }
+            lastEntered = link;
+        }
+
+        public void linkExited(HyperlinkEvent e) {
+            linkExited(cast(Hyperlink) e.widget);
+        }
+        private void linkExited(Hyperlink link) {
+            link.setCursor(null);
+            if (lastEntered is link)
+                lastEntered = null;
+        }
+    }
+
+    /**
+     * Creates a hyperlink group.
+     */
+
+    public this(Display display) {
+        links = new ArraySeq!(Object);
+        super(display);
+        listener = new GroupListener();
+    }
+
+    /**
+     * Returns the link that has been active the last, or <code>null</code>
+     * if no link has been active yet or the last active link has been
+     * disposed.
+     *
+     * @return the last active link or <code>null</code>
+     */
+    public Hyperlink getLastActivated() {
+        return lastActivated;
+    }
+    /**
+     * Adds a hyperlink to the group to be jointly managed. Hyperlink will be
+     * managed until it is disposed. Settings like colors, cursors and modes
+     * will affect all managed hyperlinks.
+     *
+     * @param link
+     */
+
+    public void add(Hyperlink link) {
+        if (isBackgroundSet)
+            link.setBackground(getBackground());
+        if (isForegroundSet)
+            link.setForeground(getForeground());
+        if (getHyperlinkUnderlineMode() is UNDERLINE_ALWAYS)
+            link.setUnderlined(true);
+        hook(link);
+    }
+
+    /**
+     * Sets the new active hyperlink background for all the links.
+     *
+     * @param newActiveBackground
+     *            the new active background
+     */
+    public void setActiveBackground(Color newActiveBackground) {
+        super.setActiveBackground(newActiveBackground);
+        isActiveBackgroundSet = true;
+    }
+
+    /**
+     * Sets the new active hyperlink foreground for all the links.
+     *
+     * @param newActiveForeground
+     *            the new active foreground
+     */
+    public void setActiveForeground(Color newActiveForeground) {
+        super.setActiveForeground(newActiveForeground);
+        isActiveForegroundSet = true;
+    }
+
+    /**
+     * Sets the group background and also sets the background of all the
+     * currently managed links.
+     *
+     * @param bg
+     *            the new background
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        isBackgroundSet = true;
+        if (links !is null) {
+            for (int i = 0; i < links.size(); i++) {
+                Hyperlink label = cast(Hyperlink) links.get(i);
+                label.setBackground(bg);
+            }
+        }
+    }
+    /**
+     * Sets the group foreground and also sets the background of all the
+     * currently managed links.
+     *
+     * @param fg
+     *            the new foreground
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        isForegroundSet = true;
+        if (links !is null) {
+            for (int i = 0; i < links.size(); i++) {
+                Hyperlink label = cast(Hyperlink) links.get(i);
+                label.setForeground(fg);
+            }
+        }
+    }
+    /**
+     * Sets the hyperlink underline mode.
+     *
+     * @param mode
+     *            the new hyperlink underline mode
+     * @see HyperlinkSettings
+     */
+    public void setHyperlinkUnderlineMode(int mode) {
+        super.setHyperlinkUnderlineMode(mode);
+        if (links !is null) {
+            for (int i = 0; i < links.size(); i++) {
+                Hyperlink label = cast(Hyperlink) links.get(i);
+                label.setUnderlined(mode is UNDERLINE_ALWAYS);
+            }
+        }
+    }
+
+    private void hook(Hyperlink link) {
+        link.addListener(DWT.MouseDown, listener);
+        link.addHyperlinkListener(listener);
+        link.addListener(DWT.Dispose, listener);
+        link.addListener(DWT.MouseEnter, listener);
+        link.addListener(DWT.MouseExit, listener);
+        links.append(link);
+    }
+
+    private void unhook(Hyperlink link) {
+        link.removeListener(DWT.MouseDown, listener);
+        link.removeHyperlinkListener(listener);
+        link.removeListener(DWT.MouseEnter, listener);
+        link.removeListener(DWT.MouseExit, listener);
+        if (lastActivated is link)
+            lastActivated = null;
+        if (lastEntered is link)
+            lastEntered = null;
+        links.remove(link);
+    }
+
+    private void onMouseDown(Event e) {
+        if (e.button is 1)
+            return;
+        lastActivated = cast(Hyperlink) e.widget;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/HyperlinkSettings.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.HyperlinkSettings;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.Cursor;
+import dwt.widgets.Display;
+import dwtx.jface.resource.JFaceColors;
+import dwtx.ui.internal.forms.widgets.FormsResources;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Manages color and underline mode settings for a group of hyperlinks. The
+ * class is extended by HyperlinkGroup but is otwerwise not intended to be
+ * subclassed.
+ *
+ * @since 3.0
+ */
+public class HyperlinkSettings {
+    /**
+     * Underline mode to be used when hyperlinks should not be underlined.
+     */
+    public static const int UNDERLINE_NEVER = 1;
+    /**
+     * Underline mode to be used when hyperlinks should only be underlined on
+     * mouse hover.
+     */
+    public static const int UNDERLINE_HOVER = 2;
+    /**
+     * Underline mode to be used when hyperlinks should always be underlined.
+     */
+    public static const int UNDERLINE_ALWAYS = 3;
+    private int hyperlinkUnderlineMode = UNDERLINE_ALWAYS;
+    private Color background;
+    private Color foreground;
+    private Color activeBackground;
+    private Color activeForeground;
+    /**
+     * The constructor.
+     *
+     * @param display
+     *            the display to use when creating colors.
+     */
+    public this(Display display) {
+        initializeDefaultForegrounds(display);
+    }
+    /**
+     * Initializes the hyperlink foregrounds from the JFace defaults set for the
+     * entire workbench.
+     *
+     * @see JFaceColors
+     * @param display
+     *            the display to use when creating colors
+     */
+    public void initializeDefaultForegrounds(Display display) {
+        Color fg = JFaceColors.getHyperlinkText(display);
+        Color afg = JFaceColors.getActiveHyperlinkText(display);
+        if (fg is null)
+            fg = display.getSystemColor(DWT.COLOR_BLUE);
+        setForeground(fg);
+        setActiveForeground(afg);
+    }
+    /**
+     * Returns the background to use for the active hyperlink.
+     *
+     * @return active hyperlink background
+     */
+    public Color getActiveBackground() {
+        return activeBackground;
+    }
+    /**
+     * Returns the foreground to use for the active hyperlink.
+     *
+     * @return active hyperlink foreground
+     */
+    public Color getActiveForeground() {
+        return activeForeground;
+    }
+    /**
+     * Returns the background to use for the normal hyperlink.
+     *
+     * @return normal hyperlink background
+     */
+    public Color getBackground() {
+        return background;
+    }
+    /**
+     * Returns the cursor to use when the hyperlink is active. This cursor will
+     * be shown before hyperlink listeners have been notified of hyperlink
+     * activation and hidden when the notification method returns.
+     *
+     * @return the busy cursor
+     */
+    public Cursor getBusyCursor() {
+        return FormsResources.getBusyCursor();
+    }
+    /**
+     * Returns the cursor to use when over text.
+     *
+     * @return the text cursor
+     */
+    public Cursor getTextCursor() {
+        return FormsResources.getTextCursor();
+    }
+    /**
+     * Returns the foreground to use for the normal hyperlink.
+     *
+     * @return the normal hyperlink foreground
+     */
+    public Color getForeground() {
+        return foreground;
+    }
+    /**
+     * Returns the cursor to use when hovering over the hyperlink.
+     *
+     * @return the hyperlink cursor
+     */
+    public Cursor getHyperlinkCursor() {
+        return FormsResources.getHandCursor();
+    }
+    /**
+     * Returns the underline mode to be used for all the hyperlinks in this
+     * group.
+     *
+     * @return one of UNDERLINE_NEVER, UNDERLINE_ALWAYS, UNDERLINE_HOVER
+     */
+    public int getHyperlinkUnderlineMode() {
+        return hyperlinkUnderlineMode;
+    }
+    /**
+     * Sets the new active hyperlink background for all the links.
+     *
+     * @param newActiveBackground
+     *            the new active background
+     */
+    public void setActiveBackground(Color newActiveBackground) {
+        activeBackground = newActiveBackground;
+    }
+    /**
+     * Sets the new active hyperlink foreground for all the links.
+     *
+     * @param newActiveForeground
+     *            the new active foreground
+     */
+    public void setActiveForeground(Color newActiveForeground) {
+        activeForeground = newActiveForeground;
+    }
+    /**
+     * Sets the new hyperlink background for all the links.
+     *
+     * @param newBackground
+     *            the new hyperlink background
+     */
+    public void setBackground(Color newBackground) {
+        background = newBackground;
+    }
+    /**
+     * Sets the new hyperlink foreground for all the links.
+     *
+     * @param newForeground
+     *            the new hyperlink foreground
+     */
+    public void setForeground(Color newForeground) {
+        foreground = newForeground;
+    }
+    /**
+     * Sets the new hyperlink underline mode for all the links in this group.
+     *
+     * @param mode
+     *            one of <code>UNDERLINE_NEVER</code>,
+     *            <code>UNDERLINE_HOVER</code> and
+     *            <code>UNDERLINE_ALWAYS</code>.
+     */
+    public void setHyperlinkUnderlineMode(int mode) {
+        hyperlinkUnderlineMode = mode;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IDetailsPage.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.IDetailsPage;
+
+import dwtx.ui.forms.IFormPart;
+import dwtx.ui.forms.IPartSelectionListener;
+
+import dwt.widgets.Composite;
+
+/**
+ * This interface should be implemented by clients providing
+ * pages to handle object types in DetailsPart. Most of the
+ * life cycle is the same as for the IFormPart. The page is
+ * a part selection listener since selections in the master
+ * part will be passed to the currently visible page.
+ *
+ * @see DetailsPart
+ * @see MasterDetailsBlock
+ * @since 3.0
+ */
+public interface IDetailsPage : IFormPart, IPartSelectionListener {
+    /**
+     * Creates the contents of the page in the provided parent.
+     * @param parent the parent to create the page in
+     */
+    void createContents(Composite parent);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IDetailsPageProvider.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.IDetailsPageProvider;
+
+import dwtx.ui.forms.IDetailsPage;
+
+/**
+ * The class that implements this interface provides for dynamic
+ * computation of page key and the page itself based on the
+ * input object. It should be used in situations where
+ * using the object class as a static key is not enough
+ * i.e. different pages may need to be loaded for objects
+ * of the same type depending on their state.
+ *
+ * @see DetailsPart
+ * @see MasterDetailsBlock
+ * @since 3.0
+ */
+public interface IDetailsPageProvider {
+/**
+ * Returns the page key for the provided object. The assumption is
+ * that the provider knows about various object types and
+ * is in position to cast the object into a type and call methods
+ * on it to determine the matching page key.
+ * @param object the input object
+ * @return the page key for the provided object
+ */
+    Object getPageKey(Object object);
+/**
+ * Returns the page for the provided key. This method is the dynamic
+ * alternative to registering pages with the details part directly.
+ * @param key the page key
+ * @return the matching page for the provided key
+ */
+    IDetailsPage getPage(Object key);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IFormColors.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.forms.IFormColors;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A place to hold all the color constants used in the forms package.
+ *
+ * @since 3.3
+ */
+
+public interface IFormColors {
+    /**
+     * A prefix for all the keys.
+     */
+    static const String PREFIX = "dwtx.ui.forms."; //$NON-NLS-1$
+    /**
+     * Key for the form title foreground color.
+     */
+    static const String TITLE = PREFIX ~ "TITLE"; //$NON-NLS-1$
+
+    /**
+     * A prefix for the header color constants.
+     */
+    static const String H_PREFIX = PREFIX ~ "H_"; //$NON-NLS-1$
+    /*
+     * A prefix for the section title bar color constants.
+     */
+    static const String TB_PREFIX = PREFIX ~ "TB_"; //$NON-NLS-1$
+    /**
+     * Key for the form header background gradient ending color.
+     */
+    static const String H_GRADIENT_END = H_PREFIX ~ "GRADIENT_END"; //$NON-NLS-1$
+
+    /**
+     * Key for the form header background gradient starting color.
+     *
+     */
+    static const String H_GRADIENT_START = H_PREFIX ~ "GRADIENT_START"; //$NON-NLS-1$
+    /**
+     * Key for the form header bottom keyline 1 color.
+     *
+     */
+    static const String H_BOTTOM_KEYLINE1 = H_PREFIX ~ "BOTTOM_KEYLINE1"; //$NON-NLS-1$
+    /**
+     * Key for the form header bottom keyline 2 color.
+     *
+     */
+    static const String H_BOTTOM_KEYLINE2 = H_PREFIX ~ "BOTTOM_KEYLINE2"; //$NON-NLS-1$
+    /**
+     * Key for the form header light hover color.
+     *
+     */
+    static const String H_HOVER_LIGHT = H_PREFIX ~ "H_HOVER_LIGHT"; //$NON-NLS-1$
+    /**
+     * Key for the form header full hover color.
+     *
+     */
+    static const String H_HOVER_FULL = H_PREFIX ~ "H_HOVER_FULL"; //$NON-NLS-1$
+
+    /**
+     * Key for the tree/table border color.
+     */
+    static const String BORDER = PREFIX ~ "BORDER"; //$NON-NLS-1$
+
+    /**
+     * Key for the section separator color.
+     */
+    static const String SEPARATOR = PREFIX ~ "SEPARATOR"; //$NON-NLS-1$
+
+    /**
+     * Key for the section title bar background.
+     */
+    static const String TB_BG = TB_PREFIX ~ "BG"; //$NON-NLS-1$
+
+    /**
+     * Key for the section title bar foreground.
+     */
+    static const String TB_FG = TB_PREFIX ~ "FG"; //$NON-NLS-1$
+
+    /**
+     * Key for the section title bar gradient.
+     * @deprecated Since 3.3, this color is not used any more. The
+     * tool bar gradient is created starting from {@link #TB_BG} to
+     * the section background color.
+     */
+    static const String TB_GBG = TB_BG;
+
+    /**
+     * Key for the section title bar border.
+     */
+    static const String TB_BORDER = TB_PREFIX ~ "BORDER"; //$NON-NLS-1$
+
+    /**
+     * Key for the section toggle color. Since 3.1, this color is used for all
+     * section styles.
+     */
+    static const String TB_TOGGLE = TB_PREFIX ~ "TOGGLE"; //$NON-NLS-1$
+
+    /**
+     * Key for the section toggle hover color.
+     *
+     */
+    static const String TB_TOGGLE_HOVER = TB_PREFIX ~ "TOGGLE_HOVER"; //$NON-NLS-1$
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IFormPart.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.IFormPart;
+
+import dwtx.ui.forms.IManagedForm;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Classes that implement this interface can be added to the managed form and
+ * take part in the form life cycle. The part is initialized with the form and
+ * will be asked to accept focus. The part can receive form input and can elect
+ * to do something according to it (for example, select an object that matches
+ * the input).
+ * <p>
+ * The form part has two 'out of sync' states in respect to the model(s) that
+ * feed the form: <b>dirty</b> and <b>stale</b>. When a part is dirty, it
+ * means that the user interacted with it and now its widgets contain state that
+ * is newer than the model. In order to sync up with the model, 'commit' needs
+ * to be called. In contrast, the model can change 'under' the form (as a result
+ * of some actions outside the form), resulting in data in the model being
+ * 'newer' than the content presented in the form. A 'stale' form part is
+ * brought in sync with the model by calling 'refresh'. The part is responsible
+ * for notifying the form when one of these states change in the part. The form
+ * reserves the right to handle this notification in the most appropriate way
+ * for the situation (for example, if the form is in a page of the multi-page
+ * editor, it may do nothing for stale parts if the page is currently not
+ * showing).
+ * <p>
+ * When the form is disposed, each registered part is disposed as well. Parts
+ * are responsible for releasing any system resources they created and for
+ * removing themselves as listeners from all event providers.
+ *
+ * @see IManagedForm
+ * @since 3.0
+ *
+ */
+public interface IFormPart {
+    /**
+     * Initializes the part.
+     *
+     * @param form
+     *            the managed form that manages the part
+     */
+    void initialize(IManagedForm form);
+
+    /**
+     * Disposes the part allowing it to release allocated resources.
+     */
+    void dispose();
+
+    /**
+     * Returns true if the part has been modified with respect to the data
+     * loaded from the model.
+     *
+     * @return true if the part has been modified with respect to the data
+     *         loaded from the model
+     */
+    bool isDirty();
+
+    /**
+     * If part is displaying information loaded from a model, this method
+     * instructs it to commit the new (modified) data back into the model.
+     *
+     * @param onSave
+     *            indicates if commit is called during 'save' operation or for
+     *            some other reason (for example, if form is contained in a
+     *            wizard or a multi-page editor and the user is about to leave
+     *            the page).
+     */
+    void commit(bool onSave);
+
+    /**
+     * Notifies the part that an object has been set as overall form's input.
+     * The part can elect to react by revealing or selecting the object, or do
+     * nothing if not applicable.
+     *
+     * @return <code>true</code> if the part has selected and revealed the
+     *         input object, <code>false</code> otherwise.
+     */
+    bool setFormInput(Object input);
+
+    /**
+     * Instructs form part to transfer focus to the widget that should has focus
+     * in that part. The method can do nothing (if it has no widgets capable of
+     * accepting focus).
+     */
+    void setFocus();
+
+    /**
+     * Tests whether the form part is stale and needs refreshing. Parts can
+     * receive notification from models that will make their content stale, but
+     * may need to delay refreshing to improve performance (for example, there
+     * is no need to immediately refresh a part on a form that is current on a
+     * hidden page).
+     * <p>
+     * It is important to differentiate 'stale' and 'dirty' states. Part is
+     * 'dirty' if user interacted with its editable widgets and changed the
+     * values. In contrast, part is 'stale' when the data it presents in the
+     * widgets has been changed in the model without direct user interaction.
+     *
+     * @return <code>true</code> if the part needs refreshing,
+     *         <code>false</code> otherwise.
+     */
+    bool isStale();
+
+    /**
+     * Refreshes the part completely from the information freshly obtained from
+     * the model. The method will not be called if the part is not stale.
+     * Otherwise, the part is responsible for clearing the 'stale' flag after
+     * refreshing itself.
+     */
+    void refresh();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IManagedForm.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 dwtx.ui.forms.IManagedForm;
+
+import dwtx.ui.forms.IFormPart;
+import dwtx.ui.forms.IMessageManager;
+
+import dwtx.jface.viewers.ISelection;
+import dwtx.ui.forms.widgets.FormToolkit;
+import dwtx.ui.forms.widgets.ScrolledForm;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Managed form wraps a form widget and adds life cycle methods for form parts.
+ * A form part is a portion of the form that participates in form life cycle
+ * events.
+ * <p>
+ * There is no 1/1 mapping between widgets and form parts. A widget like Section
+ * can be a part by itself, but a number of widgets can gather around one form
+ * part.
+ * <p>
+ * This interface should not be extended or implemented. New form instances
+ * should be created using ManagedForm.
+ *
+ * @see ManagedForm
+ * @since 3.0
+ */
+public interface IManagedForm {
+    /**
+     * Initializes the form by looping through the managed parts and
+     * initializing them. Has no effect if already called once.
+     *
+     * @since 3.1
+     */
+    public void initialize();
+
+    /**
+     * Returns the toolkit used by this form.
+     *
+     * @return the toolkit
+     */
+    public FormToolkit getToolkit();
+
+    /**
+     * Returns the form widget managed by this form.
+     *
+     * @return the form widget
+     */
+    public ScrolledForm getForm();
+
+    /**
+     * Reflows the form as a result of the layout change.
+     *
+     * @param changed
+     *            if <code>true</code>, discard cached layout information
+     */
+    public void reflow(bool changed);
+
+    /**
+     * A part can use this method to notify other parts that implement
+     * IPartSelectionListener about selection changes.
+     *
+     * @param part
+     *            the part that broadcasts the selection
+     * @param selection
+     *            the selection in the part
+     */
+    public void fireSelectionChanged(IFormPart part, ISelection selection);
+
+    /**
+     * Returns all the parts currently managed by this form.
+     *
+     * @return the managed parts
+     */
+    IFormPart[] getParts();
+
+    /**
+     * Adds the new part to the form.
+     *
+     * @param part
+     *            the part to add
+     */
+    void addPart(IFormPart part);
+
+    /**
+     * Removes the part from the form.
+     *
+     * @param part
+     *            the part to remove
+     */
+    void removePart(IFormPart part);
+
+    /**
+     * Sets the input of this page to the provided object.
+     *
+     * @param input
+     *            the new page input
+     * @return <code>true</code> if the form contains this object,
+     *         <code>false</code> otherwise.
+     */
+    bool setInput(Object input);
+
+    /**
+     * Returns the current page input.
+     *
+     * @return page input object or <code>null</code> if not applicable.
+     */
+    Object getInput();
+
+    /**
+     * Tests if form is dirty. A managed form is dirty if at least one managed
+     * part is dirty.
+     *
+     * @return <code>true</code> if at least one managed part is dirty,
+     *         <code>false</code> otherwise.
+     */
+    bool isDirty();
+
+    /**
+     * Notifies the form that the dirty state of one of its parts has changed.
+     * The global dirty state of the form can be obtained by calling 'isDirty'.
+     *
+     * @see #isDirty
+     */
+    void dirtyStateChanged();
+
+    /**
+     * Commits the dirty form. All pending changes in the widgets are flushed
+     * into the model.
+     *
+     * @param onSave
+     */
+    void commit(bool onSave);
+
+    /**
+     * Tests if form is stale. A managed form is stale if at least one managed
+     * part is stale. This can happen when the underlying model changes,
+     * resulting in the presentation of the part being out of sync with the
+     * model and needing refreshing.
+     *
+     * @return <code>true</code> if the form is stale, <code>false</code>
+     *         otherwise.
+     */
+    bool isStale();
+
+    /**
+     * Notifies the form that the stale state of one of its parts has changed.
+     * The global stale state of the form can be obtained by calling 'isStale'.
+     */
+    void staleStateChanged();
+
+    /**
+     * Refreshes the form by refreshing every part that is stale.
+     */
+    void refresh();
+
+    /**
+     * Sets the container that owns this form. Depending on the context, the
+     * container may be wizard, editor page, editor etc.
+     *
+     * @param container
+     *            the container of this form
+     */
+    void setContainer(Object container);
+
+    /**
+     * Returns the container of this form.
+     *
+     * @return the form container
+     */
+    Object getContainer();
+
+    /**
+     * Returns the message manager that will keep track of messages in this
+     * form.
+     *
+     * @return the message manager instance
+     * @since 3.3
+     */
+    IMessageManager getMessageManager();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IMessage.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.forms.IMessage;
+
+import dwt.widgets.Control;
+import dwtx.jface.dialogs.IMessageProvider;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This interface encapsulates a single message that can be shown in a form.
+ * Messages can be associated with controls, or be of a general nature.
+ *
+ * @see IMessageManager
+ * @since 3.3
+ */
+public interface IMessage : IMessageProvider {
+    /**
+     * Returns the unique message key
+     *
+     * @return the unique message key
+     */
+    Object getKey();
+
+    /**
+     * Returns data for application use
+     *
+     * @return data object
+     */
+    Object getData();
+
+    /**
+     * Returns the control this message is associated with.
+     *
+     * @return the control or <code>null</code> if this is a general message.
+     */
+    Control getControl();
+
+    /**
+     * Messages that are associated with controls can be shown with a prefix
+     * that indicates the origin of the message (e.g. the label preceeding the
+     * control).
+     *
+     * @return the message prefix or <code>null</code> if this is a general
+     *         message
+     */
+    String getPrefix();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IMessageManager.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.forms.IMessageManager;
+
+import dwtx.ui.forms.IMessagePrefixProvider;
+import dwtx.ui.forms.IMessage;
+
+import dwt.widgets.Control;
+import dwtx.jface.dialogs.IMessageProvider;
+import dwtx.jface.fieldassist.ControlDecoration;
+import dwtx.ui.forms.widgets.Form;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This interface provides for managing typed messages in a form. Typed messages
+ * are messages associated with a type that indicates their severity (error,
+ * warning, information). The interface is responsible for:
+ * <ul>
+ * <li>Bridging the concept of typed messages and control decorations</li>
+ * <li>Adding one or more messages per control in a form</li>
+ * <li>Rolling the local messages up to the form header</li>
+ * <li>Adding one or more general messages to the form header</li>
+ * </ul>
+ * <p>
+ * To use it in a form, do the following:
+ * <ol>
+ * <li>For each interactive control, add a listener to it to monitor user input</li>
+ * <li>Every time the input changes, validate it. If there is a problem, add a
+ * message with a unique key to the manager. If there is already a message with
+ * the same key in the manager, its type and message text will be replaced (no
+ * duplicates). Note that you add can messages with different keys to the same
+ * control to track multiple problems with the user input.</li>
+ * <li>If the problem has been cleared, remove the message using the key
+ * (attempting to remove a message that is not in the manager is safe).</li>
+ * <li>If something happens in the form that is not related to any control, use
+ * the other <code>addMessage</code> method.</li>
+ * </ol>
+ * <p>
+ * This interface should only be referenced. It must not be implemented or
+ * extended.
+ * </p>
+ *
+ * @since 3.3
+ * @see IMessageProvider
+ * @see IManagedForm
+ */
+
+public interface IMessageManager {
+    /**
+     * Adds a general message that is not associated with any decorated field.
+     * Note that subsequent calls using the same key will not result in
+     * duplicate messages. Instead, the previous message with the same key will
+     * be replaced with the new message.
+     *
+     * @param key
+     *            a unique message key that will be used to look the message up
+     *            later
+     *
+     * @param messageText
+     *            the message to add
+     * @param data
+     *            an object for application use (can be <code>null</code>)
+     * @param type
+     *            the message type as defined in <code>IMessageProvider</code>.
+     */
+    void addMessage(Object key, String messageText, Object data, int type);
+
+    /**
+     * Adds a message that should be associated with the provided control. Note
+     * that subsequent calls using the same key will not result in duplicate
+     * messages. Instead, the previous message with the same key will be
+     * replaced with the new message.
+     *
+     * @param key
+     *            the unique message key
+     * @param messageText
+     *            the message to add
+     * @param data
+     *            an object for application use (can be <code>null</code>)
+     * @param type
+     *            the message type
+     * @param control
+     *            the control to associate the message with
+     */
+    void addMessage(Object key, String messageText, Object data, int type,
+            Control control);
+
+    /**
+     * Removes the general message with the provided key. Does nothing if
+     * message for the key does not exist.
+     *
+     * @param key
+     *            the key of the message to remove
+     */
+    void removeMessage(Object key);
+
+    /**
+     * Removes all the general messages. If there are local messages associated
+     * with controls, the replacement message may show up drawing user's
+     * attention to these local messages. Otherwise, the container will clear
+     * the message area.
+     */
+    void removeMessages();
+
+    /**
+     * Removes a keyed message associated with the provided control. Does
+     * nothing if the message for that key does not exist.
+     *
+     * @param key
+     *            the id of the message to remove
+     * @param control
+     *            the control the message is associated with
+     */
+    void removeMessage(Object key, Control control);
+
+    /**
+     * Removes all the messages associated with the provided control. Does
+     * nothing if there are no messages for this control.
+     *
+     * @param control
+     *            the control the messages are associated with
+     */
+    void removeMessages(Control control);
+
+    /**
+     * Removes all the local field messages and all the general container
+     * messages.
+     */
+    void removeAllMessages();
+
+    /**
+     * Updates the message container with the messages currently in the manager.
+     * There are two scenarios in which a client may want to use this method:
+     * <ol>
+     * <li>When controls previously managed by this manager have been disposed.</li>
+     * <li>When automatic update has been turned off.</li>
+     * </ol>
+     * In all other situations, the manager will keep the form in sync
+     * automatically.
+     *
+     * @see #setAutoUpdate(bool)
+     */
+    void update();
+
+    /**
+     * Controls whether the form is automatically updated when messages are
+     * added or removed. By default, auto update is on. Clients can turn it off
+     * prior to adding or removing a number of messages as a batch. Turning it
+     * back on will trigger an update.
+     *
+     * @param enabled
+     *            sets the state of the automatic update
+     */
+    void setAutoUpdate(bool enabled);
+
+    /**
+     * Tests whether the form will be automatically updated when messages are
+     * added or removed.
+     *
+     * @return <code>true</code> if auto update is active, <code>false</code>
+     *         otherwise.
+     */
+    bool isAutoUpdate();
+
+    /**
+     * Sets the alternative message prefix provider. The default prefix provider
+     * is set by the manager.
+     *
+     * @param provider
+     *            the new prefix provider or <code>null</code> to turn the
+     *            prefix generation off.
+     */
+    void setMessagePrefixProvider(IMessagePrefixProvider provider);
+
+    /**
+     * @return the current prefix provider or <code>null</code> if prefixes
+     *         are not generated.
+     */
+    IMessagePrefixProvider getMessagePrefixProvider();
+
+    /**
+     * Message manager uses DWT.LEFT|DWT.BOTTOM for the default decoration
+     * position. Use this method to change it.
+     *
+     * @param position
+     *            the decoration position
+     * @see ControlDecoration
+     */
+    void setDecorationPosition(int position);
+
+    /**
+     * Returns the currently used decoration position for all control messages.
+     *
+     * @return the current decoration position
+     */
+
+    int getDecorationPosition();
+
+    /**
+     * When message manager is used in context of a form, and there are
+     * hyperlink listeners for messages in the header, the hyperlink event will
+     * carry an object of type <code>IMessage[]</code> as an href. You can use
+     * this method to create a summary text from this array consistent with the
+     * tool tip used by the form header.
+     *
+     * @param messages
+     *            an array of messages
+     * @return a textual representation of the messages with one message per
+     *         line.
+     * @see Form#addMessageHyperlinkListener(dwtx.ui.forms.events.IHyperlinkListener)
+     */
+    String createSummary(IMessage[] messages);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IMessagePrefixProvider.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.forms.IMessagePrefixProvider;
+
+import dwt.widgets.Control;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This interface computes the prefix of a message that is created in the
+ * context of a control. Since messages are rolled up to the header in the
+ * message manager, it is important to create a prefix to indicate the context
+ * of a message in the form. Typically the prefix is computed by looking at the
+ * label that preceeds the control, if present. Alternative providers may
+ * include other text to further specify the location of the message.
+ *
+ * @see IMessageManager
+ * @see IMessage
+ * @since 3.3
+ */
+public interface IMessagePrefixProvider {
+    /**
+     * Returns the computed prefix for the provided control.
+     *
+     * @param control
+     *            the control to provide the prefix for
+     * @return the computed prefix
+     */
+    String getPrefix(Control control);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/IPartSelectionListener.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.IPartSelectionListener;
+
+import dwtx.ui.forms.IFormPart;
+
+import dwtx.jface.viewers.ISelection;
+
+/**
+ * Form parts can implement this interface if they want to be
+ * notified when another part on the same form changes selection
+ * state.
+ *
+ * @see IFormPart
+ * @since 3.0
+ */
+public interface IPartSelectionListener {
+    /**
+     * Called when the provided part has changed selection state.
+     *
+     * @param part
+     *            the selection source
+     * @param selection
+     *            the new selection
+     */
+    public void selectionChanged(IFormPart part, ISelection selection);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/ManagedForm.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,347 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.ManagedForm;
+
+import dwtx.ui.forms.IManagedForm;
+import dwtx.ui.forms.IFormPart;
+import dwtx.ui.forms.IMessageManager;
+import dwtx.ui.forms.IPartSelectionListener;
+
+import dwt.widgets.Composite;
+import dwtx.jface.viewers.ISelection;
+import dwtx.ui.forms.widgets.FormToolkit;
+import dwtx.ui.forms.widgets.ScrolledForm;
+import dwtx.ui.internal.forms.MessageManager;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+import tango.util.collection.ArraySeq;
+import tango.core.Thread;
+
+/**
+ * Managed form wraps a form widget and adds life cycle methods for form parts.
+ * A form part is a portion of the form that participates in form life cycle
+ * events.
+ * <p>
+ * There is requirement for 1/1 mapping between widgets and form parts. A widget
+ * like Section can be a part by itself, but a number of widgets can join around
+ * one form part.
+ * <p>
+ * Note to developers: this class is left public to allow its use beyond the
+ * original intention (inside a multi-page editor's page). You should limit the
+ * use of this class to make new instances inside a form container (wizard page,
+ * dialog etc.). Clients that need access to the class should not do it
+ * directly. Instead, they should do it through IManagedForm interface as much
+ * as possible.
+ *
+ * @since 3.0
+ */
+public class ManagedForm : IManagedForm {
+    private Object input;
+
+    private ScrolledForm form;
+
+    private FormToolkit toolkit;
+
+    private Object container;
+
+    private bool ownsToolkit;
+
+    private bool initialized;
+
+    private MessageManager messageManager;
+
+    private ArraySeq!(Object) parts;
+
+    /**
+     * Creates a managed form in the provided parent. Form toolkit and widget
+     * will be created and owned by this object.
+     *
+     * @param parent
+     *            the parent widget
+     */
+    public this(Composite parent) {
+        parts = new ArraySeq!(Object);
+        toolkit = new FormToolkit(parent.getDisplay());
+        ownsToolkit = true;
+        form = toolkit.createScrolledForm(parent);
+    }
+
+    /**
+     * Creates a managed form that will use the provided toolkit and
+     *
+     * @param toolkit
+     * @param form
+     */
+    public this(FormToolkit toolkit, ScrolledForm form) {
+        parts = new ArraySeq!(Object);
+        this.form = form;
+        this.toolkit = toolkit;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#addPart(dwtx.ui.forms.IFormPart)
+     */
+    public void addPart(IFormPart part) {
+        parts.append(cast(Object)part);
+        part.initialize(this);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#removePart(dwtx.ui.forms.IFormPart)
+     */
+    public void removePart(IFormPart part) {
+        parts.remove(cast(Object)part);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#getParts()
+     */
+    public IFormPart[] getParts() {
+        return arraycast!(IFormPart)(parts.toArray());
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#getToolkit()
+     */
+    public FormToolkit getToolkit() {
+        return toolkit;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#getForm()
+     */
+    public ScrolledForm getForm() {
+        return form;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#reflow(bool)
+     */
+    public void reflow(bool changed) {
+        form.reflow(changed);
+    }
+
+    /**
+     * A part can use this method to notify other parts that implement
+     * IPartSelectionListener about selection changes.
+     *
+     * @param part
+     *            the part that broadcasts the selection
+     * @param selection
+     *            the selection in the part
+     * @see IPartSelectionListener
+     */
+    public void fireSelectionChanged(IFormPart part, ISelection selection) {
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart cpart = cast(IFormPart) parts.get(i);
+            if ((cast(Object)part).opEquals(cast(Object)cpart))
+                continue;
+            if (null !is cast(IPartSelectionListener)cpart ) {
+                (cast(IPartSelectionListener) cpart).selectionChanged(part,
+                        selection);
+            }
+        }
+    }
+
+    /**
+     * Initializes the form by looping through the managed parts and
+     * initializing them. Has no effect if already called once.
+     */
+    public void initialize() {
+        if (initialized)
+            return;
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart part = cast(IFormPart) parts.get(i);
+            part.initialize(this);
+        }
+        initialized = true;
+    }
+
+    /**
+     * Disposes all the parts in this form.
+     */
+    public void dispose() {
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart part = cast(IFormPart) parts.get(i);
+            part.dispose();
+        }
+        if (ownsToolkit) {
+            toolkit.dispose();
+        }
+    }
+
+    /**
+     * Refreshes the form by refreshes all the stale parts. Since 3.1, this
+     * method is performed on a UI thread when called from another thread so it
+     * is not needed to wrap the call in <code>Display.syncExec</code> or
+     * <code>asyncExec</code>.
+     */
+    public void refresh() {
+        Thread t = Thread.getThis();
+        Thread dt = toolkit.getColors().getDisplay().getThread();
+        if (t.opEquals(dt))
+            doRefresh();
+        else {
+            toolkit.getColors().getDisplay().asyncExec(dgRunnable( {
+                doRefresh();
+            }));
+        }
+    }
+
+    private void doRefresh() {
+        int nrefreshed = 0;
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart part = cast(IFormPart) parts.get(i);
+            if (part.isStale()) {
+                part.refresh();
+                nrefreshed++;
+            }
+        }
+        if (nrefreshed > 0)
+            form.reflow(true);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#commit(bool)
+     */
+    public void commit(bool onSave) {
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart part = cast(IFormPart) parts.get(i);
+            if (part.isDirty())
+                part.commit(onSave);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#setInput(java.lang.Object)
+     */
+    public bool setInput(Object input) {
+        bool pageResult = false;
+
+        this.input = input;
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart part = cast(IFormPart) parts.get(i);
+            bool result = part.setFormInput(input);
+            if (result)
+                pageResult = true;
+        }
+        return pageResult;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#getInput()
+     */
+    public Object getInput() {
+        return input;
+    }
+
+    /**
+     * Transfers the focus to the first form part.
+     */
+    public void setFocus() {
+        if (parts.size() > 0) {
+            IFormPart part = cast(IFormPart) parts.get(0);
+            part.setFocus();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#isDirty()
+     */
+    public bool isDirty() {
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart part = cast(IFormPart) parts.get(i);
+            if (part.isDirty())
+                return true;
+        }
+        return false;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#isStale()
+     */
+    public bool isStale() {
+        for (int i = 0; i < parts.size(); i++) {
+            IFormPart part = cast(IFormPart) parts.get(i);
+            if (part.isStale())
+                return true;
+        }
+        return false;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#dirtyStateChanged()
+     */
+    public void dirtyStateChanged() {
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#staleStateChanged()
+     */
+    public void staleStateChanged() {
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#getContainer()
+     */
+    public Object getContainer() {
+        return container;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IManagedForm#setContainer(java.lang.Object)
+     */
+    public void setContainer(Object container) {
+        this.container = container;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.ui.forms.IManagedForm#getMessageManager()
+     */
+    public IMessageManager getMessageManager() {
+        if (messageManager is null)
+            messageManager = new MessageManager(form);
+        return messageManager;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/MasterDetailsBlock.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.MasterDetailsBlock;
+
+import dwtx.ui.forms.DetailsPart;
+import dwtx.ui.forms.IManagedForm;
+import dwtx.ui.forms.FormColors;
+import dwtx.ui.forms.IFormColors;
+
+import dwt.DWT;
+import dwt.custom.SashForm;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.layout.GridData;
+import dwt.layout.GridLayout;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.Sash;
+import dwtx.ui.forms.widgets.FormToolkit;
+import dwtx.ui.forms.widgets.ScrolledForm;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+
+/**
+ * This class implements the 'master/details' UI pattern suitable for inclusion
+ * in a form. The block consists of two parts: 'master' and 'details' in a sash
+ * form that allows users to change the relative ratio on the page. The master
+ * part needs to be created by the users of this class. The details part is
+ * created by the block.
+ * <p>
+ * The master part is responsible for adding itself as a form part and firing
+ * selection events. The details part catches the selection events and tries to
+ * load a page registered to handle the selected object(s). The page shows the
+ * details of the selected object(s) and allows users to edit them.
+ * <p>
+ * Details pages can be registered statically using 'registerPage' or
+ * dynamically through the use of 'IDetailsPageProvider' in case where different
+ * pages need to be shown for objects of the same type depending on their state.
+ * <p>
+ * Subclasses are required to implement abstract methods of this class. Master
+ * part must be created and at least one details page should be registered in
+ * order to show details of the objects selected in the master part. Tool bar
+ * actions can be optionally added to the tool bar manager.
+ *
+ * @see DetailsPart
+ * @see IDetailsPage
+ * @see IDetailsPageProvider
+ * @since 3.0
+ */
+public abstract class MasterDetailsBlock {
+    /**
+     * Details part created by the block. No attempt should be made to access
+     * this field inside <code>createMasterPart</code> because it has not been
+     * created yet and will be <code>null</code>.
+     */
+    protected DetailsPart detailsPart;
+
+    /**
+     * The form that is the parent of both master and details part. The form
+     * allows users to change the ratio between the two parts.
+     */
+    protected SashForm sashForm;
+
+    static const int DRAGGER_SIZE = 40;
+
+    class MDSashForm : SashForm {
+        ArraySeq!(Sash) sashes;
+        Listener listener;
+        public this(Composite parent, int style) {
+            sashes = new ArraySeq!(Sash);
+            listener = dgListener ( (Event e){
+                switch (e.type) {
+                case DWT.MouseEnter:
+                    e.widget.setData("hover", Boolean.TRUE); //$NON-NLS-1$
+                    (cast(Control)e.widget).redraw();
+                    break;
+                case DWT.MouseExit:
+                    e.widget.setData("hover", null); //$NON-NLS-1$
+                    (cast(Control)e.widget).redraw();
+                    break;
+                case DWT.Paint:
+                    onSashPaint(e);
+                break;
+                case DWT.Resize:
+                    hookSashListeners();
+                break;
+                }
+            });
+            super(parent, style);
+        }
+
+        public void layout(bool changed) {
+            super.layout(changed);
+            hookSashListeners();
+        }
+
+        public void layout(Control [] children) {
+            super.layout(children);
+            hookSashListeners();
+        }
+
+        private void hookSashListeners() {
+            purgeSashes();
+            Control [] children = getChildren();
+            for (int i=0; i<children.length; i++) {
+                if ( auto sash = cast(Sash)children[i] ) {
+                    if (sashes.contains(sash))
+                        continue;
+                    sash.addListener(DWT.Paint, listener);
+                    sash.addListener(DWT.MouseEnter, listener);
+                    sash.addListener(DWT.MouseExit, listener);
+                    sashes.append(sash);
+                }
+            }
+        }
+        private void purgeSashes() {
+            foreach ( sash; sashes.dup ) {
+                if (sash.isDisposed())
+                    sashes.remove(sash);
+            }
+        }
+    }
+
+    /**
+     * Creates the content of the master/details block inside the managed form.
+     * This method should be called as late as possible inside the parent part.
+     *
+     * @param managedForm
+     *            the managed form to create the block in
+     */
+    public void createContent(IManagedForm managedForm) {
+        final ScrolledForm form = managedForm.getForm();
+        FormToolkit toolkit = managedForm.getToolkit();
+        GridLayout layout = new GridLayout();
+        layout.marginWidth = 0;
+        layout.marginHeight = 0;
+        form.getBody().setLayout(layout);
+        sashForm = new MDSashForm(form.getBody(), DWT.NULL);
+        sashForm.setData("form", cast(Object)managedForm); //$NON-NLS-1$
+        toolkit.adapt(sashForm, false, false);
+        sashForm.setMenu(form.getBody().getMenu());
+        sashForm.setLayoutData(new GridData(GridData.FILL_BOTH));
+        createMasterPart(managedForm, sashForm);
+        createDetailsPart(managedForm, sashForm);
+        hookResizeListener();
+        createToolBarActions(managedForm);
+        form.updateToolBar();
+    }
+
+    private void hookResizeListener() {
+        Listener listener = (cast(MDSashForm)sashForm).listener;
+        Control [] children = sashForm.getChildren();
+        for (int i=0; i<children.length; i++) {
+            if (null !is cast(Sash)children[i] ) continue;
+            children[i].addListener(DWT.Resize, listener);
+        }
+    }
+
+    /**
+     * Implement this method to create a master part in the provided parent.
+     * Typical master parts are section parts that contain tree or table viewer.
+     *
+     * @param managedForm
+     *            the parent form
+     * @param parent
+     *            the parent composite
+     */
+    protected abstract void createMasterPart(IManagedForm managedForm,
+            Composite parent);
+
+    /**
+     * Implement this method to statically register pages for the expected
+     * object types. This mechanism can be used when there is 1-&gt;1 mapping
+     * between object classes and details pages.
+     *
+     * @param detailsPart
+     *            the details part
+     */
+    protected abstract void registerPages(DetailsPart detailsPart);
+
+    /**
+     * Implement this method to create form tool bar actions and add them to the
+     * form tool bar if desired.
+     *
+     * @param managedForm
+     *            the form that owns the tool bar
+     */
+    protected abstract void createToolBarActions(IManagedForm managedForm);
+
+    private void createDetailsPart(IManagedForm mform, Composite parent) {
+        detailsPart = new DetailsPart(mform, parent, DWT.NULL);
+        mform.addPart(detailsPart);
+        registerPages(detailsPart);
+    }
+
+    private void onSashPaint(Event e) {
+        Sash sash = cast(Sash)e.widget;
+        IManagedForm form = cast(IManagedForm)sash.getParent().getData("form"); //$NON-NLS-1$
+        FormColors colors = form.getToolkit().getColors();
+        bool vertical = (sash.getStyle() & DWT.VERTICAL) !is 0;
+        GC gc = e.gc;
+        Boolean hover = cast(Boolean)sash.getData("hover"); //$NON-NLS-1$
+        gc.setBackground(colors.getColor(IFormColors.TB_BG));
+        gc.setForeground(colors.getColor(IFormColors.TB_BORDER));
+        Point size = sash.getSize();
+        if (vertical) {
+            if (hover !is null)
+                gc.fillRectangle(0, 0, size.x, size.y);
+            //else
+                //gc.drawLine(1, 0, 1, size.y-1);
+        }
+        else {
+            if (hover !is null)
+                gc.fillRectangle(0, 0, size.x, size.y);
+            //else
+                //gc.drawLine(0, 1, size.x-1, 1);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/SectionPart.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.SectionPart;
+
+import dwtx.ui.forms.AbstractFormPart;
+
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwtx.ui.forms.events.ExpansionAdapter;
+import dwtx.ui.forms.events.ExpansionEvent;
+import dwtx.ui.forms.widgets.FormToolkit;
+import dwtx.ui.forms.widgets.Section;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Section part implements IFormPart interface based on the Section widget. It
+ * can either wrap the widget or create one itself.
+ * <p>
+ * Subclasses should extend <code>SectionPart</code> and implement life cycle
+ * methods like <code>refresh</code>, <code>commit</code>,
+ * <code>setFocus</code> etc. Note that most of these methods are not empty -
+ * calling <code>super</code> is required.
+ *
+ * @see Section
+ * @since 3.0
+ */
+public class SectionPart : AbstractFormPart {
+    private Section section;
+
+    /**
+     * Creates a new section part based on the provided section.
+     *
+     * @param section
+     *            the section to use
+     */
+    public this(Section section) {
+        this.section = section;
+        hookListeners();
+    }
+
+    /**
+     * Creates a new section part inside the provided parent and using the
+     * provided toolkit. The section part will create the section widget.
+     *
+     * @param parent
+     *            the parent
+     * @param toolkit
+     *            the toolkit to use
+     * @param style
+     *            the section widget style
+     */
+    public this(Composite parent, FormToolkit toolkit, int style) {
+        this(toolkit.createSection(parent, style));
+    }
+
+    /**
+     * Adds listeners to the underlying widget.
+     */
+    protected void hookListeners() {
+        if ((section.getExpansionStyle() & Section.TWISTIE) !is 0
+                || (section.getExpansionStyle() & Section.TREE_NODE) !is 0) {
+            section.addExpansionListener(new class ExpansionAdapter {
+                public void expansionStateChanging(ExpansionEvent e) {
+                    this.outer.expansionStateChanging(e.getState());
+                }
+
+                public void expansionStateChanged(ExpansionEvent e) {
+                    this.outer.expansionStateChanged(e.getState());
+                }
+            });
+        }
+    }
+
+    /**
+     * Returns the section widget used in this part.
+     *
+     * @return the section widget
+     */
+    public Section getSection() {
+        return section;
+    }
+
+    /**
+     * The section is about to expand or collapse.
+     *
+     * @param expanding
+     *            <code>true</code> for expansion, <code>false</code> for
+     *            collapse.
+     */
+    protected void expansionStateChanging(bool expanding) {
+    }
+
+    /**
+     * The section has expanded or collapsed.
+     *
+     * @param expanded
+     *            <code>true</code> for expansion, <code>false</code> for
+     *            collapse.
+     */
+    protected void expansionStateChanged(bool expanded) {
+        getManagedForm().getForm().reflow(false);
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.ui.forms.AbstractFormPart#setFocus()
+     */
+    public void setFocus() {
+        Control client = section.getClient();
+        if (client !is null)
+            client.setFocus();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/events/ExpansionAdapter.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.events.ExpansionAdapter;
+import dwtx.ui.forms.events.IExpansionListener;
+import dwtx.ui.forms.events.ExpansionEvent;
+import dwt.dwthelper.utils;
+/**
+ * This adapter class provides default implementations for the methods
+ * described by the <code>ExpansionListener</code> interface.
+ * <p>
+ * Classes that wish to deal with <code>ExpansionEvent</code>s can extend
+ * this class and override only the methods which they are interested in.
+ * </p>
+ *
+ * @see IExpansionListener
+ * @see ExpansionEvent
+ * @since 3.0
+ */
+public class ExpansionAdapter : IExpansionListener {
+    /**
+     * Sent when the link is entered. The default behaviour is to do nothing.
+     *
+     * @param e
+     *            the event
+     */
+    public void expansionStateChanging(ExpansionEvent e) {
+    }
+    /**
+     * Sent when the link is exited. The default behaviour is to do nothing.
+     *
+     * @param e
+     *            the event
+     */
+    public void expansionStateChanged(ExpansionEvent e) {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/events/ExpansionEvent.d	Sat May 24 05:11:16 2008 +0200
@@ -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 dwtx.ui.forms.events.ExpansionEvent;
+import dwt.events.TypedEvent;
+import dwt.dwthelper.utils;
+/**
+ * Notifies listeners when expandable controls change expansion state.
+ *
+ * @since 3.0
+ */
+public final class ExpansionEvent : TypedEvent {
+    private static const long serialVersionUID = 6009335074727417445L;
+    /**
+     * Creates a new expansion ecent.
+     *
+     * @param obj
+     *            event source
+     * @param state
+     *            the new expansion state
+     */
+    public this(Object obj, bool state) {
+        super(obj);
+        data = state ? Boolean.TRUE : Boolean.FALSE;
+    }
+    /**
+     * Returns the new expansion state of the widget.
+     *
+     * @return <code>true</code> if the widget is now expaned, <code>false</code>
+     *         otherwise.
+     */
+    public bool getState() {
+        return data.opEquals(Boolean.TRUE) ? true : false;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/events/HyperlinkAdapter.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.events.HyperlinkAdapter;
+import dwtx.ui.forms.events.IHyperlinkListener;
+import dwtx.ui.forms.events.HyperlinkEvent;
+import dwt.dwthelper.utils;
+/**
+ * This adapter class provides default implementations for the methods
+ * described by the <code>HyperlinkListener</code> interface.
+ * <p>
+ * Classes that wish to deal with <code>HyperlinkEvent</code> s can extend
+ * this class and override only the methods which they are interested in.
+ * </p>
+ *
+ * @see IHyperlinkListener
+ * @see HyperlinkEvent
+ * @since 3.0
+ */
+public class HyperlinkAdapter : IHyperlinkListener {
+    /**
+     * Sent when the link is entered. The default behaviour is to do nothing.
+     *
+     * @param e
+     *            the event
+     */
+    public void linkEntered(HyperlinkEvent e) {
+    }
+    /**
+     * Sent when the link is exited. The default behaviour is to do nothing.
+     *
+     * @param e
+     *            the event
+     */
+    public void linkExited(HyperlinkEvent e) {
+    }
+    /**
+     * Sent when the link is activated. The default behaviour is to do nothing.
+     *
+     * @param e
+     *            the event
+     */
+    public void linkActivated(HyperlinkEvent e) {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/events/HyperlinkEvent.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.events.HyperlinkEvent;
+import dwt.events.TypedEvent;
+import dwt.widgets.Widget;
+import dwt.dwthelper.utils;
+/**
+ * Notifies listeners about a hyperlink change.
+ *
+ * @since 3.0
+ */
+public final class HyperlinkEvent : TypedEvent {
+    private static const long serialVersionUID = 6009335074727417445L;
+    private String label;
+    private int stateMask;
+    /**
+     * Creates a new hyperlink
+     *
+     * @param widget
+     *            event source
+     * @param href
+     *            the hyperlink reference that will be followed upon when the
+     *            hyperlink is activated.
+     * @param label
+     *            the name of the hyperlink (the text that is rendered as a
+     *            link in the source widget).
+     * @param stateMask
+     *            the given state mask
+     */
+    public this(Widget widget, Object href, String label, int stateMask) {
+        super(widget);
+        this.widget = widget;
+        this.data = href;
+        this.label = label;
+        this.stateMask = stateMask;
+    }
+    /**
+     * The hyperlink reference that will be followed when the hyperlink is
+     * activated.
+     *
+     * @return the hyperlink reference object
+     */
+    public Object getHref() {
+        return this.data;
+    }
+    /**
+     * The text of the hyperlink rendered in the source widget.
+     *
+     * @return the hyperlink label
+     */
+    public String getLabel() {
+        return label;
+    }
+    /**
+     * Returns the value of the keyboard state mask present when
+     * the event occured, or DWT.NULL for no modifiers.
+     * @return the keyboard state mask or <code>DWT.NULL</code>.
+     */
+    public int getStateMask() {
+        return stateMask;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/events/IExpansionListener.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.events.IExpansionListener;
+
+import dwtx.ui.forms.events.ExpansionEvent;
+
+/**
+ * Classes that implement this interface will be notified before and after the
+ * expandable control's expansion state changes.
+ *
+ * @since 3.0
+ */
+public interface IExpansionListener {
+    /**
+     * Notifies the listener that the expandable control is about to change its
+     * expansion state. The state provided by the event is the new state.
+     *
+     * @param e
+     *            the expansion event
+     */
+    void expansionStateChanging(ExpansionEvent e);
+    /**
+     * Notifies the listener after the expandable control has changed its
+     * expansion state. The state provided by the event is the new state.
+     *
+     * @param e
+     *            the expansion event
+     */
+    void expansionStateChanged(ExpansionEvent e);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/events/IHyperlinkListener.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.events.IHyperlinkListener;
+
+import dwtx.ui.forms.events.HyperlinkEvent;
+
+/**
+ * Classes that implement this interface will be notified when hyperlinks are
+ * entered, exited and activated.
+ *
+ * @see dwtx.ui.forms.widgets.Hyperlink
+ * @see dwtx.ui.forms.widgets.ImageHyperlink
+ * @see dwtx.ui.forms.widgets.FormText
+ * @since 3.0
+ */
+public interface IHyperlinkListener {
+    /**
+     * Sent when hyperlink is entered either by mouse entering the link client
+     * area, or keyboard focus switching to the hyperlink.
+     *
+     * @param e
+     *            an event containing information about the hyperlink
+     */
+    void linkEntered(HyperlinkEvent e);
+    /**
+     * Sent when hyperlink is exited either by mouse exiting the link client
+     * area, or keyboard focus switching from the hyperlink.
+     *
+     * @param e
+     *            an event containing information about the hyperlink
+     */
+    void linkExited(HyperlinkEvent e);
+    /**
+     * Sent when hyperlink is activated either by mouse click inside the link
+     * client area, or by pressing 'Enter' key while hyperlink has keyboard
+     * focus.
+     *
+     * @param e
+     *            an event containing information about the hyperlink
+     */
+    void linkActivated(HyperlinkEvent e);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/AbstractHyperlink.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,341 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.AbstractHyperlink;
+
+
+import dwt.DWT;
+import dwt.accessibility.ACC;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Canvas;
+import dwt.widgets.Composite;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwtx.core.runtime.ListenerList;
+import dwtx.ui.forms.events.HyperlinkEvent;
+import dwtx.ui.forms.events.IHyperlinkListener;
+import dwtx.ui.internal.forms.widgets.FormsResources;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This is the base class for custom hyperlink widget. It is responsible for
+ * processing mouse and keyboard events, and converting them into unified
+ * hyperlink events. Subclasses are responsible for rendering the hyperlink in
+ * the client area.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractHyperlink : Canvas {
+    private bool hasFocus;
+    bool paintFocus=true;
+
+    /*
+     * Armed link is one that will activate on a mouse up event, i.e.
+     * it has received a mouse down and mouse still on top of it.
+     */
+    private bool armed;
+
+    private ListenerList listeners;
+
+    /**
+     * Amount of the margin width around the hyperlink (default is 1).
+     */
+    protected int marginWidth = 1;
+
+    /**
+     * Amount of the margin height around the hyperlink (default is 1).
+     */
+    protected int marginHeight = 1;
+
+    /**
+     * Creates a new hyperlink in the provided parent.
+     *
+     * @param parent
+     *            the control parent
+     * @param style
+     *            the widget style
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        addListener(DWT.KeyDown, new class Listener {
+            public void handleEvent(Event e) {
+                if (e.character is '\r') {
+                    handleActivate(e);
+                }
+            }
+        });
+        addPaintListener(new class PaintListener {
+            public void paintControl(PaintEvent e) {
+                paint(e);
+            }
+        });
+        addListener(DWT.Traverse, new class Listener {
+            public void handleEvent(Event e) {
+                switch (e.detail) {
+                case DWT.TRAVERSE_PAGE_NEXT:
+                case DWT.TRAVERSE_PAGE_PREVIOUS:
+                case DWT.TRAVERSE_ARROW_NEXT:
+                case DWT.TRAVERSE_ARROW_PREVIOUS:
+                case DWT.TRAVERSE_RETURN:
+                    e.doit = false;
+                    return;
+                }
+                e.doit = true;
+            }
+        });
+        Listener listener = new class Listener {
+            public void handleEvent(Event e) {
+                switch (e.type) {
+                case DWT.FocusIn:
+                    hasFocus = true;
+                    handleEnter(e);
+                    break;
+                case DWT.FocusOut:
+                    hasFocus = false;
+                    handleExit(e);
+                    break;
+                case DWT.DefaultSelection:
+                    handleActivate(e);
+                    break;
+                case DWT.MouseEnter:
+                    handleEnter(e);
+                    break;
+                case DWT.MouseExit:
+                    handleExit(e);
+                    break;
+                case DWT.MouseDown:
+                    handleMouseDown(e);
+                    break;
+                case DWT.MouseUp:
+                    handleMouseUp(e);
+                    break;
+                case DWT.MouseMove:
+                    handleMouseMove(e);
+                    break;
+                }
+            }
+        };
+        addListener(DWT.MouseEnter, listener);
+        addListener(DWT.MouseExit, listener);
+        addListener(DWT.MouseDown, listener);
+        addListener(DWT.MouseUp, listener);
+        addListener(DWT.MouseMove, listener);
+        addListener(DWT.FocusIn, listener);
+        addListener(DWT.FocusOut, listener);
+        setCursor(FormsResources.getHandCursor());
+    }
+
+    /**
+     * Adds the event listener to this hyperlink.
+     *
+     * @param listener
+     *            the event listener to add
+     */
+    public void addHyperlinkListener(IHyperlinkListener listener) {
+        if (listeners is null)
+            listeners = new ListenerList();
+        listeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Removes the event listener from this hyperlink.
+     *
+     * @param listener
+     *            the event listener to remove
+     */
+    public void removeHyperlinkListener(IHyperlinkListener listener) {
+        if (listeners is null)
+            return;
+        listeners.remove(cast(Object)listener);
+    }
+
+    /**
+     * Returns the selection state of the control. When focus is gained, the
+     * state will be <samp>true </samp>; it will switch to <samp>false </samp>
+     * when the control looses focus.
+     *
+     * @return <code>true</code> if the widget has focus, <code>false</code>
+     *         otherwise.
+     */
+    public bool getSelection() {
+        return hasFocus;
+    }
+
+    /**
+     * Called when hyperlink is entered. Subclasses that override this method
+     * must call 'super'.
+     */
+    protected void handleEnter(Event e) {
+        redraw();
+        if (listeners is null)
+            return;
+        int size = listeners.size();
+        HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(),
+                e.stateMask);
+        Object[] listenerList = listeners.getListeners();
+        for (int i = 0; i < size; i++) {
+            IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
+            listener.linkEntered(he);
+        }
+    }
+
+    /**
+     * Called when hyperlink is exited. Subclasses that override this method
+     * must call 'super'.
+     */
+    protected void handleExit(Event e) {
+        // disarm the link; won't activate on mouseup
+        armed = false;
+        redraw();
+        if (listeners is null)
+            return;
+        int size = listeners.size();
+        HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(),
+                e.stateMask);
+        Object[] listenerList = listeners.getListeners();
+        for (int i = 0; i < size; i++) {
+            IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
+            listener.linkExited(he);
+        }
+    }
+
+    /**
+     * Called when hyperlink has been activated. Subclasses that override this
+     * method must call 'super'.
+     */
+    protected void handleActivate(Event e) {
+        // disarm link, back to normal state
+        armed = false;
+        getAccessible().setFocus(ACC.CHILDID_SELF);
+        if (listeners is null)
+            return;
+        int size = listeners.size();
+        setCursor(FormsResources.getBusyCursor());
+        HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(),
+                e.stateMask);
+        Object[] listenerList = listeners.getListeners();
+        for (int i = 0; i < size; i++) {
+            IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
+            listener.linkActivated(he);
+        }
+        if (!isDisposed())
+            setCursor(FormsResources.getHandCursor());
+    }
+
+    /**
+     * Sets the object associated with this hyperlink. Concrete implementation
+     * of this class can use if to store text, URLs or model objects that need
+     * to be processed on hyperlink events.
+     *
+     * @param href
+     *            the hyperlink object reference
+     */
+    public void setHref(Object href) {
+        setData("href", href); //$NON-NLS-1$
+    }
+
+    /**
+     * Returns the object associated with this hyperlink.
+     *
+     * @see #setHref
+     * @return the hyperlink object reference
+     */
+    public Object getHref() {
+        return getData("href"); //$NON-NLS-1$
+    }
+
+    /**
+     * Returns the textual representation of this hyperlink suitable for showing
+     * in tool tips or on the status line.
+     *
+     * @return the hyperlink text
+     */
+    public String getText() {
+        return getToolTipText();
+    }
+
+    /**
+     * Paints the hyperlink as a reaction to the provided paint event.
+     *
+     * @param gc
+     *            graphic context
+     */
+    protected abstract void paintHyperlink(GC gc);
+
+    /**
+     * Paints the control as a reaction to the provided paint event.
+     *
+     * @param e
+     *            the paint event
+     */
+    protected void paint(PaintEvent e) {
+        GC gc = e.gc;
+        Rectangle clientArea = getClientArea();
+        if (clientArea.width is 0 || clientArea.height is 0)
+            return;
+        paintHyperlink(gc);
+        if (paintFocus && hasFocus) {
+            Rectangle carea = getClientArea();
+            gc.setForeground(getForeground());
+            gc.drawFocus(0, 0, carea.width, carea.height);
+        }
+    }
+
+    private void handleMouseDown(Event e) {
+        if (e.button !is 1)
+            return;
+        // armed and ready to activate on mouseup
+        armed = true;
+    }
+
+    private void handleMouseUp(Event e) {
+        if (!armed || e.button !is 1)
+            return;
+        Point size = getSize();
+        // Filter out mouse up events outside
+        // the link. This can happen when mouse is
+        // clicked, dragged outside the link, then
+        // released.
+        if (e.x < 0)
+            return;
+        if (e.y < 0)
+            return;
+        if (e.x >= size.x)
+            return;
+        if (e.y >= size.y)
+            return;
+        handleActivate(e);
+    }
+
+    private void handleMouseMove(Event e) {
+        // disarm link if we move out of bounds
+        if (armed) {
+            Point size = getSize();
+            armed = (e.x >= 0 && e.y >= 0 && e.x < size.x && e.y < size.y);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see dwt.widgets.Control#setEnabled(bool)
+     */
+
+    public void setEnabled (bool enabled) {
+        super.setEnabled(enabled);
+        redraw();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ColumnLayout.d	Sat May 24 05:11:16 2008 +0200
@@ -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
+ *     dinko.ivanov@sap.com - patch #70790
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.forms.widgets.ColumnLayout;
+
+import dwtx.ui.forms.widgets.ILayoutExtension;
+import dwtx.ui.forms.widgets.ColumnLayoutData;
+
+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.dwthelper.utils;
+
+/**
+ * This layout manager arranges children of the composite parent in vertical
+ * columns. All the columns are identical size and children are stretched
+ * horizontally to fill the column width. The goal is to give layout some
+ * reasonable range of column numbers to allow it to handle various parent
+ * widths. That way, column number will drop to the lowest number in the range
+ * when width decreases, and grow up to the highest number in the range when
+ * allowed by the parent width.
+ * <p>
+ * In addition, the layout attempts to 'fill the space' equally i.e. to avoid
+ * large gaps at the and of the last column.
+ * <p>
+ * Child controls are layed out according to their 'natural' (preferred) size.
+ * For 'stretchy' controls that do not have natural preferred size, it is
+ * possible to set width and/or height hints using ColumnLayoutData objects.
+ *
+ * @see ColumnLayoutData
+ * @since 3.0
+ */
+public final class ColumnLayout : Layout, ILayoutExtension {
+    /**
+     * Minimum number of columns (default is 1).
+     */
+    public int minNumColumns = 1;
+    /**
+     * Maximum number of columns (default is 3).
+     */
+    public int maxNumColumns = 3;
+    /**
+     * Horizontal spacing between columns (default is 5).
+     */
+    public int horizontalSpacing = 5;
+    /**
+     * Vertical spacing between controls (default is 5).
+     */
+    public int verticalSpacing = 5;
+    /**
+     * Top margin (default is 5).
+     */
+    public int topMargin = 5;
+    /**
+     * Left margin (default is 5).
+     */
+    public int leftMargin = 5;
+    /**
+     * Bottom margin (default is 5).
+     */
+    public int bottomMargin = 5;
+    /**
+     * Right margin (default is 5).
+     */
+    public int rightMargin = 5;
+
+    /**
+     * Creates a new instance of the column layout.
+     */
+    public this() {
+    }
+
+    /+protected+/ Point computeSize(Composite composite, int wHint, int hHint, bool flushCache) {
+        if (wHint is 0)
+            return computeSize(composite, wHint, hHint, minNumColumns);
+        else if (wHint is DWT.DEFAULT)
+            return computeSize(composite, wHint, hHint, maxNumColumns);
+        else
+            return computeSize(composite, wHint, hHint, -1);
+    }
+
+    private Point computeSize(Composite parent, int wHint, int hHint, int ncolumns) {
+        Control[] children = parent.getChildren();
+        int cwidth = 0;
+        int cheight = 0;
+        Point[] sizes = new Point[children.length];
+
+        int cwHint = DWT.DEFAULT;
+        if (ncolumns !is -1) {
+            cwHint = wHint - leftMargin - rightMargin - (ncolumns - 1) * horizontalSpacing;
+            if (cwHint <= 0)
+                cwHint = 0;
+            else
+                cwHint /= ncolumns;
+        }
+
+        for (int i = 0; i < children.length; i++) {
+            sizes[i] = computeControlSize(children[i], cwHint);
+            cwidth = Math.max(cwidth, sizes[i].x);
+            cheight += sizes[i].y;
+        }
+        if (ncolumns is -1) {
+            // must compute
+            ncolumns = (wHint - leftMargin - rightMargin - horizontalSpacing) / (cwidth + horizontalSpacing);
+            ncolumns = Math.min(ncolumns, children.length);
+            ncolumns = Math.max(ncolumns, minNumColumns);
+            ncolumns = Math.min(ncolumns, maxNumColumns);
+        }
+        int perColHeight = cheight / ncolumns;
+        if (cheight % ncolumns !is 0)
+            perColHeight++;
+        int colHeight = 0;
+        int[] heights = new int[ncolumns];
+        int ncol = 0;
+
+        bool fillIn = false;
+
+        for (int i = 0; i < sizes.length; i++) {
+            int childHeight = sizes[i].y;
+            if (i>0 && colHeight + childHeight > perColHeight) {
+                heights[ncol] = colHeight;
+                ncol++;
+                if (ncol is ncolumns || fillIn) {
+                    // overflow - start filling in
+                    fillIn = true;
+                    ncol = findShortestColumn(heights);
+                }
+                colHeight = heights[ncol];
+            }
+            if (colHeight > 0)
+                colHeight += verticalSpacing;
+            colHeight += childHeight;
+        }
+        heights[ncol] = Math.max(heights[ncol],colHeight);
+
+        Point size = new Point(0, 0);
+        for (int i = 0; i < ncolumns; i++) {
+            size.y = Math.max(size.y, heights[i]);
+        }
+        size.x = cwidth * ncolumns + (ncolumns - 1) * horizontalSpacing;
+        size.x += leftMargin + rightMargin;
+        //System.out.println("ColumnLayout: whint="+wHint+", size.x="+size.x);
+        size.y += topMargin + bottomMargin;
+        return size;
+    }
+
+    private Point computeControlSize(Control c, int wHint) {
+        ColumnLayoutData cd = cast(ColumnLayoutData) c.getLayoutData();
+        int widthHint = cd !is null ? cd.widthHint : wHint;
+        int heightHint = cd !is null ? cd.heightHint : DWT.DEFAULT;
+        return c.computeSize(widthHint, heightHint);
+    }
+
+    private int findShortestColumn(int[] heights) {
+        int result = 0;
+        int height = Integer.MAX_VALUE;
+        for (int i = 0; i < heights.length; i++) {
+            if (height > heights[i]) {
+                height = heights[i];
+                result = i;
+            }
+        }
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwt.widgets.Layout#layout(dwt.widgets.Composite,
+     *      bool)
+     */
+    protected void layout(Composite parent, bool flushCache) {
+        Control[] children = parent.getChildren();
+        Rectangle carea = parent.getClientArea();
+        int cwidth = 0;
+        int cheight = 0;
+        Point[] sizes = new Point[children.length];
+        for (int i = 0; i < children.length; i++) {
+            sizes[i] = computeControlSize(children[i], DWT.DEFAULT);
+            cwidth = Math.max(cwidth, sizes[i].x);
+            cheight += sizes[i].y;
+        }
+        int ncolumns = (carea.width - leftMargin - rightMargin - horizontalSpacing) / (cwidth + horizontalSpacing);
+        ncolumns = Math.min(ncolumns, children.length);
+        ncolumns = Math.max(ncolumns, minNumColumns);
+        ncolumns = Math.min(ncolumns, maxNumColumns);
+        int realWidth = (carea.width - leftMargin - rightMargin + horizontalSpacing) / ncolumns - horizontalSpacing;
+//      int childrenPerColumn = children.length / ncolumns;
+//      if (children.length % ncolumns !is 0)
+//          childrenPerColumn++;
+//      int colWidth = 0;
+
+        int fillWidth = Math.max(cwidth, realWidth);
+
+        int perColHeight = cheight / ncolumns;
+        if (cheight % ncolumns !is 0)
+            perColHeight++;
+
+        int colHeight = 0;
+        int[] heights = new int[ncolumns];
+        int ncol = 0;
+        int x = leftMargin;
+        bool fillIn = false;
+
+        for (int i = 0; i < sizes.length; i++) {
+            Control child = children[i];
+            Point csize = sizes[i];
+            ColumnLayoutData cd = cast(ColumnLayoutData) child.getLayoutData();
+            int align_ = cd !is null ? cd.horizontalAlignment : ColumnLayoutData.FILL;
+            int childWidth = align_ is ColumnLayoutData.FILL ? fillWidth : csize.x;
+
+            if (i>0 && colHeight + csize.y > perColHeight) {
+                heights[ncol] = colHeight;
+                if (fillIn || ncol is ncolumns-1) {
+                    // overflow - start filling in
+                    fillIn = true;
+                    ncol = findShortestColumn(heights);
+
+                    x = leftMargin + ncol * (fillWidth + horizontalSpacing);
+
+                }
+                else {
+                    ncol++;
+                    x += fillWidth + horizontalSpacing;
+                }
+                colHeight = heights[ncol];
+            }
+            if (colHeight > 0)
+                colHeight += verticalSpacing;
+
+
+            switch (align_) {
+                case ColumnLayoutData.LEFT :
+                case ColumnLayoutData.FILL :
+                    child.setBounds(x, topMargin+colHeight, childWidth, csize.y);
+                    break;
+                case ColumnLayoutData.RIGHT :
+                    child.setBounds(x + fillWidth - childWidth, topMargin+colHeight, childWidth, csize.y);
+                    break;
+                case ColumnLayoutData.CENTER :
+                    child.setBounds(x + fillWidth / 2 - childWidth / 2, topMargin+colHeight, childWidth, csize.y);
+                    break;
+            }
+
+            colHeight += csize.y;
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMaximumWidth(dwt.widgets.Composite,
+     *      bool)
+     */
+    public int computeMaximumWidth(Composite parent, bool changed) {
+        return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite,
+     *      bool)
+     */
+    public int computeMinimumWidth(Composite parent, bool changed) {
+        return computeSize(parent, 0, DWT.DEFAULT, changed).x;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ColumnLayoutData.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.ColumnLayoutData;
+import dwt.DWT;
+/**
+ * This class is used to store layout data for the <code>ColumnLayout</code>
+ * class. You can control width and height hints, as well as horizontal
+ * alignment using instances of this class. As with other layouts, they are not
+ * required to get the default behaviour.
+ *
+ * @see ColumnLayout
+ * @since 3.0
+ */
+public final class ColumnLayoutData {
+    /**
+     * Width hint that will be used instead of the computed control width when
+     * used in conjunction with <code>ColumnLayout</code> class (default is
+     * DWT.DEFAULT).
+     */
+    public int widthHint = DWT.DEFAULT;
+    /**
+     * Height hint that will be used instead of the computed control height
+     * when used in conjunction with <code>ColumnLayout</code> class (default
+     * is DWT.DEFAULT).
+     */
+    public int heightHint = DWT.DEFAULT;
+    /**
+     * Horizontal alignment constant - control will be aligned to the left.
+     */
+    public static const int LEFT = 1;
+    /**
+     * Horizontal alignment constant - control will be aligned to the right.
+     */
+    public static const int CENTER = 2;
+    /**
+     * Horizontal alignment constant - control will be centered.
+     */
+    public static const int RIGHT = 3;
+    /**
+     * Horizontal alignment constant - control will fill the column.
+     */
+    public static const int FILL = 4;
+    /**
+     * Horizontal alignment variable (default is FILL).
+     */
+    public int horizontalAlignment = FILL;
+    /**
+     * Convinience constructor for the class.
+     *
+     * @param wHint
+     *            width hint for the control
+     * @param hHint
+     *            height hint for the control
+     */
+    public this(int wHint, int hHint) {
+        this.widthHint = wHint;
+        this.heightHint = hHint;
+    }
+    /**
+     * Convinience constructor for the class.
+     *
+     * @param wHint
+     *            width hint for the control
+     */
+    public this(int wHint) {
+        this.widthHint = wHint;
+    }
+    /**
+     * The default constructor.
+     *
+     */
+    public this() {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ExpandableComposite.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,1136 @@
+/*******************************************************************************
+ * 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
+ *     Kai Nacke - Fix for Bug 202382
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.forms.widgets.ExpandableComposite;
+
+import dwtx.ui.forms.widgets.ToggleHyperlink;
+import dwtx.ui.forms.widgets.ILayoutExtension;
+import dwtx.ui.forms.widgets.SizeCache;
+import dwtx.ui.forms.widgets.Twistie;
+import dwtx.ui.forms.widgets.TreeNode;
+import dwtx.ui.forms.widgets.Hyperlink;
+
+import dwt.DWT;
+import dwt.events.FocusEvent;
+import dwt.events.FocusListener;
+import dwt.events.KeyAdapter;
+import dwt.events.KeyEvent;
+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.FontMetrics;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Canvas;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Label;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.widgets.Menu;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.ListenerList;
+import dwtx.ui.forms.events.ExpansionEvent;
+import dwtx.ui.forms.events.HyperlinkAdapter;
+import dwtx.ui.forms.events.HyperlinkEvent;
+import dwtx.ui.forms.events.IExpansionListener;
+import dwtx.ui.internal.forms.widgets.FormUtil;
+import dwtx.ui.internal.forms.widgets.FormsResources;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This composite is capable of expanding or collapsing a single client that is
+ * its direct child. The composite renders an expansion toggle affordance
+ * (according to the chosen style), and a title that also acts as a hyperlink
+ * (can be selected and is traversable). The client is layed out below the title
+ * when expanded, or hidden when collapsed.
+ * <p>
+ * The widget can be instantiated as-is, or subclassed to modify some aspects of
+ * it. *
+ * <p>
+ * Since 3.1, left/right arrow keys can be used to control the expansion state.
+ * If several expandable composites are created in the same parent, up/down
+ * arrow keys can be used to traverse between them. Expandable text accepts
+ * mnemonics and mnemonic activation will toggle the expansion state.
+ *
+ * <p>
+ * While expandable composite recognize that different styles can be used to
+ * render the title bar, and even defines the constants for these styles (<code>TITLE_BAR</code>
+ * and <code>SHORT_TITLE_BAR</code> the actual painting is done in the
+ * subclasses.
+ *
+ * @see Section
+ * @since 3.0
+ */
+public class ExpandableComposite : Canvas {
+    /**
+     * If this style is used, a twistie will be used to render the expansion
+     * toggle.
+     */
+    public static const int TWISTIE = 1 << 1;
+
+    /**
+     * If this style is used, a tree node with either + or - signs will be used
+     * to render the expansion toggle.
+     */
+    public static const int TREE_NODE = 1 << 2;
+
+    /**
+     * If this style is used, the title text will be rendered as a hyperlink
+     * that can individually accept focus. Otherwise, it will still act like a
+     * hyperlink, but only the toggle control will accept focus.
+     */
+    public static const int FOCUS_TITLE = 1 << 3;
+
+    /**
+     * If this style is used, the client origin will be vertically aligned with
+     * the title text. Otherwise, it will start at x = 0.
+     */
+    public static const int CLIENT_INDENT = 1 << 4;
+
+    /**
+     * If this style is used, computed size of the composite will take the
+     * client width into consideration only in the expanded state. Otherwise,
+     * client width will always be taken into acount.
+     */
+    public static const int COMPACT = 1 << 5;
+
+    /**
+     * If this style is used, the control will be created in the expanded state.
+     * This state can later be changed programmatically or by the user if
+     * TWISTIE or TREE_NODE style is used.
+     */
+    public static const int EXPANDED = 1 << 6;
+
+    /**
+     * If this style is used, title bar decoration will be painted behind the
+     * text.
+     */
+    public static const int TITLE_BAR = 1 << 8;
+
+    /**
+     * If this style is used, a short version of the title bar decoration will
+     * be painted behind the text. This style is useful when a more descrete
+     * option is needed for the title bar.
+     *
+     * @since 3.1
+     */
+    public static const int SHORT_TITLE_BAR = 1 << 9;
+
+    /**
+     * If this style is used, title will not be rendered.
+     */
+    public static const int NO_TITLE = 1 << 12;
+
+    /**
+     * By default, text client is right-aligned. If this style is used, it will
+     * be positioned after the text control and vertically centered with it.
+     */
+    public static const int LEFT_TEXT_CLIENT_ALIGNMENT = 1 << 13;
+
+    /**
+     * Width of the margin that will be added around the control (default is 0).
+     */
+    public int marginWidth = 0;
+
+    /**
+     * Height of the margin that will be added around the control (default is
+     * 0).
+     */
+    public int marginHeight = 0;
+
+    /**
+     * Vertical spacing between the title area and the composite client control
+     * (default is 3).
+     */
+    public int clientVerticalSpacing = 3;
+
+    /**
+     * Vertical spacing between the title area and the description control
+     * (default is 0). The description control is normally placed at the new
+     * line as defined in the font used to render it. This value will be added
+     * to it.
+     *
+     * @since 3.3
+     */
+    public int descriptionVerticalSpacing = 0;
+
+    /**
+     * Horizontal margin around the inside of the title bar area when TITLE_BAR
+     * or SHORT_TITLE_BAR style is used. This variable is not used otherwise.
+     *
+     * @since 3.3
+     */
+    public int titleBarTextMarginWidth = 6;
+
+    /**
+     * The toggle widget used to expand the composite.
+     */
+    protected ToggleHyperlink toggle;
+    package ToggleHyperlink toggle_package(){
+        return toggle;
+    }
+    package ToggleHyperlink toggle_package(ToggleHyperlink t){
+        return (toggle = t);
+    }
+
+    /**
+     * The text label for the title.
+     */
+    protected Control textLabel;
+    package Control textLabel_package(){
+        return textLabel;
+    }
+    package Control textLabel_package(Control t){
+        return (textLabel = t);
+    }
+
+    /**
+     * @deprecated this variable was left as protected by mistake. It will be
+     *             turned into static and hidden in the future versions. Do not
+     *             use them and do not change its value.
+     */
+    protected int VGAP = 3;
+    /**
+     * @deprecated this variable was left as protected by mistake. It will be
+     *             turned into static and hidden in the future versions. Do not
+     *             use it and do not change its value.
+     */
+    protected int GAP = 4;
+
+    static const int IGAP = 4;
+    static const int IVGAP = 3;
+
+    private static Point NULL_SIZE_;
+    private static Point NULL_SIZE(){
+        if( NULL_SIZE_ is null ){
+            synchronized(ExpandableComposite.classinfo ){
+                if( NULL_SIZE_ is null ){
+                    NULL_SIZE_ = new Point(0, 0);
+                }
+            }
+        }
+        return NULL_SIZE_;
+    }
+
+    private static const int VSPACE = 3;
+
+    private static const int SEPARATOR_HEIGHT = 2;
+
+    private int expansionStyle = TWISTIE | FOCUS_TITLE | EXPANDED;
+
+    private bool expanded;
+
+    private Control textClient;
+
+    private Control client;
+
+    private ListenerList listeners;
+
+    private Color titleBarForeground;
+
+    private class ExpandableLayout : Layout, ILayoutExtension {
+
+        private SizeCache toggleCache;
+
+        private SizeCache textClientCache;
+
+        private SizeCache textLabelCache;
+
+        private SizeCache descriptionCache;
+
+        private SizeCache clientCache;
+
+        this(){
+            toggleCache = new SizeCache();
+            textClientCache = new SizeCache();
+            textLabelCache = new SizeCache();
+            descriptionCache = new SizeCache();
+            clientCache = new SizeCache();
+        }
+        private void initCache(bool shouldFlush) {
+            toggleCache.setControl(toggle);
+            textClientCache.setControl(textClient);
+            textLabelCache.setControl(textLabel);
+            descriptionCache.setControl(getDescriptionControl());
+            clientCache.setControl(client);
+
+            if (shouldFlush) {
+                toggleCache.flush();
+                textClientCache.flush();
+                textLabelCache.flush();
+                descriptionCache.flush();
+                clientCache.flush();
+            }
+        }
+
+        protected void layout(Composite parent, bool changed) {
+            initCache(changed);
+
+            Rectangle clientArea = parent.getClientArea();
+            int thmargin = 0;
+            int tvmargin = 0;
+
+            if (hasTitleBar()) {
+                thmargin = titleBarTextMarginWidth;
+                tvmargin = IVGAP;
+            }
+            int x = marginWidth + thmargin;
+            int y = marginHeight + tvmargin;
+            Point tsize = NULL_SIZE;
+            Point tcsize = NULL_SIZE;
+            if (toggle !is null)
+                tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+            int twidth = clientArea.width - marginWidth - marginWidth
+                    - thmargin - thmargin;
+            if (tsize.x > 0)
+                twidth -= tsize.x + IGAP;
+            if (textClient !is null) {
+                tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+            }
+            Point size = NULL_SIZE;
+            if (textLabel !is null) {
+                if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) {
+                    size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+                    if (twidth < size.x + IGAP + tcsize.x) {
+                        twidth -= IGAP;
+                        if (null !is cast(Label)textLabel )
+                            size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x))));
+                        else
+                            size = textLabelCache.computeSize(cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT);
+                        tcsize = textClientCache.computeSize(twidth-size.x, DWT.DEFAULT);
+                    }
+                }
+                else {
+                    if (tcsize.x > 0)
+                        twidth -= tcsize.x + IGAP;
+                    size = textLabelCache.computeSize(twidth, DWT.DEFAULT);
+                }
+            }
+            if (null !is cast(Label)textLabel ) {
+                Point defSize = textLabelCache.computeSize(DWT.DEFAULT,
+                        DWT.DEFAULT);
+                if (defSize.y is size.y) {
+                    // One line - pick the smaller of the two widths
+                    size.x = Math.min(defSize.x, size.x);
+                }
+            }
+            if (toggle !is null) {
+                GC gc = new GC(this.outer);
+                gc.setFont(getFont());
+                FontMetrics fm = gc.getFontMetrics();
+                int textHeight = fm.getHeight();
+                gc.dispose();
+                if (textClient !is null
+                        && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) {
+                    textHeight = Math.max(textHeight, tcsize.y);
+                }
+                int ty = textHeight / 2 - tsize.y / 2 + 1;
+                ty = Math.max(ty, 0);
+                ty += marginHeight + tvmargin;
+                toggle.setLocation(x, ty);
+                toggle.setSize(tsize);
+                x += tsize.x + IGAP;
+            }
+            if (textLabel !is null) {
+                int ty = y;
+                if (textClient !is null
+                        && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) {
+                    if (size.y < tcsize.y)
+                        ty = tcsize.y / 2 - size.y / 2 + marginHeight
+                                + tvmargin;
+                }
+                textLabelCache.setBounds(x, ty, size.x, size.y);
+            }
+            if (textClient !is null) {
+                int tcx;
+                if ((expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) {
+                    tcx = x + size.x + GAP;
+                } else {
+                    tcx = clientArea.width - tcsize.x - marginWidth - thmargin;
+                }
+                textClientCache.setBounds(tcx, y, tcsize.x, tcsize.y);
+            }
+            int tbarHeight = 0;
+            if (size.y > 0)
+                tbarHeight = size.y;
+            if (tcsize.y > 0)
+                tbarHeight = Math.max(tbarHeight, tcsize.y);
+            y += tbarHeight;
+            if (hasTitleBar())
+                y += tvmargin;
+            if (getSeparatorControl() !is null) {
+                y += VSPACE;
+                getSeparatorControl().setBounds(marginWidth, y,
+                        clientArea.width - marginWidth - marginWidth,
+                        SEPARATOR_HEIGHT);
+                y += SEPARATOR_HEIGHT;
+                if (expanded)
+                    y += VSPACE;
+            }
+            if (expanded) {
+                int areaWidth = clientArea.width - marginWidth - marginWidth
+                        - thmargin - thmargin;
+                int cx = marginWidth + thmargin;
+                if ((expansionStyle & CLIENT_INDENT) !is 0) {
+                    cx = x;
+                    areaWidth -= x;
+                }
+                if (client !is null) {
+                    Point dsize = null;
+                    Control desc = getDescriptionControl();
+                    if (desc !is null) {
+                        dsize = descriptionCache.computeSize(areaWidth,
+                                DWT.DEFAULT);
+                        y += descriptionVerticalSpacing;
+                        descriptionCache.setBounds(cx, y, areaWidth, dsize.y);
+                        y += dsize.y + clientVerticalSpacing;
+                    } else {
+                        y += clientVerticalSpacing;
+                        if (getSeparatorControl() !is null)
+                            y -= VSPACE;
+                    }
+                    int cwidth = areaWidth;
+                    int cheight = clientArea.height - marginHeight
+                            - marginHeight - y;
+                    clientCache.setBounds(cx, y, cwidth, cheight);
+                }
+            }
+        }
+
+        protected Point computeSize(Composite parent, int wHint, int hHint,
+                bool changed) {
+            initCache(changed);
+
+            int width = 0, height = 0;
+            Point tsize = NULL_SIZE;
+            int twidth = 0;
+            if (toggle !is null) {
+                tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+                twidth = tsize.x + IGAP;
+            }
+            int thmargin = 0;
+            int tvmargin = 0;
+
+            if (hasTitleBar()) {
+                thmargin = titleBarTextMarginWidth;
+                tvmargin = IVGAP;
+            }
+            int innerwHint = wHint;
+            if (innerwHint !is DWT.DEFAULT)
+                innerwHint -= twidth + marginWidth + marginWidth + thmargin
+                        + thmargin;
+
+            int innertHint = innerwHint;
+
+            Point tcsize = NULL_SIZE;
+            if (textClient !is null) {
+                tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+            }
+            Point size = NULL_SIZE;
+
+            if (textLabel !is null) {
+                if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) {
+                    size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+                    if (innertHint !is DWT.DEFAULT && innertHint < size.x + IGAP + tcsize.x) {
+                        innertHint -= IGAP;
+                        if (null !is cast(Label)textLabel )
+                            size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x))));
+                        else
+                            size = textLabelCache.computeSize(cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT);
+                        tcsize = textClientCache.computeSize(innertHint-size.x, DWT.DEFAULT);
+                    }
+                } else {
+                    if (innertHint !is DWT.DEFAULT && tcsize.x > 0)
+                        innertHint -= IGAP + tcsize.x;
+                    size = textLabelCache.computeSize(innertHint, DWT.DEFAULT);
+                }
+            }
+            if (null !is cast(Label)textLabel ) {
+                Point defSize = textLabelCache.computeSize(DWT.DEFAULT,
+                        DWT.DEFAULT);
+                if (defSize.y is size.y) {
+                    // One line - pick the smaller of the two widths
+                    size.x = Math.min(defSize.x, size.x);
+                }
+            }
+            if (size.x > 0)
+                width = size.x;
+            if (tcsize.x > 0)
+                width += IGAP + tcsize.x;
+            if (toggle !is null)
+                width += twidth;
+            height = tcsize.y > 0 ? Math.max(tcsize.y, size.y) : size.y;
+            if (getSeparatorControl() !is null) {
+                height += VSPACE + SEPARATOR_HEIGHT;
+                if (expanded && client !is null)
+                    height += VSPACE;
+            }
+            // if (hasTitleBar())
+            // height += VSPACE;
+            if ((expanded || (expansionStyle & COMPACT) is 0) && client !is null) {
+                int cwHint = wHint;
+                int clientIndent = 0;
+                if ((expansionStyle & CLIENT_INDENT) !is 0)
+                    clientIndent = twidth;
+
+                if (cwHint !is DWT.DEFAULT) {
+                    cwHint -= marginWidth + marginWidth + thmargin + thmargin;
+                    if ((expansionStyle & CLIENT_INDENT) !is 0)
+                        if (tcsize.x > 0)
+                            cwHint -= twidth;
+                }
+                Point dsize = null;
+                Point csize = clientCache.computeSize(FormUtil.getWidthHint(
+                        cwHint, client), DWT.DEFAULT);
+                if (getDescriptionControl() !is null) {
+                    int dwHint = cwHint;
+                    if (dwHint is DWT.DEFAULT) {
+                        dwHint = csize.x;
+                        if ((expansionStyle & CLIENT_INDENT) !is 0)
+                            dwHint -= twidth;
+                    }
+                    dsize = descriptionCache.computeSize(dwHint, DWT.DEFAULT);
+                }
+                if (dsize !is null) {
+                    width = Math.max(width, dsize.x + clientIndent);
+                    if (expanded)
+                        height += descriptionVerticalSpacing + dsize.y
+                                + clientVerticalSpacing;
+                } else {
+                    height += clientVerticalSpacing;
+                    if (getSeparatorControl() !is null)
+                        height -= VSPACE;
+                }
+                width = Math.max(width, csize.x + clientIndent);
+                if (expanded)
+                    height += csize.y;
+            }
+            if (toggle !is null)
+                height = height - size.y + Math.max(size.y, tsize.y);
+
+            Point result = new Point(width + marginWidth + marginWidth
+                    + thmargin + thmargin, height + marginHeight + marginHeight
+                    + tvmargin + tvmargin);
+            return result;
+        }
+
+        public int computeMinimumWidth(Composite parent, bool changed) {
+            return computeSize(parent, 0, DWT.DEFAULT, changed).x;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.ui.forms.parts.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite,
+         *      bool)
+         */
+        public int computeMaximumWidth(Composite parent, bool changed) {
+            return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x;
+        }
+    }
+
+    /**
+     * Creates an expandable composite using a TWISTIE toggle.
+     *
+     * @param parent
+     *            the parent composite
+     * @param style
+     *            DWT style bits
+     */
+    public this(Composite parent, int style) {
+        this(parent, style, TWISTIE);
+    }
+
+    /**
+     * Creates the expandable composite in the provided parent.
+     *
+     * @param parent
+     *            the parent
+     * @param style
+     *            the control style (as expected by DWT subclass)
+     * @param expansionStyle
+     *            the style of the expansion widget (TREE_NODE, TWISTIE,
+     *            CLIENT_INDENT, COMPACT, FOCUS_TITLE,
+     *            LEFT_TEXT_CLIENT_ALIGNMENT, NO_TITLE)
+     */
+    public this(Composite parent, int style, int expansionStyle) {
+        listeners = new ListenerList();
+        super(parent, style);
+        this.expansionStyle = expansionStyle;
+        if ((expansionStyle & TITLE_BAR) !is 0)
+            setBackgroundMode(DWT.INHERIT_DEFAULT);
+        super.setLayout(new ExpandableLayout());
+        if (hasTitleBar()) {
+            this.addPaintListener(new class PaintListener {
+                public void paintControl(PaintEvent e) {
+                    onPaint(e);
+                }
+            });
+        }
+        if ((expansionStyle & TWISTIE) !is 0)
+            toggle = new Twistie(this, DWT.NULL);
+        else if ((expansionStyle & TREE_NODE) !is 0)
+            toggle = new TreeNode(this, DWT.NULL);
+        else
+            expanded = true;
+        if ((expansionStyle & EXPANDED) !is 0)
+            expanded = true;
+        if (toggle !is null) {
+            toggle.setExpanded(expanded);
+            toggle.addHyperlinkListener(new class HyperlinkAdapter {
+                public void linkActivated(HyperlinkEvent e) {
+                    toggleState();
+                }
+            });
+            toggle.addPaintListener(new class PaintListener {
+                public void paintControl(PaintEvent e) {
+                    if (null !is cast(Label)textLabel  && !isFixedStyle())
+                        textLabel.setForeground(toggle.hover_package ? toggle
+                                .getHoverDecorationColor()
+                                : getTitleBarForeground());
+                }
+            });
+            toggle.addKeyListener(new class KeyAdapter {
+                public void keyPressed(KeyEvent e) {
+                    if (e.keyCode is DWT.ARROW_UP) {
+                        verticalMove(false);
+                        e.doit = false;
+                    } else if (e.keyCode is DWT.ARROW_DOWN) {
+                        verticalMove(true);
+                        e.doit = false;
+                    }
+                }
+            });
+            if ((getExpansionStyle()&FOCUS_TITLE) is 0) {
+                toggle.paintFocus=false;
+                toggle.addFocusListener(new class FocusListener {
+                    public void focusGained(FocusEvent e) {
+                        textLabel.redraw();
+                    }
+
+                    public void focusLost(FocusEvent e) {
+                        textLabel.redraw();
+                    }
+                });
+            }
+        }
+        if ((expansionStyle & FOCUS_TITLE) !is 0) {
+            Hyperlink link = new Hyperlink(this, DWT.WRAP);
+            link.addHyperlinkListener(new class HyperlinkAdapter {
+                public void linkActivated(HyperlinkEvent e) {
+                    programmaticToggleState();
+                }
+            });
+            textLabel = link;
+        } else if ((expansionStyle & NO_TITLE) is 0) {
+            final Label label = new Label(this, DWT.WRAP);
+            if (!isFixedStyle()) {
+                label.setCursor(FormsResources.getHandCursor());
+                Listener listener = dgListener( (Event e, Label label) {
+                    switch (e.type) {
+                    case DWT.MouseDown:
+                        if (toggle !is null)
+                            toggle.setFocus();
+                        break;
+                    case DWT.MouseUp:
+                        label.setCursor(FormsResources.getBusyCursor());
+                        programmaticToggleState();
+                        label.setCursor(FormsResources.getHandCursor());
+                        break;
+                    case DWT.MouseEnter:
+                        if (toggle !is null) {
+                            label.setForeground(toggle
+                                    .getHoverDecorationColor());
+                            toggle.hover_package = true;
+                            toggle.redraw();
+                        }
+                        break;
+                    case DWT.MouseExit:
+                        if (toggle !is null) {
+                            label.setForeground(getTitleBarForeground());
+                            toggle.hover_package = false;
+                            toggle.redraw();
+                        }
+                        break;
+                    case DWT.Paint:
+                        if (toggle !is null) {
+                            paintTitleFocus(e.gc);
+                        }
+                        break;
+                    }
+                }, label );
+                label.addListener(DWT.MouseDown, listener);
+                label.addListener(DWT.MouseUp, listener);
+                label.addListener(DWT.MouseEnter, listener);
+                label.addListener(DWT.MouseExit, listener);
+                label.addListener(DWT.Paint, listener);
+            }
+            textLabel = label;
+        }
+        if (textLabel !is null) {
+            textLabel.setMenu(getMenu());
+            textLabel.addTraverseListener(new class TraverseListener {
+                public void keyTraversed(TraverseEvent e) {
+                    if (e.detail is DWT.TRAVERSE_MNEMONIC) {
+                        // steal the mnemonic
+                        if (!isVisible() || !isEnabled())
+                            return;
+                        if (FormUtil.mnemonicMatch(getText(), e.character)) {
+                            e.doit = false;
+                            if (!isFixedStyle()) {
+                                programmaticToggleState();
+                            }
+                            setFocus();
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see dwt.widgets.Control#forceFocus()
+     */
+    public bool forceFocus() {
+        return false;
+    }
+
+    /**
+     * Overrides 'super' to pass the menu to the text label.
+     *
+     * @param menu
+     *            the menu from the parent to attach to this control.
+     */
+
+    public void setMenu(Menu menu) {
+        if (textLabel !is null)
+            textLabel.setMenu(menu);
+        super.setMenu(menu);
+    }
+
+    /**
+     * Prevents assignment of the layout manager - expandable composite uses its
+     * own layout.
+     */
+    public final void setLayout(Layout layout) {
+    }
+
+    /**
+     * Sets the background of all the custom controls in the expandable.
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        if ((getExpansionStyle() & TITLE_BAR) is 0) {
+            if (textLabel !is null)
+                textLabel.setBackground(bg);
+            if (toggle !is null)
+                toggle.setBackground(bg);
+        }
+    }
+
+    /**
+     * Sets the foreground of all the custom controls in the expandable.
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        if (textLabel !is null)
+            textLabel.setForeground(fg);
+        if (toggle !is null)
+            toggle.setForeground(fg);
+    }
+
+    /**
+     * Sets the color of the toggle control.
+     *
+     * @param c
+     *            the color object
+     */
+    public void setToggleColor(Color c) {
+        if (toggle !is null)
+            toggle.setDecorationColor(c);
+    }
+
+    /**
+     * Sets the active color of the toggle control (when the mouse enters the
+     * toggle area).
+     *
+     * @param c
+     *            the active color object
+     */
+    public void setActiveToggleColor(Color c) {
+        if (toggle !is null)
+            toggle.setHoverDecorationColor(c);
+    }
+
+    /**
+     * Sets the fonts of all the custom controls in the expandable.
+     */
+    public void setFont(Font font) {
+        super.setFont(font);
+        if (textLabel !is null)
+            textLabel.setFont(font);
+        if (toggle !is null)
+            toggle.setFont(font);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwt.widgets.Control#setEnabled(bool)
+     */
+
+    public void setEnabled(bool enabled) {
+        if (textLabel !is null)
+            textLabel.setEnabled(enabled);
+        if (toggle !is null)
+            toggle.setEnabled(enabled);
+        super.setEnabled(enabled);
+    }
+
+    /**
+     * Sets the client of this expandable composite. The client must not be
+     * <samp>null </samp> and must be a direct child of this container.
+     *
+     * @param client
+     *            the client that will be expanded or collapsed
+     */
+    public void setClient(Control client) {
+        Assert.isTrue(client !is null && client.getParent().opEquals(this));
+        this.client = client;
+    }
+
+    /**
+     * Returns the current expandable client.
+     *
+     * @return the client control
+     */
+    public Control getClient() {
+        return client;
+    }
+
+    /**
+     * Sets the title of the expandable composite. The title will act as a
+     * hyperlink and activating it will toggle the client between expanded and
+     * collapsed state.
+     *
+     * @param title
+     *            the new title string
+     * @see #getText()
+     */
+    public void setText(String title) {
+        if (null !is cast(Label)textLabel )
+            (cast(Label) textLabel).setText(title);
+        else if (null !is cast(Hyperlink)textLabel )
+            (cast(Hyperlink) textLabel).setText(title);
+    }
+
+    /**
+     * Returns the title string.
+     *
+     * @return the title string
+     * @see #setText(String)
+     */
+    public String getText() {
+        if (null !is cast(Label)textLabel )
+            return (cast(Label) textLabel).getText();
+        else if (null !is cast(Hyperlink)textLabel )
+            return (cast(Hyperlink) textLabel).getText();
+        else
+            return ""; //$NON-NLS-1$
+    }
+
+    /**
+     * Tests the expanded state of the composite.
+     *
+     * @return <samp>true </samp> if expanded, <samp>false </samp> if collapsed.
+     */
+    public bool isExpanded() {
+        return expanded;
+    }
+
+    /**
+     * Returns the bitwise-ORed style bits for the expansion control.
+     *
+     * @return the bitwise-ORed style bits for the expansion control
+     */
+    public int getExpansionStyle() {
+        return expansionStyle;
+    }
+
+    /**
+     * Programmatically changes expanded state.
+     *
+     * @param expanded
+     *            the new expanded state
+     */
+    public void setExpanded(bool expanded) {
+        internalSetExpanded(expanded);
+        if (toggle !is null)
+            toggle.setExpanded(expanded);
+    }
+
+    /**
+     * Performs the expansion state change for the expandable control.
+     *
+     * @param expanded
+     *            the expansion state
+     */
+    protected void internalSetExpanded(bool expanded) {
+        if (this.expanded !is expanded) {
+            this.expanded = expanded;
+            if (getDescriptionControl() !is null)
+                getDescriptionControl().setVisible(expanded);
+            if (client !is null)
+                client.setVisible(expanded);
+            layout();
+        }
+    }
+
+    /**
+     * Adds the listener that will be notified when the expansion state changes.
+     *
+     * @param listener
+     *            the listener to add
+     */
+    public void addExpansionListener(IExpansionListener listener) {
+        listeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Removes the expansion listener.
+     *
+     * @param listener
+     *            the listner to remove
+     */
+    public void removeExpansionListener(IExpansionListener listener) {
+        listeners.remove(cast(Object)listener);
+    }
+
+    /**
+     * If TITLE_BAR or SHORT_TITLE_BAR style is used, title bar decoration will
+     * be painted behind the text in this method. The default implementation
+     * does nothing - subclasses are responsible for rendering the title area.
+     *
+     * @param e
+     *            the paint event
+     */
+    protected void onPaint(PaintEvent e) {
+    }
+
+    /**
+     * Returns description control that will be placed under the title if
+     * present.
+     *
+     * @return the description control or <samp>null </samp> if not used.
+     */
+    protected Control getDescriptionControl() {
+        return null;
+    }
+
+    /**
+     * Returns the separator control that will be placed between the title and
+     * the description if present.
+     *
+     * @return the separator control or <samp>null </samp> if not used.
+     */
+    protected Control getSeparatorControl() {
+        return null;
+    }
+
+    /**
+     * Computes the size of the expandable composite.
+     *
+     * @see dwt.widgets.Composite#computeSize
+     */
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        checkWidget();
+        Point size;
+        ExpandableLayout layout = cast(ExpandableLayout) getLayout();
+        if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) {
+            size = layout.computeSize(this, wHint, hHint, changed);
+        } else {
+            size = new Point(wHint, hHint);
+        }
+        Rectangle trim = computeTrim(0, 0, size.x, size.y);
+        return new Point(trim.width, trim.height);
+    }
+
+    /**
+     * Returns <samp>true </samp> if the composite is fixed i.e. cannot be
+     * expanded or collapsed. Fixed control will still contain the title,
+     * separator and description (if present) as well as the client, but will be
+     * in the permanent expanded state and the toggle affordance will not be
+     * shown.
+     *
+     * @return <samp>true </samp> if the control is fixed in the expanded state,
+     *         <samp>false </samp> if it can be collapsed.
+     */
+    protected bool isFixedStyle() {
+        return (expansionStyle & TWISTIE) is 0
+                && (expansionStyle & TREE_NODE) is 0;
+    }
+
+    /**
+     * Returns the text client control.
+     *
+     * @return Returns the text client control if specified, or
+     *         <code>null</code> if not.
+     */
+    public Control getTextClient() {
+        return textClient;
+    }
+
+    /**
+     * Sets the text client control. Text client is a control that is a child of
+     * the expandable composite and is placed to the right of the text. It can
+     * be used to place small image hyperlinks. If more than one control is
+     * needed, use Composite to hold them. Care should be taken that the height
+     * of the control is comparable to the height of the text.
+     *
+     * @param textClient
+     *            the textClient to set or <code>null</code> if not needed any
+     *            more.
+     */
+    public void setTextClient(Control textClient) {
+        if (this.textClient !is null)
+            this.textClient.dispose();
+        this.textClient = textClient;
+    }
+
+    /**
+     * Returns the difference in height between the text and the text client (if
+     * set). This difference can cause vertical alignment problems when two
+     * expandable composites are placed side by side, one with and one without
+     * the text client. Use this method obtain the value to add to either
+     * <code>descriptionVerticalSpacing</code> (if you have description) or
+     * <code>clientVerticalSpacing</code> to correct the alignment of the
+     * expandable without the text client.
+     *
+     * @return the difference in height between the text and the text client or
+     *         0 if no corrective action is needed.
+     * @since 3.3
+     */
+    public int getTextClientHeightDifference() {
+        if (textClient is null || textLabel is null)
+            return 0;
+        int theight = textLabel.computeSize(DWT.DEFAULT, DWT.DEFAULT).y;
+        int tcheight = textClient.computeSize(DWT.DEFAULT, DWT.DEFAULT).y;
+        return Math.max(tcheight - theight, 0);
+    }
+
+    /**
+     * Tests if this expandable composite renders a title bar around the text.
+     *
+     * @return <code>true</code> for <code>TITLE_BAR</code> or
+     *         <code>SHORT_TITLE_BAR</code> styles, <code>false</code>
+     *         otherwise.
+     */
+    protected bool hasTitleBar() {
+        return (getExpansionStyle() & TITLE_BAR) !is 0
+                || (getExpansionStyle() & SHORT_TITLE_BAR) !is 0;
+    }
+
+    /**
+     * Sets the color of the title bar foreground when TITLE_BAR style is used.
+     *
+     * @param color
+     *            the title bar foreground
+     */
+    public void setTitleBarForeground(Color color) {
+        titleBarForeground = color;
+        textLabel.setForeground(color);
+    }
+
+    /**
+     * Returns the title bar foreground when TITLE_BAR style is used.
+     *
+     * @return the title bar foreground
+     */
+    public Color getTitleBarForeground() {
+        return titleBarForeground;
+    }
+
+    // end of APIs
+
+    private void toggleState() {
+        bool newState = !isExpanded();
+        fireExpanding(newState, true);
+        internalSetExpanded(newState);
+        fireExpanding(newState, false);
+        if (newState)
+            FormUtil.ensureVisible(this);
+    }
+
+    private void fireExpanding(bool state, bool before) {
+        int size = listeners.size();
+        if (size is 0)
+            return;
+        ExpansionEvent e = new ExpansionEvent(this, state);
+        Object [] listenerList = listeners.getListeners();
+        for (int i = 0; i < size; i++) {
+            IExpansionListener listener = cast(IExpansionListener) listenerList[i];
+            if (before)
+                listener.expansionStateChanging(e);
+            else
+                listener.expansionStateChanged(e);
+        }
+    }
+
+    private void verticalMove(bool down) {
+        Composite parent = getParent();
+        Control[] children = parent.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            Control child = children[i];
+            if (child is this) {
+                ExpandableComposite sibling = getSibling(children, i, down);
+                if (sibling !is null && sibling.toggle !is null) {
+                    sibling.setFocus();
+                }
+                break;
+            }
+        }
+    }
+
+    private ExpandableComposite getSibling(Control[] children, int index,
+            bool down) {
+        int loc = down ? index + 1 : index - 1;
+        while (loc >= 0 && loc < children.length) {
+            Control c = children[loc];
+            if (null !is cast(ExpandableComposite)c  && c.isVisible())
+                return cast(ExpandableComposite) c;
+            loc = down ? loc + 1 : loc - 1;
+        }
+        return null;
+    }
+
+    private void programmaticToggleState() {
+        if (toggle !is null)
+            toggle.setExpanded(!toggle.isExpanded());
+        toggleState();
+    }
+
+    private void paintTitleFocus(GC gc) {
+        Point size = textLabel.getSize();
+        gc.setBackground(textLabel.getBackground());
+        gc.setForeground(textLabel.getForeground());
+        if (toggle.isFocusControl())
+            gc.drawFocus(0, 0, size.x, size.y);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/Form.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,801 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.Form;
+
+import dwtx.ui.forms.widgets.SizeCache;
+import dwtx.ui.forms.widgets.FormText;
+import dwtx.ui.forms.widgets.ILayoutExtension;
+import dwtx.ui.forms.widgets.LayoutComposite;
+
+import dwt.DWT;
+import dwt.dnd.DragSourceListener;
+import dwt.dnd.DropTargetListener;
+import dwt.dnd.Transfer;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Layout;
+import dwt.widgets.Menu;
+import dwtx.jface.action.IMenuManager;
+import dwtx.jface.action.IToolBarManager;
+import dwtx.ui.forms.IFormColors;
+import dwtx.ui.forms.IMessage;
+import dwtx.ui.forms.events.IHyperlinkListener;
+import dwtx.ui.internal.forms.widgets.FormHeading;
+import dwtx.ui.internal.forms.widgets.FormUtil;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Form is a custom control that renders a title and an optional background
+ * image above the body composite. It can be used alone when part of parents
+ * that are scrolled. If scrolling is required, use <code>ScrolledForm</code>
+ * instead because it has an instance of <code>Form</code> and adds scrolling
+ * capability.
+ * <p>
+ * Form can have a title if set. If not set, title area will not be left empty -
+ * form body will be resized to fill the entire form. In addition, an optional
+ * title image can be set and is rendered to the left of the title (since 3.2).
+ * <p>
+ * Form can have a title drop down menu if the menu bar manager is not empty
+ * (since 3.3).
+ * <p>
+ * Form title can support drag and drop if drag and drop support methods are
+ * invoked. When used, additional decoration is rendered behind the title to
+ * reinforce the drag and drop ability (since 3.3).
+ * <p>
+ * The form supports status messages. These messages can have various severity
+ * (error, warning, info or none). If status hyperlink handler is specified, the
+ * messages with the specified severity other than none will be rendered as
+ * hyperlinks.
+ * <p>
+ * Form can have a background image behind the title text. The image is tiled as
+ * many times as needed to fill the title area. Alternatively, gradient
+ * background can be painted vertically or horizontally.
+ * <p>
+ * Form can be put in a 'busy' state. While in this state, title image is
+ * replaced with an animation that lasts as long as the 'busy' state is active.
+ * <p>
+ * It is possible to create an optional head client control. When created, this
+ * control is placed in the form heading as a second row.
+ * <p>
+ * Form has a custom layout manager that is wrap-enabled. If a form is placed in
+ * a composite whose layout manager implements ILayoutExtension, the body of the
+ * form will participate in wrapping as long as its layout manager implements
+ * ILayoutExtension as well.
+ * <p>
+ * Children of the form should typically be created using FormToolkit to match
+ * the appearance and behaviour. When creating children, use the form body as a
+ * parent by calling 'getBody()' on the form instance. Example:
+ *
+ * <pre>
+ * FormToolkit toolkit = new FormToolkit(parent.getDisplay());
+ * Form form = toolkit.createForm(parent);
+ * form.setText(&quot;Sample form&quot;);
+ * form.getBody().setLayout(new GridLayout());
+ * toolkit.createButton(form.getBody(), &quot;Checkbox&quot;, DWT.CHECK);
+ * </pre>
+ *
+ * <p>
+ * No layout manager has been set on the body. Clients are required to set the
+ * desired layout manager explicitly.
+ * <p>
+ * Although the class is not final, it should not be subclassed.
+ *
+ * @since 3.0
+ */
+public class Form : Composite {
+    private FormHeading head;
+
+    private Composite body_;
+
+    private SizeCache body_Cache;
+
+    private SizeCache headCache;
+
+    private FormText selectionText;
+
+    private class FormLayout : Layout, ILayoutExtension {
+        public int computeMinimumWidth(Composite composite, bool flushCache) {
+            return computeSize(composite, 5, DWT.DEFAULT, flushCache).x;
+        }
+
+        public int computeMaximumWidth(Composite composite, bool flushCache) {
+            return computeSize(composite, DWT.DEFAULT, DWT.DEFAULT, flushCache).x;
+        }
+
+        public Point computeSize(Composite composite, int wHint, int hHint,
+                bool flushCache) {
+            if (flushCache) {
+                body_Cache.flush();
+                headCache.flush();
+            }
+            body_Cache.setControl(body_);
+            headCache.setControl(head);
+
+            int width = 0;
+            int height = 0;
+
+            Point hsize = headCache.computeSize(FormUtil.getWidthHint(wHint,
+                    head), DWT.DEFAULT);
+            width = Math.max(hsize.x, width);
+            height = hsize.y;
+
+            bool ignoreBody=getData(FormUtil.IGNORE_BODY) !is null;
+
+            Point bsize;
+            if (ignoreBody)
+                bsize = new Point(0,0);
+            else
+                bsize = body_Cache.computeSize(FormUtil.getWidthHint(wHint,
+                    body_), DWT.DEFAULT);
+            width = Math.max(bsize.x, width);
+            height += bsize.y;
+            return new Point(width, height);
+        }
+
+        protected void layout(Composite composite, bool flushCache) {
+            if (flushCache) {
+                body_Cache.flush();
+                headCache.flush();
+            }
+            body_Cache.setControl(body_);
+            headCache.setControl(head);
+            Rectangle carea = composite.getClientArea();
+
+            Point hsize = headCache.computeSize(carea.width, DWT.DEFAULT);
+            headCache.setBounds(0, 0, carea.width, hsize.y);
+            body_Cache
+                    .setBounds(0, hsize.y, carea.width, carea.height - hsize.y);
+        }
+    }
+
+    /**
+     * Creates the form content control as a child of the provided parent.
+     *
+     * @param parent
+     *            the parent widget
+     */
+    public this(Composite parent, int style) {
+
+        body_Cache = new SizeCache();
+        headCache = new SizeCache();
+
+        super(parent, DWT.NO_BACKGROUND | style);
+        super.setLayout(new FormLayout());
+        head = new FormHeading(this, DWT.NULL);
+        head.setMenu(parent.getMenu());
+        body_ = new LayoutComposite(this, DWT.NULL);
+        body_.setMenu(parent.getMenu());
+    }
+
+    /**
+     * Passes the menu to the form body.
+     *
+     * @param menu
+     *            the parent menu
+     */
+    public void setMenu(Menu menu) {
+        super.setMenu(menu);
+        head.setMenu(menu);
+        body_.setMenu(menu);
+    }
+
+    /**
+     * Fully delegates the size computation to the internal layout manager.
+     */
+    public final Point computeSize(int wHint, int hHint, bool changed) {
+        return (cast(FormLayout) getLayout()).computeSize(this, wHint, hHint,
+                changed);
+    }
+
+    /**
+     * Prevents from changing the custom control layout.
+     */
+    public final void setLayout(Layout layout) {
+    }
+
+    /**
+     * Returns the title text that will be rendered at the top of the form.
+     *
+     * @return the title text
+     */
+    public String getText() {
+        return head.getText();
+    }
+
+    /**
+     * Returns the title image that will be rendered to the left of the title.
+     *
+     * @return the title image or <code>null</code> if not set.
+     * @since 3.2
+     */
+    public Image getImage() {
+        return head.getImage();
+    }
+
+    /**
+     * Sets the foreground color of the form. This color will also be used for
+     * the body.
+     *
+     * @param fg
+     *            the foreground color
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        head.setForeground(fg);
+        body_.setForeground(fg);
+    }
+
+    /**
+     * Sets the background color of the form. This color will also be used for
+     * the body.
+     *
+     * @param bg
+     *            the background color
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        head.setBackground(bg);
+        body_.setBackground(bg);
+    }
+
+    /**
+     * Sets the font of the header text.
+     *
+     * @param font
+     *            the new font
+     */
+    public void setFont(Font font) {
+        super.setFont(font);
+        head.setFont(font);
+    }
+
+    /**
+     * Sets the text to be rendered at the top of the form above the body as a
+     * title.
+     * <p>
+     * <strong>Note:</strong> Mnemonics are indicated by an '&amp;' that causes
+     * the next character to be the mnemonic. Mnemonics are not applicable in
+     * the case of the form title but need to be taken into acount due to the
+     * usage of the underlying widget that renders mnemonics in the title area.
+     * The mnemonic indicator character '&amp;' can be escaped by doubling it in
+     * the string, causing a single '&amp;' to be displayed.
+     * </p>
+     *
+     * @param text
+     *            the title text
+     */
+    public void setText(String text) {
+        head.setText(text);
+        layout();
+        redraw();
+    }
+
+    /**
+     * Sets the image to be rendered to the left of the title. This image will
+     * be temporarily hidden in two cases:
+     *
+     * <ol>
+     * <li>When the form is busy - replaced with a busy animation</li>
+     * <li>When the form has message set - replaced with the image indicating
+     * message severity</li>
+     * </ol>
+     *
+     * @param image
+     *            the title image or <code>null</code> to show no image.
+     * @since 3.2
+     */
+    public void setImage(Image image) {
+        head.setImage(image);
+        layout();
+        redraw();
+    }
+
+    /**
+     * Sets the background colors to be painted behind the title text in a
+     * gradient. Note that this method will reset color previously set by
+     * {@link #setBackground(Color)}. This is necessary for the simulated
+     * transparency of the heading in all of its children control.
+     *
+     * @param gradientColors
+     *            the array of colors that form the gradient
+     * @param percents
+     *            the partition of the overall space between the gradient colors
+     * @param vertical
+     *            of <code>true</code>, the gradient will be rendered
+     *            vertically, if <code>false</code> the orientation will be
+     *            horizontal.
+     */
+
+    public void setTextBackground(Color[] gradientColors, int[] percents,
+            bool vertical) {
+        head.setTextBackground(gradientColors, percents, vertical);
+    }
+
+    /**
+     * Returns the optional background image of the form head.
+     *
+     * @return the background image or <code>null</code> if not specified.
+     */
+    public Image getBackgroundImage() {
+        return head.getHeadingBackgroundImage();
+    }
+
+    /**
+     * Sets the optional background image to be rendered behind the title
+     * starting at the position 0,0. If the image is smaller than the container
+     * in any dimension, it will be tiled.
+     *
+     * @since 3.2
+     *
+     * @param backgroundImage
+     *            the head background image.
+     *
+     */
+    public void setBackgroundImage(Image backgroundImage) {
+        head.setHeadingBackgroundImage(backgroundImage);
+    }
+
+    /**
+     * Returns the tool bar manager that is used to manage tool items in the
+     * form's title area.
+     *
+     * @return form tool bar manager
+     */
+    public IToolBarManager getToolBarManager() {
+        return head.getToolBarManager();
+    }
+
+    /**
+     * Sets the tool bar vertical alignment relative to the header. Can be
+     * useful when there is more free space at the second row (with the head
+     * client).
+     *
+     * @param alignment
+     *            DWT.TOP or DWT.BOTTOM
+     * @since 3.3
+     */
+
+    public void setToolBarVerticalAlignment(int alignment) {
+        head.setToolBarAlignment(alignment);
+    }
+
+    /**
+     * Returns the current tool bar alignment (if used).
+     *
+     * @return DWT.TOP or DWT.BOTTOM
+     * @since 3.3
+     */
+
+    public int getToolBarVerticalAlignment() {
+        return head.getToolBarAlignment();
+    }
+
+    /**
+     * Returns the menu manager that is used to manage title area drop-down menu
+     * items.
+     *
+     * @return title area drop-down menu manager
+     * @since 3.3
+     */
+    public IMenuManager getMenuManager() {
+        return head.getMenuManager();
+    }
+
+    /**
+     * Updates the local tool bar manager if used. Does nothing if local tool
+     * bar manager has not been created yet.
+     */
+    public void updateToolBar() {
+        head.updateToolBar();
+    }
+
+    /**
+     * Returns the container that occupies the head of the form (the form area
+     * above the body). Use this container as a parent for the head client.
+     *
+     * @return the head of the form.
+     * @since 3.2
+     */
+    public Composite getHead() {
+        return head;
+    }
+
+    /**
+     * Returns the optional head client if set.
+     *
+     * @return the head client or <code>null</code> if not set.
+     * @see #setHeadClient(Control)
+     * @since 3.2
+     */
+    public Control getHeadClient() {
+        return head.getHeadClient();
+    }
+
+    /**
+     * Sets the optional head client. Head client is placed after the form
+     * title. This option causes the tool bar to be placed in the second raw of
+     * the header (below the head client).
+     * <p>
+     * The head client must be a child of the composite returned by
+     * <code>getHead()</code> method.
+     *
+     * @param headClient
+     *            the optional child of the head
+     * @since 3.2
+     */
+    public void setHeadClient(Control headClient) {
+        head.setHeadClient(headClient);
+        layout();
+    }
+
+    /**
+     * Returns the container that occupies the body of the form (the form area
+     * below the title). Use this container as a parent for the controls that
+     * should be in the form. No layout manager has been set on the form body.
+     *
+     * @return Returns the body of the form.
+     */
+    public Composite getBody() {
+        return body_;
+    }
+
+    /**
+     * Tests if the background image is tiled to cover the entire area of the
+     * form heading.
+     *
+     * @return <code>true</code> if heading background image is tiled,
+     *         <code>false</code> otherwise.
+     */
+    public bool isBackgroundImageTiled() {
+        return head.isBackgroundImageTiled();
+    }
+
+    /**
+     * Sets whether the header background image is repeated to cover the entire
+     * heading area or not.
+     *
+     * @param backgroundImageTiled
+     *            set <code>true</code> to tile the image, or
+     *            <code>false</code> to paint the background image only once
+     *            at 0,0
+     */
+    public void setBackgroundImageTiled(bool backgroundImageTiled) {
+        head.setBackgroundImageTiled(backgroundImageTiled);
+    }
+
+    /**
+     * Returns the background image alignment.
+     *
+     * @deprecated due to the underlying widget limitations, background image is
+     *             either painted at 0,0 and/or tiled.
+     * @return DWT.LEFT
+     */
+    public int getBackgroundImageAlignment() {
+        return DWT.LEFT;
+    }
+
+    /**
+     * Sets the background image alignment.
+     *
+     * @deprecated due to the underlying widget limitations, background image is
+     *             always tiled and alignment cannot be controlled.
+     * @param backgroundImageAlignment
+     *            The backgroundImageAlignment to set.
+     * @since 3.1
+     */
+    public void setBackgroundImageAlignment(int backgroundImageAlignment) {
+    }
+
+    /**
+     * Tests if background image is clipped.
+     *
+     * @deprecated due to the underlying widget limitations, background image is
+     *             always clipped.
+     * @return true
+     * @since 3.1
+     */
+    public bool isBackgroundImageClipped() {
+        return true;
+    }
+
+    /**
+     * Sets whether the background image is clipped.
+     *
+     * @deprecated due to the underlying widget limitations, background image is
+     *             always clipped.
+     * @param backgroundImageClipped
+     *            the value to set
+     * @since 3.1
+     */
+    public void setBackgroundImageClipped(bool backgroundImageClipped) {
+    }
+
+    /**
+     * Tests if the form head separator is visible.
+     *
+     * @return <code>true</code> if the head/body separator is visible,
+     *         <code>false</code> otherwise
+     * @since 3.2
+     */
+    public bool isSeparatorVisible() {
+        return head.isSeparatorVisible();
+    }
+
+    /**
+     * If set, adds a separator between the head and body. Since 3.3, the colors
+     * that are used to render it are {@link IFormColors#H_BOTTOM_KEYLINE1} and
+     * {@link IFormColors#H_BOTTOM_KEYLINE2}.
+     *
+     * @param addSeparator
+     *            <code>true</code> to make the separator visible,
+     *            <code>false</code> otherwise.
+     * @since 3.2
+     */
+    public void setSeparatorVisible(bool addSeparator) {
+        head.setSeparatorVisible(addSeparator);
+    }
+
+    /**
+     * Returns the color used to render the optional head separator. If gradient
+     * text background is used additional colors from the gradient will be used
+     * to render the separator.
+     *
+     * @return separator color or <code>null</code> if not set.
+     * @since 3.2
+     * @deprecated use <code>getHeadColor(IFormColors.H_BOTTOM_KEYLINE2)</code>
+     */
+
+    public Color getSeparatorColor() {
+        return head.getColor(IFormColors.H_BOTTOM_KEYLINE2);
+    }
+
+    /**
+     * Sets the color to be used to render the optional head separator.
+     *
+     * @param separatorColor
+     *            the color to render the head separator or <code>null</code>
+     *            to use the default color.
+     * @since 3.2
+     * @deprecated use
+     *             <code>setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, separatorColor)</code>
+     */
+    public void setSeparatorColor(Color separatorColor) {
+        head.putColor(IFormColors.H_BOTTOM_KEYLINE2, separatorColor);
+    }
+
+    /**
+     * Sets the color used to paint an aspect of the form heading.
+     *
+     * @param key
+     *            a valid form heading color key as defined in
+     *            {@link IFormColors}. Relevant keys all start with an H_
+     *            prefix.
+     * @param color
+     *            the color to use for the provided key
+     * @since 3.3
+     */
+
+    public void setHeadColor(String key, Color color) {
+        head.putColor(key, color);
+    }
+
+    /**
+     * Returns the color that is currently use to paint an aspect of the form
+     * heading, or <code>null</code> if not defined.
+     *
+     * @param key
+     *            the color key
+     * @return the color object or <code>null</code> if not set.
+     * @since 3.3
+     */
+
+    public Color getHeadColor(String key) {
+        return head.getColor(key);
+    }
+
+    /**
+     * Sets the message for this form. Message text is rendered in the form head
+     * when shown.
+     *
+     * @param message
+     *            the message, or <code>null</code> to clear the message
+     * @see #setMessage(String, int)
+     * @since 3.2
+     */
+    public void setMessage(String message) {
+        this.setMessage(message, 0, null);
+    }
+
+    /**
+     * Sets the message for this form with an indication of what type of message
+     * it is.
+     * <p>
+     * The valid message types are one of <code>NONE</code>,
+     * <code>INFORMATION</code>,<code>WARNING</code>, or
+     * <code>ERROR</code> defined in IMessageProvider interface.
+     * </p>
+     *
+     * @param newMessage
+     *            the message, or <code>null</code> to clear the message
+     * @param newType
+     *            the message type
+     * @see dwtx.jface.dialogs.IMessageProvider
+     * @since 3.2
+     */
+
+    public void setMessage(String newMessage, int newType) {
+        this.setMessage(newMessage, newType, null);
+    }
+
+    /**
+     * Sets the message for this form with an indication of what type of message
+     * it is.
+     * <p>
+     * The valid message types are one of <code>NONE</code>,
+     * <code>INFORMATION</code>,<code>WARNING</code>, or
+     * <code>ERROR</code> defined in IMessageProvider interface.
+     * </p>
+     * <p>
+     * In addition to the summary message, this method also sets an array of
+     * individual messages.
+     *
+     *
+     * @param newMessage
+     *            the message, or <code>null</code> to clear the message
+     * @param newType
+     *            the message type
+     * @param children
+     *            the individual messages that contributed to the overall
+     *            message
+     * @see dwtx.jface.dialogs.IMessageProvider
+     * @since 3.3
+     */
+
+    public void setMessage(String newMessage, int newType, IMessage[] children) {
+        head.showMessage(newMessage, newType, children);
+        layout();
+    }
+
+    /**
+     * Adds a message hyperlink listener. If at least one listener is present,
+     * messages will be rendered as hyperlinks.
+     *
+     * @param listener
+     * @see #removeMessageHyperlinkListener(IHyperlinkListener)
+     * @since 3.3
+     */
+    public void addMessageHyperlinkListener(IHyperlinkListener listener) {
+        head.addMessageHyperlinkListener(listener);
+    }
+
+    /**
+     * Remove the message hyperlink listener.
+     *
+     * @param listener
+     * @see #addMessageHyperlinkListener(IHyperlinkListener)
+     * @since 3.3
+     */
+    public void removeMessageHyperlinkListener(IHyperlinkListener listener) {
+        head.removeMessageHyperlinkListener(listener);
+    }
+
+    /**
+     * Tests if the form is in the 'busy' state. Busy form displays 'busy'
+     * animation in the area of the title image.
+     *
+     * @return <code>true</code> if busy, <code>false</code> otherwise.
+     * @since 3.2
+     */
+
+    public bool isBusy() {
+        return head.isBusy();
+    }
+
+    /**
+     * Sets the form's busy state. Busy form will display 'busy' animation in
+     * the area of the title image.
+     *
+     * @param busy
+     *            the form's busy state
+     * @since 3.2
+     */
+
+    public void setBusy(bool busy) {
+        head.setBusy(busy);
+    }
+
+    /**
+     * Adds support for dragging items out of the form title area via a user
+     * drag-and-drop operation.
+     *
+     * @param operations
+     *            a bitwise OR of the supported drag and drop operation types (
+     *            <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
+     *            <code>DROP_MOVE</code>)
+     * @param transferTypes
+     *            the transfer types that are supported by the drag operation
+     * @param listener
+     *            the callback that will be invoked to set the drag data and to
+     *            cleanup after the drag and drop operation finishes
+     * @see dwt.dnd.DND
+     * @since 3.3
+     */
+    public void addTitleDragSupport(int operations, Transfer[] transferTypes,
+            DragSourceListener listener) {
+        head.addDragSupport(operations, transferTypes, listener);
+    }
+
+    /**
+     * Adds support for dropping items into the form title area via a user
+     * drag-and-drop operation.
+     *
+     * @param operations
+     *            a bitwise OR of the supported drag and drop operation types (
+     *            <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
+     *            <code>DROP_MOVE</code>)
+     * @param transferTypes
+     *            the transfer types that are supported by the drop operation
+     * @param listener
+     *            the callback that will be invoked after the drag and drop
+     *            operation finishes
+     * @see dwt.dnd.DND
+     * @since 3.3
+     */
+    public void addTitleDropSupport(int operations, Transfer[] transferTypes,
+            DropTargetListener listener) {
+        head.addDropSupport(operations, transferTypes, listener);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.IMessageProvider#getMessage()
+     */
+    public String getMessage() {
+        return head.getMessage();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.IMessageProvider#getMessageType()
+     */
+    public int getMessageType() {
+        return head.getMessageType();
+    }
+
+    /**
+     * Returns the children messages that the cause of the summary message
+     * currently set on the form.
+     *
+     * @return an array of children messages or <code>null</code> if not set.
+     * @see #setMessage(String, int, IMessage[])
+     * @since 3.3
+     */
+    public IMessage[] getChildrenMessages() {
+        return head.getChildrenMessages();
+    }
+
+    void setSelectionText(FormText text) {
+        if (selectionText !is null && selectionText !is text) {
+            selectionText.clearSelection();
+        }
+        this.selectionText = text;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/FormText.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,1710 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.FormText;
+
+import dwtx.ui.forms.widgets.ILayoutExtension;
+import dwtx.ui.forms.widgets.Form;
+
+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.custom.ScrolledComposite;
+import dwt.dnd.Clipboard;
+import dwt.dnd.TextTransfer;
+import dwt.dnd.Transfer;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.FocusEvent;
+import dwt.events.FocusListener;
+import dwt.events.MenuEvent;
+import dwt.events.MenuListener;
+import dwt.events.MouseEvent;
+import dwt.events.MouseListener;
+import dwt.events.MouseMoveListener;
+import dwt.events.MouseTrackListener;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.events.SelectionAdapter;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.FontMetrics;
+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.Event;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.widgets.Menu;
+import dwt.widgets.MenuItem;
+import dwt.widgets.TypedListener;
+import dwtx.core.runtime.ListenerList;
+import dwtx.ui.forms.HyperlinkSettings;
+import dwtx.ui.forms.events.HyperlinkEvent;
+import dwtx.ui.forms.events.IHyperlinkListener;
+import dwtx.ui.internal.forms.Messages;
+import dwtx.ui.internal.forms.widgets.ControlSegment;
+import dwtx.ui.internal.forms.widgets.FormFonts;
+import dwtx.ui.internal.forms.widgets.FormTextModel;
+import dwtx.ui.internal.forms.widgets.FormUtil;
+import dwtx.ui.internal.forms.widgets.IFocusSelectable;
+import dwtx.ui.internal.forms.widgets.IHyperlinkSegment;
+import dwtx.ui.internal.forms.widgets.ImageSegment;
+import dwtx.ui.internal.forms.widgets.Locator;
+import dwtx.ui.internal.forms.widgets.Paragraph;
+import dwtx.ui.internal.forms.widgets.ParagraphSegment;
+import dwtx.ui.internal.forms.widgets.SelectionData;
+import dwtx.ui.internal.forms.widgets.TextSegment;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.InputStream;
+import tango.io.Stdout;
+import tango.util.collection.HashMap;
+import tango.util.collection.ArraySeq;
+
+/**
+ * This class is a read-only text control that is capable of rendering wrapped
+ * text. Text can be rendered as-is or by parsing the formatting XML tags.
+ * Independently, words that start with http:// can be converted into hyperlinks
+ * on the fly.
+ * <p>
+ * When configured to use formatting XML, the control requires the root element
+ * <code>form</code> to be used. The following tags can be children of the
+ * <code>form</code> element:
+ * </p>
+ * <ul>
+ * <li><b>p </b>- for defining paragraphs. The following attributes are
+ * allowed:
+ * <ul>
+ * <li><b>vspace </b>- if set to 'false', no vertical space will be added
+ * (default is 'true')</li>
+ * </ul>
+ * </li>
+ * <li><b>li </b>- for defining list items. The following attributes are
+ * allowed:
+ * <ul>
+ * <li><b>vspace </b>- the same as with the <b>p </b> tag</li>
+ * <li><b>style </b>- could be 'bullet' (default), 'text' and 'image'</li>
+ * <li><b>value </b>- not used for 'bullet'. For text, it is the value of the
+ * text that is rendered as a bullet. For image, it is the href of the image to
+ * be rendered as a bullet.</li>
+ * <li><b>indent </b>- the number of pixels to indent the text in the list item
+ * </li>
+ * <li><b>bindent </b>- the number of pixels to indent the bullet itself</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>
+ * Text in paragraphs and list items will be wrapped according to the width of
+ * the control. The following tags can appear as children of either <b>p </b> or
+ * <b>li </b> elements:
+ * <ul>
+ * <li><b>img </b>- to render an image. Element accepts attribute 'href' that
+ * is a key to the <code>Image</code> set using 'setImage' method. Vertical
+ * position of image relative to surrounding text is optionally controlled by
+ * the attribute <b>align</b> that can have values <b>top</b>, <b>middle</b>
+ * and <b>bottom</b></li>
+ * <li><b>a </b>- to render a hyperlink. Element accepts attribute 'href' that
+ * will be provided to the hyperlink listeners via HyperlinkEvent object. The
+ * element also accepts 'nowrap' attribute (default is false). When set to
+ * 'true', the hyperlink will not be wrapped. Hyperlinks automatically created
+ * when 'http://' is encountered in text are not wrapped.</li>
+ * <li><b>b </b>- the enclosed text will use bold font.</li>
+ * <li><b>br </b>- forced line break (no attributes).</li>
+ * <li><b>span </b>- the enclosed text will have the color and font specified
+ * in the element attributes. Color is provided using 'color' attribute and is a
+ * key to the Color object set by 'setColor' method. Font is provided using
+ * 'font' attribute and is a key to the Font object set by 'setFont' method. As with
+ * hyperlinks, it is possible to block wrapping by setting 'nowrap' to true
+ * (false by default).
+ * </li>
+ * <li><b>control (new in 3.1)</b> - to place a control that is a child of the
+ * text control. Element accepts attribute 'href' that is a key to the Control
+ * object set using 'setControl' method. Optionally, attribute 'fill' can be set
+ * to <code>true</code> to make the control fill the entire width of the text.
+ * Form text is not responsible for creating or disposing controls, it only
+ * places them relative to the surrounding text. Similar to <b>img</b>,
+ * vertical position of the control can be set using the <b>align</b>
+ * attribute. In addition, <b>width</b> and <b>height</b> attributes can
+ * be used to force the dimensions of the control. If not used,
+ * the preferred control size will be used.
+ * </ul>
+ * <p>
+ * None of the elements can nest. For example, you cannot have <b>b </b> inside
+ * a <b>span </b>. This was done to keep everything simple and transparent.
+ * Since 3.1, an exception to this rule has been added to support nesting images
+ * and text inside the hyperlink tag (<b>a</b>). Image enclosed in the
+ * hyperlink tag acts as a hyperlink, can be clicked on and can accept and
+ * render selection focus. When both text and image is enclosed, selection and
+ * rendering will affect both as a single hyperlink.
+ * </p>
+ * <p>
+ * Since 3.1, it is possible to select text. Text selection can be
+ * programmatically accessed and also copied to clipboard. Non-textual objects
+ * (images, controls etc.) in the selection range are ignored.
+ * <p>
+ * Care should be taken when using this control. Form text is not an HTML
+ * browser and should not be treated as such. If you need complex formatting
+ * capabilities, use Browser widget. If you need editing capabilities and
+ * font/color styles of text segments is all you need, use StyleText widget.
+ * Finally, if all you need is to wrap text, use DWT Label widget and create it
+ * with DWT.WRAP style.
+ *
+ * @see FormToolkit
+ * @see TableWrapLayout
+ * @since 3.0
+ */
+public class FormText : Canvas {
+    /**
+     * The object ID to be used when registering action to handle URL hyperlinks
+     * (those that should result in opening the web browser). Value is
+     * "urlHandler".
+     */
+    public static const String URL_HANDLER_ID = "urlHandler"; //$NON-NLS-1$
+
+    /**
+     * Value of the horizontal margin (default is 0).
+     */
+    public int marginWidth = 0;
+
+    /**
+     * Value of tue vertical margin (default is 1).
+     */
+    public int marginHeight = 1;
+
+    // private fields
+    private static const bool DEBUG_TEXT = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXT));
+    private static const bool DEBUG_TEXTSIZE = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXTSIZE));
+
+    private static const bool DEBUG_FOCUS = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_FOCUS));
+
+    private bool hasFocus;
+
+    private bool paragraphsSeparated = true;
+
+    private FormTextModel model;
+
+    private ListenerList listeners;
+
+    private HashMap!(String,Object) resourceTable;
+
+    private IHyperlinkSegment entered;
+
+    private IHyperlinkSegment armed;
+
+    private bool mouseFocus = false;
+
+    private bool controlFocusTransfer = false;
+
+    private bool inSelection = false;
+
+    private SelectionData selData;
+
+    private static const String INTERNAL_MENU = "__internal_menu__"; //$NON-NLS-1$
+
+    private static const String CONTROL_KEY = "__segment__"; //$NON-NLS-1$
+
+    private class FormTextLayout : Layout, ILayoutExtension {
+        public this() {
+        }
+
+        public int computeMaximumWidth(Composite parent, bool changed) {
+            return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x;
+        }
+
+        public int computeMinimumWidth(Composite parent, bool changed) {
+            return computeSize(parent, 5, DWT.DEFAULT, true).x;
+        }
+
+        /*
+         * @see Layout#computeSize(Composite, int, int, bool)
+         */
+        public Point computeSize(Composite composite, int wHint, int hHint,
+                bool changed) {
+            long start = 0;
+
+            if (DEBUG_TEXT)
+                start = System.currentTimeMillis();
+            int innerWidth = wHint;
+            if (innerWidth !is DWT.DEFAULT)
+                innerWidth -= marginWidth * 2;
+            Point textSize = computeTextSize(innerWidth);
+            int textWidth = textSize.x + 2 * marginWidth;
+            int textHeight = textSize.y + 2 * marginHeight;
+            Point result = new Point(textWidth, textHeight);
+            if (DEBUG_TEXT) {
+                long stop = System.currentTimeMillis();
+                Stdout.formatln("FormText computeSize: {}ms", (stop - start)); //$NON-NLS-1$
+            }
+            if (DEBUG_TEXTSIZE) {
+                Stdout.formatln("FormText ({}), computeSize: wHint={}, result={}", model.getAccessibleText(), wHint, result); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+            }
+            return result;
+        }
+
+        private Point computeTextSize(int wHint) {
+            Paragraph[] paragraphs = model.getParagraphs();
+            GC gc = new GC(this.outer);
+            gc.setFont(getFont());
+            Locator loc = new Locator();
+            int width = wHint !is DWT.DEFAULT ? wHint : 0;
+            FontMetrics fm = gc.getFontMetrics();
+            int lineHeight = fm.getHeight();
+            bool selectableInTheLastRow = false;
+            for (int i = 0; i < paragraphs.length; i++) {
+                Paragraph p = paragraphs[i];
+                if (i > 0 && getParagraphsSeparated()
+                        && p.getAddVerticalSpace())
+                    loc.y += getParagraphSpacing(lineHeight);
+                loc.rowHeight = 0;
+                loc.indent = p.getIndent();
+                loc.x = p.getIndent();
+                ParagraphSegment[] segments = p.getSegments();
+                if (segments.length > 0) {
+                    selectableInTheLastRow = false;
+                    int pwidth = 0;
+                    for (int j = 0; j < segments.length; j++) {
+                        ParagraphSegment segment = segments[j];
+                        segment.advanceLocator(gc, wHint, loc, resourceTable,
+                                false);
+                        if (wHint !is DWT.DEFAULT) {
+                            width = Math.max(width, loc.width);
+                        } else {
+                            pwidth += loc.width;
+                        }
+                        if (null !is cast(IFocusSelectable)segment )
+                            selectableInTheLastRow = true;
+                    }
+                    if (wHint is DWT.DEFAULT)
+                        width = Math.max(width, pwidth);
+                    loc.y += loc.rowHeight;
+                } else {
+                    // empty new line
+                    loc.y += lineHeight;
+                }
+            }
+            gc.dispose();
+            if (selectableInTheLastRow)
+                loc.y += 1;
+            return new Point(width, loc.y);
+        }
+
+        protected void layout(Composite composite, bool flushCache) {
+            long start = 0;
+
+            if (DEBUG_TEXT) {
+                start = System.currentTimeMillis();
+            }
+            selData = null;
+            Rectangle carea = composite.getClientArea();
+            if (DEBUG_TEXTSIZE) {
+                Stdout.formatln("FormText layout ({}), carea={}",model.getAccessibleText(),carea); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+            GC gc = new GC(composite);
+            gc.setFont(getFont());
+            ensureBoldFontPresent(getFont());
+            gc.setForeground(getForeground());
+            gc.setBackground(getBackground());
+
+            Locator loc = new Locator();
+            loc.marginWidth = marginWidth;
+            loc.marginHeight = marginHeight;
+            loc.x = marginWidth;
+            loc.y = marginHeight;
+            FontMetrics fm = gc.getFontMetrics();
+            int lineHeight = fm.getHeight();
+
+            Paragraph[] paragraphs = model.getParagraphs();
+            IHyperlinkSegment selectedLink = getSelectedLink();
+            for (int i = 0; i < paragraphs.length; i++) {
+                Paragraph p = paragraphs[i];
+                if (i > 0 && paragraphsSeparated && p.getAddVerticalSpace())
+                    loc.y += getParagraphSpacing(lineHeight);
+                loc.indent = p.getIndent();
+                loc.resetCaret();
+                loc.rowHeight = 0;
+                p.layout(gc, carea.width, loc, lineHeight, resourceTable,
+                        selectedLink);
+            }
+            gc.dispose();
+            if (DEBUG_TEXT) {
+                long stop = System.currentTimeMillis();
+                Stdout.formatln("FormText.layout: {}ms", (stop - start)); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+        }
+    }
+
+    /**
+     * Contructs a new form text widget in the provided parent and using the
+     * styles.
+     *
+     * @param parent
+     *            form text parent control
+     * @param style
+     *            the widget style
+     */
+    public this(Composite parent, int style) {
+        resourceTable = new HashMap!(String,Object);
+        super(parent, DWT.NO_BACKGROUND | DWT.WRAP | style);
+        setLayout(new FormTextLayout());
+        model = new FormTextModel();
+        addDisposeListener(new class DisposeListener {
+            public void widgetDisposed(DisposeEvent e) {
+                model.dispose();
+                disposeResourceTable(true);
+            }
+        });
+        addPaintListener(new class PaintListener {
+            public void paintControl(PaintEvent e) {
+                paint(e);
+            }
+        });
+        addListener(DWT.KeyDown, new class Listener {
+            public void handleEvent(Event e) {
+                if (e.character is '\r') {
+                    activateSelectedLink();
+                    return;
+                }
+            }
+        });
+        addListener(DWT.Traverse, new class Listener {
+            public void handleEvent(Event e) {
+                if (DEBUG_FOCUS)
+                    Stdout.formatln("Traversal: {}", e); //$NON-NLS-1$
+                switch (e.detail) {
+                case DWT.TRAVERSE_PAGE_NEXT:
+                case DWT.TRAVERSE_PAGE_PREVIOUS:
+                case DWT.TRAVERSE_ARROW_NEXT:
+                case DWT.TRAVERSE_ARROW_PREVIOUS:
+                    e.doit = false;
+                    return;
+                }
+                if (!model.hasFocusSegments()) {
+                    e.doit = true;
+                    return;
+                }
+                if (e.detail is DWT.TRAVERSE_TAB_NEXT)
+                    e.doit = advance(true);
+                else if (e.detail is DWT.TRAVERSE_TAB_PREVIOUS)
+                    e.doit = advance(false);
+                else if (e.detail !is DWT.TRAVERSE_RETURN)
+                    e.doit = true;
+            }
+        });
+        addFocusListener(new class FocusListener {
+            public void focusGained(FocusEvent e) {
+                if (!hasFocus) {
+                    hasFocus = true;
+                    if (DEBUG_FOCUS) {
+                        Stdout.formatln("FormText: focus gained"); //$NON-NLS-1$
+                    }
+                    if (!mouseFocus && !controlFocusTransfer) {
+                        handleFocusChange();
+                    }
+                }
+            }
+
+            public void focusLost(FocusEvent e) {
+                if (DEBUG_FOCUS) {
+                    Stdout.formatln("FormText: focus lost"); //$NON-NLS-1$
+                }
+                if (hasFocus) {
+                    hasFocus = false;
+                    if (!controlFocusTransfer)
+                        handleFocusChange();
+                }
+            }
+        });
+        addMouseListener(new class MouseListener {
+            public void mouseDoubleClick(MouseEvent e) {
+            }
+
+            public void mouseDown(MouseEvent e) {
+                // select a link
+                handleMouseClick(e, true);
+            }
+
+            public void mouseUp(MouseEvent e) {
+                // activate a link
+                handleMouseClick(e, false);
+            }
+        });
+        addMouseTrackListener(new class MouseTrackListener {
+            public void mouseEnter(MouseEvent e) {
+                handleMouseMove(e);
+            }
+
+            public void mouseExit(MouseEvent e) {
+                if (entered !is null) {
+                    exitLink(entered, e.stateMask);
+                    paintLinkHover(entered, false);
+                    entered = null;
+                    setCursor(null);
+                }
+            }
+
+            public void mouseHover(MouseEvent e) {
+                handleMouseHover(e);
+            }
+        });
+        addMouseMoveListener(new class MouseMoveListener {
+            public void mouseMove(MouseEvent e) {
+                handleMouseMove(e);
+            }
+        });
+        initAccessible();
+        ensureBoldFontPresent(getFont());
+        createMenu();
+        // we will handle traversal of controls, if any
+        setTabList(cast(Control[])null);
+    }
+
+    /**
+     * Test for focus.
+     *
+     * @return <samp>true </samp> if the widget has focus.
+     */
+    public bool getFocus() {
+        return hasFocus;
+    }
+
+    /**
+     * Test if the widget is currently processing the text it is about to
+     * render.
+     *
+     * @return <samp>true </samp> if the widget is still loading the text,
+     *         <samp>false </samp> otherwise.
+     * @deprecated not used any more - returns <code>false</code>
+     */
+    public bool isLoading() {
+        return false;
+    }
+
+    /**
+     * Returns the text that will be shown in the control while the real content
+     * is loading.
+     *
+     * @return loading text message
+     * @deprecated loading text is not used since 3.1
+     */
+    public String getLoadingText() {
+        return null;
+    }
+
+    /**
+     * Sets the text that will be shown in the control while the real content is
+     * loading. This is significant when content to render is loaded from the
+     * input stream that was created from a remote URL, and the time to load the
+     * entire content is nontrivial.
+     *
+     * @param loadingText
+     *            loading text message
+     * @deprecated use setText(loadingText, false, false);
+     */
+    public void setLoadingText(String loadingText) {
+        setText(loadingText, false, false);
+    }
+
+    /**
+     * If paragraphs are separated, spacing will be added between them.
+     * Otherwise, new paragraphs will simply start on a new line with no
+     * spacing.
+     *
+     * @param value
+     *            <samp>true </samp> if paragraphs are separated, </samp> false
+     *            </samp> otherwise.
+     */
+    public void setParagraphsSeparated(bool value) {
+        paragraphsSeparated = value;
+    }
+
+    /**
+     * Tests if there is some inter-paragraph spacing.
+     *
+     * @return <samp>true </samp> if paragraphs are separated, <samp>false
+     *         </samp> otherwise.
+     */
+    public bool getParagraphsSeparated() {
+        return paragraphsSeparated;
+    }
+
+    /**
+     * Registers the image referenced by the provided key.
+     * <p>
+     * For <samp>img </samp> tags, an object of a type <samp>Image </samp> must
+     * be registered using the key equivalent to the value of the <samp>href
+     * </samp> attribute used in the tag.
+     *
+     * @param key
+     *            unique key that matches the value of the <samp>href </samp>
+     *            attribute.
+     * @param image
+     *            an object of a type <samp>Image </samp>.
+     */
+    public void setImage(String key, Image image) {
+        resourceTable.add("i." ~ key, image); //$NON-NLS-1$
+    }
+
+    /**
+     * Registers the color referenced by the provided key.
+     * <p>
+     * For <samp>span </samp> tags, an object of a type <samp>Color </samp> must
+     * be registered using the key equivalent to the value of the <samp>color
+     * </samp> attribute.
+     *
+     * @param key
+     *            unique key that matches the value of the <samp>color </samp>
+     *            attribute.
+     * @param color
+     *            an object of the type <samp>Color </samp> or <samp>null</samp>
+     *            if the key needs to be cleared.
+     */
+    public void setColor(String key, Color color) {
+        String fullKey = "c." ~ key; //$NON-NLS-1$
+        if (color is null)
+            resourceTable.removeKey(fullKey);
+        else
+            resourceTable.add(fullKey, color);
+    }
+
+    /**
+     * Registers the font referenced by the provided key.
+     * <p>
+     * For <samp>span </samp> tags, an object of a type <samp>Font </samp> must
+     * be registered using the key equivalent to the value of the <samp>font
+     * </samp> attribute.
+     *
+     * @param key
+     *            unique key that matches the value of the <samp>font </samp>
+     *            attribute.
+     * @param font
+     *            an object of the type <samp>Font </samp> or <samp>null</samp>
+     *            if the key needs to be cleared.
+     */
+    public void setFont(String key, Font font) {
+        String fullKey = "f." ~ key; //$NON-NLS-1$
+        if (font is null)
+            resourceTable.removeKey(fullKey);
+        else
+            resourceTable.add(fullKey, font);
+        model.clearCache(fullKey);
+    }
+
+    /**
+     * Registers the control referenced by the provided key.
+     * <p>
+     * For <samp>control</samp> tags, an object of a type <samp>Control</samp>
+     * must be registered using the key equivalent to the value of the
+     * <samp>control</samp> attribute.
+     *
+     * @param key
+     *            unique key that matches the value of the <samp>control</samp>
+     *            attribute.
+     * @param control
+     *            an object of the type <samp>Control</samp> or <samp>null</samp>
+     *            if the existing control at the specified key needs to be
+     *            removed.
+     * @since 3.1
+     */
+    public void setControl(String key, Control control) {
+        String fullKey = "o." ~ key; //$NON-NLS-1$
+        if (control is null)
+            resourceTable.removeKey(fullKey);
+        else
+            resourceTable.add(fullKey, control);
+    }
+
+    /**
+     * Sets the font to use to render the default text (text that does not have
+     * special font property assigned). Bold font will be constructed from this
+     * font.
+     *
+     * @param font
+     *            the default font to use
+     */
+    public void setFont(Font font) {
+        super.setFont(font);
+        model.clearCache(null);
+        Font boldFont = cast(Font) resourceTable.get(FormTextModel.BOLD_FONT_ID);
+        if (boldFont !is null) {
+            FormFonts.getInstance().markFinished(boldFont);
+            resourceTable.removeKey(FormTextModel.BOLD_FONT_ID);
+        }
+        ensureBoldFontPresent(getFont());
+    }
+
+    /**
+     * Sets the provided text. Text can be rendered as-is, or by parsing the
+     * formatting tags. Optionally, sections of text starting with http:// will
+     * be converted to hyperlinks.
+     *
+     * @param text
+     *            the text to render
+     * @param parseTags
+     *            if <samp>true </samp>, formatting tags will be parsed.
+     *            Otherwise, text will be rendered as-is.
+     * @param expandURLs
+     *            if <samp>true </samp>, URLs found in the untagged text will be
+     *            converted into hyperlinks.
+     */
+    public void setText(String text, bool parseTags, bool expandURLs) {
+        disposeResourceTable(false);
+        entered = null;
+        if (parseTags)
+            model.parseTaggedText(text, expandURLs);
+        else
+            model.parseRegularText(text, expandURLs);
+        hookControlSegmentFocus();
+        layout();
+        redraw();
+    }
+
+    /**
+     * Sets the contents of the stream. Optionally, URLs in untagged text can be
+     * converted into hyperlinks. The caller is responsible for closing the
+     * stream.
+     *
+     * @param is
+     *            stream to render
+     * @param expandURLs
+     *            if <samp>true </samp>, URLs found in untagged text will be
+     *            converted into hyperlinks.
+     */
+    public void setContents(InputStream is_, bool expandURLs) {
+        entered = null;
+        disposeResourceTable(false);
+        model.parseInputStream(is_, expandURLs);
+        hookControlSegmentFocus();
+        layout();
+        redraw();
+    }
+
+    private void hookControlSegmentFocus() {
+        Paragraph[] paragraphs = model.getParagraphs();
+        if (paragraphs is null)
+            return;
+        Listener listener = new class Listener {
+            public void handleEvent(Event e) {
+                switch (e.type) {
+                case DWT.FocusIn:
+                    if (!controlFocusTransfer)
+                        syncControlSegmentFocus(cast(Control) e.widget);
+                    break;
+                case DWT.Traverse:
+                    if (DEBUG_FOCUS)
+                        Stdout.formatln("Control traversal: {}", e); //$NON-NLS-1$
+                    switch (e.detail) {
+                    case DWT.TRAVERSE_PAGE_NEXT:
+                    case DWT.TRAVERSE_PAGE_PREVIOUS:
+                    case DWT.TRAVERSE_ARROW_NEXT:
+                    case DWT.TRAVERSE_ARROW_PREVIOUS:
+                        e.doit = false;
+                        return;
+                    }
+                    Control c = cast(Control) e.widget;
+                    ControlSegment segment = cast(ControlSegment) c
+                            .getData(CONTROL_KEY);
+                    if (e.detail is DWT.TRAVERSE_TAB_NEXT)
+                        e.doit = advanceControl(c, segment, true);
+                    else if (e.detail is DWT.TRAVERSE_TAB_PREVIOUS)
+                        e.doit = advanceControl(c, segment, false);
+                    if (!e.doit)
+                        e.detail = DWT.TRAVERSE_NONE;
+                    break;
+                }
+            }
+        };
+        for (int i = 0; i < paragraphs.length; i++) {
+            Paragraph p = paragraphs[i];
+            ParagraphSegment[] segments = p.getSegments();
+            for (int j = 0; j < segments.length; j++) {
+                if (auto cs = cast(ControlSegment)segments[j] ) {
+                    Control c = cs.getControl(resourceTable);
+                    if (c !is null) {
+                        if (c.getData(CONTROL_KEY) is null) {
+                            // first time - hook
+                            c.setData(CONTROL_KEY, cs);
+                            attachTraverseListener(c, listener);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void attachTraverseListener(Control c, Listener listener) {
+        if ( auto parent = cast(Composite) c ) {
+            Control[] children = parent.getChildren();
+            for (int i = 0; i < children.length; i++) {
+                attachTraverseListener(children[i], listener);
+            }
+            if (auto canv = cast(Canvas)c ) {
+                // If Canvas, the control iteself can accept
+                // traverse events and should be monitored
+                c.addListener(DWT.Traverse, listener);
+                c.addListener(DWT.FocusIn, listener);
+            }
+        } else {
+            c.addListener(DWT.Traverse, listener);
+            c.addListener(DWT.FocusIn, listener);
+        }
+    }
+
+    /**
+     * If we click on the control randomly, our internal book-keeping will be
+     * off. We need to update the model and mark the control segment and
+     * currently selected. Hyperlink that may have had focus must also be
+     * exited.
+     *
+     * @param control
+     *            the control that got focus
+     */
+    private void syncControlSegmentFocus(Control control) {
+        ControlSegment cs = null;
+
+        while (control !is null) {
+            cs = cast(ControlSegment) control.getData(CONTROL_KEY);
+            if (cs !is null)
+                break;
+            control = control.getParent();
+        }
+        if (cs is null)
+            return;
+        IFocusSelectable current = model.getSelectedSegment();
+        // If the model and the control match, all is well
+        if (current is cs)
+            return;
+        IHyperlinkSegment oldLink = null;
+        if (current !is null && null !is cast(IHyperlinkSegment)current ) {
+            oldLink = cast(IHyperlinkSegment) current;
+            exitLink(oldLink, DWT.NULL);
+        }
+        if (DEBUG_FOCUS)
+            Stdout.formatln("Sync control: {}, oldLink={}", cs, oldLink); //$NON-NLS-1$ //$NON-NLS-2$
+        model.select(cs);
+        if (oldLink !is null)
+            paintFocusTransfer(oldLink, null);
+        // getAccessible().setFocus(model.getSelectedSegmentIndex());
+    }
+
+    private bool advanceControl(Control c, ControlSegment segment,
+            bool next) {
+        Composite parent = c.getParent();
+        if (parent is this) {
+            // segment-level control
+            IFocusSelectable nextSegment = model.getNextFocusSegment(next);
+            if (nextSegment !is null) {
+                controlFocusTransfer = true;
+                super.forceFocus();
+                controlFocusTransfer = false;
+                model.select(segment);
+                return advance(next);
+            }
+            // nowhere to go
+            return setFocusToNextSibling(this, next);
+        }
+        if (setFocusToNextSibling(c, next))
+            return true;
+        // still here - must go one level up
+        segment = cast(ControlSegment) parent.getData(CONTROL_KEY);
+        return advanceControl(parent, segment, next);
+    }
+
+    private bool setFocusToNextSibling(Control c, bool next) {
+        Composite parent = c.getParent();
+        Control[] children = parent.getTabList();
+        for (int i = 0; i < children.length; i++) {
+            Control child = children[i];
+            if (child is c) {
+                // here
+                if (next) {
+                    for (int j = i + 1; j < children.length; j++) {
+                        Control nc = children[j];
+                        if (nc.setFocus())
+                            return false;
+                    }
+                } else {
+                    for (int j = i - 1; j >= 0; j--) {
+                        Control pc = children[j];
+                        if (pc.setFocus())
+                            return false;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Controls whether whitespace inside paragraph and list items is
+     * normalized. Note that the new value will not affect the current text in
+     * the control, only subsequent calls to <code>setText</code> or
+     * <code>setContents</code>.
+     * <p>
+     * If normalized:
+     * <ul>
+     * <li>all white space characters will be condensed into at most one when
+     * between words.</li>
+     * <li>new line characters will be ignored and replaced with one white
+     * space character</li>
+     * <li>white space characters after the opening tags and before the closing
+     * tags will be trimmed</li>
+     *
+     * @param value
+     *            <code>true</code> if whitespace is normalized,
+     *            <code>false</code> otherwise.
+     */
+    public void setWhitespaceNormalized(bool value) {
+        model.setWhitespaceNormalized(value);
+    }
+
+    /**
+     * Tests whether whitespace inside paragraph and list item is normalized.
+     *
+     * @see #setWhitespaceNormalized(bool)
+     * @return <code>true</code> if whitespace is normalized,
+     *         <code>false</code> otherwise.
+     */
+    public bool isWhitespaceNormalized() {
+        return model.isWhitespaceNormalized();
+    }
+
+    /**
+     * Disposes the internal menu if created and sets the menu provided as a
+     * parameter.
+     *
+     * @param menu
+     *            the menu to associate with this text control
+     */
+    public void setMenu(Menu menu) {
+        Menu currentMenu = super.getMenu();
+        if (currentMenu !is null && INTERNAL_MENU.equals(stringcast(currentMenu.getData()))) {
+            // internal menu set
+            if (menu !is null) {
+                currentMenu.dispose();
+                super.setMenu(menu);
+            }
+        } else
+            super.setMenu(menu);
+    }
+
+    private void createMenu() {
+        Menu menu = new Menu(this);
+        final MenuItem copyItem = new MenuItem(menu, DWT.PUSH);
+        copyItem.setText(Messages.FormText_copy);
+
+        SelectionListener listener = new class SelectionAdapter {
+            public void widgetSelected(SelectionEvent e) {
+                if (e.widget is copyItem) {
+                    copy();
+                }
+            }
+        };
+        copyItem.addSelectionListener(listener);
+        menu.addMenuListener(new class MenuListener {
+            public void menuShown(MenuEvent e) {
+                copyItem.setEnabled(canCopy());
+            }
+
+            public void menuHidden(MenuEvent e) {
+            }
+        });
+        menu.setData(stringcast(INTERNAL_MENU));
+        super.setMenu(menu);
+    }
+
+    /**
+     * Returns the hyperlink settings that are in effect for this control.
+     *
+     * @return current hyperlinks settings
+     */
+    public HyperlinkSettings getHyperlinkSettings() {
+        return model.getHyperlinkSettings();
+    }
+
+    /**
+     * Sets the hyperlink settings to be used for this control. Settings will
+     * affect things like hyperlink color, rendering style, cursor etc.
+     *
+     * @param settings
+     *            hyperlink settings for this control
+     */
+    public void setHyperlinkSettings(HyperlinkSettings settings) {
+        model.setHyperlinkSettings(settings);
+    }
+
+    /**
+     * Adds a listener that will handle hyperlink events.
+     *
+     * @param listener
+     *            the listener to add
+     */
+    public void addHyperlinkListener(IHyperlinkListener listener) {
+        if (listeners is null)
+            listeners = new ListenerList();
+        listeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Removes the hyperlink listener.
+     *
+     * @param listener
+     *            the listener to remove
+     */
+    public void removeHyperlinkListener(IHyperlinkListener listener) {
+        if (listeners is null)
+            return;
+        listeners.remove(cast(Object)listener);
+    }
+
+    /**
+     * Adds a selection listener. A Selection event is sent by the widget when
+     * the selection has changed.
+     * <p>
+     * <code>widgetDefaultSelected</code> is not called for FormText.
+     * </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>
+     * @since 3.1
+     */
+    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);
+    }
+
+    /**
+     * Removes the specified selection listener.
+     * <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>
+     * @since 3.1
+     */
+    public void removeSelectionListener(SelectionListener listener) {
+        checkWidget();
+        if (listener is null) {
+            DWT.error(DWT.ERROR_NULL_ARGUMENT);
+        }
+        removeListener(DWT.Selection, listener);
+    }
+
+    /**
+     * Returns the selected text.
+     * <p>
+     *
+     * @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>
+     * @since 3.1
+     */
+
+    public String getSelectionText() {
+        checkWidget();
+        if (selData !is null)
+            return selData.getSelectionText();
+        return ""; //$NON-NLS-1$
+    }
+
+    /**
+     * Tests if the text is selected and can be copied into the clipboard.
+     *
+     * @return <code>true</code> if the selected text can be copied into the
+     *         clipboard, <code>false</code> otherwise.
+     * @since 3.1
+     */
+    public bool canCopy() {
+        return selData !is null && selData.canCopy();
+    }
+
+    /**
+     * Copies the selected text into the clipboard. Does nothing if no text is
+     * selected or the text cannot be copied for any other reason.
+     *
+     * @since 3.1
+     */
+
+    public void copy() {
+        if (!canCopy())
+            return;
+        Clipboard clipboard = new Clipboard(getDisplay());
+        Object[] o = [ stringcast(getSelectionText()) ];
+        Transfer[] t = [ TextTransfer.getInstance() ];
+        clipboard.setContents(o, t);
+        clipboard.dispose();
+    }
+
+    /**
+     * Returns the reference of the hyperlink that currently has keyboard focus,
+     * or <code>null</code> if there are no hyperlinks in the receiver or no
+     * hyperlink has focus at the moment.
+     *
+     * @return href of the selected hyperlink or <code>null</code> if none
+     *         selected.
+     * @since 3.1
+     */
+    public Object getSelectedLinkHref() {
+        IHyperlinkSegment link = getSelectedLink();
+        return link !is null ? stringcast(link.getHref()) : null;
+    }
+
+    /**
+     * Returns the text of the hyperlink that currently has keyboard focus, or
+     * <code>null</code> if there are no hyperlinks in the receiver or no
+     * hyperlink has focus at the moment.
+     *
+     * @return text of the selected hyperlink or <code>null</code> if none
+     *         selected.
+     * @since 3.1
+     */
+    public String getSelectedLinkText() {
+        IHyperlinkSegment link = getSelectedLink();
+        return link !is null ? link.getText() : null;
+    }
+
+    private IHyperlinkSegment getSelectedLink() {
+        IFocusSelectable segment = model.getSelectedSegment();
+        if (segment !is null && null !is cast(IHyperlinkSegment)segment )
+            return cast(IHyperlinkSegment) segment;
+        return null;
+    }
+
+    private void initAccessible() {
+        Accessible accessible = getAccessible();
+        accessible.addAccessibleListener(new class AccessibleAdapter {
+            public void getName(AccessibleEvent e) {
+                if (e.childID is ACC.CHILDID_SELF)
+                    e.result = model.getAccessibleText();
+                else {
+                    int linkCount = model.getHyperlinkCount();
+                    if (e.childID >= 0 && e.childID < linkCount) {
+                        IHyperlinkSegment link = model.getHyperlink(e.childID);
+                        e.result = link.getText();
+                    }
+                }
+            }
+
+            public void getHelp(AccessibleEvent e) {
+                e.result = getToolTipText();
+                int linkCount = model.getHyperlinkCount();
+                if (e.result is null && e.childID >= 0 && e.childID < linkCount) {
+                    IHyperlinkSegment link = model.getHyperlink(e.childID);
+                    e.result = link.getText();
+                }
+            }
+        });
+        accessible.addAccessibleControlListener(new class AccessibleControlAdapter {
+            public void getChildAtPoint(AccessibleControlEvent e) {
+                Point pt = toControl(new Point(e.x, e.y));
+                IHyperlinkSegment link = model.findHyperlinkAt(pt.x, pt.y);
+                if (link !is null)
+                    e.childID = model.indexOf(link);
+                else
+                    e.childID = ACC.CHILDID_SELF;
+            }
+
+            public void getLocation(AccessibleControlEvent e) {
+                Rectangle location = null;
+                if (e.childID !is ACC.CHILDID_SELF
+                        && e.childID !is ACC.CHILDID_NONE) {
+                    int index = e.childID;
+                    IHyperlinkSegment link = model.getHyperlink(index);
+                    if (link !is null) {
+                        location = link.getBounds();
+                    }
+                }
+                if (location is null) {
+                    location = getBounds();
+                }
+                Point pt = toDisplay(new Point(location.x, location.y));
+                e.x = pt.x;
+                e.y = pt.y;
+                e.width = location.width;
+                e.height = location.height;
+            }
+
+            public void getFocus(AccessibleControlEvent e) {
+                int childID = ACC.CHILDID_NONE;
+
+                if (model.hasFocusSegments()) {
+                    int selectedIndex = model.getSelectedSegmentIndex();
+                    if (selectedIndex !is -1) {
+                        childID = selectedIndex;
+                    }
+                }
+                e.childID = childID;
+            }
+
+            public void getDefaultAction (AccessibleControlEvent e) {
+                if (model.getHyperlinkCount() > 0) {
+                    e.result = DWT.getMessage ("SWT_Press"); //$NON-NLS-1$
+                }
+            }
+
+            public void getChildCount(AccessibleControlEvent e) {
+                e.detail = model.getHyperlinkCount();
+            }
+
+            public void getRole(AccessibleControlEvent e) {
+                int role = 0;
+                int childID = e.childID;
+                int linkCount = model.getHyperlinkCount();
+                if (childID is ACC.CHILDID_SELF) {
+                    if (linkCount > 0) {
+                        role = ACC.ROLE_LINK;
+                    } else {
+                        role = ACC.ROLE_TEXT;
+                    }
+                } else if (childID >= 0 && childID < linkCount) {
+                    role = ACC.ROLE_LINK;
+                }
+                e.detail = role;
+            }
+
+            public void getSelection(AccessibleControlEvent e) {
+                int selectedIndex = model.getSelectedSegmentIndex();
+                e.childID = (selectedIndex is -1) ? ACC.CHILDID_NONE
+                        : selectedIndex;
+            }
+
+            public void getState(AccessibleControlEvent e) {
+                int linkCount = model.getHyperlinkCount();
+                int selectedIndex = model.getSelectedSegmentIndex();
+                int state = 0;
+                int childID = e.childID;
+                if (childID is ACC.CHILDID_SELF) {
+                    state = ACC.STATE_NORMAL;
+                } else if (childID >= 0 && childID < linkCount) {
+                    state = ACC.STATE_SELECTABLE;
+                    if (isFocusControl()) {
+                        state |= ACC.STATE_FOCUSABLE;
+                    }
+                    if (selectedIndex is childID) {
+                        state |= ACC.STATE_SELECTED;
+                        if (isFocusControl()) {
+                            state |= ACC.STATE_FOCUSED;
+                        }
+                    }
+                }
+                state |= ACC.STATE_READONLY;
+                e.detail = state;
+            }
+
+            public void getChildren(AccessibleControlEvent e) {
+                int linkCount = model.getHyperlinkCount();
+                Object[] children = new Object[linkCount];
+                for (int i = 0; i < linkCount; i++) {
+                    children[i] = new Integer(i);
+                }
+                e.children = children;
+            }
+
+            public void getValue(AccessibleControlEvent e) {
+                // e.result = model.getAccessibleText();
+            }
+        });
+    }
+
+    private void startSelection(MouseEvent e) {
+        inSelection = true;
+        selData = new SelectionData(e);
+        redraw();
+        Form form = FormUtil.getForm(this);
+        if (form !is null)
+            form.setSelectionText(this);
+    }
+
+    private void endSelection(MouseEvent e) {
+        inSelection = false;
+        if (selData !is null) {
+            if (!selData.isEnclosed())
+                selData = null;
+            else
+                computeSelection();
+        }
+        notifySelectionChanged();
+    }
+
+    private void computeSelection() {
+        GC gc = new GC(this);
+        Paragraph[] paragraphs = model.getParagraphs();
+        IHyperlinkSegment selectedLink = getSelectedLink();
+        if (getDisplay().getFocusControl() !is this)
+            selectedLink = null;
+        for (int i = 0; i < paragraphs.length; i++) {
+            Paragraph p = paragraphs[i];
+            if (i > 0)
+                selData.markNewLine();
+            p.computeSelection(gc, resourceTable, selectedLink, selData);
+        }
+        gc.dispose();
+    }
+
+    void clearSelection() {
+        selData = null;
+        if (!isDisposed()) {
+            redraw();
+            notifySelectionChanged();
+        }
+    }
+
+    private void notifySelectionChanged() {
+        Event event = new Event();
+        event.widget = this;
+        event.display = this.getDisplay();
+        event.type = DWT.Selection;
+        notifyListeners(DWT.Selection, event);
+        getAccessible().selectionChanged();
+    }
+
+    private void handleDrag(MouseEvent e) {
+        if (selData !is null) {
+            ScrolledComposite scomp = FormUtil.getScrolledComposite(this);
+            if (scomp !is null) {
+                FormUtil.ensureVisible(scomp, this, e);
+            }
+            selData.update(e);
+            redraw();
+        }
+    }
+
+    private void handleMouseClick(MouseEvent e, bool down) {
+        if (DEBUG_FOCUS)
+            Stdout.formatln("FormText: mouse click({})", down ); //$NON-NLS-1$ //$NON-NLS-2$
+        if (down) {
+            // select a hyperlink
+            mouseFocus = true;
+            IHyperlinkSegment segmentUnder = model.findHyperlinkAt(e.x, e.y);
+            if (segmentUnder !is null) {
+                IHyperlinkSegment oldLink = getSelectedLink();
+                if (getDisplay().getFocusControl() !is this) {
+                    setFocus();
+                }
+                model.selectLink(segmentUnder);
+                enterLink(segmentUnder, e.stateMask);
+                paintFocusTransfer(oldLink, segmentUnder);
+            }
+            if (e.button is 1) {
+                startSelection(e);
+                armed = segmentUnder;
+            }
+            else {
+            }
+        } else {
+            if (e.button is 1) {
+                endSelection(e);
+                IHyperlinkSegment segmentUnder = model
+                        .findHyperlinkAt(e.x, e.y);
+                if (segmentUnder !is null && armed is segmentUnder && selData is null) {
+                    activateLink(segmentUnder, e.stateMask);
+                    armed = null;
+                }
+            }
+            mouseFocus = false;
+        }
+    }
+
+    private void handleMouseHover(MouseEvent e) {
+    }
+
+    private void updateTooltipText(ParagraphSegment segment) {
+        String tooltipText = null;
+        if (segment !is null) {
+            tooltipText = segment.getTooltipText();
+        }
+        String currentTooltipText = getToolTipText();
+
+        if ((currentTooltipText !is null && tooltipText is null)
+                || (currentTooltipText is null && tooltipText !is null))
+            setToolTipText(tooltipText);
+    }
+
+    private void handleMouseMove(MouseEvent e) {
+        if (inSelection) {
+            handleDrag(e);
+            return;
+        }
+        ParagraphSegment segmentUnder = model.findSegmentAt(e.x, e.y);
+        updateTooltipText(segmentUnder);
+        if (segmentUnder is null) {
+            if (entered !is null) {
+                exitLink(entered, e.stateMask);
+                paintLinkHover(entered, false);
+                entered = null;
+            }
+            setCursor(null);
+        } else {
+            if (auto linkUnder = cast(IHyperlinkSegment) segmentUnder ) {
+                if (entered !is null && linkUnder !is entered) {
+                    // Special case: links are so close that there are 0 pixels between.
+                    // Must exit the link before entering the next one.
+                    exitLink(entered, e.stateMask);
+                    paintLinkHover(entered, false);
+                    entered = null;
+                }
+                if (entered is null) {
+                    entered = linkUnder;
+                    enterLink(linkUnder, e.stateMask);
+                    paintLinkHover(entered, true);
+                    setCursor(model.getHyperlinkSettings().getHyperlinkCursor());
+                }
+            } else {
+                if (entered !is null) {
+                    exitLink(entered, e.stateMask);
+                    paintLinkHover(entered, false);
+                    entered = null;
+                }
+                if (null !is cast(TextSegment)segmentUnder )
+                    setCursor(model.getHyperlinkSettings().getTextCursor());
+                else
+                    setCursor(null);
+            }
+        }
+    }
+
+    private bool advance(bool next) {
+        if (DEBUG_FOCUS)
+            Stdout.formatln("Advance: next={}", next); //$NON-NLS-1$
+        IFocusSelectable current = model.getSelectedSegment();
+        if (current !is null && null !is cast(IHyperlinkSegment)current )
+            exitLink(cast(IHyperlinkSegment) current, DWT.NULL);
+        IFocusSelectable newSegment = null;
+        bool valid = false;
+        // get the next segment that can accept focus. Links
+        // can always accept focus but controls may not
+        while (!valid) {
+            if (!model.traverseFocusSelectableObjects(next))
+                break;
+            newSegment = model.getSelectedSegment();
+            if (newSegment is null)
+                break;
+            valid = setControlFocus(next, newSegment);
+        }
+        IHyperlinkSegment newLink = null !is cast(IHyperlinkSegment)newSegment ? cast(IHyperlinkSegment) newSegment
+                : null;
+        if (valid)
+            enterLink(newLink, DWT.NULL);
+        IHyperlinkSegment oldLink = null !is cast(IHyperlinkSegment)current ? cast(IHyperlinkSegment) current
+                : null;
+        if (oldLink !is null || newLink !is null)
+            paintFocusTransfer(oldLink, newLink);
+        if (newLink !is null)
+            ensureVisible(newLink);
+        if (newLink !is null)
+            getAccessible().setFocus(model.getSelectedSegmentIndex());
+        return !valid;
+    }
+
+    private bool setControlFocus(bool next, IFocusSelectable selectable) {
+        controlFocusTransfer = true;
+        bool result = selectable.setFocus(resourceTable, next);
+        controlFocusTransfer = false;
+        return result;
+    }
+
+    private void handleFocusChange() {
+        if (DEBUG_FOCUS) {
+            Stdout.formatln("Handle focus change: hasFocus={}, mouseFocus={}", hasFocus, //$NON-NLS-1$
+                    mouseFocus); //$NON-NLS-1$
+        }
+        if (hasFocus) {
+            bool advance = true;
+            if (!mouseFocus) {
+                // if (model.restoreSavedLink() is false)
+                bool valid = false;
+                IFocusSelectable selectable = null;
+                while (!valid) {
+                    if (!model.traverseFocusSelectableObjects(advance))
+                        break;
+                    selectable = model.getSelectedSegment();
+                    if (selectable is null)
+                        break;
+                    valid = setControlFocus(advance, selectable);
+                }
+                if (selectable is null)
+                    setFocusToNextSibling(this, true);
+                else
+                    ensureVisible(selectable);
+                if ( auto hls = cast(IHyperlinkSegment)selectable ) {
+                    enterLink(hls, DWT.NULL);
+                    paintFocusTransfer(null, hls);
+                }
+            }
+        } else {
+            paintFocusTransfer(getSelectedLink(), null);
+            model.selectLink(null);
+        }
+    }
+
+    private void enterLink(IHyperlinkSegment link, int stateMask) {
+        if (link is null || listeners is null)
+            return;
+        int size = listeners.size();
+        HyperlinkEvent he = new HyperlinkEvent(this, stringcast(link.getHref()), link
+                .getText(), stateMask);
+        Object [] listenerList = listeners.getListeners();
+        for (int i = 0; i < size; i++) {
+            IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
+            listener.linkEntered(he);
+        }
+    }
+
+    private void exitLink(IHyperlinkSegment link, int stateMask) {
+        if (link is null || listeners is null)
+            return;
+        int size = listeners.size();
+        HyperlinkEvent he = new HyperlinkEvent(this, stringcast(link.getHref()), link
+                .getText(), stateMask);
+        Object [] listenerList = listeners.getListeners();
+        for (int i = 0; i < size; i++) {
+            IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
+            listener.linkExited(he);
+        }
+    }
+
+    private void paintLinkHover(IHyperlinkSegment link, bool hover) {
+        GC gc = new GC(this);
+        HyperlinkSettings settings = getHyperlinkSettings();
+        Color newFg = hover ? settings.getActiveForeground() : settings
+                .getForeground();
+        if (newFg !is null)
+            gc.setForeground(newFg);
+        gc.setBackground(getBackground());
+        gc.setFont(getFont());
+        bool selected = (link is getSelectedLink());
+        (cast(ParagraphSegment) link).paint(gc, hover, resourceTable, selected,
+                selData, null);
+        gc.dispose();
+    }
+
+    private void activateSelectedLink() {
+        IHyperlinkSegment link = getSelectedLink();
+        if (link !is null)
+            activateLink(link, DWT.NULL);
+    }
+
+    private void activateLink(IHyperlinkSegment link, int stateMask) {
+        setCursor(model.getHyperlinkSettings().getBusyCursor());
+        if (listeners !is null) {
+            int size = listeners.size();
+            HyperlinkEvent e = new HyperlinkEvent(this, stringcast(link.getHref()), link
+                    .getText(), stateMask);
+            Object [] listenerList = listeners.getListeners();
+            for (int i = 0; i < size; i++) {
+                IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
+                listener.linkActivated(e);
+            }
+        }
+        if (!isDisposed() && model.linkExists(link)) {
+            setCursor(model.getHyperlinkSettings().getHyperlinkCursor());
+        }
+    }
+
+    private void ensureBoldFontPresent(Font regularFont) {
+        Font boldFont = cast(Font) resourceTable.get(FormTextModel.BOLD_FONT_ID);
+        if (boldFont !is null)
+            return;
+        boldFont = FormFonts.getInstance().getBoldFont(getDisplay(), regularFont);
+        resourceTable.add(FormTextModel.BOLD_FONT_ID, boldFont);
+    }
+
+    private void paint(PaintEvent e) {
+        GC gc = e.gc;
+        gc.setFont(getFont());
+        ensureBoldFontPresent(getFont());
+        gc.setForeground(getForeground());
+        gc.setBackground(getBackground());
+        repaint(gc, e.x, e.y, e.width, e.height);
+    }
+
+    private void repaint(GC gc, int x, int y, int width, int height) {
+        Image textBuffer = new Image(getDisplay(), width, height);
+        Color bg = getBackground();
+        Color fg = getForeground();
+        if (!getEnabled()) {
+            bg = getDisplay().getSystemColor(DWT.COLOR_WIDGET_BACKGROUND);
+            fg = getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW);
+        }
+        GC textGC = new GC(textBuffer, gc.getStyle());
+        textGC.setForeground(fg);
+        textGC.setBackground(bg);
+        textGC.setFont(getFont());
+        textGC.fillRectangle(0, 0, width, height);
+        Rectangle repaintRegion = new Rectangle(x, y, width, height);
+
+        Paragraph[] paragraphs = model.getParagraphs();
+        IHyperlinkSegment selectedLink = getSelectedLink();
+        if (getDisplay().getFocusControl() !is this)
+            selectedLink = null;
+        for (int i = 0; i < paragraphs.length; i++) {
+            Paragraph p = paragraphs[i];
+            p
+                    .paint(textGC, repaintRegion, resourceTable, selectedLink,
+                            selData);
+        }
+        textGC.dispose();
+        gc.drawImage(textBuffer, x, y);
+        textBuffer.dispose();
+    }
+
+    private int getParagraphSpacing(int lineHeight) {
+        return lineHeight / 2;
+    }
+
+    private void paintFocusTransfer(IHyperlinkSegment oldLink,
+            IHyperlinkSegment newLink) {
+        GC gc = new GC(this);
+        Color bg = getBackground();
+        Color fg = getForeground();
+        gc.setFont(getFont());
+        if (oldLink !is null) {
+            gc.setBackground(bg);
+            gc.setForeground(fg);
+            oldLink.paintFocus(gc, bg, fg, false, null);
+        }
+        if (newLink !is null) {
+            // ensureVisible(newLink);
+            gc.setBackground(bg);
+            gc.setForeground(fg);
+            newLink.paintFocus(gc, bg, fg, true, null);
+        }
+        gc.dispose();
+    }
+
+    private void ensureVisible(IFocusSelectable segment) {
+        if (mouseFocus) {
+            mouseFocus = false;
+            return;
+        }
+        if (segment is null)
+            return;
+        Rectangle bounds = segment.getBounds();
+        ScrolledComposite scomp = FormUtil.getScrolledComposite(this);
+        if (scomp is null)
+            return;
+        Point origin = FormUtil.getControlLocation(scomp, this);
+        origin.x += bounds.x;
+        origin.y += bounds.y;
+        FormUtil.ensureVisible(scomp, origin, new Point(bounds.width,
+                bounds.height));
+    }
+
+    /**
+     * Overrides the method by fully trusting the layout manager (computed width
+     * or height may be larger than the provider width or height hints). Callers
+     * should be prepared that the computed width is larger than the provided
+     * wHint.
+     *
+     * @see dwt.widgets.Composite#computeSize(int, int, bool)
+     */
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        checkWidget();
+        Point size;
+        FormTextLayout layout = cast(FormTextLayout) getLayout();
+        if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) {
+            size = layout.computeSize(this, wHint, hHint, changed);
+        } else {
+            size = new Point(wHint, hHint);
+        }
+        Rectangle trim = computeTrim(0, 0, size.x, size.y);
+        if (DEBUG_TEXTSIZE)
+            Stdout.formatln("FormText Computed size: {}",trim); //$NON-NLS-1$
+        return new Point(trim.width, trim.height);
+    }
+
+    private void disposeResourceTable(bool disposeBoldFont) {
+        if (disposeBoldFont) {
+            Font boldFont = cast(Font) resourceTable
+                    .get(FormTextModel.BOLD_FONT_ID);
+            if (boldFont !is null) {
+                FormFonts.getInstance().markFinished(boldFont);
+                resourceTable.removeKey(FormTextModel.BOLD_FONT_ID);
+            }
+        }
+        ArraySeq!(String) imagesToRemove = new ArraySeq!(String);
+        foreach( key, obj; resourceTable ){
+            if (key.startsWith(ImageSegment.SEL_IMAGE_PREFIX)) {
+                if (auto image = cast(Image)obj ) {
+                    if (!image.isDisposed()) {
+                        image.dispose();
+                        imagesToRemove.append(key);
+                    }
+                }
+            }
+        }
+        for (int i = 0; i < imagesToRemove.size(); i++) {
+            resourceTable.removeKey(imagesToRemove.get(i));
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwt.widgets.Control#setEnabled(bool)
+     */
+    public void setEnabled(bool enabled) {
+        super.setEnabled(enabled);
+        redraw();
+    }
+
+    /* (non-Javadoc)
+     * @see dwt.widgets.Control#setFocus()
+     */
+    public bool setFocus() {
+        FormUtil.setFocusScrollingEnabled(this, false);
+        bool result = super.setFocus();
+        FormUtil.setFocusScrollingEnabled(this, true);
+        return result;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/FormToolkit.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,916 @@
+/*******************************************************************************
+ * 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
+ *     Michael Williamson (eclipse-bugs@magnaworks.com) - patch (see Bugzilla #92545)
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.forms.widgets.FormToolkit;
+
+import dwtx.ui.forms.widgets.Hyperlink;
+import dwtx.ui.forms.widgets.ImageHyperlink;
+import dwtx.ui.forms.widgets.ExpandableComposite;
+import dwtx.ui.forms.widgets.Form;
+import dwtx.ui.forms.widgets.ScrolledPageBook;
+import dwtx.ui.forms.widgets.ScrolledForm;
+import dwtx.ui.forms.widgets.FormText;
+import dwtx.ui.forms.widgets.Section;
+import dwtx.ui.forms.widgets.LayoutComposite;
+
+import dwt.DWT;
+import dwt.custom.CCombo;
+import dwt.custom.ScrolledComposite;
+import dwt.events.FocusAdapter;
+import dwt.events.FocusEvent;
+import dwt.events.KeyAdapter;
+import dwt.events.KeyEvent;
+import dwt.events.MouseAdapter;
+import dwt.events.MouseEvent;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.RGB;
+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.Listener;
+import dwt.widgets.Table;
+import dwt.widgets.Text;
+import dwt.widgets.Tree;
+import dwt.widgets.Widget;
+import dwtx.jface.resource.JFaceResources;
+import dwtx.jface.window.Window;
+import dwtx.ui.forms.FormColors;
+import dwtx.ui.forms.HyperlinkGroup;
+import dwtx.ui.forms.IFormColors;
+import dwtx.ui.internal.forms.widgets.FormFonts;
+import dwtx.ui.internal.forms.widgets.FormUtil;
+
+import dwt.dwthelper.utils;
+
+/**
+ * The toolkit is responsible for creating DWT controls adapted to work in
+ * Eclipse forms. In addition to changing their presentation properties (fonts,
+ * colors etc.), various listeners are attached to make them behave correctly in
+ * the form context.
+ * <p>
+ * In addition to being the control factory, the toolkit is also responsible for
+ * painting flat borders for select controls, managing hyperlink groups and
+ * control colors.
+ * <p>
+ * The toolkit creates some of the most common controls used to populate Eclipse
+ * forms. Controls that must be created using their constructors,
+ * <code>adapt()</code> method is available to change its properties in the
+ * same way as with the supported toolkit controls.
+ * <p>
+ * Typically, one toolkit object is created per workbench part (for example, an
+ * editor or a form wizard). The toolkit is disposed when the part is disposed.
+ * To conserve resources, it is possible to create one color object for the
+ * entire plug-in and share it between several toolkits. The plug-in is
+ * responsible for disposing the colors (disposing the toolkit that uses shared
+ * color object will not dispose the colors).
+ * <p>
+ * FormToolkit is normally instantiated, but can also be subclassed if some of
+ * the methods needs to be modified. In those cases, <code>super</code> must
+ * be called to preserve normal behaviour.
+ *
+ * @since 3.0
+ */
+public class FormToolkit {
+    public static const String KEY_DRAW_BORDER = "FormWidgetFactory.drawBorder"; //$NON-NLS-1$
+
+    public static const String TREE_BORDER = "treeBorder"; //$NON-NLS-1$
+
+    public static const String TEXT_BORDER = "textBorder"; //$NON-NLS-1$
+
+    private int borderStyle = DWT.NULL;
+
+    private FormColors colors;
+
+    private int orientation;
+
+    // private KeyListener deleteListener;
+    private BorderPainter borderPainter;
+
+    private BoldFontHolder boldFontHolder;
+
+    private HyperlinkGroup hyperlinkGroup;
+
+    /* default */
+    VisibilityHandler visibilityHandler;
+
+    /* default */
+    KeyboardHandler keyboardHandler;
+
+    private class BorderPainter : PaintListener {
+        public void paintControl(PaintEvent event) {
+            Composite composite = cast(Composite) event.widget;
+            Control[] children = composite.getChildren();
+            for (int i = 0; i < children.length; i++) {
+                Control c = children[i];
+                bool inactiveBorder = false;
+                bool textBorder = false;
+                if (!c.isVisible())
+                    continue;
+                /*
+                 * if (c.getEnabled() is false && !(c instanceof CCombo))
+                 * continue;
+                 */
+                if (null !is cast(Hyperlink)c )
+                    continue;
+                Object flag = c.getData(KEY_DRAW_BORDER);
+                if (flag !is null) {
+                    if (flag.opEquals(Boolean.FALSE))
+                        continue;
+                    if (flag.opEquals(stringcast(TREE_BORDER)))
+                        inactiveBorder = true;
+                    else if (flag.opEquals(stringcast(TEXT_BORDER)))
+                        textBorder = true;
+                }
+                if (getBorderStyle() is DWT.BORDER) {
+                    if (!inactiveBorder && !textBorder) {
+                        continue;
+                    }
+                    if (null !is cast(Text)c  || null !is cast(Table)c
+                            || null !is cast(Tree)c )
+                        continue;
+                }
+                if (!inactiveBorder
+                        && (null !is cast(Text)c  || null !is cast(CCombo)c  || textBorder)) {
+                    Rectangle b = c.getBounds();
+                    GC gc = event.gc;
+                    gc.setForeground(c.getBackground());
+                    gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+                            b.height + 1);
+                    // gc.setForeground(getBorderStyle() is DWT.BORDER ? colors
+                    // .getBorderColor() : colors.getForeground());
+                    gc.setForeground(colors.getBorderColor());
+                    if (null !is cast(CCombo)c )
+                        gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+                                b.height + 1);
+                    else
+                        gc.drawRectangle(b.x - 1, b.y - 2, b.width + 1,
+                                b.height + 3);
+                } else if (inactiveBorder || null !is cast(Table)c
+                        || null !is cast(Tree)c ) {
+                    Rectangle b = c.getBounds();
+                    GC gc = event.gc;
+                    gc.setForeground(colors.getBorderColor());
+                    gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+                            b.height + 1);
+                }
+            }
+        }
+    }
+
+    private static class VisibilityHandler : FocusAdapter {
+        public void focusGained(FocusEvent e) {
+            Widget w = e.widget;
+            if (null !is cast(Control)w ) {
+                FormUtil.ensureVisible(cast(Control) w);
+            }
+        }
+    }
+
+    private static class KeyboardHandler : KeyAdapter {
+        public void keyPressed(KeyEvent e) {
+            Widget w = e.widget;
+            if (null !is cast(Control)w ) {
+                if (e.doit)
+                    FormUtil.processKey(e.keyCode, cast(Control) w);
+            }
+        }
+    }
+
+    private class BoldFontHolder {
+        private Font normalFont;
+
+        private Font boldFont;
+
+        public this() {
+        }
+
+        public Font getBoldFont(Font font) {
+            createBoldFont(font);
+            return boldFont;
+        }
+
+        private void createBoldFont(Font font) {
+            if (normalFont is null || !normalFont.opEquals(font)) {
+                normalFont = font;
+                dispose();
+            }
+            if (boldFont is null) {
+                boldFont = FormFonts.getInstance().getBoldFont(colors.getDisplay(),
+                        normalFont);
+            }
+        }
+
+        public void dispose() {
+            if (boldFont !is null) {
+                FormFonts.getInstance().markFinished(boldFont);
+                boldFont = null;
+            }
+        }
+    }
+
+    /**
+     * Creates a toolkit that is self-sufficient (will manage its own colors).
+     *
+     */
+    public this(Display display) {
+        this(new FormColors(display));
+    }
+
+    /**
+     * Creates a toolkit that will use the provided (shared) colors. The toolkit
+     * will dispose the colors if and only if they are <b>not</b> marked as
+     * shared via the <code>markShared()</code> method.
+     *
+     * @param colors
+     *            the shared colors
+     */
+    public this(FormColors colors) {
+        orientation = Window.getDefaultOrientation();
+        this.colors = colors;
+        initialize();
+    }
+
+    /**
+     * Creates a button as a part of the form.
+     *
+     * @param parent
+     *            the button parent
+     * @param text
+     *            an optional text for the button (can be <code>null</code>)
+     * @param style
+     *            the button style (for example, <code>DWT.PUSH</code>)
+     * @return the button widget
+     */
+    public Button createButton(Composite parent, String text, int style) {
+        Button button = new Button(parent, style | DWT.FLAT | orientation);
+        if (text !is null)
+            button.setText(text);
+        adapt(button, true, true);
+        return button;
+    }
+
+    /**
+     * Creates the composite as a part of the form.
+     *
+     * @param parent
+     *            the composite parent
+     * @return the composite widget
+     */
+    public Composite createComposite(Composite parent) {
+        return createComposite(parent, DWT.NULL);
+    }
+
+    /**
+     * Creates the composite as part of the form using the provided style.
+     *
+     * @param parent
+     *            the composite parent
+     * @param style
+     *            the composite style
+     * @return the composite widget
+     */
+    public Composite createComposite(Composite parent, int style) {
+        Composite composite = new LayoutComposite(parent, style | orientation);
+        adapt(composite);
+        return composite;
+    }
+
+    /**
+     * Creats the composite that can server as a separator between various parts
+     * of a form. Separator height should be controlled by setting the height
+     * hint on the layout data for the composite.
+     *
+     * @param parent
+     *            the separator parent
+     * @return the separator widget
+     */
+    public Composite createCompositeSeparator(Composite parent) {
+        final Composite composite = new Composite(parent, orientation);
+        composite.addListener(DWT.Paint, dgListener( (Event e, Composite composite) {
+            if (composite.isDisposed())
+                return;
+            Rectangle bounds = composite.getBounds();
+            GC gc = e.gc;
+            gc.setForeground(colors.getColor(IFormColors.SEPARATOR));
+            if (colors.getBackground() !is null)
+                gc.setBackground(colors.getBackground());
+            gc.fillGradientRectangle(0, 0, bounds.width, bounds.height,
+                    false);
+        }, composite));
+        if (null !is cast(Section)parent )
+            (cast(Section) parent).setSeparatorControl(composite);
+        return composite;
+    }
+
+    /**
+     * Creates a label as a part of the form.
+     *
+     * @param parent
+     *            the label parent
+     * @param text
+     *            the label text
+     * @return the label widget
+     */
+    public Label createLabel(Composite parent, String text) {
+        return createLabel(parent, text, DWT.NONE);
+    }
+
+    /**
+     * Creates a label as a part of the form.
+     *
+     * @param parent
+     *            the label parent
+     * @param text
+     *            the label text
+     * @param style
+     *            the label style
+     * @return the label widget
+     */
+    public Label createLabel(Composite parent, String text, int style) {
+        Label label = new Label(parent, style | orientation);
+        if (text !is null)
+            label.setText(text);
+        adapt(label, false, false);
+        return label;
+    }
+
+    /**
+     * Creates a hyperlink as a part of the form. The hyperlink will be added to
+     * the hyperlink group that belongs to this toolkit.
+     *
+     * @param parent
+     *            the hyperlink parent
+     * @param text
+     *            the text of the hyperlink
+     * @param style
+     *            the hyperlink style
+     * @return the hyperlink widget
+     */
+    public Hyperlink createHyperlink(Composite parent, String text, int style) {
+        Hyperlink hyperlink = new Hyperlink(parent, style | orientation);
+        if (text !is null)
+            hyperlink.setText(text);
+        hyperlink.addFocusListener(visibilityHandler);
+        hyperlink.addKeyListener(keyboardHandler);
+        hyperlinkGroup.add(hyperlink);
+        return hyperlink;
+    }
+
+    /**
+     * Creates an image hyperlink as a part of the form. The hyperlink will be
+     * added to the hyperlink group that belongs to this toolkit.
+     *
+     * @param parent
+     *            the hyperlink parent
+     * @param style
+     *            the hyperlink style
+     * @return the image hyperlink widget
+     */
+    public ImageHyperlink createImageHyperlink(Composite parent, int style) {
+        ImageHyperlink hyperlink = new ImageHyperlink(parent, style
+                | orientation);
+        hyperlink.addFocusListener(visibilityHandler);
+        hyperlink.addKeyListener(keyboardHandler);
+        hyperlinkGroup.add(hyperlink);
+        return hyperlink;
+    }
+
+    /**
+     * Creates a rich text as a part of the form.
+     *
+     * @param parent
+     *            the rich text parent
+     * @param trackFocus
+     *            if <code>true</code>, the toolkit will monitor focus
+     *            transfers to ensure that the hyperlink in focus is visible in
+     *            the form.
+     * @return the rich text widget
+     */
+    public FormText createFormText(Composite parent, bool trackFocus) {
+        FormText engine = new FormText(parent, DWT.WRAP | orientation);
+        engine.marginWidth = 1;
+        engine.marginHeight = 0;
+        engine.setHyperlinkSettings(getHyperlinkGroup());
+        adapt(engine, trackFocus, true);
+        engine.setMenu(parent.getMenu());
+        return engine;
+    }
+
+    /**
+     * Adapts a control to be used in a form that is associated with this
+     * toolkit. This involves adjusting colors and optionally adding handlers to
+     * ensure focus tracking and keyboard management.
+     *
+     * @param control
+     *            a control to adapt
+     * @param trackFocus
+     *            if <code>true</code>, form will be scrolled horizontally
+     *            and/or vertically if needed to ensure that the control is
+     *            visible when it gains focus. Set it to <code>false</code> if
+     *            the control is not capable of gaining focus.
+     * @param trackKeyboard
+     *            if <code>true</code>, the control that is capable of
+     *            gaining focus will be tracked for certain keys that are
+     *            important to the underlying form (for example, PageUp,
+     *            PageDown, ScrollUp, ScrollDown etc.). Set it to
+     *            <code>false</code> if the control is not capable of gaining
+     *            focus or these particular key event are already used by the
+     *            control.
+     */
+    public void adapt(Control control, bool trackFocus, bool trackKeyboard) {
+        control.setBackground(colors.getBackground());
+        control.setForeground(colors.getForeground());
+        if (null !is cast(ExpandableComposite)control ) {
+            ExpandableComposite ec = cast(ExpandableComposite) control;
+            if (ec.toggle_package !is null) {
+                if (trackFocus)
+                    ec.toggle_package.addFocusListener(visibilityHandler);
+                if (trackKeyboard)
+                    ec.toggle_package.addKeyListener(keyboardHandler);
+            }
+            if (ec.textLabel_package !is null) {
+                if (trackFocus)
+                    ec.textLabel_package.addFocusListener(visibilityHandler);
+                if (trackKeyboard)
+                    ec.textLabel_package.addKeyListener(keyboardHandler);
+            }
+            return;
+        }
+        if (trackFocus)
+            control.addFocusListener(visibilityHandler);
+        if (trackKeyboard)
+            control.addKeyListener(keyboardHandler);
+    }
+
+    /**
+     * Adapts a composite to be used in a form associated with this toolkit.
+     *
+     * @param composite
+     *            the composite to adapt
+     */
+    public void adapt(Composite composite) {
+        composite.setBackground(colors.getBackground());
+        composite.addMouseListener(new class MouseAdapter {
+            public void mouseDown(MouseEvent e) {
+                (cast(Control) e.widget).setFocus();
+            }
+        });
+        composite.setMenu(composite.getParent().getMenu());
+    }
+
+    /**
+     * A helper method that ensures the provided control is visible when
+     * ScrolledComposite is somewhere in the parent chain. If scroll bars are
+     * visible and the control is clipped, the client of the scrolled composite
+     * will be scrolled to reveal the control.
+     *
+     * @param c
+     *            the control to reveal
+     */
+    public static void ensureVisible(Control c) {
+        FormUtil.ensureVisible(c);
+    }
+
+    /**
+     * Creates a section as a part of the form.
+     *
+     * @param parent
+     *            the section parent
+     * @param sectionStyle
+     *            the section style
+     * @return the section widget
+     */
+    public Section createSection(Composite parent, int sectionStyle) {
+        Section section = new Section(parent, orientation, sectionStyle);
+        section.setMenu(parent.getMenu());
+        adapt(section, true, true);
+        if (section.toggle_package !is null) {
+            section.toggle_package.setHoverDecorationColor(colors
+                    .getColor(IFormColors.TB_TOGGLE_HOVER));
+            section.toggle_package.setDecorationColor(colors
+                    .getColor(IFormColors.TB_TOGGLE));
+        }
+        section.setFont(boldFontHolder.getBoldFont(parent.getFont()));
+        if ((sectionStyle & Section.TITLE_BAR) !is 0
+                || (sectionStyle & Section.SHORT_TITLE_BAR) !is 0) {
+            colors.initializeSectionToolBarColors();
+            section.setTitleBarBackground(colors.getColor(IFormColors.TB_BG));
+            section.setTitleBarBorderColor(colors
+                    .getColor(IFormColors.TB_BORDER));
+            section.setTitleBarForeground(colors
+                    .getColor(IFormColors.TB_TOGGLE));
+        }
+        return section;
+    }
+
+    /**
+     * Creates an expandable composite as a part of the form.
+     *
+     * @param parent
+     *            the expandable composite parent
+     * @param expansionStyle
+     *            the expandable composite style
+     * @return the expandable composite widget
+     */
+    public ExpandableComposite createExpandableComposite(Composite parent,
+            int expansionStyle) {
+        ExpandableComposite ec = new ExpandableComposite(parent, orientation,
+                expansionStyle);
+        ec.setMenu(parent.getMenu());
+        adapt(ec, true, true);
+        ec.setFont(boldFontHolder.getBoldFont(ec.getFont()));
+        return ec;
+    }
+
+    /**
+     * Creates a separator label as a part of the form.
+     *
+     * @param parent
+     *            the separator parent
+     * @param style
+     *            the separator style
+     * @return the separator label
+     */
+    public Label createSeparator(Composite parent, int style) {
+        Label label = new Label(parent, DWT.SEPARATOR | style | orientation);
+        label.setBackground(colors.getBackground());
+        label.setForeground(colors.getBorderColor());
+        return label;
+    }
+
+    /**
+     * Creates a table as a part of the form.
+     *
+     * @param parent
+     *            the table parent
+     * @param style
+     *            the table style
+     * @return the table widget
+     */
+    public Table createTable(Composite parent, int style) {
+        Table table = new Table(parent, style | borderStyle | orientation);
+        adapt(table, false, false);
+        // hookDeleteListener(table);
+        return table;
+    }
+
+    /**
+     * Creates a text as a part of the form.
+     *
+     * @param parent
+     *            the text parent
+     * @param value
+     *            the text initial value
+     * @return the text widget
+     */
+    public Text createText(Composite parent, String value) {
+        return createText(parent, value, DWT.SINGLE);
+    }
+
+    /**
+     * Creates a text as a part of the form.
+     *
+     * @param parent
+     *            the text parent
+     * @param value
+     *            the text initial value
+     * @param style
+     *            the text style
+     * @return the text widget
+     */
+    public Text createText(Composite parent, String value, int style) {
+        Text text = new Text(parent, borderStyle | style | orientation);
+        if (value !is null)
+            text.setText(value);
+        text.setForeground(colors.getForeground());
+        text.setBackground(colors.getBackground());
+        text.addFocusListener(visibilityHandler);
+        return text;
+    }
+
+    /**
+     * Creates a tree widget as a part of the form.
+     *
+     * @param parent
+     *            the tree parent
+     * @param style
+     *            the tree style
+     * @return the tree widget
+     */
+    public Tree createTree(Composite parent, int style) {
+        Tree tree = new Tree(parent, borderStyle | style | orientation);
+        adapt(tree, false, false);
+        // hookDeleteListener(tree);
+        return tree;
+    }
+
+    /**
+     * Creates a scrolled form widget in the provided parent. If you do not
+     * require scrolling because there is already a scrolled composite up the
+     * parent chain, use 'createForm' instead.
+     *
+     * @param parent
+     *            the scrolled form parent
+     * @return the form that can scroll itself
+     * @see #createForm
+     */
+    public ScrolledForm createScrolledForm(Composite parent) {
+        ScrolledForm form = new ScrolledForm(parent, DWT.V_SCROLL
+                | DWT.H_SCROLL | orientation);
+        form.setExpandHorizontal(true);
+        form.setExpandVertical(true);
+        form.setBackground(colors.getBackground());
+        form.setForeground(colors.getColor(IFormColors.TITLE));
+        form.setFont(JFaceResources.getHeaderFont());
+        return form;
+    }
+
+    /**
+     * Creates a form widget in the provided parent. Note that this widget does
+     * not scroll its content, so make sure there is a scrolled composite up the
+     * parent chain. If you require scrolling, use 'createScrolledForm' instead.
+     *
+     * @param parent
+     *            the form parent
+     * @return the form that does not scroll
+     * @see #createScrolledForm
+     */
+    public Form createForm(Composite parent) {
+        Form formContent = new Form(parent, orientation);
+        formContent.setBackground(colors.getBackground());
+        formContent.setForeground(colors.getColor(IFormColors.TITLE));
+        formContent.setFont(JFaceResources.getHeaderFont());
+        return formContent;
+    }
+
+    /**
+     * Takes advantage of the gradients and other capabilities to decorate the
+     * form heading using colors computed based on the current skin and
+     * operating system.
+     *
+     * @since 3.3
+     * @param form
+     *            the form to decorate
+     */
+
+    public void decorateFormHeading(Form form) {
+        Color top = colors.getColor(IFormColors.H_GRADIENT_END);
+        Color bot = colors.getColor(IFormColors.H_GRADIENT_START);
+        form.setTextBackground([ top, bot ], [ 100 ],
+                true);
+        form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE1, colors
+                .getColor(IFormColors.H_BOTTOM_KEYLINE1));
+        form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, colors
+                .getColor(IFormColors.H_BOTTOM_KEYLINE2));
+        form.setHeadColor(IFormColors.H_HOVER_LIGHT, colors
+                .getColor(IFormColors.H_HOVER_LIGHT));
+        form.setHeadColor(IFormColors.H_HOVER_FULL, colors
+                .getColor(IFormColors.H_HOVER_FULL));
+        form.setHeadColor(IFormColors.TB_TOGGLE, colors
+                .getColor(IFormColors.TB_TOGGLE));
+        form.setHeadColor(IFormColors.TB_TOGGLE_HOVER, colors
+                .getColor(IFormColors.TB_TOGGLE_HOVER));
+        form.setSeparatorVisible(true);
+    }
+
+    /**
+     * Creates a scrolled page book widget as a part of the form.
+     *
+     * @param parent
+     *            the page book parent
+     * @param style
+     *            the text style
+     * @return the scrolled page book widget
+     */
+    public ScrolledPageBook createPageBook(Composite parent, int style) {
+        ScrolledPageBook book = new ScrolledPageBook(parent, style
+                | orientation);
+        adapt(book, true, true);
+        book.setMenu(parent.getMenu());
+        return book;
+    }
+
+    /**
+     * Disposes the toolkit.
+     */
+    public void dispose() {
+        if (colors.isShared() is false) {
+            colors.dispose();
+            colors = null;
+        }
+        boldFontHolder.dispose();
+    }
+
+    /**
+     * Returns the hyperlink group that manages hyperlinks for this toolkit.
+     *
+     * @return the hyperlink group
+     */
+    public HyperlinkGroup getHyperlinkGroup() {
+        return hyperlinkGroup;
+    }
+
+    /**
+     * Sets the background color for the entire toolkit. The method delegates
+     * the call to the FormColors object and also updates the hyperlink group so
+     * that hyperlinks and other objects are in sync.
+     *
+     * @param bg
+     *            the new background color
+     */
+    public void setBackground(Color bg) {
+        hyperlinkGroup.setBackground(bg);
+        colors.setBackground(bg);
+    }
+
+    /**
+     * Refreshes the hyperlink colors by loading from JFace settings.
+     */
+    public void refreshHyperlinkColors() {
+        hyperlinkGroup.initializeDefaultForegrounds(colors.getDisplay());
+    }
+
+    /**
+     * Paints flat borders for widgets created by this toolkit within the
+     * provided parent. Borders will not be painted if the global border style
+     * is DWT.BORDER (i.e. if native borders are used). Call this method during
+     * creation of a form composite to get the borders of its children painted.
+     * Care should be taken when selection layout margins. At least one pixel
+     * pargin width and height must be chosen to allow the toolkit to paint the
+     * border on the parent around the widgets.
+     * <p>
+     * Borders are painted for some controls that are selected by the toolkit by
+     * default. If a control needs a border but is not on its list, it is
+     * possible to force border in the following way:
+     *
+     * <pre>
+     *
+     *
+     *
+     *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
+     *
+     *             or
+     *
+     *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
+     *
+     *
+     *
+     * </pre>
+     *
+     * @param parent
+     *            the parent that owns the children for which the border needs
+     *            to be painted.
+     */
+    public void paintBordersFor(Composite parent) {
+        // if (borderStyle is DWT.BORDER)
+        // return;
+        if (borderPainter is null)
+            borderPainter = new BorderPainter();
+        parent.addPaintListener(borderPainter);
+    }
+
+    /**
+     * Returns the colors used by this toolkit.
+     *
+     * @return the color object
+     */
+    public FormColors getColors() {
+        return colors;
+    }
+
+    /**
+     * Returns the border style used for various widgets created by this
+     * toolkit. The intent of the toolkit is to create controls with styles that
+     * yield a 'flat' appearance. On systems where the native borders are
+     * already flat, we set the style to DWT.BORDER and don't paint the borders
+     * ourselves. Otherwise, the style is set to DWT.NULL, and borders are
+     * painted by the toolkit.
+     *
+     * @return the global border style
+     */
+    public int getBorderStyle() {
+        return borderStyle;
+    }
+
+    /**
+     * Returns the margin required around the children whose border is being
+     * painted by the toolkit using {@link #paintBordersFor(Composite)}. Since
+     * the border is painted around the controls on the parent, a number of
+     * pixels needs to be reserved for this border. For windowing systems where
+     * the native border is used, this margin is 0.
+     *
+     * @return the margin in the parent when children have their border painted
+     * @since 3.3
+     */
+    public int getBorderMargin() {
+        return getBorderStyle() is DWT.BORDER ? 0 : 2;
+    }
+
+    /**
+     * Sets the border style to be used when creating widgets. The toolkit
+     * chooses the correct style based on the platform but this value can be
+     * changed using this method.
+     *
+     * @param style
+     *            <code>DWT.BORDER</code> or <code>DWT.NULL</code>
+     * @see #getBorderStyle
+     */
+    public void setBorderStyle(int style) {
+        this.borderStyle = style;
+    }
+
+    /**
+     * A utility method that ensures that the control is visible in the scrolled
+     * composite. The prerequisite for this method is that the control has a
+     * class that extends ScrolledComposite somewhere in the parent chain. If
+     * the control is partially or fully clipped, the composite is scrolled to
+     * set by setting the origin to the control origin.
+     *
+     * @param c
+     *            the control to make visible
+     * @param verticalOnly
+     *            if <code>true</code>, the scrolled composite will be
+     *            scrolled only vertically if needed. Otherwise, the scrolled
+     *            composite origin will be set to the control origin.
+     * @since 3.1
+     */
+    public static void setControlVisible(Control c, bool verticalOnly) {
+        ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+        if (scomp is null)
+            return;
+        Point location = FormUtil.getControlLocation(scomp, c);
+        scomp.setOrigin(location);
+    }
+
+    private void initialize() {
+        initializeBorderStyle();
+        hyperlinkGroup = new HyperlinkGroup(colors.getDisplay());
+        hyperlinkGroup.setBackground(colors.getBackground());
+        visibilityHandler = new VisibilityHandler();
+        keyboardHandler = new KeyboardHandler();
+        boldFontHolder = new BoldFontHolder();
+    }
+
+    private void initializeBorderStyle() {
+        String osname = System.getProperty("os.name"); //$NON-NLS-1$
+        String osversion = System.getProperty("os.version"); //$NON-NLS-1$
+        if (osname.startsWith("Windows") && "5.1".compareTo(osversion) <= 0) { //$NON-NLS-1$ //$NON-NLS-2$
+            // Skinned widgets used on newer Windows (e.g. XP (5.1), Vista
+            // (6.0))
+            // Check for Windows Classic. If not used, set the style to BORDER
+            RGB rgb = colors.getSystemColor(DWT.COLOR_WIDGET_BACKGROUND);
+            if (rgb.red !is 212 || rgb.green !is 208 || rgb.blue !is 200)
+                borderStyle = DWT.BORDER;
+        } else if (osname.startsWith("Mac")) //$NON-NLS-1$
+            borderStyle = DWT.BORDER;
+    }
+
+    /**
+     * Returns the orientation that all the widgets created by this toolkit will
+     * inherit, if set. Can be <code>DWT.NULL</code>,
+     * <code>DWT.LEFT_TO_RIGHT</code> and <code>DWT.RIGHT_TO_LEFT</code>.
+     *
+     * @return orientation style for this toolkit, or <code>DWT.NULL</code> if
+     *         not set. The default orientation is inherited from the Window
+     *         default orientation.
+     * @see dwtx.jface.window.Window#getDefaultOrientation()
+     * @since 3.1
+     */
+
+    public int getOrientation() {
+        return orientation;
+    }
+
+    /**
+     * Sets the orientation that all the widgets created by this toolkit will
+     * inherit. Can be <code>DWT.NULL</code>, <code>DWT.LEFT_TO_RIGHT</code>
+     * and <code>DWT.RIGHT_TO_LEFT</code>.
+     *
+     * @param orientation
+     *            style for this toolkit.
+     * @since 3.1
+     */
+
+    public void setOrientation(int orientation) {
+        this.orientation = orientation;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/Hyperlink.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,291 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 dwtx.ui.forms.widgets.Hyperlink;
+
+import dwtx.ui.forms.widgets.AbstractHyperlink;
+
+import dwt.DWT;
+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.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+
+import dwtx.ui.internal.forms.widgets.FormUtil;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Hyperlink is a concrete implementation of the abstract base class that draws
+ * text in the client area. Text can be wrapped and underlined. Hyperlink is
+ * typically added to the hyperlink group so that certain properties are managed
+ * for all the hyperlinks that belong to it.
+ * <p>
+ * Hyperlink can be extended.
+ *
+ * @see dwtx.ui.forms.HyperlinkGroup
+ * @since 3.0
+ */
+public class Hyperlink : AbstractHyperlink {
+    private String text;
+    private static const String ELLIPSIS = "..."; //$NON-NLS-1$
+    private bool underlined;
+    // 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 String appToolTipText;
+
+    /**
+     * Creates a new hyperlink control in the provided parent.
+     *
+     * @param parent
+     *            the control parent
+     * @param style
+     *            the widget style
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        initAccessible();
+    }
+
+    protected void initAccessible() {
+        Accessible accessible = getAccessible();
+        accessible.addAccessibleListener(new class AccessibleAdapter {
+            public void getName(AccessibleEvent e) {
+                e.result = getText();
+                if (e.result is null)
+                    getHelp(e);
+            }
+
+            public void getHelp(AccessibleEvent e) {
+                e.result = getToolTipText();
+            }
+        });
+        accessible.addAccessibleControlListener(new class AccessibleControlAdapter {
+            public void getChildAtPoint(AccessibleControlEvent e) {
+                Point pt = toControl(new Point(e.x, e.y));
+                e.childID = (getBounds().contains(pt)) ? ACC.CHILDID_SELF
+                        : ACC.CHILDID_NONE;
+            }
+
+            public void getLocation(AccessibleControlEvent e) {
+                Rectangle location = getBounds();
+                Point pt = toDisplay(new Point(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_LINK;
+            }
+
+            public void getDefaultAction (AccessibleControlEvent e) {
+                e.result = DWT.getMessage ("SWT_Press"); //$NON-NLS-1$
+            }
+
+            public void getState(AccessibleControlEvent e) {
+                int state = ACC.STATE_NORMAL;
+                if (this.outer.getSelection())
+                    state = ACC.STATE_SELECTED | ACC.STATE_FOCUSED;
+                e.detail = state;
+            }
+        });
+    }
+
+    /**
+     * Sets the underlined state. It is not necessary to call this method when
+     * in a hyperlink group.
+     *
+     * @param underlined
+     *            if <samp>true </samp>, a line will be drawn below the text for
+     *            each wrapped line.
+     */
+    public void setUnderlined(bool underlined) {
+        this.underlined = underlined;
+        redraw();
+    }
+
+    /**
+     * Returns the underline state of the hyperlink.
+     *
+     * @return <samp>true </samp> if text is underlined, <samp>false </samp>
+     *         otherwise.
+     */
+    public bool isUnderlined() {
+        return underlined;
+    }
+
+    /**
+     * Overrides the parent by incorporating the margin.
+     */
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        checkWidget();
+        int innerWidth = wHint;
+        if (innerWidth !is DWT.DEFAULT)
+            innerWidth -= marginWidth * 2;
+        Point textSize = computeTextSize(innerWidth, hHint);
+        int textWidth = textSize.x + 2 * marginWidth;
+        int textHeight = textSize.y + 2 * marginHeight;
+        return new Point(textWidth, textHeight);
+    }
+
+    /**
+     * Returns the current hyperlink text.
+     *
+     * @return hyperlink text
+     */
+    public String getText() {
+        return text;
+    }
+
+    /* (non-Javadoc)
+     * @see dwt.widgets.Control#getToolTipText()
+     */
+    public String getToolTipText () {
+        checkWidget();
+        return appToolTipText;
+    }
+
+    /* (non-Javadoc)
+     * @see dwt.widgets.Control#setToolTipText(java.lang.String)
+     */
+    public void setToolTipText (String string) {
+        super.setToolTipText (string);
+        appToolTipText = super.getToolTipText();
+    }
+
+    /**
+     * Sets the text of this hyperlink.
+     *
+     * @param text
+     *            the hyperlink text
+     */
+    public void setText(String text) {
+        if (text !is null)
+            this.text = text;
+        else
+            this.text = ""; //$NON-NLS-1$
+        redraw();
+    }
+
+    /**
+     * Paints the hyperlink text.
+     *
+     * @param gc
+     *            graphic context
+     */
+    protected void paintHyperlink(GC gc) {
+        Rectangle carea = getClientArea();
+        Rectangle bounds = new Rectangle(marginWidth, marginHeight, carea.width
+                - marginWidth - marginWidth, carea.height - marginHeight
+                - marginHeight);
+        paintText(gc, bounds);
+    }
+
+    /**
+     * Paints the hyperlink text in provided bounding rectangle.
+     *
+     * @param gc
+     *            graphic context
+     * @param bounds
+     *            the bounding rectangle in which to paint the text
+     */
+    protected void paintText(GC gc, Rectangle bounds) {
+        gc.setFont(getFont());
+        gc.setForeground(getForeground());
+        if ((getStyle() & DWT.WRAP) !is 0) {
+            FormUtil.paintWrapText(gc, text, bounds, underlined);
+        } else {
+            Point totalSize = computeTextSize(DWT.DEFAULT, DWT.DEFAULT);
+            bool shortenText_ =false;
+            if (bounds.width<totalSize.x) {
+                // shorten
+                shortenText_=true;
+            }
+            int textWidth = Math.min(bounds.width, totalSize.x);
+            int textHeight = totalSize.y;
+            String textToDraw = getText();
+            if (shortenText_) {
+                textToDraw = shortenText(gc, getText(), bounds.width);
+                if (appToolTipText is null) {
+                    super.setToolTipText(getText());
+                }
+            }
+            else {
+                super.setToolTipText(appToolTipText);
+            }
+            gc.drawText(textToDraw, bounds.x, bounds.y, true);
+            if (underlined) {
+                int descent = gc.getFontMetrics().getDescent();
+                int lineY = bounds.y + textHeight - descent + 1;
+                gc.drawLine(bounds.x, lineY, bounds.x + textWidth, lineY);
+            }
+        }
+    }
+
+    protected String shortenText(GC gc, String t, int width) {
+        if (t is null) return null;
+        int w = gc.textExtent(ELLIPSIS).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) {
+            String s1 = t.substring(0, mid);
+            String s2 = t.substring(l-mid, l);
+            int l1 = gc.textExtent(s1).x;
+            int l2 = gc.textExtent(s2).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.substring(0, mid)~ELLIPSIS~t.substring(l-mid, l);
+    }
+
+    protected Point computeTextSize(int wHint, int hHint) {
+        Point extent;
+        GC gc = new GC(this);
+        gc.setFont(getFont());
+        if ((getStyle() & DWT.WRAP) !is 0 && wHint !is DWT.DEFAULT) {
+            extent = FormUtil.computeWrapSize(gc, getText(), wHint);
+        } else {
+            extent = gc.textExtent(getText());
+            if ((getStyle() & DWT.WRAP) is 0 && wHint !is DWT.DEFAULT)
+                extent.x = wHint;
+        }
+        gc.dispose();
+        return extent;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ILayoutExtension.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.ILayoutExtension;
+import dwt.widgets.Composite;
+/**
+ * Classes that extend abstract class Layout and implement this interface can
+ * take part in layout computation of the TableWrapLayout manager. This layout
+ * uses alternative algorithm that computes columns before rows. It allows it
+ * to 'flow' wrapped text proportionally (similar to the way web browser
+ * renders tables). Custom layout managers that implement this interface will
+ * allow TableWrapLayout to properly compute width hint to pass.
+ * 
+ * @see TableWrapLayout
+ * @see ColumnLayout
+ * @since 3.0
+ */
+public interface ILayoutExtension {
+    /**
+     * Computes the minimum width of the parent. All widgets capable of word
+     * wrapping should return the width of the longest word that cannot be
+     * broken any further.
+     * 
+     * @param parent the parent composite
+     * @param changed <code>true</code> if the cached information should be
+     * flushed, <code>false</code> otherwise.
+     * @return the minimum width of the parent composite
+     */
+    public int computeMinimumWidth(Composite parent, bool changed);
+    /**
+     * Computes the maximum width of the parent. All widgets capable of word
+     * wrapping should return the length of the entire text with wrapping
+     * turned off.
+     * 
+     * @param parent the parent composite
+     * @param changed <code>true</code> if the cached information
+     * should be flushed, <code>false</code> otherwise.
+     * @return the maximum width of the parent composite
+     */
+    public int computeMaximumWidth(Composite parent, bool changed);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ImageHyperlink.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,299 @@
+/*******************************************************************************
+ * 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
+ *     Chriss Gross (schtoo@schtoo.com) - fix for 61670
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.forms.widgets.ImageHyperlink;
+
+import dwtx.ui.forms.widgets.Hyperlink;
+
+import dwt.DWT;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Event;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This class extends hyperlink widget by adding the capability to render an
+ * image relative to the text. If no text has been set, only image will be
+ * shown. Images for hover and active states can be set in addition to the
+ * normal state image.
+ * <p>
+ * When image is taller than the text, additional style can be provided to
+ * control vertical alignment (supported values are DWT.TOP, DWT.BOTTOM and
+ * DWT.CENTER).
+ * <p>
+ * The class does not need to be sublassed but it is allowed to do so if some
+ * aspect of the image hyperlink needs to be modified.
+ *
+ * @since 3.0
+ */
+public class ImageHyperlink : Hyperlink {
+    /**
+     * Amount of pixels between the image and the text (default is 5).
+     */
+    public int textSpacing = 5;
+
+    private Image image;
+
+    private Image hoverImage;
+
+    private Image activeImage;
+
+    private int state;
+
+    private static const int HOVER = 1 << 1;
+
+    private static const int ACTIVE = 1 << 2;
+
+    private int verticalAlignment = DWT.CENTER;
+
+    private int horizontalAlignment = DWT.LEFT;
+
+    /**
+     * Creates the image hyperlink instance.
+     *
+     * @param parent
+     *            the control parent
+     * @param style
+     *            the control style (DWT.WRAP, BOTTOM, TOP, MIDDLE, LEFT, RIGHT)
+     */
+    public this(Composite parent, int style) {
+        super(parent, removeAlignment(style));
+        extractAlignment(style);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.widgets.AbstractHyperlink#paintHyperlink(dwt.events.PaintEvent)
+     */
+    protected void paintHyperlink(GC gc) {
+        paintHyperlink(gc, getClientArea());
+    }
+
+    protected void paintHyperlink(GC gc, Rectangle bounds) {
+        Image image = null;
+        if ((state & ACTIVE) !is 0)
+            image = activeImage;
+        else if ((state & HOVER) !is 0)
+            image = hoverImage;
+        if (image is null)
+            image = this.image;
+        Rectangle ibounds = image !is null ? image.getBounds() : new Rectangle(0, 0, 0, 0);
+        Point maxsize = computeMaxImageSize();
+        int spacing = image !is null?textSpacing:0;
+        int textWidth = bounds.width - maxsize.x - spacing
+                - marginWidth - marginWidth;
+        int y = bounds.y+marginHeight + maxsize.y / 2 - ibounds.height / 2;
+
+        if (horizontalAlignment is DWT.LEFT) {
+            int x = bounds.x+marginWidth + maxsize.x / 2 - ibounds.width / 2;
+            int textX = bounds.x + marginWidth + maxsize.x + spacing;
+            if (image !is null)
+                gc.drawImage(image, x, y);
+            if (getText() !is null)
+                drawText(gc, bounds, textX, textWidth);
+        } else if (horizontalAlignment is DWT.RIGHT) {
+            int x = bounds.x+marginWidth;
+            if (getText() !is null) {
+                x += drawText(gc, bounds, x, textWidth);
+            }
+            x += maxsize.x / 2 - ibounds.width / 2 + spacing;
+            if (image !is null)
+                gc.drawImage(image, x, y);
+        }
+    }
+
+    private int drawText(GC gc, Rectangle clientArea, int textX, int textWidth) {
+        Point textSize = computeTextSize(textWidth, DWT.DEFAULT);
+        int slotHeight = clientArea.height - marginHeight - marginHeight;
+        int textY;
+        textWidth = textSize.x;
+        int textHeight = textSize.y;
+        if (verticalAlignment is DWT.BOTTOM) {
+            textY = marginHeight + slotHeight - textHeight;
+        } else if (verticalAlignment is DWT.CENTER) {
+            textY = marginHeight + slotHeight / 2 - textHeight / 2;
+        } else {
+            textY = marginHeight;
+        }
+        paintText(gc, new Rectangle(textX, textY, textWidth, textHeight));
+        return textWidth;
+    }
+
+    /**
+     * Computes the control size by reserving space for images in addition to
+     * text.
+     *
+     * @param wHint
+     *            width hint
+     * @param hHint
+     *            height hint
+     * @param changed
+     *            if <code>true</code>, any cached layout data should be
+     *            computed anew
+     */
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        checkWidget();
+        Point isize = computeMaxImageSize();
+        int spacing = isize.x>0?textSpacing:0;
+        Point textSize = null;
+        if (getText() !is null) {
+            int innerWHint = wHint;
+            if (wHint !is DWT.DEFAULT) {
+                innerWHint = wHint - 2 * marginWidth - isize.x - spacing;
+            }
+            textSize = super.computeSize(innerWHint, hHint, changed);
+        }
+        int width = isize.x;
+        int height = isize.y;
+        if (textSize !is null) {
+            width += spacing;
+            width += textSize.x;
+            height = Math.max(height, textSize.y);
+        }
+        width += 2 * marginWidth;
+        height += 2 * marginHeight;
+        return new Point(width, height);
+    }
+
+    protected void handleEnter(Event e) {
+        state = HOVER;
+        super.handleEnter(e);
+    }
+
+    protected void handleExit(Event e) {
+        state = 0;
+        super.handleExit(e);
+    }
+
+    protected void handleActivate(Event e) {
+        state &= ACTIVE;
+        redraw();
+        super.handleActivate(e);
+        state &= ~ACTIVE;
+        if (!isDisposed())
+            redraw();
+    }
+
+    /**
+     * Returns active image.
+     *
+     * @return active image or <code>null</code> if not set.
+     */
+    public Image getActiveImage() {
+        return activeImage;
+    }
+
+    /**
+     * Sets the image to show when link is activated.
+     *
+     * @param activeImage
+     *
+     */
+    public void setActiveImage(Image activeImage) {
+        this.activeImage = activeImage;
+    }
+
+    /**
+     * Returns the hover image.
+     *
+     * @return hover image or <code>null</code> if not set.
+     */
+    public Image getHoverImage() {
+        return hoverImage;
+    }
+
+    /**
+     * Sets the image to show when link is hover state (on mouse over).
+     *
+     * @param hoverImage
+     */
+    public void setHoverImage(Image hoverImage) {
+        this.hoverImage = hoverImage;
+    }
+
+    /**
+     * Returns the image to show in the normal state.
+     *
+     * @return normal image or <code>null</code> if not set.
+     */
+    public Image getImage() {
+        return image;
+    }
+
+    /**
+     * Sets the image to show when link is in the normal state.
+     *
+     * @param image
+     */
+    public void setImage(Image image) {
+        this.image = image;
+    }
+
+    private Point computeMaxImageSize() {
+        int x = 0;
+        int y = 0;
+        if (image !is null) {
+            x = Math.max(image.getBounds().width, x);
+            y = Math.max(image.getBounds().height, y);
+        }
+        if (hoverImage !is null) {
+            x = Math.max(hoverImage.getBounds().width, x);
+            y = Math.max(hoverImage.getBounds().height, y);
+        }
+        if (activeImage !is null) {
+            x = Math.max(activeImage.getBounds().width, x);
+            y = Math.max(activeImage.getBounds().height, y);
+        }
+        return new Point(x, y);
+    }
+
+    private static int removeAlignment(int style) {
+        int resultStyle = style;
+        if ((style & DWT.CENTER) !is 0) {
+            resultStyle &= (~DWT.CENTER);
+        }
+        if ((style & DWT.TOP) !is 0) {
+            resultStyle &= (~DWT.TOP);
+        }
+        if ((style & DWT.BOTTOM) !is 0) {
+            resultStyle &= (~DWT.BOTTOM);
+        }
+        if ((style & DWT.LEFT) !is 0) {
+            resultStyle &= (~DWT.LEFT);
+        }
+        if ((style & DWT.RIGHT) !is 0) {
+            resultStyle &= (~DWT.RIGHT);
+        }
+        return resultStyle;
+    }
+
+    private void extractAlignment(int style) {
+        if ((style & DWT.CENTER) !is 0) {
+            verticalAlignment = DWT.CENTER;
+        } else if ((style & DWT.TOP) !is 0) {
+            verticalAlignment = DWT.TOP;
+        } else if ((style & DWT.BOTTOM) !is 0) {
+            verticalAlignment = DWT.BOTTOM;
+        }
+        if ((style & DWT.LEFT) !is 0) {
+            horizontalAlignment = DWT.LEFT;
+        } else if ((style & DWT.RIGHT) !is 0) {
+            horizontalAlignment = DWT.RIGHT;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/LayoutCache.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 dwtx.ui.forms.widgets.LayoutCache;
+
+import dwtx.ui.forms.widgets.SizeCache;
+
+import dwt.graphics.Point;
+import dwt.widgets.Control;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Caches the preferred sizes of an array of controls
+ *
+ * @since 3.0
+ */
+public class LayoutCache {
+    private SizeCache[] caches;
+
+    /**
+     * Creates an empty layout cache
+     */
+    public this() {
+    }
+
+    /**
+     * Creates a cache for the given array of controls
+     *
+     * @param controls
+     */
+    public this(Control[] controls) {
+        rebuildCache(controls);
+    }
+
+    /**
+     * Returns the size cache for the given control
+     *
+     * @param idx
+     * @return the size cache for the given control
+     */
+    public SizeCache getCache(int idx) {
+        return caches[idx];
+    }
+
+    /**
+     * Sets the controls that are being cached here. If these are the same
+     * controls that were used last time, this method does nothing. Otherwise,
+     * the cache is flushed and a new cache is created for the new controls.
+     *
+     * @param controls
+     */
+    public void setControls(Control[] controls) {
+        // If the number of controls has changed, discard the entire cache
+        if (controls.length !is caches.length) {
+            rebuildCache(controls);
+            return;
+        }
+
+        for (int idx = 0; idx < controls.length; idx++) {
+            caches[idx].setControl(controls[idx]);
+        }
+    }
+
+    /**
+     * Creates a new size cache for the given set of controls, discarding any
+     * existing cache.
+     *
+     * @param controls the controls whose size is being cached
+     */
+    private void rebuildCache(Control[] controls) {
+        SizeCache[] newCache = new SizeCache[controls.length];
+
+        for (int idx = 0; idx < controls.length; idx++) {
+            // Try to reuse existing caches if possible
+            if (idx < caches.length) {
+                newCache[idx] = caches[idx];
+                newCache[idx].setControl(controls[idx]);
+            } else {
+                newCache[idx] = new SizeCache(controls[idx]);
+            }
+        }
+
+        caches = newCache;
+    }
+
+    /**
+     * Computes the preferred size of the nth control
+     *
+     * @param controlIndex index of the control whose size will be computed
+     * @param widthHint width of the control (or DWT.DEFAULT if unknown)
+     * @param heightHint height of the control (or DWT.DEFAULT if unknown)
+     * @return the preferred size of the control
+     */
+    public Point computeSize(int controlIndex, int widthHint, int heightHint) {
+        return caches[controlIndex].computeSize(widthHint, heightHint);
+    }
+
+    /**
+     * Flushes the cache for the given control. This should be called if exactly
+     * one of the controls has changed but the remaining controls remain unmodified
+     *
+     * @param controlIndex
+     */
+    public void flush(int controlIndex) {
+        caches[controlIndex].flush();
+    }
+
+    /**
+     * Flushes the cache.
+     */
+    public void flush() {
+        for (int idx = 0; idx < caches.length; idx++) {
+            caches[idx].flush();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/LayoutComposite.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.LayoutComposite;
+
+import dwtx.ui.forms.widgets.TableWrapLayout;
+import dwtx.ui.forms.widgets.ColumnLayout;
+
+import dwt.graphics.Point;
+import dwt.widgets.Composite;
+import dwt.widgets.Layout;
+
+import dwt.dwthelper.utils;
+
+/**
+ * The class overrides default method for computing size in Composite by
+ * accepting size returned from layout managers as-is. The default code accepts
+ * width or height hint assuming it is correct. However, it is possible that
+ * the computation using the provided width hint results in a real size that is
+ * larger. This can result in wrapped text widgets being clipped, asking to
+ * render in bounds narrower than the longest word.
+ */
+/* package */class LayoutComposite : Composite {
+    public this(Composite parent, int style) {
+        super(parent, style);
+        setMenu(parent.getMenu());
+    }
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        Layout layout = getLayout();
+        if (null !is cast(TableWrapLayout)layout )
+            return (cast(TableWrapLayout) layout).computeSize(cast(Composite)this, wHint, hHint,
+                    changed);
+        if (null !is cast(ColumnLayout)layout )
+            return (cast(ColumnLayout) layout).computeSize(cast(Composite)this, wHint, hHint,
+                    changed);
+        return super.computeSize(wHint, hHint, changed);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ScrolledForm.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,293 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.ScrolledForm;
+
+import dwtx.ui.forms.widgets.SharedScrolledComposite;
+import dwtx.ui.forms.widgets.Form;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.Image;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Menu;
+import dwtx.jface.action.IToolBarManager;
+import dwtx.ui.forms.IMessage;
+
+import dwt.dwthelper.utils;
+
+/**
+ * ScrolledForm is a control that is capable of scrolling an instance of the
+ * Form class. It should be created in a parent that will allow it to use all
+ * the available area (for example, a shell, a view or an editor).
+ * <p>
+ * Children of the form should typically be created using FormToolkit to match
+ * the appearance and behaviour. When creating children, use a form body as a
+ * parent by calling 'getBody()' on the form instance. Example:
+ *
+ * <pre>
+ * FormToolkit toolkit = new FormToolkit(parent.getDisplay());
+ * ScrolledForm form = toolkit.createScrolledForm(parent);
+ * form.setText(&quot;Sample form&quot;);
+ * form.getBody().setLayout(new GridLayout());
+ * toolkit.createButton(form.getBody(), &quot;Checkbox&quot;, DWT.CHECK);
+ * </pre>
+ *
+ * <p>
+ * No layout manager has been set on the body. Clients are required to set the
+ * desired layout manager explicitly.
+ * <p>
+ * Although the class is not final, it is not expected to be be extended.
+ *
+ * @since 3.0
+ */
+public class ScrolledForm : SharedScrolledComposite {
+    private Form content;
+
+    public this(Composite parent) {
+        this(parent, DWT.V_SCROLL | DWT.H_SCROLL);
+    }
+
+    /**
+     * Creates the form control as a child of the provided parent.
+     *
+     * @param parent
+     *            the parent widget
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        super.setMenu(parent.getMenu());
+        content = new Form(this, DWT.NULL);
+        super.setContent(content);
+        content.setMenu(getMenu());
+    }
+
+    /**
+     * Passes the menu to the body.
+     *
+     * @param menu
+     */
+    public void setMenu(Menu menu) {
+        super.setMenu(menu);
+        if (content !is null)
+            content.setMenu(menu);
+    }
+
+    /**
+     * Returns the title text that will be rendered at the top of the form.
+     *
+     * @return the title text
+     */
+    public String getText() {
+        return content.getText();
+    }
+
+    /**
+     * Returns the title image that will be rendered to the left of the title.
+     *
+     * @return the title image
+     */
+    public Image getImage() {
+        return content.getImage();
+    }
+
+    /**
+     * Sets the foreground color of the form. This color will also be used for
+     * the body.
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        content.setForeground(fg);
+    }
+
+    /**
+     * Sets the background color of the form. This color will also be used for
+     * the body.
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        content.setBackground(bg);
+    }
+
+    /**
+     * The form sets the content widget. This method should not be called by
+     * classes that instantiate this widget.
+     */
+    public final void setContent(Control c) {
+    }
+
+    /**
+     * Sets the text to be rendered at the top of the form above the body as a
+     * title.
+     * <p>
+     * <strong>Note:</strong> Mnemonics are indicated by an '&amp;' that causes
+     * the next character to be the mnemonic. Mnemonics are not applicable in
+     * the case of the form title but need to be taken into acount due to the
+     * usage of the underlying widget that renders mnemonics in the title area.
+     * The mnemonic indicator character '&amp;' can be escaped by doubling it in
+     * the string, causing a single '&amp;' to be displayed.
+     * </p>
+     *
+     * @param text
+     *            the title text
+     */
+    public void setText(String text) {
+        content.setText(text);
+        reflow(true);
+    }
+
+    /**
+     * Sets the image to be rendered to the left of the title.
+     *
+     * @param image
+     *            the title image or <code>null</code> for no image.
+     */
+    public void setImage(Image image) {
+        content.setImage(image);
+        reflow(true);
+    }
+
+    /**
+     * Returns the optional background image of this form. The image is rendered
+     * starting at the position 0,0 and is painted behind the title.
+     *
+     * @return Returns the background image.
+     */
+    public Image getBackgroundImage() {
+        return content.getBackgroundImage();
+    }
+
+    /**
+     * Sets the optional background image to be rendered behind the title
+     * starting at the position 0,0.
+     *
+     * @param backgroundImage
+     *            The backgroundImage to set.
+     */
+    public void setBackgroundImage(Image backgroundImage) {
+        content.setBackgroundImage(backgroundImage);
+    }
+
+    /**
+     * Returns the tool bar manager that is used to manage tool items in the
+     * form's title area.
+     *
+     * @return form tool bar manager
+     */
+    public IToolBarManager getToolBarManager() {
+        return content.getToolBarManager();
+    }
+
+    /**
+     * Updates the local tool bar manager if used. Does nothing if local tool
+     * bar manager has not been created yet.
+     */
+    public void updateToolBar() {
+        content.updateToolBar();
+    }
+
+    /**
+     * Returns the container that occupies the body of the form (the form area
+     * below the title). Use this container as a parent for the controls that
+     * should be in the form. No layout manager has been set on the form body.
+     *
+     * @return Returns the body of the form.
+     */
+    public Composite getBody() {
+        return content.getBody();
+    }
+
+    /**
+     * Returns the instance of the form owned by the scrolled form.
+     *
+     * @return the form instance
+     */
+    public Form getForm() {
+        return content;
+    }
+
+    /**
+     * Sets the form's busy state. Busy form will display 'busy' animation in
+     * the area of the title image.
+     *
+     * @param busy
+     *            the form's busy state
+     * @see Form#setBusy(bool)
+     * @since 3.3
+     */
+
+    public void setBusy(bool busy) {
+        content.setBusy(busy);
+        reflow(true);
+    }
+
+    /**
+     * Sets the optional head client.
+     *
+     * @param headClient
+     *            the optional child of the head
+     * @see Form#setHeadClient(Control)
+     * @since 3.3
+     */
+    public void setHeadClient(Control headClient) {
+        content.setHeadClient(headClient);
+        reflow(true);
+    }
+
+    /**
+     * Sets the form message.
+     *
+     * @param newMessage
+     *            the message text or <code>null</code> to reset.
+     * @param newType
+     *            as defined in
+     *            {@link dwtx.jface.dialogs.IMessageProvider}.
+     * @param messages
+     *           an optional array of children that itemize individual
+     *          messages or <code>null</code> for a simple message.
+     * @since 3.3
+     * @see Form#setMessage(String, int)
+     */
+    public void setMessage(String newMessage, int newType, IMessage[] messages) {
+        content.setMessage(newMessage, newType, messages);
+        reflow(true);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageContainer#setMessage(java.lang.String,
+     *      int)
+     */
+    public void setMessage(String newMessage, int newType) {
+        this.setMessage(newMessage, newType, null);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.IMessageProvider#getMessage()
+     */
+    public String getMessage() {
+        return content.getMessage();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.IMessageProvider#getMessageType()
+     */
+    public int getMessageType() {
+        return content.getMessageType();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ScrolledFormText.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,174 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.ScrolledFormText;
+
+import dwtx.ui.forms.widgets.SharedScrolledComposite;
+import dwtx.ui.forms.widgets.FormText;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.InputStream;
+
+/**
+ * ScrolledFormText is a control that is capable of scrolling an instance of
+ * the FormText class. It should be created in a parent that will allow it to
+ * use all the available area (for example, a shell, a view or an editor). The
+ * form text can be created by the class itself, or set from outside. In the
+ * later case, the form text instance must be a direct child of the
+ * ScrolledFormText instance.
+ * <p>
+ * The class assumes that text to be rendered contains formatting tags. In case
+ * of a string, it will enclose the text in 'form' root element if missing from
+ * the text as a convinience. For example:
+ *
+ * <pre>
+ *  ftext.setText(&quot;&lt;p&gt;Some text here&lt;/&gt;&quot;);
+ * </pre>
+ *
+ * will not cause an error. The same behavior does not exist for content from
+ * the input stream, however - it must be well formed in that case.
+ * </p>
+
+ * @since 3.0
+ * @see FormText
+ */
+public class ScrolledFormText : SharedScrolledComposite {
+    private FormText content;
+    private String text;
+    /**
+     * Creates the new scrolled text instance in the provided parent
+     *
+     * @param parent
+     *            the parent composite
+     * @param createFormText
+     *            if <code>true</code>, enclosing form text instance will be
+     *            created in this constructor.
+     */
+    public this(Composite parent, bool createFormText) {
+        this(parent, DWT.V_SCROLL | DWT.H_SCROLL, createFormText);
+    }
+    /**
+     * Creates the new scrolled text instance in the provided parent
+     *
+     * @param parent
+     *            the parent composite
+     * @param style
+     *            the style to pass to the scrolled composite
+     * @param createFormText
+     *            if <code>true</code>, enclosing form text instance will be
+     *            created in this constructor.
+     */
+    public this(Composite parent, int style, bool createFormText) {
+        super(parent, style);
+        if (createFormText)
+            setFormText(new FormText(this, DWT.NULL));
+    }
+    /**
+     * Sets the form text to be managed by this scrolled form text. The
+     * instance must be a direct child of this class. If this method is used,
+     * <code>false</code> must be passed in either of the constructors to
+     * avoid creating form text instance.
+     *
+     * @param formText
+     *            the form text instance to use.
+     */
+    public void setFormText(FormText formText) {
+        this.content = formText;
+        super.setContent(content);
+        content.setMenu(getMenu());
+        if (text !is null)
+            loadText(text);
+    }
+    /**
+     * Sets the foreground color of the scrolled form text.
+     *
+     * @param fg
+     *            the foreground color
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        if (content !is null)
+            content.setForeground(fg);
+    }
+    /**
+     * Sets the background color of the scrolled form text.
+     *
+     * @param bg
+     *            the background color
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        if (content !is null)
+            content.setBackground(bg);
+    }
+    /**
+     * The class sets the content widget. This method should not be called by
+     * classes that instantiate this widget.
+     *
+     * @param c
+     *            content control
+     */
+    public final void setContent(Control c) {
+    }
+    /**
+     * Sets the text to be rendered in the scrolled form text. The text must
+     * contain formatting tags.
+     *
+     * @param text
+     *            the text to be rendered
+     */
+    public void setText(String text) {
+        this.text = text;
+        loadText(text);
+        reflow(true);
+    }
+    /**
+     * Sets the contents to rendered in the scrolled form text. The stream must
+     * contain formatting tags. The caller is responsible for closing the input
+     * stream. The call may be long running. For best results, call this method
+     * from another thread and call 'reflow' when done (but make both calls
+     * using 'Display.asyncExec' because these calls must be made in the event
+     * dispatching thread).
+     *
+     * @param is
+     *            content input stream
+     */
+    public void setContents(InputStream is_) {
+        loadContents(is_);
+    }
+    /**
+     * Returns the instance of the form text.
+     *
+     * @return the form text instance
+     */
+    public FormText getFormText() {
+        return content;
+    }
+    private void loadText(String text) {
+        if (content !is null) {
+            String markup = text;
+            if (!markup.startsWith("<form>")) //$NON-NLS-1$
+                markup = "<form>" ~ text ~ "</form>";  //$NON-NLS-1$//$NON-NLS-2$
+            content.setText(markup, true, false);
+        }
+    }
+    private void loadContents(InputStream is_) {
+        if (content !is null) {
+            content.setContents(is_, false);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ScrolledPageBook.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.ScrolledPageBook;
+
+import dwtx.ui.forms.widgets.SharedScrolledComposite;
+import dwtx.ui.forms.widgets.LayoutComposite;
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.layout.GridLayout;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwtx.ui.internal.forms.widgets.WrappedPageBook;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.HashMap;
+/**
+ * ScrolledPageBook is a class that is capable of stacking several composites
+ * (pages), while showing one at a time. The content is scrolled if there is
+ * not enough space to fit it in the client area.
+ *
+ * @since 3.0
+ */
+public class ScrolledPageBook : SharedScrolledComposite {
+    private WrappedPageBook pageBook;
+    private HashMap!(Object,Object) pages;
+    private Composite emptyPage;
+    private Control currentPage;
+    /**
+     * Creates a new instance in the provided parent
+     *
+     * @param parent
+     */
+    public this(Composite parent) {
+        this(parent, DWT.H_SCROLL | DWT.V_SCROLL);
+    }
+    /**
+     * Creates a new instance in the provided parent and with the provided
+     * style.
+     *
+     * @param parent
+     *            the control parent
+     * @param style
+     *            the style to use
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        pageBook = new WrappedPageBook(this, DWT.NULL);
+        setContent(pageBook);
+        pages = new HashMap!(Object,Object);
+        setExpandHorizontal(true);
+        setExpandVertical(true);
+        this.addListener(DWT.Traverse, dgListener( (Event e) {
+            switch (e.detail) {
+                case DWT.TRAVERSE_ESCAPE :
+                case DWT.TRAVERSE_RETURN :
+                case DWT.TRAVERSE_TAB_NEXT :
+                case DWT.TRAVERSE_TAB_PREVIOUS :
+                    e.doit = true;
+                    break;
+            }
+        }));
+    }
+    /**
+     * Removes the default size of the composite, allowing the control to
+     * shrink to the trim.
+     *
+     * @param wHint
+     *            the width hint
+     * @param hHint
+     *            the height hint
+     * @param changed
+     *            if <code>true</code>, do not use cached values
+     */
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        Rectangle trim = computeTrim(0, 0, 10, 10);
+        return new Point(trim.width, trim.height);
+    }
+    /**
+     * Tests if the page under the provided key is currently in the book.
+     *
+     * @param key
+     *            the page key
+     * @return <code>true</code> if page exists, <code>false</code>
+     *         otherwise.
+     */
+    public bool hasPage(Object key) {
+        return pages.containsKey(key);
+    }
+    /**
+     * Creates a new page for the provided key. Use the returned composite to
+     * create children in it.
+     *
+     * @param key
+     *            the page key
+     * @return the newly created page composite
+     */
+    public Composite createPage(Object key) {
+        Composite page = createPage();
+        pages.add(key, page);
+        return page;
+    }
+    /**
+     * Returns the page book container.
+     *
+     * @return the page book container
+     */
+    public Composite getContainer() {
+        return pageBook;
+    }
+    /**
+     * Registers a page under the privided key to be managed by the page book.
+     * The page must be a direct child of the page book container.
+     *
+     * @param key
+     *            the page key
+     * @param page
+     *            the page composite to register
+     * @see #createPage(Object)
+     * @see #getContainer
+     */
+    public void registerPage(Object key, Control page) {
+        pages.add(key, page);
+    }
+    /**
+     * Removes the page under the provided key from the page book. Does nothing
+     * if page with that key does not exist.
+     *
+     * @param key
+     *            the page key.
+     */
+    public void removePage(Object key) {
+        removePage(key, true);
+    }
+    /**
+     * Removes the page under the provided key from the page book. Does nothing
+     * if page with that key does not exist.
+     *
+     * @param key
+     *            the page key.
+     * @param showEmptyPage
+     *            if <code>true</code>, shows the empty page
+     *            after page removal.
+     */
+    public void removePage(Object key, bool showEmptyPage_) {
+        Control page = cast(Control) pages.get(key);
+        if (page !is null) {
+            pages.remove(key);
+            page.dispose();
+            if (showEmptyPage_)
+                showEmptyPage();
+        }
+    }
+    /**
+     * Shows the page with the provided key and hides the page previously
+     * showing. Does nothing if the page with that key does not exist.
+     *
+     * @param key
+     *            the page key
+     */
+    public void showPage(Object key) {
+        Control page = cast(Control) pages.get(key);
+        if (page !is null) {
+            pageBook.showPage(page);
+            if (currentPage !is null && currentPage !is page) {
+                // switching pages - force layout
+                if (null !is cast(Composite)page )
+                    (cast(Composite) page).layout(false);
+            }
+            currentPage = page;
+        } else {
+            showEmptyPage();
+        }
+        reflow(true);
+    }
+    /**
+     * Shows a page with no children to be used if the desire is to not show
+     * any registered page.
+     */
+    public void showEmptyPage() {
+        if (emptyPage is null) {
+            emptyPage = createPage();
+            emptyPage.setLayout(new GridLayout());
+        }
+        pageBook.showPage(emptyPage);
+        currentPage = emptyPage;
+        reflow(true);
+    }
+    /**
+     * Sets focus on the current page if shown.
+     */
+    public bool setFocus() {
+        if (currentPage !is null)
+            return currentPage.setFocus();
+        return super.setFocus();
+    }
+    /**
+     * Returns the page currently showing.
+     *
+     * @return the current page
+     */
+    public Control getCurrentPage() {
+        return currentPage;
+    }
+    private Composite createPage() {
+        Composite page = new LayoutComposite(pageBook, DWT.NULL);
+        page.setBackground(getBackground());
+        page.setForeground(getForeground());
+        page.setMenu(pageBook.getMenu());
+        return page;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/Section.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,484 @@
+/*******************************************************************************
+ * 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
+ *     Michael Williamson (eclipse-bugs@magnaworks.com) - patch (see Bugzilla #92545)
+ *
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.forms.widgets.Section;
+
+import dwtx.ui.forms.widgets.ExpandableComposite;
+import dwtx.ui.forms.widgets.SharedScrolledComposite;
+
+import dwt.DWT;
+import dwt.events.PaintEvent;
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.Text;
+import dwtx.core.runtime.Assert;
+import dwtx.ui.internal.forms.widgets.FormImages;
+import dwtx.ui.internal.forms.widgets.FormUtil;
+
+import dwt.dwthelper.utils;
+
+import tango.util.collection.HashMap;
+
+/**
+ * A variation of the expandable composite that adds optional description below
+ * the title. Section is often used as a basic building block in forms because
+ * it provides for logical grouping of information.
+ * <p>
+ * In case of the TITLE_BAR style, Section renders the title bar in a way
+ * compatible with the rest of the workbench. Since it is a widget, all the
+ * colors must be supplied directly. When created by the form toolkit, these
+ * colors are supplied by the toolkit. The toolkit initializes these colors
+ * based on the system colors. For this reason, it is recommended to create the
+ * section by the toolkit instead of through its own constructor.
+ * <p>
+ * Since 3.1, it is possible to set a control to be used for section
+ * description. If used, <code>DESCRIPTION</code> style should not be set. A
+ * typical way to take advantage of the new method is to set an instance of
+ * <code>FormText</code> to provide for hyperlinks and images in the
+ * description area.
+ *
+ * @since 3.0
+ */
+public class Section : ExpandableComposite {
+    /**
+     * Description style. If used, description will be rendered below the title.
+     */
+    public static const int DESCRIPTION = 1 << 7;
+
+    private Control descriptionControl;
+
+    private Control separator;
+
+    private HashMap!(String,Object) titleColors;
+
+    private static const String COLOR_BG = "bg"; //$NON-NLS-1$
+
+    private static const String COLOR_GBG = "gbg"; //$NON-NLS-1$
+
+    private static const String COLOR_BORDER = "border"; //$NON-NLS-1$
+
+    /**
+     * Creates a new section instance in the provided parent.
+     *
+     * @param parent
+     *            the parent composite
+     * @param style
+     *            the style to use
+     */
+    public this(Composite parent, int style) {
+        this(parent, DWT.NULL, style);
+    }
+
+    this(Composite parent, int cstyle, int style) {
+        super(parent, cstyle | getBackgroundStyle(style), style);
+        int rtl = cstyle & DWT.RIGHT_TO_LEFT;
+        if ((style & DESCRIPTION) !is 0) {
+            descriptionControl = new Text(this, DWT.READ_ONLY | DWT.WRAP | rtl);
+        }
+        if ((style & TITLE_BAR) !is 0) {
+            Listener listener = new class Listener{
+                public void handleEvent(Event e) {
+                    Image image = this.outer.callSuperGetBackgroundImage();
+                    if (image !is null) {
+                        FormImages.getInstance().markFinished(image);
+                    }
+                    this.outer.callSuperSetBackgroundImage(null);
+                }
+            };
+            addListener(DWT.Dispose, listener);
+            addListener(DWT.Resize, listener);
+        }
+    }
+
+    private static int getBackgroundStyle(int estyle) {
+        return ((estyle & TITLE_BAR) !is 0) ? DWT.NO_BACKGROUND : DWT.NULL;
+    }
+
+    private Image callSuperGetBackgroundImage(){
+        return super.getBackgroundImage();
+    }
+    private void callSuperSetBackgroundImage(Image image){
+        return super.setBackgroundImage(image);
+    }
+    protected void internalSetExpanded(bool expanded) {
+        super.internalSetExpanded(expanded);
+        if ((getExpansionStyle() & TITLE_BAR) !is 0) {
+            if (!expanded)
+                super.setBackgroundImage(null);
+        }
+        reflow();
+    }
+
+    /**
+     * Reflows this section and all the parents up the hierarchy until a
+     * SharedScrolledComposite is reached.
+     */
+    protected void reflow() {
+        Composite c = this;
+        while (c !is null) {
+            c.setRedraw(false);
+            c = c.getParent();
+            if (null !is cast(SharedScrolledComposite)c ) {
+                break;
+            }
+        }
+        c = this;
+        while (c !is null) {
+            c.layout(true);
+            c = c.getParent();
+            if (null !is cast(SharedScrolledComposite)c ) {
+                (cast(SharedScrolledComposite) c).reflow(true);
+                break;
+            }
+        }
+        c = this;
+        while (c !is null) {
+            c.setRedraw(true);
+            c = c.getParent();
+            if (null !is cast(SharedScrolledComposite)c ) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Sets the description text. Has no effect if DESCRIPTION style was not
+     * used to create the control.
+     *
+     * @param description
+     */
+    public void setDescription(String description) {
+        if (null !is cast(Text)descriptionControl )
+            (cast(Text) descriptionControl).setText(description);
+    }
+
+    /**
+     * Returns the current description text.
+     *
+     * @return description text or <code>null</code> if DESCRIPTION style was
+     *         not used to create the control.
+     */
+    public String getDescription() {
+        if (null !is cast(Text)descriptionControl )
+            return (cast(Text) descriptionControl).getText();
+        return null;
+    }
+
+    /**
+     * Sets the separator control of this section. The separator must not be
+     * <samp>null </samp> and must be a direct child of this container. If
+     * defined, separator will be placed below the title text and will remain
+     * visible regardless of the expansion state.
+     *
+     * @param separator
+     *            the separator that will be placed below the title text.
+     */
+    public void setSeparatorControl(Control separator) {
+        Assert.isTrue(separator !is null && separator.getParent().opEquals(this));
+        this.separator = separator;
+    }
+
+    /**
+     * Returns the control that is used as a separator betweeen the title and
+     * the client, or <samp>null </samp> if not set.
+     *
+     * @return separator control or <samp>null </samp> if not set.
+     */
+    public Control getSeparatorControl() {
+        return separator;
+    }
+
+    /**
+     * Sets the background of the section.
+     *
+     * @param bg
+     *            the new background
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        if (descriptionControl !is null
+                && (getExpansionStyle() & DESCRIPTION) !is 0)
+            descriptionControl.setBackground(bg);
+    }
+
+    /**
+     * Sets the foreground of the section.
+     *
+     * @param fg
+     *            the new foreground.
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        if (descriptionControl !is null
+                && (getExpansionStyle() & DESCRIPTION) !is 0)
+            descriptionControl.setForeground(fg);
+    }
+
+    /**
+     * Returns the control used to render the description. In 3.1, this method
+     * was promoted to public.
+     *
+     * @return description control or <code>null</code> if DESCRIPTION style
+     *         was not used to create the control and description control was
+     *         not set by the client.
+     * @see #setDescriptionControl(dwt.widgets.Control)
+     */
+    public Control getDescriptionControl() {
+        return descriptionControl;
+    }
+
+    /**
+     * Sets the description control of this section. The control must not be
+     * <samp>null</samp> and must be a direct child of this container. If
+     * defined, contol will be placed below the title text and the separator and
+     * will be hidden int he collapsed state.
+     * <p>
+     * This method and <code>DESCRIPTION</code> style are mutually exclusive.
+     * Use the method only if you want to create the description control
+     * yourself.
+     *
+     * @since 3.1
+     * @param descriptionControl
+     *            the control that will be placed below the title text.
+     */
+    public void setDescriptionControl(Control descriptionControl) {
+        Assert.isTrue((getExpansionStyle() & DESCRIPTION) is 0);
+        Assert.isTrue(descriptionControl !is null
+                && descriptionControl.getParent().opEquals(this));
+        this.descriptionControl = descriptionControl;
+    }
+
+    /**
+     * Sets the color of the title bar border when TITLE_BAR style is used.
+     *
+     * @param color
+     *            the title bar border color
+     */
+    public void setTitleBarBorderColor(Color color) {
+        putTitleBarColor(COLOR_BORDER, color);
+    }
+
+    /**
+     * Sets the color of the title bar background when TITLE_BAR style is used.
+     * This color is used as a starting color for the vertical gradient.
+     *
+     * @param color
+     *            the title bar border background
+     */
+    public void setTitleBarBackground(Color color) {
+        putTitleBarColor(COLOR_BG, color);
+    }
+
+    /**
+     * Sets the color of the title bar gradient background when TITLE_BAR style
+     * is used. This color is used at the height where title controls end
+     * (toggle, tool bar).
+     *
+     * @param color
+     *            the title bar gradient background
+     */
+    public void setTitleBarGradientBackground(Color color) {
+        putTitleBarColor(COLOR_GBG, color);
+    }
+
+    /**
+     * Returns the title bar border color when TITLE_BAR style is used.
+     *
+     * @return the title bar border color
+     */
+    public Color getTitleBarBorderColor() {
+        if (titleColors is null)
+            return null;
+        return cast(Color) titleColors.get(COLOR_BORDER);
+    }
+
+    /**
+     * Returns the title bar gradient background color when TITLE_BAR style is
+     * used.
+     *
+     * @return the title bar gradient background
+     */
+    public Color getTitleBarGradientBackground() {
+        if (titleColors is null)
+            return null;
+        if ((getExpansionStyle() & SHORT_TITLE_BAR) !is 0)
+            return getBackground();
+        return cast(Color) titleColors.get(COLOR_GBG);
+    }
+
+    /**
+     * Returns the title bar background when TITLE_BAR style is used.
+     *
+     * @return the title bar background
+     */
+    public Color getTitleBarBackground() {
+        if (titleColors is null)
+            return null;
+        return cast(Color) titleColors.get(COLOR_BG);
+    }
+
+    private void putTitleBarColor(String key, Color color) {
+        if (color is null)
+            return;
+        if (titleColors is null)
+            titleColors = new HashMap!(String,Object);
+        titleColors.add(key, color);
+    }
+
+    protected void onPaint(PaintEvent e) {
+        Color bg = null;
+        Color fg = null;
+        Color border = null;
+
+        GC gc = e.gc;
+        Image buffer = null;
+        Rectangle bounds = getClientArea();
+
+        if ((getExpansionStyle() & TITLE_BAR) !is 0) {
+            buffer = new Image(getDisplay(), bounds.width, bounds.height);
+            buffer.setBackground(getBackground());
+            gc = new GC(buffer);
+        }
+        if (titleColors !is null) {
+            bg = cast(Color) titleColors.get(COLOR_BG);
+            fg = getTitleBarForeground();
+            border = cast(Color) titleColors.get(COLOR_BORDER);
+        }
+        if (bg is null)
+            bg = getBackground();
+        if (fg is null)
+            fg = getForeground();
+        if (border is null)
+            border = fg;
+        int theight = 0;
+        int gradientheight = 0;
+        int tvmargin = IGAP;
+        if ((getExpansionStyle() & TITLE_BAR) !is 0) {
+            Point tsize = null;
+            Point tcsize = null;
+            if (toggle !is null)
+                tsize = toggle.getSize();
+            int twidth = bounds.width - marginWidth - marginWidth;
+            if (tsize !is null)
+                twidth -= tsize.x + IGAP;
+            if (getTextClient() !is null)
+                tcsize = getTextClient().getSize();
+            if (tcsize !is null)
+                twidth -= tcsize.x + IGAP;
+            Point size = textLabel.getSize();
+            if (tsize !is null)
+                theight += Math.max(theight, tsize.y);
+            gradientheight = theight;
+            if (tcsize !is null) {
+                theight = Math.max(theight, tcsize.y);
+            }
+            theight = Math.max(theight, size.y);
+            gradientheight = Math.max(gradientheight, size.y);
+            theight += tvmargin + tvmargin;
+            gradientheight += tvmargin + tvmargin;
+        } else {
+            theight = 5;
+        }
+        if ((getExpansionStyle() & TITLE_BAR) !is 0) {
+            if (getBackgroundImage() is null)
+                updateHeaderImage(bg, bounds, gradientheight, theight);
+            gc.setBackground(getBackground());
+            gc.fillRectangle(bounds.x, bounds.y, bounds.width, bounds.height);
+            drawBackground(gc, bounds.x, bounds.y, bounds.width, theight);
+            if (marginWidth > 0) {
+                // fix up margins
+                gc.setBackground(getBackground());
+                gc.fillRectangle(0, 0, marginWidth, theight);
+                gc.fillRectangle(bounds.x + bounds.width - marginWidth, 0,
+                        marginWidth, theight);
+            }
+        } else if (isExpanded()) {
+            gc.setForeground(bg);
+            gc.setBackground(getBackground());
+            gc.fillGradientRectangle(marginWidth, marginHeight, bounds.width
+                    - marginWidth - marginWidth, theight, true);
+        }
+        gc.setBackground(getBackground());
+        FormUtil.setAntialias(gc, DWT.ON);
+        // repair the upper left corner
+        gc.fillPolygon([ marginWidth, marginHeight, marginWidth,
+                marginHeight + 2, marginWidth + 2, marginHeight ]);
+        // repair the upper right corner
+        gc.fillPolygon([ bounds.width - marginWidth - 3,
+                marginHeight, bounds.width - marginWidth, marginHeight,
+                bounds.width - marginWidth, marginHeight + 3 ]);
+        gc.setForeground(border);
+        if (isExpanded() || (getExpansionStyle() & TITLE_BAR) !is 0) {
+            // top left curve
+            gc.drawLine(marginWidth, marginHeight + 2, marginWidth + 2,
+                    marginHeight);
+            // top edge
+            gc.drawLine(marginWidth + 2, marginHeight, bounds.width
+                    - marginWidth - 3, marginHeight);
+            // top right curve
+            gc.drawLine(bounds.width - marginWidth - 3, marginHeight,
+                    bounds.width - marginWidth - 1, marginHeight + 2);
+        } else {
+            // collapsed short title bar
+            // top edge
+            gc.drawLine(marginWidth, marginHeight, bounds.width - 1,
+                    marginHeight);
+        }
+        if ((getExpansionStyle() & TITLE_BAR) !is 0 || isExpanded()) {
+            // left vertical edge gradient
+            gc.fillGradientRectangle(marginWidth, marginHeight + 2, 1,
+                    gradientheight - 2, true);
+            // right vertical edge gradient
+            gc.fillGradientRectangle(bounds.width - marginWidth - 1,
+                    marginHeight + 2, 1, gradientheight - 2, true);
+        }
+        if ((getExpansionStyle() & TITLE_BAR) !is 0) {
+            // New in 3.3 - edge treatmant
+            gc.setForeground(getDisplay().getSystemColor(DWT.COLOR_WHITE));
+            gc.drawPolyline([ marginWidth + 1,
+                    marginHeight + gradientheight - 1, marginWidth + 1,
+                    marginHeight + 2, marginWidth + 2, marginHeight + 2,
+                    marginWidth + 2, marginHeight + 1,
+                    bounds.width - marginWidth - 3, marginHeight + 1,
+                    bounds.width - marginWidth - 3, marginHeight + 2,
+                    bounds.width - marginWidth - 2, marginHeight + 2,
+                    bounds.width - marginWidth - 2,
+                    marginHeight + gradientheight - 1 ]);
+        }
+        if (buffer !is null) {
+            gc.dispose();
+            e.gc.drawImage(buffer, 0, 0);
+            buffer.dispose();
+        }
+    }
+
+    private void updateHeaderImage(Color bg, Rectangle bounds, int theight,
+            int realtheight) {
+        Image image = FormImages.getInstance().getGradient(getDisplay(), getBackground(), bg, realtheight, theight, marginHeight);
+        super.setBackgroundImage(image);
+    }
+
+    /**
+     * Background image is used for the title gradient - does nothing.
+     */
+    public final void setBackgroundImage(Image image) {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/SharedScrolledComposite.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.SharedScrolledComposite;
+
+import dwtx.ui.forms.widgets.SizeCache;
+
+import dwt.DWT;
+import dwt.custom.ScrolledComposite;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.Point;
+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 dwtx.ui.internal.forms.widgets.FormUtil;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+
+/**
+ * This class is used to provide common scrolling services to a number of
+ * controls in the toolkit. Classes that extend it are not required to implement
+ * any method.
+ *
+ * @since 3.0
+ */
+public abstract class SharedScrolledComposite : ScrolledComposite {
+    private static const int H_SCROLL_INCREMENT = 5;
+
+    private static const int V_SCROLL_INCREMENT = 64;
+
+    private bool ignoreLayouts = true;
+
+    private bool ignoreResizes = false;
+
+    private bool expandHorizontal = false;
+
+    private bool expandVertical = false;
+
+    private SizeCache contentCache;
+
+    private bool reflowPending = false;
+
+    private bool delayedReflow = true;
+
+    /**
+     * Creates the new instance.
+     *
+     * @param parent
+     *            the parent composite
+     * @param style
+     *            the style to use
+     */
+    public this(Composite parent, int style) {
+        contentCache = new SizeCache();
+        super(parent, style);
+        addListener(DWT.Resize, new class Listener {
+            public void handleEvent(Event e) {
+                if (!ignoreResizes) {
+                    scheduleReflow(false);
+                }
+            }
+        });
+        initializeScrollBars();
+    }
+
+    /**
+     * Sets the foreground of the control and its content.
+     *
+     * @param fg
+     *            the new foreground color
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        if (getContent() !is null)
+            getContent().setForeground(fg);
+    }
+
+    /**
+     * Sets the background of the control and its content.
+     *
+     * @param bg
+     *            the new background color
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        if (getContent() !is null)
+            getContent().setBackground(bg);
+    }
+
+    /**
+     * Sets the font of the form. This font will be used to render the title
+     * text. It will not affect the body.
+     */
+    public void setFont(Font font) {
+        super.setFont(font);
+        if (getContent() !is null)
+            getContent().setFont(font);
+    }
+
+    /**
+     * Overrides 'super' to pass the proper colors and font
+     */
+    public void setContent(Control content) {
+        super.setContent(content);
+        if (content !is null) {
+            content.setForeground(getForeground());
+            content.setBackground(getBackground());
+            content.setFont(getFont());
+        }
+    }
+
+    /**
+     * If content is set, transfers focus to the content.
+     */
+    public bool setFocus() {
+        bool result;
+        FormUtil.setFocusScrollingEnabled(this, false);
+        if (getContent() !is null)
+            result = getContent().setFocus();
+        else
+            result = super.setFocus();
+        FormUtil.setFocusScrollingEnabled(this, true);
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwt.widgets.Composite#layout(bool)
+     */
+    public void layout(bool changed) {
+        if (ignoreLayouts) {
+            return;
+        }
+
+        ignoreLayouts = true;
+        ignoreResizes = true;
+        super.layout(changed);
+        ignoreResizes = false;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwt.custom.ScrolledComposite#setExpandHorizontal(bool)
+     */
+    public void setExpandHorizontal(bool expand) {
+        expandHorizontal = expand;
+        super.setExpandHorizontal(expand);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwt.custom.ScrolledComposite#setExpandVertical(bool)
+     */
+    public void setExpandVertical(bool expand) {
+        expandVertical = expand;
+        super.setExpandVertical(expand);
+    }
+
+    /**
+     * Recomputes the body layout and the scroll bars. The method should be used
+     * when changes somewhere in the form body invalidate the current layout
+     * and/or scroll bars.
+     *
+     * @param flushCache
+     *            if <code>true</code>, drop the cached data
+     */
+    public void reflow(bool flushCache) {
+        Composite c = cast(Composite) getContent();
+        Rectangle clientArea = getClientArea();
+        if (c is null)
+            return;
+
+        contentCache.setControl(c);
+        if (flushCache) {
+            contentCache.flush();
+        }
+        try {
+            setRedraw(false);
+            Point newSize = contentCache.computeSize(FormUtil.getWidthHint(
+                    clientArea.width, c), FormUtil.getHeightHint(clientArea.height,
+                    c));
+
+            // Point currentSize = c.getSize();
+            if (!(expandHorizontal && expandVertical)) {
+                c.setSize(newSize);
+            }
+
+            setMinSize(newSize);
+            FormUtil.updatePageIncrement(this);
+
+            // reduce vertical scroll increment if necessary
+            ScrollBar vbar = getVerticalBar();
+            if (vbar !is null) {
+                if (getClientArea().height - 5 < V_SCROLL_INCREMENT)
+                    getVerticalBar().setIncrement(getClientArea().height - 5);
+                else
+                    getVerticalBar().setIncrement(V_SCROLL_INCREMENT);
+            }
+
+            ignoreLayouts = false;
+            layout(flushCache);
+            ignoreLayouts = true;
+
+            contentCache.layoutIfNecessary();
+        } finally {
+            setRedraw(true);
+        }
+    }
+
+    private void updateSizeWhilePending() {
+        Control c = getContent();
+        Rectangle area = getClientArea();
+        setMinSize(area.width, c.getSize().y);
+    }
+
+    private void scheduleReflow(bool flushCache) {
+        if (delayedReflow) {
+            if (reflowPending) {
+                updateSizeWhilePending();
+                return;
+            }
+            getDisplay().asyncExec( dgRunnable( (bool flushCache) {
+                if (!isDisposed())
+                    reflow(flushCache);
+                reflowPending = false;
+            }, flushCache));
+            reflowPending = true;
+        } else
+            reflow(flushCache);
+    }
+
+    private void initializeScrollBars() {
+        ScrollBar hbar = getHorizontalBar();
+        if (hbar !is null) {
+            hbar.setIncrement(H_SCROLL_INCREMENT);
+        }
+        ScrollBar vbar = getVerticalBar();
+        if (vbar !is null) {
+            vbar.setIncrement(V_SCROLL_INCREMENT);
+        }
+        FormUtil.updatePageIncrement(this);
+    }
+
+    /**
+     * Tests if the control uses delayed reflow.
+     * @return <code>true</code> if reflow requests will
+     * be delayed, <code>false</code> otherwise.
+     */
+    public bool isDelayedReflow() {
+        return delayedReflow;
+    }
+
+    /**
+     * Sets the delayed reflow feature. When used,
+     * it will schedule a reflow on resize requests
+     * and reject subsequent reflows until the
+     * scheduled one is performed. This improves
+     * performance by
+     * @param delayedReflow
+     *            The delayedReflow to set.
+     */
+    public void setDelayedReflow(bool delayedReflow) {
+        this.delayedReflow = delayedReflow;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/SizeCache.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,537 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 dwtx.ui.forms.widgets.SizeCache;
+
+import dwtx.ui.forms.widgets.ILayoutExtension;
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Button;
+import dwt.widgets.Combo;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Label;
+import dwt.widgets.List;
+import dwt.widgets.Layout;
+import dwt.widgets.ProgressBar;
+import dwt.widgets.Sash;
+import dwt.widgets.Scale;
+import dwt.widgets.Scrollable;
+import dwt.widgets.Slider;
+import dwt.widgets.Text;
+import dwt.widgets.ToolBar;
+import dwt.widgets.Tree;
+import dwtx.jface.util.Geometry;
+import dwtx.ui.internal.forms.widgets.FormUtil;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+/**
+ * Caches the preferred size of an DWT control
+ *
+ * @since 3.0
+ */
+public class SizeCache {
+    private Control control;
+
+    private Point preferredSize;
+
+    private int cachedWidthQuery;
+    private int cachedWidthResult;
+
+    private int cachedHeightQuery;
+    private int cachedHeightResult;
+
+    private int minimumWidth;
+    private int heightAtMinimumWidth = -1;
+    private int maximumWidth;
+
+    /**
+     * True iff we should recursively flush all children on the next layout
+     */
+    private bool flushChildren;
+
+    /**
+     * True iff changing the height hint does not affect the preferred width and changing
+     * the width hint does not change the preferred height
+     */
+    private bool independentDimensions = false;
+
+    /**
+     * True iff the preferred height for any hint larger than the preferred width will not
+     * change the preferred height.
+     */
+    private bool preferredWidthOrLargerIsMinimumHeight = false;
+
+    // HACK: these values estimate how much to subtract from the width and height
+    // hints that get passed into computeSize, in order to produce a result
+    // that is exactly the desired size. To be removed once bug 46112 is fixed (note:
+    // bug 46112 is currently flagged as a duplicate, but there is still no workaround).
+    private int widthAdjustment = 0;
+
+    private int heightAdjustment = 0;
+
+    private int minimumHeight;
+
+    private int widthAtMinimumHeight = -1;
+
+    // If the layout is dirty, this is the size of the control at the time its
+    // layout was dirtied. null if the layout is not dirty.
+    private Point dirtySize = null;
+
+
+    // END OF HACK
+
+    public this() {
+        this(null);
+    }
+
+    /**
+     * Creates a cache for size computations on the given control
+     *
+     * @param control the control for which sizes will be calculated,
+     * or null to always return (0,0)
+     */
+    public this(Control control) {
+        setControl(control);
+    }
+
+    /**
+     * Sets the control whose size is being cached. Does nothing (will not
+     * even flush the cache) if this is the same control as last time.
+     *
+     * @param newControl the control whose size is being cached, or null to always return (0,0)
+     */
+    public void setControl(Control newControl) {
+        if (newControl !is control) {
+            control = newControl;
+            if (control is null) {
+                independentDimensions = true;
+                preferredWidthOrLargerIsMinimumHeight = false;
+                widthAdjustment = 0;
+                heightAdjustment = 0;
+            } else {
+                independentDimensions = independentLengthAndWidth(control);
+                preferredWidthOrLargerIsMinimumHeight = isPreferredWidthMaximum(control);
+                computeHintOffset(control);
+                flush();
+            }
+        }
+    }
+
+    /**
+     * Returns the control whose size is being cached
+     *
+     * @return the control whose size is being cached, or null if this cache always returns (0,0)
+     */
+    public Control getControl() {
+        return control;
+    }
+
+    /**
+     * Flush the cache (should be called if the control's contents may have changed since the
+     * last query)
+     */
+    public void flush() {
+        flush(true);
+    }
+
+    public void flush(bool recursive) {
+        preferredSize = null;
+        cachedWidthQuery = -1;
+        cachedWidthResult = -1;
+        cachedHeightQuery = -1;
+        cachedHeightResult = -1;
+        minimumWidth = -1;
+        maximumWidth = -1;
+        minimumHeight = -1;
+        heightAtMinimumWidth = -1;
+        widthAtMinimumHeight = -1;
+
+        if (recursive || dirtySize !is null) {
+            if (control is null || control.isDisposed()) {
+                dirtySize = new Point(0,0);
+                control = null;
+            } else {
+                dirtySize = control.getSize();
+            }
+        }
+
+        this.flushChildren = this.flushChildren || recursive;
+    }
+
+    private Point getPreferredSize() {
+        if (preferredSize is null) {
+            preferredSize = controlComputeSize(DWT.DEFAULT, DWT.DEFAULT);
+        }
+
+        return preferredSize;
+    }
+
+    /**
+     * Computes the preferred size of the control.
+     *
+     * @param widthHint the known width of the control (pixels) or DWT.DEFAULT if unknown
+     * @param heightHint the known height of the control (pixels) or DWT.DEFAULT if unknown
+     * @return the preferred size of the control
+     */
+    public Point computeSize(int widthHint, int heightHint) {
+        if (control is null || control.isDisposed()) {
+            return new Point(0, 0);
+        }
+
+        // If we're asking for a result smaller than the minimum width
+        int minWidth = computeMinimumWidth();
+
+        if (widthHint !is DWT.DEFAULT && widthHint + widthAdjustment < minWidth) {
+            if (heightHint is DWT.DEFAULT) {
+                return new Point(minWidth, computeHeightAtMinimumWidth());
+            }
+
+            widthHint = minWidth - widthAdjustment;
+        }
+
+        // If we're asking for a result smaller than the minimum height
+        int minHeight = computeMinimumHeight();
+
+        if (heightHint !is DWT.DEFAULT && heightHint + heightAdjustment < minHeight) {
+            if (widthHint is DWT.DEFAULT) {
+                return new Point(computeWidthAtMinimumHeight(), minHeight);
+            }
+
+            heightHint = minHeight - heightAdjustment;
+        }
+
+        // If both dimensions were supplied in the input, compute the trivial result
+        if (widthHint !is DWT.DEFAULT && heightHint !is DWT.DEFAULT) {
+            return new Point(widthHint + widthAdjustment, heightHint + heightAdjustment);
+        }
+
+        // No hints given -- find the preferred size
+        if (widthHint is DWT.DEFAULT && heightHint is DWT.DEFAULT) {
+            return Geometry.copy(getPreferredSize());
+        }
+
+        // If the length and width are independent, compute the preferred size
+        // and adjust whatever dimension was supplied in the input
+        if (independentDimensions) {
+            Point result = Geometry.copy(getPreferredSize());
+
+            if (widthHint !is DWT.DEFAULT) {
+                result.x = widthHint + widthAdjustment;
+            }
+
+            if (heightHint !is DWT.DEFAULT) {
+                result.y = heightHint + heightAdjustment;
+            }
+
+            return result;
+        }
+
+        // Computing a height
+        if (heightHint is DWT.DEFAULT) {
+            // If we know the control's preferred size
+            if (preferredSize !is null) {
+                // If the given width is the preferred width, then return the preferred size
+                if (widthHint + widthAdjustment is preferredSize.x) {
+                    return Geometry.copy(preferredSize);
+                }
+            }
+
+            // If we have a cached height measurement
+            if (cachedHeightQuery !is -1) {
+                // If this was measured with the same width hint
+                if (cachedHeightQuery is widthHint) {
+                    return new Point(widthHint + widthAdjustment, cachedHeightResult);
+                }
+            }
+
+            // If this is a control where any hint larger than the
+            // preferred width results in the minimum height, determine if
+            // we can compute the result based on the preferred height
+            if (preferredWidthOrLargerIsMinimumHeight) {
+                // Computed the preferred size (if we don't already know it)
+                getPreferredSize();
+
+                // If the width hint is larger than the preferred width, then
+                // we can compute the result from the preferred width
+                if (widthHint + widthAdjustment >= preferredSize.x) {
+                    return new Point(widthHint + widthAdjustment, preferredSize.y);
+                }
+            }
+
+            // Else we can't find an existing size in the cache, so recompute
+            // it from scratch.
+            Point newHeight = controlComputeSize(widthHint - widthAdjustment, DWT.DEFAULT);
+
+            cachedHeightQuery = heightHint;
+            cachedHeightResult = newHeight.y;
+
+            return newHeight;
+        }
+
+        // Computing a width
+        if (widthHint is DWT.DEFAULT) {
+            // If we know the control's preferred size
+            if (preferredSize !is null) {
+                // If the given height is the preferred height, then return the preferred size
+                if (heightHint + heightAdjustment is preferredSize.y) {
+                    return Geometry.copy(preferredSize);
+                }
+            }
+
+            // If we have a cached width measurement with the same height hint
+            if (cachedWidthQuery is heightHint) {
+                return new Point(cachedWidthResult, heightHint + heightAdjustment);
+            }
+
+            Point widthResult = controlComputeSize(DWT.DEFAULT, heightHint - heightAdjustment);
+
+            cachedWidthQuery = heightHint;
+            cachedWidthResult = widthResult.x;
+
+            return widthResult;
+        }
+
+        return controlComputeSize(widthHint, heightHint);
+    }
+
+    /**
+     * Compute the control's size, and ensure that non-default hints are returned verbatim
+     * (this tries to compensate for DWT's hints, which aren't really the outer width of the
+     * control).
+     *
+     * @param widthHint the horizontal hint
+     * @param heightHint the vertical hint
+     * @return the control's size
+     */
+    public Point computeAdjustedSize(int widthHint, int heightHint) {
+        int adjustedWidthHint = widthHint is DWT.DEFAULT ? DWT.DEFAULT : Math
+                .max(0, widthHint - widthAdjustment);
+        int adjustedHeightHint = heightHint is DWT.DEFAULT ? DWT.DEFAULT : Math
+                .max(0, heightHint - heightAdjustment);
+
+        Point result = computeSize(adjustedWidthHint, adjustedHeightHint);
+
+        // If the amounts we subtracted off the widthHint and heightHint didn't do the trick, then
+        // manually adjust the result to ensure that a non-default hint will return that result verbatim.
+
+        return result;
+    }
+
+    /**
+     * Returns true if the preferred length of the given control is
+     * independent of the width and visa-versa. If this returns true,
+     * then changing the widthHint argument to control.computeSize will
+     * never change the resulting height and changing the heightHint
+     * will never change the resulting width. Returns false if unknown.
+     * <p>
+     * This information can be used to improve caching. Incorrectly returning
+     * a value of false may decrease performance, but incorrectly returning
+     * a value of true will generate incorrect layouts... so always return
+     * false if unsure.
+     * </p>
+     *
+     * @param control
+     * @return
+     */
+    static bool independentLengthAndWidth(Control control) {
+        if (control is null || control.isDisposed()) {
+            return true;
+        }
+
+        if (null !is cast(Button)control || null !is cast(ProgressBar)control
+                || null !is cast(Sash)control || null !is cast(Scale)control
+                || null !is cast(Slider)control || null !is cast(List)control
+                || null !is cast(Combo)control || null !is cast(Tree)control ) {
+            return true;
+        }
+
+        if (null !is cast(Label)control || null !is cast(Text)control ) {
+            return (control.getStyle() & DWT.WRAP) is 0;
+        }
+
+        // Unless we're certain that the control has this property, we should
+        // return false.
+
+        return false;
+    }
+
+    /**
+     * Try to figure out how much we need to subtract from the hints that we
+     * pass into the given control's computeSize(...) method. This tries to
+     * compensate for bug 46112. To be removed once DWT provides an "official"
+     * way to compute one dimension of a control's size given the other known
+     * dimension.
+     *
+     * @param control
+     */
+    private void computeHintOffset(Control control) {
+        if (null !is cast(Scrollable)control ) {
+            // For scrollables, subtract off the trim size
+            Scrollable scrollable = cast(Scrollable) control;
+            Rectangle trim = scrollable.computeTrim(0, 0, 0, 0);
+
+            widthAdjustment = trim.width;
+            heightAdjustment = trim.height;
+        } else {
+            // For non-composites, subtract off 2 * the border size
+            widthAdjustment = control.getBorderWidth() * 2;
+            heightAdjustment = widthAdjustment;
+        }
+    }
+
+    private Point controlComputeSize(int widthHint, int heightHint) {
+        Point result = control.computeSize(widthHint, heightHint, flushChildren);
+        flushChildren = false;
+
+        return result;
+    }
+
+    /**
+     * Returns true only if the control will return a constant height for any
+     * width hint larger than the preferred width. Returns false if there is
+     * any situation in which the control does not have this property.
+     *
+     * <p>
+     * Note: this method is only important for wrapping controls, and it can
+     * safely return false for anything else. AFAIK, all DWT controls have this
+     * property, but to be safe they will only be added to the list once the
+     * property has been confirmed.
+     * </p>
+     *
+     * @param control
+     * @return
+     */
+    private static bool isPreferredWidthMaximum(Control control) {
+        return (null !is cast(ToolBar)control
+        //|| control instanceof CoolBar
+        || null !is cast(Label)control );
+    }
+
+    public int computeMinimumWidth() {
+        if (minimumWidth is -1) {
+            if (null !is cast(Composite)control ) {
+                Layout layout = (cast(Composite)control).getLayout();
+                if (null !is cast(ILayoutExtension)layout ) {
+                    minimumWidth = (cast(ILayoutExtension)layout).computeMinimumWidth(cast(Composite)control, flushChildren);
+                    flushChildren = false;
+                }
+            }
+        }
+
+        if (minimumWidth is -1) {
+            Point minWidth = controlComputeSize(FormUtil.getWidthHint(5, control), DWT.DEFAULT);
+            minimumWidth = minWidth.x;
+            heightAtMinimumWidth = minWidth.y;
+        }
+
+        return minimumWidth;
+    }
+
+    public int computeMaximumWidth() {
+        if (maximumWidth is -1) {
+            if (null !is cast(Composite)control ) {
+                Layout layout = (cast(Composite)control).getLayout();
+                if (null !is cast(ILayoutExtension)layout ) {
+                    maximumWidth = (cast(ILayoutExtension)layout).computeMaximumWidth(cast(Composite)control, flushChildren);
+                    flushChildren = false;
+                }
+            }
+        }
+
+        if (maximumWidth is -1) {
+            maximumWidth = getPreferredSize().x;
+        }
+
+        return maximumWidth;
+    }
+
+    private int computeHeightAtMinimumWidth() {
+        int minimumWidth = computeMinimumWidth();
+
+        if (heightAtMinimumWidth is -1) {
+            heightAtMinimumWidth = controlComputeSize(minimumWidth - widthAdjustment, DWT.DEFAULT).y;
+        }
+
+        return heightAtMinimumWidth;
+    }
+
+    private int computeWidthAtMinimumHeight() {
+        int minimumHeight = computeMinimumHeight();
+
+        if (widthAtMinimumHeight is -1) {
+            widthAtMinimumHeight = controlComputeSize(DWT.DEFAULT, minimumHeight - heightAdjustment).x;
+        }
+
+        return widthAtMinimumHeight;
+    }
+
+    private int computeMinimumHeight() {
+        if (minimumHeight is -1) {
+            Point sizeAtMinHeight = controlComputeSize(DWT.DEFAULT, 0);
+
+            minimumHeight = sizeAtMinHeight.y;
+            widthAtMinimumHeight = sizeAtMinHeight.x;
+        }
+
+        return minimumHeight;
+    }
+
+    public Point computeMinimumSize() {
+        return new Point(computeMinimumWidth(), computeMinimumHeight());
+    }
+
+    public void setSize(Point newSize) {
+        if (control !is null) {
+            control.setSize(newSize);
+        }
+
+        layoutIfNecessary();
+    }
+
+    public void setSize(int width, int height) {
+        if (control !is null) {
+            control.setSize(width, height);
+        }
+
+        layoutIfNecessary();
+    }
+
+    public void setBounds(int x, int y, int width, int height) {
+        if (control !is null) {
+            control.setBounds(x, y, width, height);
+        }
+
+        layoutIfNecessary();
+    }
+
+    public void setBounds(Rectangle bounds) {
+        if (control !is null) {
+            control.setBounds(bounds);
+        }
+
+        layoutIfNecessary();
+    }
+
+    public void layoutIfNecessary() {
+        if (dirtySize !is null && control !is null && null !is cast(Composite)control ) {
+            if (control.getSize().opEquals(dirtySize)) {
+                (cast(Composite)control).layout(flushChildren);
+                flushChildren = false;
+            }
+        }
+        dirtySize = null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/TableWrapData.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.TableWrapData;
+
+import dwt.DWT;
+import dwt.graphics.Point;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Layout data used in conjunction with <code>HTMLTableLayout</code>.
+ * Children in a composite that uses this layout should call <samp>setLayoutData
+ * </samp> and pass an instance of this class to control physical placement in
+ * the parent.
+ *
+ * @see TableWrapLayout
+ * @since 3.0
+ */
+public final class TableWrapData {
+    /**
+     * The control will be left-justified.
+     */
+    public static const int LEFT = 1 << 1;
+
+    /**
+     * The control will be centered horizontally.
+     */
+    public static const int CENTER = 1 << 2;
+
+    /**
+     * The control will be right-justified.
+     */
+    public static const int RIGHT = 1 << 3;
+
+    /**
+     * The control will be aligned with the top of the cell.
+     */
+    public static const int TOP = 1 << 4;
+
+    /**
+     * The control will be centered vertically.
+     */
+    public static const int MIDDLE = 1 << 5;
+
+    /**
+     * The control will be aligned with the bottom of the cell.
+     */
+    public static const int BOTTOM = 1 << 6;
+
+    /**
+     * The control will have the same width as the column it occupies.
+     */
+    public static const int FILL = 1 << 7;
+
+    /**
+     * In addition to filling width or height, the control will take part in
+     * allocation of any excess space. Note that this constant can only be
+     * passed to the constructor (cannot be directly assigned to
+     * <code>align</code> variable).
+     */
+    public static const int FILL_GRAB = 1 << 8;
+
+    /**
+     * Number of columns to span (default is 1).
+     */
+    public int colspan = 1;
+
+    /**
+     * Number of rows to span (default is 1).
+     */
+    public int rowspan = 1;
+
+    /**
+     * Horizontal alignment (LEFT, CENTER, RIGHT or FILL; default is LEFT).
+     */
+    public int align_ = LEFT;
+
+    /**
+     * Vertical alignment (TOP, MIDDLE, BOTTOM or FILL; default is TOP).
+     */
+    public int valign = TOP;
+
+    /**
+     * Horizontal indent (default is 0).
+     */
+    public int indent = 0;
+
+    /**
+     * Maximum width of the control (default is DWT.DEFAULT).
+     */
+    public int maxWidth = DWT.DEFAULT;
+
+    /**
+     * Maximum height of the control (default is DWT.DEFAULT).
+     */
+    public int maxHeight = DWT.DEFAULT;
+
+    /**
+     * Height hint of the control (default is DWT.DEFAULT).
+     */
+    public int heightHint = DWT.DEFAULT;
+
+    /**
+     * If <code>true</code>, take part in excess horizontal space
+     * distribution. (default is <code>false</code>).
+     */
+    public bool grabHorizontal;
+
+    /**
+     * If <code>true</code>, will grab any excess vertical space (default is
+     * <code>false</code>). Note that since TableWrapLayout works top-down
+     * and does not grows to fill the parent, this only applies to local excess
+     * space created by fixed-height children that span multiple rows.
+     */
+    public bool grabVertical;
+
+    int childIndex;
+
+    bool isItemData = true;
+
+    int compWidth;
+
+    Point compSize;
+
+    /**
+     * The default constructor.
+     */
+    public this() {
+    }
+
+    /**
+     * The convenience constructor - allows passing the horizontal alignment
+     * style.
+     *
+     * @param align
+     *            horizontal alignment (LEFT, CENTER, RIGHT, FILL or FILL_GRAB).
+     */
+    public this(int align_) {
+        this(align_, TOP, 1, 1);
+    }
+
+    /**
+     * The convenience constructor - allows passing the alignment styles.
+     *
+     * @param align
+     *            horizontal alignment (LEFT, CENTER, RIGHT, FILL or FILL_GRAB).
+     * @param valign
+     *            vertical alignment (TOP, MIDDLE, BOTTOM, FILL or FILL_GRAB).
+     */
+    public this(int align_, int valign) {
+        this(align_, valign, 1, 1);
+    }
+
+    /**
+     * The convenience constructor - allows passing the alignment styles, column
+     * and row spans.
+     *
+     * @param align
+     *            horizontal alignment (LEFT, CENTER, RIGHT, FILL or FILL_GRAB).
+     * @param valign
+     *            vertical alignment (TOP, MIDDLE, BOTTOM, FILL or FILL_GRAB)
+     * @param rowspan
+     *            row span (1 or more)
+     * @param colspan
+     *            column span (1 or more)
+     */
+    public this(int align_, int valign, int rowspan, int colspan) {
+        if (align_ !is LEFT && align_ !is CENTER && align_ !is RIGHT && align_ !is FILL
+                && align_ !is FILL_GRAB)
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "align"); //$NON-NLS-1$
+        if (valign !is TOP && valign !is MIDDLE && valign !is BOTTOM
+                && valign !is FILL && valign !is FILL_GRAB)
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "valign"); //$NON-NLS-1$
+        if (rowspan < 1)
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "rowspan"); //$NON-NLS-1$
+        if (colspan < 1)
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "colspan"); //$NON-NLS-1$
+        if (align_ is FILL_GRAB) {
+            this.align_ = FILL;
+            grabHorizontal = true;
+        } else
+            this.align_ = align_;
+        if (valign is FILL_GRAB) {
+            this.valign = FILL;
+            grabVertical = true;
+        } else
+            this.valign = valign;
+        this.rowspan = rowspan;
+        this.colspan = colspan;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/TableWrapLayout.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,888 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.TableWrapLayout;
+
+import dwtx.ui.forms.widgets.TableWrapData;
+import dwtx.ui.forms.widgets.ILayoutExtension;
+import dwtx.ui.forms.widgets.LayoutCache;
+import dwtx.ui.forms.widgets.SizeCache;
+
+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.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+import tango.util.collection.HashMap;
+
+/**
+ * This implementation of the layout algorithm attempts to position controls in
+ * the composite using a two-pass autolayout HTML table altorithm recommeded by
+ * HTML 4.01 W3C specification (see
+ * http://www.w3.org/TR/html4/appendix/notes.html#h-B.5.2.2). The main
+ * differences with GridLayout is that it has two passes and that width and
+ * height are not calculated in the same pass.
+ * <p>
+ * The advantage of the algorithm over GridLayout is that it is capable of
+ * flowing text controls capable of line wrap. These controls do not have
+ * natural 'preferred size'. Instead, they are capable of providing the required
+ * height if the width is set. Consequently, this algorithm first calculates the
+ * widths that will be assigned to columns, and then passes those widths to the
+ * controls to calculate the height. When a composite with this layout is a
+ * child of the scrolling composite, they should interact in such a way that
+ * reduction in the scrolling composite width results in the reflow and increase
+ * of the overall height.
+ * <p>
+ * If none of the columns contain expandable and wrappable controls, the
+ * end-result will be similar to the one provided by GridLayout. The difference
+ * will show up for layouts that contain controls whose minimum and maximum
+ * widths are not the same.
+ *
+ * @see TableWrapData
+ * @since 3.0
+ */
+public final class TableWrapLayout : Layout, ILayoutExtension {
+
+    public alias Layout.computeSize computeSize;
+    /**
+     * Number of columns to use when positioning children (default is 1).
+     */
+    public int numColumns = 1;
+
+    /**
+     * Left margin variable (default is 5).
+     */
+    public int leftMargin = 5;
+
+    /**
+     * Right margin variable (default is 5).
+     */
+    public int rightMargin = 5;
+
+    /**
+     * Top margin variable (default is 5).
+     */
+    public int topMargin = 5;
+
+    /**
+     * Botom margin variable (default is 5).
+     */
+    public int bottomMargin = 5;
+
+    /**
+     * Horizontal spacing (default is 5).
+     */
+    public int horizontalSpacing = 5;
+
+    /**
+     * Vertical spacing (default is 5).
+     */
+    public int verticalSpacing = 5;
+
+    /**
+     * If set to <code>true</code>, all the columns will have the same width.
+     * Otherwise, column widths will be computed based on controls in them and
+     * their layout data (default is <code>false</code>).
+     */
+    public bool makeColumnsEqualWidth = false;
+
+    private bool initialLayout = true;
+
+    private ArraySeq!(Object) grid = null;
+
+    private HashMap!(Object,Object) rowspans;
+
+    private int[] minColumnWidths, maxColumnWidths;
+
+    private int widestColumnWidth;
+
+    private int[] growingColumns;
+
+    private int[] growingRows;
+
+    private LayoutCache cache;
+
+    private class RowSpan {
+        Control child;
+
+        int row;
+
+        int column;
+
+        int height;
+
+        int totalHeight;
+
+        public this(Control child, int column, int row) {
+            this.child = child;
+            this.column = column;
+            this.row = row;
+        }
+
+        /*
+         * Updates this row span's height with the given one if it is within
+         * this span.
+         */
+        public void update(int currentRow, int rowHeight) {
+            TableWrapData td = cast(TableWrapData) child.getLayoutData();
+            // is currentRow within this span?
+            if (currentRow >= row && currentRow < row + td.rowspan) {
+                totalHeight += rowHeight;
+                if (currentRow > row)
+                    totalHeight += verticalSpacing;
+            }
+        }
+
+        public int getRequiredHeightIncrease() {
+            if (totalHeight < height)
+                return height - totalHeight;
+            return 0;
+        }
+    }
+
+    this(){
+        cache = new LayoutCache();
+    }
+
+    /**
+     * Implements ILayoutExtension. Should not be called directly.
+     *
+     * @see ILayoutExtension
+     */
+    public int computeMinimumWidth(Composite parent, bool changed) {
+
+        Control[] children = parent.getChildren();
+        if (changed) {
+            cache.flush();
+        }
+
+        cache.setControls(children);
+
+        changed = true;
+        initializeIfNeeded(parent, changed);
+        if (initialLayout) {
+            changed = true;
+            initialLayout = false;
+        }
+        if (grid is null || changed) {
+            changed = true;
+            grid = new ArraySeq!(Object);
+            createGrid(parent);
+        }
+        if (minColumnWidths is null)
+            minColumnWidths = new int[numColumns];
+        for (int i = 0; i < numColumns; i++) {
+            minColumnWidths[i] = 0;
+        }
+        return internalGetMinimumWidth(parent, changed);
+    }
+
+    /**
+     * Implements ILayoutExtension. Should not be called directly.
+     *
+     * @see ILayoutExtension
+     */
+    public int computeMaximumWidth(Composite parent, bool changed) {
+        Control[] children = parent.getChildren();
+        if (changed) {
+            cache.flush();
+        }
+
+        cache.setControls(children);
+
+        changed = true;
+        initializeIfNeeded(parent, changed);
+        if (initialLayout) {
+            changed = true;
+            initialLayout = false;
+        }
+        if (grid is null || changed) {
+            changed = true;
+            grid = new ArraySeq!(Object);
+            createGrid(parent);
+        }
+        if (maxColumnWidths is null)
+            maxColumnWidths = new int[numColumns];
+        for (int i = 0; i < numColumns; i++) {
+            maxColumnWidths[i] = 0;
+        }
+        return internalGetMaximumWidth(parent, changed);
+    }
+
+    /**
+     * @see Layout#layout(Composite, bool)
+     */
+    protected void layout(Composite parent, bool changed) {
+
+        Rectangle clientArea = parent.getClientArea();
+        Control[] children = parent.getChildren();
+        if (changed) {
+            cache.flush();
+        }
+
+        if (children.length is 0)
+            return;
+
+        cache.setControls(children);
+
+        int parentWidth = clientArea.width;
+        changed = true;
+        initializeIfNeeded(parent, changed);
+        if (initialLayout) {
+            changed = true;
+            initialLayout = false;
+        }
+        if (grid is null || changed) {
+            changed = true;
+            grid = new ArraySeq!(Object);
+            createGrid(parent);
+        }
+        resetColumnWidths();
+        int minWidth = internalGetMinimumWidth(parent, changed);
+        int maxWidth = internalGetMaximumWidth(parent, changed);
+        int tableWidth = parentWidth;
+        int[] columnWidths;
+        if (parentWidth <= minWidth) {
+            tableWidth = minWidth;
+            if (makeColumnsEqualWidth) {
+                columnWidths = new int[numColumns];
+                for (int i = 0; i < numColumns; i++) {
+                    columnWidths[i] = widestColumnWidth;
+                }
+            } else
+                columnWidths = minColumnWidths;
+        } else if (parentWidth > maxWidth) {
+            if (growingColumns.length is 0) {
+                tableWidth = maxWidth;
+                columnWidths = maxColumnWidths;
+            } else {
+                columnWidths = new int[numColumns];
+                int colSpace = tableWidth - leftMargin - rightMargin;
+                colSpace -= (numColumns - 1) * horizontalSpacing;
+                int extra = parentWidth - maxWidth;
+                int colExtra = extra / growingColumns.length;
+                for (int i = 0; i < numColumns; i++) {
+                    columnWidths[i] = maxColumnWidths[i];
+                    if (isGrowingColumn(i)) {
+                        columnWidths[i] += colExtra;
+                    }
+                }
+            }
+        } else {
+            columnWidths = new int[numColumns];
+            if (makeColumnsEqualWidth) {
+                int colSpace = tableWidth - leftMargin - rightMargin;
+                colSpace -= (numColumns - 1) * horizontalSpacing;
+                int col = colSpace / numColumns;
+                for (int i = 0; i < numColumns; i++) {
+                    columnWidths[i] = col;
+                }
+            } else {
+                columnWidths = assignExtraSpace(tableWidth, maxWidth, minWidth);
+            }
+        }
+        int y = topMargin+clientArea.y;
+        int[] rowHeights = computeRowHeights(children, columnWidths, changed);
+        for (int i = 0; i < grid.size(); i++) {
+            int rowHeight = rowHeights[i];
+            int x = leftMargin+clientArea.x;
+            TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array;
+            for (int j = 0; j < numColumns; j++) {
+                TableWrapData td = row[j];
+                if (td.isItemData) {
+                    Control child = children[td.childIndex];
+                    placeControl(child, td, x, y, rowHeights, i);
+                }
+                x += columnWidths[j];
+                if (j < numColumns - 1)
+                    x += horizontalSpacing;
+            }
+            y += rowHeight + verticalSpacing;
+        }
+    }
+
+    int[] computeRowHeights(Control[] children, int[] columnWidths,
+            bool changed) {
+        int[] rowHeights = new int[grid.size()];
+        for (int i = 0; i < grid.size(); i++) {
+            TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array;
+            rowHeights[i] = 0;
+            for (int j = 0; j < numColumns; j++) {
+                TableWrapData td = row[j];
+                if (td.isItemData is false) {
+                    continue;
+                }
+                Control child = children[td.childIndex];
+                int span = td.colspan;
+                int cwidth = 0;
+                for (int k = j; k < j + span; k++) {
+                    cwidth += columnWidths[k];
+                    if (k < j + span - 1)
+                        cwidth += horizontalSpacing;
+                }
+                Point size = computeSize(td.childIndex, cwidth, td.indent, td.maxWidth, td.maxHeight);
+                td.compWidth = cwidth;
+                if (td.heightHint !is DWT.DEFAULT) {
+                    size = new Point(size.x, td.heightHint);
+                }
+                td.compSize = size;
+                RowSpan rowspan = cast(RowSpan) rowspans.get(child);
+                if (rowspan is null) {
+                    rowHeights[i] = Math.max(rowHeights[i], size.y);
+                } else
+                    rowspan.height = size.y;
+            }
+            updateRowSpans(i, rowHeights[i]);
+        }
+        foreach( k, v; rowspans ){
+            RowSpan rowspan = cast(RowSpan) v;
+            int increase = rowspan.getRequiredHeightIncrease();
+            if (increase is 0)
+                continue;
+            TableWrapData td = cast(TableWrapData) rowspan.child.getLayoutData();
+            int ngrowing = 0;
+            int[] affectedRows = new int[grid.size()];
+            for (int i = 0; i < growingRows.length; i++) {
+                int growingRow = growingRows[i];
+                if (growingRow >= rowspan.row
+                        && growingRow < rowspan.row + td.rowspan) {
+                    affectedRows[ngrowing++] = growingRow;
+                }
+            }
+            if (ngrowing is 0) {
+                ngrowing = 1;
+                affectedRows[0] = rowspan.row + td.rowspan - 1;
+            }
+            increase += increase % ngrowing;
+            int perRowIncrease = increase / ngrowing;
+            for (int i = 0; i < ngrowing; i++) {
+                int growingRow = affectedRows[i];
+                rowHeights[growingRow] += perRowIncrease;
+            }
+        }
+        return rowHeights;
+    }
+
+    bool isGrowingColumn(int col) {
+        if (growingColumns is null)
+            return false;
+        for (int i = 0; i < growingColumns.length; i++) {
+            if (col is growingColumns[i])
+                return true;
+        }
+        return false;
+    }
+
+    int[] assignExtraSpace(int tableWidth, int maxWidth, int minWidth) {
+        int fixedPart = leftMargin + rightMargin + (numColumns - 1)
+                * horizontalSpacing;
+        int D = maxWidth - minWidth;
+        int W = tableWidth - fixedPart - minWidth;
+        int widths[] = new int[numColumns];
+        int rem = 0;
+        for (int i = 0; i < numColumns; i++) {
+            int cmin = minColumnWidths[i];
+            int cmax = maxColumnWidths[i];
+            int d = cmax - cmin;
+            int extra = D !is 0 ? (d * W) / D : 0;
+            if (i < numColumns - 1) {
+                widths[i] = cmin + extra;
+                rem += widths[i];
+            } else {
+                widths[i] = tableWidth - fixedPart - rem;
+            }
+        }
+        return widths;
+    }
+
+    Point computeSize(int childIndex, int width, int indent, int maxWidth, int maxHeight) {
+        int widthArg = width - indent;
+        SizeCache controlCache = cache.getCache(childIndex);
+        if (!isWrap(controlCache.getControl()))
+            widthArg = DWT.DEFAULT;
+        Point size = controlCache.computeSize(widthArg, DWT.DEFAULT);
+        if (maxWidth !is DWT.DEFAULT)
+            size.x = Math.min(size.x, maxWidth);
+        if (maxHeight !is DWT.DEFAULT)
+            size.y = Math.min(size.y, maxHeight);
+        size.x += indent;
+        return size;
+    }
+
+    void placeControl(Control control, TableWrapData td, int x, int y,
+            int[] rowHeights, int row) {
+        int xloc = x + td.indent;
+        int yloc = y;
+        int height = td.compSize.y;
+        int colWidth = td.compWidth - td.indent;
+        int width = td.compSize.x-td.indent;
+        width = Math.min(width, colWidth);
+        int slotHeight = rowHeights[row];
+        RowSpan rowspan = cast(RowSpan) rowspans.get(control);
+        if (rowspan !is null) {
+            slotHeight = 0;
+            for (int i = row; i < row + td.rowspan; i++) {
+                if (i > row)
+                    slotHeight += verticalSpacing;
+                slotHeight += rowHeights[i];
+            }
+        }
+        // align horizontally
+        if (td.align_ is TableWrapData.CENTER) {
+            xloc = x + colWidth / 2 - width / 2;
+        } else if (td.align_ is TableWrapData.RIGHT) {
+            xloc = x + colWidth - width;
+        } else if (td.align_ is TableWrapData.FILL) {
+            width = colWidth;
+        }
+        // align vertically
+        if (td.valign is TableWrapData.MIDDLE) {
+            yloc = y + slotHeight / 2 - height / 2;
+        } else if (td.valign is TableWrapData.BOTTOM) {
+            yloc = y + slotHeight - height;
+        } else if (td.valign is TableWrapData.FILL) {
+            height = slotHeight;
+        }
+        control.setBounds(xloc, yloc, width, height);
+    }
+
+    void createGrid(Composite composite) {
+        int row, column, rowFill, columnFill;
+        Control[] children;
+        TableWrapData spacerSpec;
+        ArraySeq!(Object) growingCols = new ArraySeq!(Object);
+        ArraySeq!(Object) growingRows = new ArraySeq!(Object);
+        rowspans = new HashMap!(Object,Object);
+        //
+        children = composite.getChildren();
+        if (children.length is 0)
+            return;
+        //
+        grid.append( new ArrayWrapperT!(TableWrapData)(createEmptyRow()));
+        row = 0;
+        column = 0;
+        // Loop through the children and place their associated layout specs in
+        // the
+        // grid. Placement occurs left to right, top to bottom (i.e., by row).
+        for (int i = 0; i < children.length; i++) {
+            // Find the first available spot in the grid.
+            Control child = children[i];
+            TableWrapData spec = cast(TableWrapData) child.getLayoutData();
+            while ((cast(ArrayWrapperT!(TableWrapData)) grid.get(row)).array[column] !is null) {
+                column = column + 1;
+                if (column >= numColumns) {
+                    row = row + 1;
+                    column = 0;
+                    if (row >= grid.size()) {
+                        grid.append(new ArrayWrapperT!(TableWrapData)(createEmptyRow()));
+                    }
+                }
+            }
+            // See if the place will support the widget's horizontal span. If
+            // not, go to the
+            // next row.
+            if (column + spec.colspan - 1 >= numColumns) {
+                grid.append(new ArrayWrapperT!(TableWrapData)(createEmptyRow()));
+                row = row + 1;
+                column = 0;
+            }
+            // The vertical span for the item will be at least 1. If it is > 1,
+            // add other rows to the grid.
+            if (spec.rowspan > 1) {
+                rowspans.add(child, new RowSpan(child, column, row));
+            }
+            for (int j = 2; j <= spec.rowspan; j++) {
+                if (row + j > grid.size()) {
+                    grid.append(new ArrayWrapperT!(TableWrapData)(createEmptyRow()));
+                }
+            }
+            // Store the layout spec. Also cache the childIndex. NOTE: That we
+            // assume the children of a
+            // composite are maintained in the order in which they are created
+            // and added to the composite.
+            (cast(ArrayWrapperT!(TableWrapData)) grid.get(row)).array[column] = spec;
+            spec.childIndex = i;
+            if (spec.grabHorizontal) {
+                updateGrowingColumns(growingCols, spec, column);
+            }
+            if (spec.grabVertical) {
+                updateGrowingRows(growingRows, spec, row);
+            }
+            // Put spacers in the grid to account for the item's vertical and
+            // horizontal
+            // span.
+            rowFill = spec.rowspan - 1;
+            columnFill = spec.colspan - 1;
+            for (int r = 1; r <= rowFill; r++) {
+                for (int c = 0; c < spec.colspan; c++) {
+                    spacerSpec = new TableWrapData();
+                    spacerSpec.isItemData = false;
+                    (cast(ArrayWrapperT!(TableWrapData)) grid.get(row + r)).array[column + c] = spacerSpec;
+                }
+            }
+            for (int c = 1; c <= columnFill; c++) {
+                for (int r = 0; r < spec.rowspan; r++) {
+                    spacerSpec = new TableWrapData();
+                    spacerSpec.isItemData = false;
+                    (cast(ArrayWrapperT!(TableWrapData)) grid.get(row + r)).array[column + c] = spacerSpec;
+                }
+            }
+            column = column + spec.colspan - 1;
+        }
+        // Fill out empty grid cells with spacers.
+        for (int k = column + 1; k < numColumns; k++) {
+            spacerSpec = new TableWrapData();
+            spacerSpec.isItemData = false;
+            (cast(ArrayWrapperT!(TableWrapData)) grid.get(row)).array[k] = spacerSpec;
+        }
+        for (int k = row + 1; k < grid.size(); k++) {
+            spacerSpec = new TableWrapData();
+            spacerSpec.isItemData = false;
+            (cast(ArrayWrapperT!(TableWrapData)) grid.get(k)).array[column] = spacerSpec;
+        }
+        growingColumns = new int[growingCols.size()];
+        for (int i = 0; i < growingCols.size(); i++) {
+            growingColumns[i] = (cast(Integer) growingCols.get(i)).intValue();
+        }
+        this.growingRows = new int[growingRows.size()];
+        for (int i = 0; i < growingRows.size(); i++) {
+            this.growingRows[i] = (cast(Integer) growingRows.get(i)).intValue();
+        }
+    }
+
+    private void updateGrowingColumns(ArraySeq!(Object) growingColumns,
+            TableWrapData spec, int column) {
+        int affectedColumn = column + spec.colspan - 1;
+        for (int i = 0; i < growingColumns.size(); i++) {
+            Integer col = cast(Integer) growingColumns.get(i);
+            if (col.intValue() is affectedColumn)
+                return;
+        }
+        growingColumns.append(new Integer(affectedColumn));
+    }
+
+    private void updateGrowingRows(ArraySeq!(Object) growingRows, TableWrapData spec,
+            int row) {
+        int affectedRow = row + spec.rowspan - 1;
+        for (int i = 0; i < growingRows.size(); i++) {
+            Integer irow = cast(Integer) growingRows.get(i);
+            if (irow.intValue() is affectedRow)
+                return;
+        }
+        growingRows.append(new Integer(affectedRow));
+    }
+
+    private TableWrapData[] createEmptyRow() {
+        TableWrapData[] row = new TableWrapData[numColumns];
+        for (int i = 0; i < numColumns; i++)
+            row[i] = null;
+        return row;
+    }
+
+    /**
+     * @see Layout#computeSize(Composite, int, int, bool)
+     */
+    /+protected+/ override Point computeSize(Composite parent, int wHint, int hHint,
+            bool changed) {
+        Control[] children = parent.getChildren();
+        if (changed) {
+            cache.flush();
+        }
+        if (children.length is 0) {
+            return new Point(0, 0);
+        }
+        cache.setControls(children);
+
+        int parentWidth = wHint;
+        changed = true;
+        initializeIfNeeded(parent, changed);
+        if (initialLayout) {
+            changed = true;
+            initialLayout = false;
+        }
+        if (grid is null || changed) {
+            changed = true;
+            grid = new ArraySeq!(Object);
+            createGrid(parent);
+        }
+        resetColumnWidths();
+        int minWidth = internalGetMinimumWidth(parent, changed);
+        int maxWidth = internalGetMaximumWidth(parent, changed);
+
+        if (wHint is DWT.DEFAULT)
+            parentWidth = maxWidth;
+
+        int tableWidth = parentWidth;
+        int[] columnWidths;
+        if (parentWidth <= minWidth) {
+            tableWidth = minWidth;
+            if (makeColumnsEqualWidth) {
+                columnWidths = new int[numColumns];
+                for (int i = 0; i < numColumns; i++) {
+                    columnWidths[i] = widestColumnWidth;
+                }
+            } else
+                columnWidths = minColumnWidths;
+        } else if (parentWidth >= maxWidth) {
+            if (makeColumnsEqualWidth) {
+                columnWidths = new int[numColumns];
+                int colSpace = parentWidth - leftMargin - rightMargin;
+                colSpace -= (numColumns - 1) * horizontalSpacing;
+                int col = colSpace / numColumns;
+                for (int i = 0; i < numColumns; i++) {
+                    columnWidths[i] = col;
+                }
+            } else {
+                tableWidth = maxWidth;
+                columnWidths = maxColumnWidths;
+            }
+        } else {
+            columnWidths = new int[numColumns];
+            if (makeColumnsEqualWidth) {
+                int colSpace = tableWidth - leftMargin - rightMargin;
+                colSpace -= (numColumns - 1) * horizontalSpacing;
+                int col = colSpace / numColumns;
+                for (int i = 0; i < numColumns; i++) {
+                    columnWidths[i] = col;
+                }
+            } else {
+                columnWidths = assignExtraSpace(tableWidth, maxWidth, minWidth);
+            }
+        }
+        int totalHeight = 0;
+        int innerHeight = 0;
+        // compute widths
+        for (int i = 0; i < grid.size(); i++) {
+            TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array;
+            // assign widths, calculate heights
+            int rowHeight = 0;
+            for (int j = 0; j < numColumns; j++) {
+                TableWrapData td = row[j];
+                if (td.isItemData is false) {
+                    continue;
+                }
+                Control child = children[td.childIndex];
+                int span = td.colspan;
+                int cwidth = 0;
+                for (int k = j; k < j + span; k++) {
+                    if (k > j)
+                        cwidth += horizontalSpacing;
+                    cwidth += columnWidths[k];
+                }
+                int cy = td.heightHint;
+                if (cy is DWT.DEFAULT) {
+                    Point size = computeSize(td.childIndex, cwidth, td.indent, td.maxWidth, td.maxHeight);
+                    cy = size.y;
+                }
+                RowSpan rowspan = cast(RowSpan) rowspans.get(child);
+                if (rowspan !is null) {
+                    // don't take the height of this child into acount
+                    // because it spans multiple rows
+                    rowspan.height = cy;
+                } else {
+                    rowHeight = Math.max(rowHeight, cy);
+                }
+            }
+            updateRowSpans(i, rowHeight);
+            if (i > 0)
+                innerHeight += verticalSpacing;
+            innerHeight += rowHeight;
+        }
+        if (!rowspans.drained())
+            innerHeight = compensateForRowSpans(innerHeight);
+        totalHeight = topMargin + innerHeight + bottomMargin;
+        return new Point(tableWidth, totalHeight);
+    }
+
+    private void updateRowSpans(int row, int rowHeight) {
+        if (rowspans is null || rowspans.size() is 0)
+            return;
+        foreach( k, v; rowspans ){
+            RowSpan rowspan = cast(RowSpan) v;
+            rowspan.update(row, rowHeight);
+        }
+    }
+
+    private int compensateForRowSpans(int totalHeight) {
+        foreach( k, v; rowspans ){
+            RowSpan rowspan = cast(RowSpan) v;
+            totalHeight += rowspan.getRequiredHeightIncrease();
+        }
+        return totalHeight;
+    }
+
+    int internalGetMinimumWidth(Composite parent, bool changed) {
+        if (changed)
+            //calculateMinimumColumnWidths(parent, true);
+            calculateColumnWidths(parent, minColumnWidths, false, true);
+        int minimumWidth = 0;
+        widestColumnWidth = 0;
+        if (makeColumnsEqualWidth) {
+            for (int i = 0; i < numColumns; i++) {
+                widestColumnWidth = Math.max(widestColumnWidth,
+                        minColumnWidths[i]);
+            }
+        }
+        for (int i = 0; i < numColumns; i++) {
+            if (i > 0)
+                minimumWidth += horizontalSpacing;
+            if (makeColumnsEqualWidth)
+                minimumWidth += widestColumnWidth;
+            else
+                minimumWidth += minColumnWidths[i];
+        }
+        // add margins
+        minimumWidth += leftMargin + rightMargin;
+        return minimumWidth;
+    }
+
+    int internalGetMaximumWidth(Composite parent, bool changed) {
+        if (changed)
+            //calculateMaximumColumnWidths(parent, true);
+            calculateColumnWidths(parent, maxColumnWidths, true, true);
+        int maximumWidth = 0;
+        for (int i = 0; i < numColumns; i++) {
+            if (i > 0)
+                maximumWidth += horizontalSpacing;
+            maximumWidth += maxColumnWidths[i];
+        }
+        // add margins
+        maximumWidth += leftMargin + rightMargin;
+        return maximumWidth;
+    }
+
+    void resetColumnWidths() {
+        if (minColumnWidths is null)
+            minColumnWidths = new int[numColumns];
+        if (maxColumnWidths is null)
+            maxColumnWidths = new int[numColumns];
+        for (int i = 0; i < numColumns; i++) {
+            minColumnWidths[i] = 0;
+        }
+        for (int i = 0; i < numColumns; i++) {
+            maxColumnWidths[i] = 0;
+        }
+    }
+
+    void calculateColumnWidths(Composite parent, int [] columnWidths, bool max, bool changed) {
+        bool secondPassNeeded=false;
+        for (int i = 0; i < grid.size(); i++) {
+            TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array;
+            for (int j = 0; j < numColumns; j++) {
+                TableWrapData td = row[j];
+                if (td.isItemData is false)
+                    continue;
+
+                if (td.colspan>1) {
+                    // we will not do controls with multiple column span
+                    // here - increment and continue
+                    secondPassNeeded=true;
+                    j+=td.colspan-1;
+                    continue;
+                }
+
+                SizeCache childCache = cache.getCache(td.childIndex);
+                // !!
+                int width = max?childCache.computeMaximumWidth():childCache.computeMinimumWidth();
+                if (td.maxWidth !is DWT.DEFAULT)
+                    width = Math.min(width, td.maxWidth);
+
+                width += td.indent;
+                columnWidths[j] = Math.max(columnWidths[j], width);
+            }
+        }
+        if (!secondPassNeeded) return;
+
+        // Second pass for controls with multi-column horizontal span
+        for (int i = 0; i < grid.size(); i++) {
+            TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array;
+            for (int j = 0; j < numColumns; j++) {
+                TableWrapData td = row[j];
+                if (td.isItemData is false || td.colspan is 1)
+                    continue;
+
+                SizeCache childCache = cache.getCache(td.childIndex);
+                int width = max?childCache.computeMaximumWidth():childCache.computeMinimumWidth();
+                if (td.maxWidth !is DWT.DEFAULT)
+                    width = Math.min(width, td.maxWidth);
+
+                width += td.indent;
+                // check if the current width is enough to
+                // support the control; if not, add the delta to
+                // the last column or to all the growing columns, if present
+                int current = 0;
+                for (int k = j; k < j + td.colspan; k++) {
+                    if (k > j)
+                        current += horizontalSpacing;
+                    current += columnWidths[k];
+                }
+                if (width <= current) {
+                    // we are ok - nothing to do here
+                } else {
+                    int ndiv = 0;
+                    if (growingColumns !is null) {
+                        for (int k = j; k < j + td.colspan; k++) {
+                            if (isGrowingColumn(k)) {
+                                ndiv++;
+                            }
+                        }
+                    }
+                    if (ndiv is 0) {
+                        // add the delta to the last column
+                        columnWidths[j + td.colspan - 1] += width
+                                - current;
+                    } else {
+                        // distribute the delta to the growing
+                        // columns
+                        int percolumn = (width - current) / ndiv;
+                        if ((width - current) % ndiv > 0)
+                            percolumn++;
+                        for (int k = j; k < j + td.colspan; k++) {
+                            if (isGrowingColumn(k))
+                                columnWidths[k] += percolumn;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    bool isWrap(Control control) {
+        if (null !is cast(Composite)control
+                && null !is cast(ILayoutExtension)((cast(Composite) control).getLayout()) )
+            return true;
+        return (control.getStyle() & DWT.WRAP) !is 0;
+    }
+
+    private void initializeIfNeeded(Composite parent, bool changed) {
+        if (changed)
+            initialLayout = true;
+        if (initialLayout) {
+            initializeLayoutData(parent);
+            initialLayout = false;
+        }
+    }
+
+    void initializeLayoutData(Composite composite) {
+        Control[] children = composite.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            Control child = children[i];
+            if (child.getLayoutData() is null) {
+                child.setLayoutData(new TableWrapData());
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/ToggleHyperlink.d	Sat May 24 05:11:16 2008 +0200
@@ -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 dwtx.ui.forms.widgets.ToggleHyperlink;
+
+import dwtx.ui.forms.widgets.AbstractHyperlink;
+
+import dwt.DWT;
+import dwt.accessibility.ACC;
+import dwt.accessibility.AccessibleAdapter;
+import dwt.accessibility.AccessibleControlAdapter;
+import dwt.accessibility.AccessibleControlEvent;
+import dwt.accessibility.AccessibleEvent;
+import dwt.graphics.Color;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwtx.ui.forms.events.HyperlinkAdapter;
+import dwtx.ui.forms.events.HyperlinkEvent;
+import dwtx.ui.internal.forms.Messages;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A custom selectable control that can be used to control areas that can be
+ * expanded or collapsed.
+ * <p>
+ * This is an abstract class. Subclasses are responsible for rendering the
+ * control using decoration and hover decoration color. Control should be
+ * rendered based on the current expansion state.
+ *
+ * @since 3.0
+ */
+public abstract class ToggleHyperlink : AbstractHyperlink {
+    protected int innerWidth;
+    protected int innerHeight;
+    protected bool hover;
+    package bool hover_package(){
+        return hover;
+    }
+    package bool hover_package( bool v){
+        return (hover = v);
+    }
+    private bool expanded;
+    private Color decorationColor;
+    private Color hoverColor;
+    /**
+     * Creates a control in a provided composite.
+     *
+     * @param parent
+     *            the parent
+     * @param style
+     *            the style
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        Listener listener = new class Listener {
+            public void handleEvent(Event e) {
+                switch (e.type) {
+                    case DWT.MouseEnter:
+                        hover=true;
+                        redraw();
+                        break;
+                    case DWT.MouseExit:
+                        hover = false;
+                        redraw();
+                        break;
+                    case DWT.KeyDown:
+                        onKeyDown(e);
+                        break;
+                }
+            }
+        };
+        addListener(DWT.MouseEnter, listener);
+        addListener(DWT.MouseExit, listener);
+        addListener(DWT.KeyDown, listener);
+        addHyperlinkListener(new class HyperlinkAdapter {
+            public void linkActivated(HyperlinkEvent e) {
+                setExpanded(!isExpanded());
+            }
+        });
+        initAccessible();
+    }
+    /**
+     * Sets the color of the decoration.
+     *
+     * @param decorationColor
+     */
+    public void setDecorationColor(Color decorationColor) {
+        this.decorationColor = decorationColor;
+    }
+    /**
+     * Returns the color of the decoration.
+     *
+     * @return decoration color
+     */
+    public Color getDecorationColor() {
+        return decorationColor;
+    }
+    /**
+     * Sets the hover color of decoration. Hover color will be used when mouse
+     * enters the decoration area.
+     *
+     * @param hoverColor
+     *            the hover color to use
+     */
+    public void setHoverDecorationColor(Color hoverColor) {
+        this.hoverColor = hoverColor;
+    }
+    /**
+     * Returns the hover color of the decoration.
+     *
+     * @return the hover color of the decoration.
+     * @since 3.1
+     */
+    public Color getHoverDecorationColor() {
+        return hoverColor;
+    }
+
+    /**
+     * Returns the hover color of the decoration.
+     *
+     * @return the hover color of the decoration.
+     * @deprecated use <code>getHoverDecorationColor</code>
+     * @see #getHoverDecorationColor()
+     */
+    public Color geHoverDecorationColor() {
+        return hoverColor;
+    }
+    /**
+     * Computes the size of the control.
+     *
+     * @param wHint
+     *            width hint
+     * @param hHint
+     *            height hint
+     * @param changed
+     *            if true, flush any saved layout state
+     */
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        int width, height;
+        /*
+        if (wHint !is DWT.DEFAULT)
+            width = wHint;
+        else */
+            width = innerWidth + 2 * marginWidth;
+        /*
+        if (hHint !is DWT.DEFAULT)
+            height = hHint;
+        else */
+            height = innerHeight + 2 * marginHeight;
+        return new Point(width, height);
+    }
+    /**
+     * Returns the expansion state of the toggle control. When toggle is in the
+     * normal (downward) state, the value is <samp>true </samp>. Collapsed
+     * control will return <samp>false </samp>.
+     *
+     * @return <samp>false </samp> if collapsed, <samp>true </samp> otherwise.
+     */
+    public bool isExpanded() {
+        return expanded;
+    }
+    /**
+     * Sets the expansion state of the twistie control
+     *
+     * @param expanded the expansion state
+     */
+    public void setExpanded(bool expanded) {
+        this.expanded = expanded;
+        getAccessible().selectionChanged();
+        redraw();
+    }
+    private void initAccessible() {
+        getAccessible().addAccessibleListener(new class AccessibleAdapter {
+            public void getHelp(AccessibleEvent e) {
+                e.result = getToolTipText();
+            }
+            public void getName(AccessibleEvent e) {
+                String name=Messages.ToggleHyperlink_accessibleName;
+                if (null !is cast(ExpandableComposite)getParent() ) {
+                    name ~= Messages.ToggleHyperlink_accessibleColumn ~ (cast(ExpandableComposite)getParent()).getText();
+                    int index = name.indexOf('&');
+                    if (index !is -1) {
+                        name = name.substring(0, index) + name.substring(index + 1);
+                    }
+                }
+                e.result = name;
+            }
+            public void getDescription(AccessibleEvent e) {
+                getName(e);
+            }
+        });
+        getAccessible().addAccessibleControlListener(
+                new class AccessibleControlAdapter {
+                    public void getChildAtPoint(AccessibleControlEvent e) {
+                        Point testPoint = toControl(new Point(e.x, e.y));
+                        if (getBounds().contains(testPoint)) {
+                            e.childID = ACC.CHILDID_SELF;
+                        }
+                    }
+                    public void getLocation(AccessibleControlEvent e) {
+                        Rectangle location = getBounds();
+                        Point pt = toDisplay(new Point(location.x, location.y));
+                        e.x = pt.x;
+                        e.y = pt.y;
+                        e.width = location.width;
+                        e.height = location.height;
+                    }
+                    public void getSelection (AccessibleControlEvent e) {
+                        if (this.outer.getSelection())
+                            e.childID = ACC.CHILDID_SELF;
+                    }
+
+                    public void getFocus (AccessibleControlEvent e) {
+                        if (this.outer.getSelection())
+                            e.childID = ACC.CHILDID_SELF;
+                    }
+                    public void getChildCount(AccessibleControlEvent e) {
+                        e.detail = 0;
+                    }
+                    public void getRole(AccessibleControlEvent e) {
+                        e.detail = ACC.ROLE_TREE;
+                    }
+                    public void getState(AccessibleControlEvent e) {
+                        int state = ACC.STATE_FOCUSABLE;
+                        if (this.outer.getSelection())
+                            state |= ACC.STATE_FOCUSED;
+                        state |= this.outer.isExpanded()
+                                ? ACC.STATE_EXPANDED
+                                : ACC.STATE_COLLAPSED;
+                        e.detail = state;
+                    }
+                });
+    }
+    private void onKeyDown(Event e) {
+        if (e.keyCodeisDWT.ARROW_RIGHT) {
+            // expand if collapsed
+            if (!isExpanded()) {
+                handleActivate(e);
+            }
+            e.doit=false;
+        }
+        else if (e.keyCodeisDWT.ARROW_LEFT) {
+            // collapse if expanded
+            if (isExpanded()) {
+                handleActivate(e);
+            }
+            e.doit=false;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/TreeNode.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.TreeNode;
+
+import dwtx.ui.forms.widgets.ToggleHyperlink;
+
+import dwt.DWT;
+import dwt.events.PaintEvent;
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Composite;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A custom selectable control that can be used to control areas that can be
+ * expanded or collapsed. The control control can be toggled between selected
+ * and deselected state with a mouse or by pressing 'Enter' while the control
+ * has focus.
+ * <p>
+ * The control is rendered as box with a '+' or '-' sign, depending on the
+ * expansion state. Focus indication is rendered around the box when the
+ * control has keyboard focus.
+ *
+ * @see Twistie
+ * @since 3.0
+ */
+public class TreeNode : ToggleHyperlink {
+    /**
+     * Creates a control in a provided composite.
+     *
+     * @param parent
+     *            the parent
+     * @param style
+     *            the style
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        innerWidth = 10;
+        innerHeight = 10;
+    }
+    protected void paint(PaintEvent e) {
+        paintHyperlink(e.gc);
+    }
+    protected void paintHyperlink(GC gc) {
+        Rectangle box = getBoxBounds(gc);
+        gc.setForeground(getDisplay().getSystemColor(
+                DWT.COLOR_WIDGET_NORMAL_SHADOW));
+        gc.drawRectangle(box);
+        gc.setForeground(getForeground());
+        gc.drawLine(box.x + 2, box.y + 4, box.x + 6, box.y + 4);
+        if (!isExpanded()) {
+            gc.drawLine(box.x + 4, box.y + 2, box.x + 4, box.y + 6);
+        }
+        if (paintFocus && getSelection()) {
+            gc.setForeground(getForeground());
+            gc.drawFocus(box.x - 1, box.y - 1, box.width + 3, box.height + 3);
+        }
+    }
+    private Rectangle getBoxBounds(GC gc) {
+        int x = 1;
+        int y = 0;
+        gc.setFont(getFont());
+        //int height = gc.getFontMetrics().getHeight();
+        //y = height / 2 - 4;
+        //y = Math.max(y, 0);
+        y = 2;
+        return new Rectangle(x, y, 8, 8);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/forms/widgets/Twistie.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * 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 dwtx.ui.forms.widgets.Twistie;
+
+import dwtx.ui.forms.widgets.ToggleHyperlink;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.widgets.Composite;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A custom selectable control that can be used to control areas that can be
+ * expanded or collapsed. The control control can be toggled between selected
+ * and deselected state with a mouse or by pressing 'Enter' while the control
+ * has focus.
+ * <p>
+ * The control is rendered as a triangle that points to the right in the
+ * collapsed and down in the expanded state. Triangle color can be changed.
+ *
+ * @see TreeNode
+ * @since 3.0
+ */
+public class Twistie : ToggleHyperlink {
+    private static const int[] onPoints = [ 0, 2, 8, 2, 4, 6 ];
+
+    private static const int[] offPoints = [ 2, -1, 2, 8, 6, 4 ];
+
+    /**
+     * Creates a control in a provided composite.
+     *
+     * @param parent
+     *            the parent
+     * @param style
+     *            the style
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        innerWidth = 9;
+        innerHeight = 9;
+    }
+
+    /*
+     * @see SelectableControl#paint(GC)
+     */
+    protected void paintHyperlink(GC gc) {
+        Color bg;
+        if (!isEnabled())
+            bg = getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW);
+        else if (hover && getHoverDecorationColor() !is null)
+            bg = getHoverDecorationColor();
+        else if (getDecorationColor() !is null)
+            bg = getDecorationColor();
+        else
+            bg = getForeground();
+        gc.setBackground(bg);
+        int[] data;
+        Point size = getSize();
+        int x = (size.x - 9) / 2;
+        int y = (size.y - 9) / 2;
+        if (isExpanded())
+            data = translate(onPoints, x, y);
+        else
+            data = translate(offPoints, x, y);
+        gc.fillPolygon(data);
+        gc.setBackground(getBackground());
+    }
+
+    private int[] translate(int[] data, int x, int y) {
+        int[] target = new int[data.length];
+        for (int i = 0; i < data.length; i += 2) {
+            target[i] = data[i] + x;
+        }
+        for (int i = 1; i < data.length; i += 2) {
+            target[i] = data[i] + y;
+        }
+        return target;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwt.widgets.Control#setEnabled(bool)
+     */
+    public void setEnabled(bool enabled) {
+        super.setEnabled(enabled);
+        redraw();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/IMessageToolTipManager.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.internal.forms.IMessageToolTipManager;
+
+
+import dwt.widgets.Control;
+import dwtx.ui.forms.IMessageManager;
+import dwtx.ui.forms.widgets.Form;
+
+import dwt.dwthelper.utils;
+
+/**
+ * The classes that implement this interface are responsible for managing custom
+ * tool tips for message-related controls in the form header. By default, a
+ * simple manager is installed by the form that uses the built-in widget tool
+ * tips for this purpose. Clients can replace this behaviour with richer tool
+ * tips that show images, links and other content.
+ * <p>
+ * The message-related controls in the header are:
+ * <ul>
+ * <li>Image label - used to replace the form title image with the message type
+ * image</li>
+ * <li>Message label - renders the message as a static text.</li>
+ * <li>Message hyperlink - renders the message as a hyperlink.</li>
+ * </ul>
+ * The message manager will be asked to create the tool tip for any and all of
+ * the controls listed above in its factory method. After that, it will be asked
+ * to update whenever the message information changes in the form. For this
+ * reason, the manager is expected to retain references to the tool tips and
+ * update them with new content when asked.
+ *
+ * @see IMessageManager
+ * @see Form
+ *      <p>
+ *      <strong>EXPERIMENTAL</strong>. This class or interface has been added
+ *      as part of a work in progress. There is no guarantee that this API will
+ *      work or that it will remain the same. Please do not use this API without
+ *      consulting with the Platform UA team.
+ *      </p>
+ * @since 3.3
+ */
+public interface IMessageToolTipManager {
+    /**
+     * Creates the custom tool tip for the provided control.
+     *
+     * @param control
+     *            the control for which to create a custom tool tip
+     * @param imageControl
+     *            <code>true</code> if the control is used to render the title
+     *            image, <code>false</code> otherwise.
+     */
+    void createToolTip(Control control, bool imageControl);
+
+    /**
+     * Updates all the managed tool tips. The manager should get the current
+     * message, message type and optional children messages from the form to
+     * update the custom tool tips.
+     */
+    void update();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/MessageManager.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,650 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.internal.forms.MessageManager;
+
+import dwtx.ui.internal.forms.Messages;
+
+import dwt.DWT;
+import dwt.custom.CLabel;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Label;
+import dwtx.jface.dialogs.IMessageProvider;
+import dwtx.jface.fieldassist.ControlDecoration;
+import dwtx.jface.fieldassist.FieldDecoration;
+import dwtx.jface.fieldassist.FieldDecorationRegistry;
+import dwtx.ui.forms.IMessage;
+import dwtx.ui.forms.IMessageManager;
+import dwtx.ui.forms.IMessagePrefixProvider;
+import dwtx.ui.forms.widgets.Hyperlink;
+import dwtx.ui.forms.widgets.ScrolledForm;
+
+import dwt.dwthelper.utils;
+
+import tango.util.collection.ArraySeq;
+import tango.util.collection.HashMap;
+import tango.util.Convert;
+
+/**
+ * @see IMessageManager
+ */
+
+public class MessageManager : IMessageManager {
+
+    private static final DefaultPrefixProvider DEFAULT_PREFIX_PROVIDER_;
+    private static final DefaultPrefixProvider DEFAULT_PREFIX_PROVIDER(){
+        if( DEFAULT_PREFIX_PROVIDER_ is null ){
+            synchronized(MessageManager.classinfo){
+                if( DEFAULT_PREFIX_PROVIDER_ is null ){
+                    DEFAULT_PREFIX_PROVIDER_ = new DefaultPrefixProvider();
+                }
+            }
+        }
+        return DEFAULT_PREFIX_PROVIDER_;
+    }
+    private ArraySeq!(Object) messages;
+    private HashMap!(Object,Object) decorators;
+    private bool autoUpdate = true;
+    private ScrolledForm scrolledForm;
+    private IMessagePrefixProvider prefixProvider;
+    private int decorationPosition = DWT.LEFT | DWT.BOTTOM;
+
+    private static FieldDecoration standardError_;
+    private static FieldDecoration standardError(){
+        if( standardError_ is null ){
+            synchronized(MessageManager.classinfo){
+                if( standardError_ is null ){
+                    standardError_ = FieldDecorationRegistry
+                        .getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR);
+                }
+            }
+        }
+        return standardError_;
+    }
+
+    private static FieldDecoration standardWarning_;
+    private static FieldDecoration standardWarning(){
+        if( standardWarning_ is null ){
+            synchronized(MessageManager.classinfo){
+                if( standardWarning_ is null ){
+                    standardWarning_ = FieldDecorationRegistry
+                        .getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_WARNING);
+                }
+            }
+        }
+        return standardWarning_;
+    }
+
+    private static FieldDecoration standardInformation_;
+    private static FieldDecoration standardInformation(){
+        if( standardInformation_ is null ){
+            synchronized(MessageManager.classinfo){
+                if( standardInformation_ is null ){
+                    standardInformation_ = FieldDecorationRegistry
+                        .getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION);
+                }
+            }
+        }
+        return standardInformation_;
+    }
+
+    private static const String[] SINGLE_MESSAGE_SUMMARY_KEYS = [
+            Messages.MessageManager_sMessageSummary,
+            Messages.MessageManager_sMessageSummary,
+            Messages.MessageManager_sWarningSummary,
+            Messages.MessageManager_sErrorSummary ];
+
+    private static const String[] MULTIPLE_MESSAGE_SUMMARY_KEYS = [
+            Messages.MessageManager_pMessageSummary,
+            Messages.MessageManager_pMessageSummary,
+            Messages.MessageManager_pWarningSummary,
+            Messages.MessageManager_pErrorSummary ];
+
+    static class Message : IMessage {
+        Control control;
+        Object data;
+        Object key;
+        String message;
+        int type;
+        String prefix;
+
+        this(Object key, String message, int type, Object data) {
+            this.key = key;
+            this.message = message;
+            this.type = type;
+            this.data = data;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.jface.dialogs.IMessage#getKey()
+         */
+        public Object getKey() {
+            return key;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.jface.dialogs.IMessageProvider#getMessage()
+         */
+        public String getMessage() {
+            return message;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.jface.dialogs.IMessageProvider#getMessageType()
+         */
+        public int getMessageType() {
+            return type;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.ui.forms.messages.IMessage#getControl()
+         */
+        public Control getControl() {
+            return control;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.ui.forms.messages.IMessage#getData()
+         */
+        public Object getData() {
+            return data;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.ui.forms.messages.IMessage#getPrefix()
+         */
+        public String getPrefix() {
+            return prefix;
+        }
+    }
+
+    static class DefaultPrefixProvider : IMessagePrefixProvider {
+
+        public String getPrefix(Control c) {
+            Composite parent = c.getParent();
+            Control[] siblings = parent.getChildren();
+            for (int i = 0; i < siblings.length; i++) {
+                if (siblings[i] is c) {
+                    // this is us - go backward until you hit
+                    // a label-like widget
+                    for (int j = i - 1; j >= 0; j--) {
+                        Control label = siblings[j];
+                        String ltext = null;
+                        if ( auto l = cast(Label)label ) {
+                            ltext = l.getText();
+                        } else if ( auto hl = cast(Hyperlink)label ) {
+                            ltext = hl.getText();
+                        } else if ( auto cl = cast(CLabel)label ) {
+                            ltext = cl.getText();
+                        }
+                        if (ltext !is null) {
+                            if (!ltext.endsWith(":")) //$NON-NLS-1$
+                                return ltext ~ ": "; //$NON-NLS-1$
+                            return ltext ~ " "; //$NON-NLS-1$
+                        }
+                    }
+                    break;
+                }
+            }
+            return null;
+        }
+    }
+
+    class ControlDecorator {
+        private ControlDecoration decoration;
+        private ArraySeq!(Message) controlMessages = new ArraySeq!(Message);
+        private String prefix;
+
+        this(Control control) {
+            this.decoration = new ControlDecoration(control, decorationPosition, scrolledForm.getBody());
+        }
+
+        public bool isDisposed() {
+            return decoration.getControl() is null;
+        }
+
+        void updatePrefix() {
+            prefix = null;
+        }
+
+        void updatePosition() {
+            Control control = decoration.getControl();
+            decoration.dispose();
+            this.decoration = new ControlDecoration(control, decorationPosition, scrolledForm.getBody());
+            update();
+        }
+
+        String getPrefix() {
+            if (prefix is null)
+                createPrefix();
+            return prefix;
+        }
+
+        private void createPrefix() {
+            if (prefixProvider is null) {
+                prefix = ""; //$NON-NLS-1$
+                return;
+            }
+            prefix = prefixProvider.getPrefix(decoration.getControl());
+            if (prefix is null)
+                // make a prefix anyway
+                prefix = ""; //$NON-NLS-1$
+        }
+
+        void addAll(ArraySeq!(Object) target) {
+            target.addAll(controlMessages);
+        }
+
+        void addMessage(Object key, String text, Object data, int type) {
+            Message message = this.outer.addMessage(getPrefix(), key,
+                    text, data, type, controlMessages);
+            message.control = decoration.getControl();
+            if (isAutoUpdate())
+                update();
+        }
+
+        bool removeMessage(Object key) {
+            Message message = findMessage(key, controlMessages);
+            if (message !is null) {
+                controlMessages.remove(message);
+                if (isAutoUpdate())
+                    update();
+            }
+            return message !is null;
+        }
+
+        bool removeMessages() {
+            if (controlMessages.isEmpty())
+                return false;
+            controlMessages.clear();
+            if (isAutoUpdate())
+                update();
+            return true;
+        }
+
+        public void update() {
+            if (controlMessages.isEmpty()) {
+                decoration.setDescriptionText(null);
+                decoration.hide();
+            } else {
+                auto peers = createPeers(controlMessages);
+                int type = (cast(IMessage) peers.get(0)).getMessageType();
+                String description = createDetails(createPeers(peers), true);
+                if (type is IMessageProvider.ERROR)
+                    decoration.setImage(standardError.getImage());
+                else if (type is IMessageProvider.WARNING)
+                    decoration.setImage(standardWarning.getImage());
+                else if (type is IMessageProvider.INFORMATION)
+                    decoration.setImage(standardInformation.getImage());
+                decoration.setDescriptionText(description);
+                decoration.show();
+            }
+        }
+    }
+
+    /**
+     * Creates a new instance of the message manager that will work with the
+     * provided form.
+     *
+     * @param scrolledForm
+     *            the form to control
+     */
+    public this(ScrolledForm scrolledForm) {
+        prefixProvider = DEFAULT_PREFIX_PROVIDER;
+        messages = new ArraySeq!(Object);
+        decorators = new HashMap!(Object,Object);
+        this.scrolledForm = scrolledForm;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#addMessage(java.lang.Object,
+     *      java.lang.String, int)
+     */
+    public void addMessage(Object key, String messageText, Object data, int type) {
+        addMessage(null, key, messageText, data, type, messages);
+        if (isAutoUpdate())
+            updateForm();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#addMessage(java.lang.Object,
+     *      java.lang.String, int, dwt.widgets.Control)
+     */
+    public void addMessage(Object key, String messageText, Object data,
+            int type, Control control) {
+        ControlDecorator dec = cast(ControlDecorator) decorators.get(control);
+
+        if (dec is null) {
+            dec = new ControlDecorator(control);
+            decorators.put(control, dec);
+        }
+        dec.addMessage(key, messageText, data, type);
+        if (isAutoUpdate())
+            updateForm();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#removeMessage(java.lang.Object)
+     */
+    public void removeMessage(Object key) {
+        Message message = findMessage(key, messages);
+        if (message !is null) {
+            messages.remove(message);
+            if (isAutoUpdate())
+                updateForm();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#removeMessages()
+     */
+    public void removeMessages() {
+        if (!messages.isEmpty()) {
+            messages.clear();
+            if (isAutoUpdate())
+                updateForm();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#removeMessage(java.lang.Object,
+     *      dwt.widgets.Control)
+     */
+    public void removeMessage(Object key, Control control) {
+        ControlDecorator dec = cast(ControlDecorator) decorators.get(control);
+        if (dec is null)
+            return;
+        if (dec.removeMessage(key))
+            if (isAutoUpdate())
+                updateForm();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#removeMessages(dwt.widgets.Control)
+     */
+    public void removeMessages(Control control) {
+        ControlDecorator dec = cast(ControlDecorator) decorators.get(control);
+        if (dec !is null) {
+            if (dec.removeMessages()) {
+                if (isAutoUpdate())
+                    updateForm();
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#removeAllMessages()
+     */
+    public void removeAllMessages() {
+        bool needsUpdate = false;
+        for (Enumeration enm = decorators.elements(); enm.hasMoreElements();) {
+            ControlDecorator control = cast(ControlDecorator) enm.nextElement();
+            if (control.removeMessages())
+                needsUpdate = true;
+        }
+        if (!messages.isEmpty()) {
+            messages.clear();
+            needsUpdate = true;
+        }
+        if (needsUpdate && isAutoUpdate())
+            updateForm();
+    }
+
+    /*
+     * Adds the message if it does not already exist in the provided list.
+     */
+
+    private Message addMessage(String prefix, Object key, String messageText,
+            Object data, int type, ArraySeq!(Object) list) {
+        Message message = findMessage(key, list);
+        if (message is null) {
+            message = new Message(key, messageText, type, data);
+            message.prefix = prefix;
+            list.add(message);
+        } else {
+            message.message = messageText;
+            message.type = type;
+            message.data = data;
+        }
+        return message;
+    }
+
+    /*
+     * Finds the message with the provided key in the provided list.
+     */
+
+    private Message findMessage(Object key, ArraySeq!(Object) list) {
+        for (int i = 0; i < list.size(); i++) {
+            Message message = cast(Message) list.get(i);
+            if (message.getKey().equals(key))
+                return message;
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#update()
+     */
+    public void update() {
+        // Update decorations
+        for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
+            ControlDecorator dec = cast(ControlDecorator) iter.next();
+            dec.update();
+        }
+        // Update the form
+        updateForm();
+    }
+
+    /*
+     * Updates the container by rolling the messages up from the controls.
+     */
+
+    private void updateForm() {
+        ArrayList mergedList = new ArrayList();
+        mergedList.addAll(messages);
+        for (Enumeration enm = decorators.elements(); enm.hasMoreElements();) {
+            ControlDecorator dec = cast(ControlDecorator) enm.nextElement();
+            dec.addAll(mergedList);
+        }
+        update(mergedList);
+    }
+
+    private void update(ArraySeq!(Object) mergedList) {
+        pruneControlDecorators();
+        if (scrolledForm.getForm().getHead().getBounds().height is 0 || mergedList.isEmpty() || mergedList is null) {
+            scrolledForm.setMessage(null, IMessageProvider.NONE);
+            return;
+        }
+        auto peers = createPeers(mergedList);
+        int maxType = (cast(IMessage) peers.get(0)).getMessageType();
+        String messageText;
+        IMessage[] array = arraycast!(IMessage)( peers
+                .toArray());
+        if (peers.size() is 1 && (cast(Message) peers.get(0)).prefix is null) {
+            // a single message
+            IMessage message = cast(IMessage) peers.get(0);
+            messageText = message.getMessage();
+            scrolledForm.setMessage(messageText, maxType, array);
+        } else {
+            // show a summary message for the message
+            // and list of errors for the details
+            if (peers.size() > 1)
+                messageText = Messages.bind(
+                        MULTIPLE_MESSAGE_SUMMARY_KEYS[maxType],
+                        [ to!(String)(peers.size()) ]); //$NON-NLS-1$
+            else
+                messageText = SINGLE_MESSAGE_SUMMARY_KEYS[maxType];
+            scrolledForm.setMessage(messageText, maxType, array);
+        }
+    }
+
+    private static String getFullMessage(IMessage message) {
+        if (message.getPrefix() is null)
+            return message.getMessage();
+        return message.getPrefix() + message.getMessage();
+    }
+
+    private ArraySeq!(Message) createPeers(ArraySeq!(Message) messages) {
+        auto peers = new ArraySeq!(Message);
+        int maxType = 0;
+        foreach( message; messages ){
+            if (message.type > maxType) {
+                peers.clear();
+                maxType = message.type;
+            }
+            if (message.type is maxType)
+                peers.append(message);
+        }
+        return peers;
+    }
+
+    private String createDetails(ArraySeq!(Object) messages, bool excludePrefix) {
+        StringWriter sw = new StringWriter();
+        PrintWriter out_ = new PrintWriter(sw);
+
+        for (int i = 0; i < messages.size(); i++) {
+            if (i > 0)
+                out_.println();
+            IMessage m = cast(IMessage) messages.get(i);
+            out_.print(excludePrefix ? m.getMessage() : getFullMessage(m));
+        }
+        out_.flush();
+        return sw.toString();
+    }
+
+    public static String createDetails(IMessage[] messages) {
+        if (messages is null || messages.length is 0)
+            return null;
+        StringWriter sw = new StringWriter();
+        PrintWriter out_ = new PrintWriter(sw);
+
+        for (int i = 0; i < messages.length; i++) {
+            if (i > 0)
+                out_.println();
+            out_.print(getFullMessage(messages[i]));
+        }
+        out_.flush();
+        return sw.toString();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#createSummary(dwtx.ui.forms.IMessage[])
+     */
+    public String createSummary(IMessage[] messages) {
+        return createDetails(messages);
+    }
+
+    private void pruneControlDecorators() {
+        for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
+            ControlDecorator dec = cast(ControlDecorator) iter.next();
+            if (dec.isDisposed())
+                iter.remove();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#getMessagePrefixProvider()
+     */
+    public IMessagePrefixProvider getMessagePrefixProvider() {
+        return prefixProvider;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#setMessagePrefixProvider(dwtx.ui.forms.IMessagePrefixProvider)
+     */
+    public void setMessagePrefixProvider(IMessagePrefixProvider provider) {
+        this.prefixProvider = provider;
+        for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
+            ControlDecorator dec = cast(ControlDecorator) iter.next();
+            dec.updatePrefix();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#getDecorationPosition()
+     */
+    public int getDecorationPosition() {
+        return decorationPosition;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#setDecorationPosition(int)
+     */
+    public void setDecorationPosition(int position) {
+        this.decorationPosition = position;
+        for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
+            ControlDecorator dec = cast(ControlDecorator) iter.next();
+            dec.updatePosition();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#isAutoUpdate()
+     */
+    public bool isAutoUpdate() {
+        return autoUpdate;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.forms.IMessageManager#setAutoUpdate(bool)
+     */
+    public void setAutoUpdate(bool autoUpdate) {
+        bool needsUpdate = !this.autoUpdate && autoUpdate;
+        this.autoUpdate = autoUpdate;
+        if (needsUpdate)
+            update();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/Messages.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 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 dwtx.ui.internal.forms.Messages;
+
+import dwt.dwthelper.utils;
+
+public class Messages /*extends NLS*/ {
+    private static const String BUNDLE_NAME = "dwtx.ui.internal.forms.Messages"; //$NON-NLS-1$
+
+//     private const() {
+//     }
+
+//     static {
+        // initialize resource bundle
+        //NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+//     }
+
+    public static String FormDialog_defaultTitle;
+    public static String FormText_copy;
+    public static String Form_tooltip_minimize;
+    public static String Form_tooltip_restore;
+    /*
+     * Message manager
+     */
+    public static String MessageManager_sMessageSummary;
+    public static String MessageManager_sWarningSummary;
+    public static String MessageManager_sErrorSummary;
+    public static String MessageManager_pMessageSummary;
+    public static String MessageManager_pWarningSummary;
+    public static String MessageManager_pErrorSummary;
+    public static String ToggleHyperlink_accessibleColumn;
+    public static String ToggleHyperlink_accessibleName;
+
+    public static String bind(String string, String[] strings) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/AggregateHyperlinkSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.AggregateHyperlinkSegment;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+
+/**
+ * This segment contains a collection of images and links that all belong to one
+ * logical hyperlink.
+ */
+public class AggregateHyperlinkSegment extends ParagraphSegment implements
+        IHyperlinkSegment {
+    private String href;
+
+    private Vector segments = new Vector();
+
+    public AggregateHyperlinkSegment() {
+    }
+
+    public void add(TextHyperlinkSegment segment) {
+        segments.add(segment);
+    }
+
+    public void add(ImageHyperlinkSegment segment) {
+        segments.add(segment);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#advanceLocator(dwt.graphics.GC,
+     *      int, dwtx.ui.internal.forms.widgets.Locator,
+     *      java.util.Hashtable, bool)
+     */
+    public bool advanceLocator(GC gc, int wHint, Locator loc,
+            Hashtable objectTable, bool computeHeightOnly) {
+        bool newLine = false;
+        for (int i = 0; i < segments.size(); i++) {
+            ParagraphSegment segment = (ParagraphSegment) segments.get(i);
+            if (segment.advanceLocator(gc, wHint, loc, objectTable,
+                    computeHeightOnly))
+                newLine = true;
+        }
+        return newLine;
+    }
+
+    /**
+     * @return Returns the href.
+     */
+    public String getHref() {
+        return href;
+    }
+
+    /**
+     * @param href
+     *            The href to set.
+     */
+    public void setHref(String href) {
+        this.href = href;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#repaint(dwt.graphics.GC,
+     *      bool)
+     */
+    public void paint(GC gc, bool hover, Hashtable resourceTable,
+            bool selected, SelectionData selData, Rectangle repaintRegion) {
+        for (int i = 0; i < segments.size(); i++) {
+            ParagraphSegment segment = (ParagraphSegment) segments.get(i);
+            segment.paint(gc, hover, resourceTable, selected, selData,
+                    repaintRegion);
+        }
+    }
+
+    public String getText() {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < segments.size(); i++) {
+            IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i);
+            buf.append(segment.getText());
+        }
+        return buf.toString();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#paintFocus(dwt.graphics.GC,
+     *      dwt.graphics.Color, dwt.graphics.Color,
+     *      bool)
+     */
+    public void paintFocus(GC gc, Color bg, Color fg, bool selected,
+            Rectangle repaintRegion) {
+        for (int i = 0; i < segments.size(); i++) {
+            IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i);
+            segment.paintFocus(gc, bg, fg, selected, repaintRegion);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#getBounds()
+     */
+    public Rectangle getBounds() {
+        Rectangle bounds = new Rectangle(Integer.MAX_VALUE, Integer.MAX_VALUE,
+                0, 0);
+        // TODO this is wrong
+        for (int i = 0; i < segments.size(); i++) {
+            IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i);
+            Rectangle sbounds = segment.getBounds();
+            bounds.x = Math.min(bounds.x, sbounds.x);
+            bounds.y = Math.min(bounds.y, sbounds.y);
+            bounds.width = Math.max(bounds.width, sbounds.width);
+            bounds.height = Math.max(bounds.height, sbounds.height);
+        }
+        return bounds;
+    }
+
+    public bool contains(int x, int y) {
+        for (int i = 0; i < segments.size(); i++) {
+            IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i);
+            if (segment.contains(x, y))
+                return true;
+        }
+        return false;
+    }
+
+    public bool intersects(Rectangle rect) {
+        for (int i = 0; i < segments.size(); i++) {
+            IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i);
+            if (segment.intersects(rect))
+                return true;
+        }
+        return false;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC,
+     *      int, dwtx.ui.internal.forms.widgets.Locator,
+     *      java.util.Hashtable, bool,
+     *      dwtx.ui.internal.forms.widgets.SelectionData)
+     */
+    public void layout(GC gc, int width, Locator locator,
+            Hashtable resourceTable, bool selected) {
+        for (int i = 0; i < segments.size(); i++) {
+            ParagraphSegment segment = (ParagraphSegment) segments.get(i);
+            segment.layout(gc, width, locator, resourceTable, selected);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#computeSelection(dwt.graphics.GC,
+     *      java.util.Hashtable, bool,
+     *      dwtx.ui.internal.forms.widgets.SelectionData)
+     */
+    public void computeSelection(GC gc, Hashtable resourceTable,
+            SelectionData selData) {
+        for (int i = 0; i < segments.size(); i++) {
+            ParagraphSegment segment = (ParagraphSegment) segments.get(i);
+            segment.computeSelection(gc, resourceTable, selData);
+        }
+    }
+
+    public void clearCache(String fontId) {
+        for (int i = 0; i < segments.size(); i++) {
+            ParagraphSegment segment = (ParagraphSegment) segments.get(i);
+            segment.clearCache(fontId);
+        }
+    }
+
+    public String getTooltipText() {
+        if (segments.size() > 0)
+            return ((ParagraphSegment) segments.get(0)).getTooltipText();
+        return super.getTooltipText();
+    }
+
+    public bool isFocusSelectable(Hashtable resourceTable) {
+        return true;
+    }
+
+    public bool setFocus(Hashtable resourceTable, bool direction) {
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/BreakSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.BreakSegment;
+
+import java.util.Hashtable;
+
+import dwt.graphics.FontMetrics;
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+
+/**
+ * This segment serves as break within a paragraph. It has no data -
+ * just starts a new line and resets the locator.
+ */
+
+public class BreakSegment extends ParagraphSegment {
+    /* (non-Javadoc)
+     * @see dwtx.ui.forms.internal.widgets.ParagraphSegment#advanceLocator(dwt.graphics.GC, int, dwtx.ui.forms.internal.widgets.Locator, java.util.Hashtable)
+     */
+    public bool advanceLocator(GC gc, int wHint, Locator locator,
+            Hashtable objectTable, bool computeHeightOnly) {
+        if (locator.rowHeightis0) {
+            FontMetrics fm = gc.getFontMetrics();
+            locator.rowHeight = fm.getHeight();
+        }
+        if (computeHeightOnly) locator.collectHeights();        
+        locator.x = locator.indent;
+        locator.y += locator.rowHeight;
+        locator.rowHeight = 0;
+        locator.leading = 0;
+        return true;
+    }
+
+    public void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle repaintRegion) {
+        //nothing to paint
+    }
+    public bool contains(int x, int y) {
+        return false;
+    }
+    public bool intersects(Rectangle rect) {
+        return false;
+    }
+    /* (non-Javadoc)
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC, int, dwtx.ui.internal.forms.widgets.Locator, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData)
+     */
+    public void layout(GC gc, int width, Locator locator, Hashtable ResourceTable,
+            bool selected) {
+        locator.resetCaret();
+        if (locator.rowHeightis0) {
+            FontMetrics fm = gc.getFontMetrics();
+            locator.rowHeight = fm.getHeight();
+        }
+        locator.y += locator.rowHeight;
+        locator.rowHeight = 0;
+        locator.rowCounter++;       
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#computeSelection(dwt.graphics.GC, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData)
+     */
+    public void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData) {
+        selData.markNewLine();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/BulletParagraph.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.BulletParagraph;
+
+import java.util.Hashtable;
+
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+
+public class BulletParagraph extends Paragraph {
+    public static final int CIRCLE = 1;
+
+    public static final int TEXT = 2;
+
+    public static final int IMAGE = 3;
+
+    private int style = CIRCLE;
+
+    private String text;
+
+    private int CIRCLE_DIAM = 5;
+
+    private int SPACING = 10;
+
+    private int indent = -1;
+
+    private int bindent = -1;
+
+    private Rectangle bbounds;
+
+    /**
+     * Constructor for BulletParagraph.
+     * 
+     * @param addVerticalSpace
+     */
+    public BulletParagraph(bool addVerticalSpace) {
+        super(addVerticalSpace);
+    }
+
+    public int getIndent() {
+        int ivalue = indent;
+        if (ivalue !is -1)
+            return ivalue;
+        switch (style) {
+        case CIRCLE:
+            ivalue = CIRCLE_DIAM + SPACING;
+            break;
+        default:
+            ivalue = 20;
+            break;
+        }
+        return getBulletIndent() + ivalue;
+    }
+
+    public int getBulletIndent() {
+        if (bindent !is -1)
+            return bindent;
+        return 0;
+    }
+
+    /*
+     * @see IBulletParagraph#getBulletStyle()
+     */
+    public int getBulletStyle() {
+        return style;
+    }
+
+    public void setBulletStyle(int style) {
+        this.style = style;
+    }
+
+    public void setBulletText(String text) {
+        this.text = text;
+    }
+
+    public void setIndent(int indent) {
+        this.indent = indent;
+    }
+
+    public void setBulletIndent(int bindent) {
+        this.bindent = bindent;
+    }
+
+    /*
+     * @see IBulletParagraph#getBulletText()
+     */
+    public String getBulletText() {
+        return text;
+    }
+
+    public void layout(GC gc, int width, Locator loc, int lineHeight,
+            Hashtable resourceTable, IHyperlinkSegment selectedLink) {
+        computeRowHeights(gc, width, loc, lineHeight, resourceTable);
+        layoutBullet(gc, loc, lineHeight, resourceTable);
+        super.layout(gc, width, loc, lineHeight, resourceTable, selectedLink);
+    }
+
+    public void paint(GC gc, Rectangle repaintRegion,
+            Hashtable resourceTable, IHyperlinkSegment selectedLink,
+            SelectionData selData) {
+        paintBullet(gc, repaintRegion, resourceTable);
+        super.paint(gc, repaintRegion, resourceTable, selectedLink, selData);
+    }
+
+    private void layoutBullet(GC gc, Locator loc, int lineHeight,
+            Hashtable resourceTable) {
+        int x = loc.x - getIndent() + getBulletIndent();
+        int rowHeight = ((int[]) loc.heights.get(0))[0];
+        if (style is CIRCLE) {
+            int y = loc.y + rowHeight / 2 - CIRCLE_DIAM / 2;
+            bbounds = new Rectangle(x, y, CIRCLE_DIAM, CIRCLE_DIAM);
+        } else if (style is TEXT && text !is null) {
+            //int height = gc.getFontMetrics().getHeight();
+            Point textSize = gc.textExtent(text);
+            bbounds = new Rectangle(x, loc.y, textSize.x, textSize.y);
+        } else if (style is IMAGE && text !is null) {
+            Image image = (Image) resourceTable.get(text);
+            if (image !is null) {
+                Rectangle ibounds = image.getBounds();
+                int y = loc.y + rowHeight / 2 - ibounds.height / 2;
+                bbounds = new Rectangle(x, y, ibounds.width, ibounds.height);
+            }
+        }
+    }
+
+    public void paintBullet(GC gc, Rectangle repaintRegion,
+            Hashtable resourceTable) {
+        if (bbounds is null)
+            return;
+        int x = bbounds.x;
+        int y = bbounds.y;
+        if (repaintRegion !is null) {
+            x -= repaintRegion.x;
+            y -= repaintRegion.y;
+        }
+        if (style is CIRCLE) {
+            Color bg = gc.getBackground();
+            Color fg = gc.getForeground();
+            gc.setBackground(fg);
+            gc.fillRectangle(x, y + 1, 5, 3);
+            gc.fillRectangle(x + 1, y, 3, 5);
+            gc.setBackground(bg);
+        } else if (style is TEXT && text !is null) {
+            gc.drawText(text, x, y);
+        } else if (style is IMAGE && text !is null) {
+            Image image = (Image) resourceTable.get(text);
+            if (image !is null)
+                gc.drawImage(image, x, y);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/BusyIndicator.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,335 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 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
+ *     Stefan Mucke - fix for Bug 156456
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.internal.forms.widgets.BusyIndicator;
+
+import dwtx.ui.internal.forms.widgets.FormUtil;
+
+import dwt.DWT;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.ImageData;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Canvas;
+import dwt.widgets.Composite;
+import dwt.widgets.Display;
+import dwtx.jface.resource.ImageDescriptor;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+
+import tango.util.Convert;
+import tango.core.Thread;
+
+public final class BusyIndicator : Canvas {
+
+    class BusyThread : Thread {
+        Rectangle bounds;
+        Display display;
+        GC offScreenImageGC;
+        Image offScreenImage;
+        Image timage;
+        bool stop;
+
+        private this(Rectangle bounds, Display display, GC offScreenImageGC, Image offScreenImage) {
+            this.bounds = bounds;
+            this.display = display;
+            this.offScreenImageGC = offScreenImageGC;
+            this.offScreenImage = offScreenImage;
+        }
+
+        public void run() {
+            try {
+                /*
+                 * Create an off-screen image to draw on, and fill it with
+                 * the shell background.
+                 */
+                FormUtil.setAntialias(offScreenImageGC, DWT.ON);
+                display.syncExec(dgRunnable( {
+                        if (!isDisposed())
+                            drawBackground(offScreenImageGC, 0, 0,
+                                    bounds.width,
+                                    bounds.height);
+                }));
+                if (isDisposed())
+                    return;
+
+                /*
+                 * Create the first image and draw it on the off-screen
+                 * image.
+                 */
+                int imageDataIndex = 0;
+                ImageData imageData;
+                synchronized (this.outer) {
+                    timage = getImage(imageDataIndex);
+                    imageData = timage.getImageData();
+                    offScreenImageGC.drawImage(timage, 0, 0,
+                            imageData.width, imageData.height, imageData.x,
+                            imageData.y, imageData.width, imageData.height);
+                }
+
+                /*
+                 * Now loop through the images, creating and drawing
+                 * each one on the off-screen image before drawing it on
+                 * the shell.
+                 */
+                while (!stop && !isDisposed() && timage !is null) {
+
+                    /*
+                     * Fill with the background color before
+                     * drawing.
+                     */
+                    display.syncExec(dgRunnable( (ImageData fimageData){
+                        if (!isDisposed()) {
+                            drawBackground(offScreenImageGC, fimageData.x,
+                                    fimageData.y, fimageData.width,
+                                    fimageData.height);
+                        }
+                    }, imageData ));
+
+                    synchronized (this.outer) {
+                        imageDataIndex = (imageDataIndex + 1) % IMAGE_COUNT;
+                        timage = getImage(imageDataIndex);
+                        imageData = timage.getImageData();
+                        offScreenImageGC.drawImage(timage, 0, 0,
+                                imageData.width, imageData.height,
+                                imageData.x, imageData.y, imageData.width,
+                                imageData.height);
+                    }
+
+                    /* Draw the off-screen image to the shell. */
+                    animationImage = offScreenImage;
+                    display.syncExec(dgRunnable( {
+                        if (!isDisposed())
+                            redraw();
+                    }));
+                    /*
+                     * Sleep for the specified delay time
+                     */
+                    try {
+                        Thread.sleep(MILLISECONDS_OF_DELAY/1000.0);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+
+
+                }
+            } catch (Exception e) {
+            } finally {
+                display.syncExec(dgRunnable( {
+                    if (offScreenImage !is null
+                            && !offScreenImage.isDisposed())
+                        offScreenImage.dispose();
+                    if (offScreenImageGC !is null
+                            && !offScreenImageGC.isDisposed())
+                        offScreenImageGC.dispose();
+                }));
+                clearImages();
+            }
+            if (busyThread is null)
+                display.syncExec(dgRunnable( {
+                    animationImage = null;
+                    if (!isDisposed())
+                        redraw();
+                }));
+        }
+
+        public void setStop(bool stop) {
+            this.stop = stop;
+        }
+    }
+
+    private static const int MARGIN = 0;
+    private static const int IMAGE_COUNT = 8;
+    private static const int MILLISECONDS_OF_DELAY = 180;
+    private Image[] imageCache;
+    protected Image image;
+
+    protected Image animationImage;
+
+    protected BusyThread busyThread;
+
+    /**
+     * BusyWidget constructor comment.
+     *
+     * @param parent
+     *            dwt.widgets.Composite
+     * @param style
+     *            int
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+
+        addPaintListener(new class PaintListener {
+            public void paintControl(PaintEvent event) {
+                onPaint(event);
+            }
+        });
+    }
+
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        Point size = new Point(0, 0);
+        if (image !is null) {
+            Rectangle ibounds = image.getBounds();
+            size.x = ibounds.width;
+            size.y = ibounds.height;
+        }
+        if (isBusy()) {
+            Rectangle bounds = getImage(0).getBounds();
+            size.x = Math.max(size.x, bounds.width);
+            size.y = Math.max(size.y, bounds.height);
+        }
+        size.x += MARGIN + MARGIN;
+        size.y += MARGIN + MARGIN;
+        return size;
+    }
+
+    /* (non-Javadoc)
+     * @see dwt.widgets.Control#forceFocus()
+     */
+    public bool forceFocus() {
+        return false;
+    }
+
+    /**
+     * Creates a thread to animate the image.
+     */
+    protected synchronized void createBusyThread() {
+        if (busyThread !is null)
+            return;
+
+        Rectangle bounds = getImage(0).getBounds();
+        Display display = getDisplay();
+        Image offScreenImage = new Image(display, bounds.width, bounds.height);
+        GC offScreenImageGC = new GC(offScreenImage);
+        busyThread = new BusyThread(bounds, display, offScreenImageGC, offScreenImage);
+        busyThread.setPriority(Thread.NORM_PRIORITY + 2);
+        busyThread.setDaemon(true);
+        busyThread.start();
+    }
+
+    public void dispose() {
+        if (busyThread !is null) {
+            busyThread.setStop(true);
+            busyThread = null;
+        }
+        super.dispose();
+    }
+
+    /**
+     * Return the image or <code>null</code>.
+     */
+    public Image getImage() {
+        return image;
+    }
+
+    /**
+     * Returns true if it is currently busy.
+     *
+     * @return bool
+     */
+    public bool isBusy() {
+        return (busyThread !is null);
+    }
+
+    /*
+     * Process the paint event
+     */
+    protected void onPaint(PaintEvent event) {
+        if (animationImage !is null && animationImage.isDisposed()) {
+            animationImage = null;
+        }
+        Rectangle rect = getClientArea();
+        if (rect.width is 0 || rect.height is 0)
+            return;
+
+        GC gc = event.gc;
+        Image activeImage = animationImage !is null ? animationImage : image;
+        if (activeImage !is null) {
+            Rectangle ibounds = activeImage.getBounds();
+            gc.drawImage(activeImage, rect.width / 2 - ibounds.width / 2,
+                    rect.height / 2 - ibounds.height / 2);
+        }
+    }
+
+    /**
+     * Sets the indicators busy count up (true) or down (false) one.
+     *
+     * @param busy
+     *            bool
+     */
+    public synchronized void setBusy(bool busy) {
+        if (busy) {
+            if (busyThread is null)
+                createBusyThread();
+        } else {
+            if (busyThread !is null) {
+                busyThread.setStop(true);
+                busyThread = null;
+            }
+        }
+    }
+
+    /**
+     * Set the image. The value <code>null</code> clears it.
+     */
+    public void setImage(Image image) {
+        if (image !is this.image && !isDisposed()) {
+            this.image = image;
+            redraw();
+        }
+    }
+
+
+    private ImageDescriptor createImageDescriptor(String relativePath) {
+        //DWT_TODO
+//      Bundle bundle = Platform.getBundle("dwtx.ui.forms"); //$NON-NLS-1$
+//      URL url = FileLocator.find(bundle, new Path(relativePath),null);
+//      if (url is null) return null;
+//      try {
+//          url = FileLocator.resolve(url);
+//          return ImageDescriptor.createFromURL(url);
+//      } catch (IOException e) {
+//          return null;
+//      }
+        return null;
+    }
+
+    private synchronized Image getImage(int index) {
+        if (imageCache is null) {
+            imageCache = new Image[IMAGE_COUNT];
+        }
+        if (imageCache[index] is null){
+            ImageDescriptor descriptor = createImageDescriptor("$nl$/icons/progress/ani/" ~ to!(String)(index + 1) ~ ".png"); //$NON-NLS-1$ //$NON-NLS-2$
+            imageCache[index] = descriptor.createImage();
+        }
+        return imageCache[index];
+    }
+
+    private synchronized void clearImages() {
+        if (busyThread !is null)
+            return;
+        if (imageCache !is null) {
+            for (int index = 0; index < IMAGE_COUNT; index++) {
+                if (imageCache[index] !is null && !imageCache[index].isDisposed()) {
+                    imageCache[index].dispose();
+                    imageCache[index] = null;
+                }
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/ControlSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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 dwtx.ui.internal.forms.widgets.ControlSegment;
+
+import dwtx.ui.internal.forms.widgets.ObjectSegment;
+import dwtx.ui.internal.forms.widgets.IFocusSelectable;
+import dwtx.ui.internal.forms.widgets.Locator;
+
+import dwt.DWT;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.widgets.Canvas;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+
+public class ControlSegment : ObjectSegment, IFocusSelectable {
+    private bool fill;
+    private int width = DWT.DEFAULT;
+    private int height = DWT.DEFAULT;
+
+    public this() {
+    }
+
+    public void setFill(bool fill) {
+        this.fill = fill;
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    public Control getControl(Hashtable resourceTable) {
+        Object obj = resourceTable.get(getObjectId());
+        if ( auto c = cast(Control)obj ) {
+            if (!c.isDisposed())
+                return c;
+        }
+        return null;
+    }
+
+    protected Point getObjectSize(Hashtable resourceTable, int wHint) {
+        Control control = getControl(resourceTable);
+        if (controlisnull)
+            return new Point(0,0);
+        int realWhint = FormUtil.getWidthHint(wHint, control);
+        Point size = control.computeSize(realWhint, DWT.DEFAULT);
+        if (realWhint !is DWT.DEFAULT && fill)
+            size.x = Math.max(size.x, realWhint);
+        if (width !is DWT.DEFAULT)
+            size.x = width;
+        if (height !is DWT.DEFAULT)
+            size.y = height;
+        return size;
+    }
+
+    public void layout(GC gc, int width, Locator loc, Hashtable resourceTable,
+            bool selected) {
+        super.layout(gc, width, loc, resourceTable, selected);
+        Control control = getControl(resourceTable);
+        if (control !is null)
+            control.setBounds(getBounds());
+    }
+
+    public bool setFocus(Hashtable resourceTable, bool next) {
+        Control c = getControl(resourceTable);
+        if (c !is null) {
+            return setFocus(c, next);
+        }
+        return false;
+    }
+
+    private bool setFocus(Control c, bool direction) {
+        if (auto comp = cast(Composite)c ) {
+            Control [] tabList = comp.getTabList();
+            if (direction) {
+                for (int i=0; i<tabList.length; i++) {
+                    if (setFocus(tabList[i], direction))
+                        return true;
+                }
+            }
+            else {
+                for (int i=tabList.length-1; i>=0; i--) {
+                    if (setFocus(tabList[i], direction))
+                        return true;
+                }
+            }
+            if (!(null !is cast(Canvas)c ))
+                return false;
+        }
+        return c.setFocus();
+    }
+
+    public bool isFocusSelectable(Hashtable resourceTable) {
+        Control c = getControl(resourceTable);
+        if (c !is null)
+            return true;
+        return false;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/FormFonts.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.internal.forms.widgets.FormFonts;
+
+import tango.util.collection.HashMap;
+
+import dwt.DWT;
+import dwt.graphics.Font;
+import dwt.graphics.FontData;
+import dwt.widgets.Display;
+
+import dwt.dwthelper.utils;
+
+public class FormFonts {
+
+    alias HashMap!(String,Object) HashMapStrToObj;
+
+    private static FormFonts instance;
+
+    public static FormFonts getInstance() {
+        if (instance is null)
+            instance = new FormFonts();
+        return instance;
+    }
+
+    private HashMapStrToObj fonts;
+    private HashMapStrToObj ids;
+
+    private this() {
+    }
+
+    private class FontIdentifier {
+        private Display fDisplay;
+        private Font fFont;
+
+        this (Display display, Font font) {
+            fDisplay = display;
+            fFont = font;
+        }
+
+        public bool equals(Object obj) {
+            if (auto id = cast(FontIdentifier)obj ) {
+                return id.fDisplay.opEquals(fDisplay) && id.fFont.opEquals(fFont);
+            }
+            return false;
+        }
+
+        public override hash_t toHash() {
+            return fDisplay.toHash() * 7 + fFont.toHash();
+        }
+    }
+
+    private class FontReference {
+        private Font fFont;
+        private int fCount;
+
+        public this(Font font) {
+            fFont = font;
+            fCount = 1;
+        }
+
+        public Font getFont() {
+            return fFont;
+        }
+        // returns a bool indicating if all clients of this font are finished
+        // a true result indicates the underlying image should be disposed
+        public bool decCount() {
+            return --fCount is 0;
+        }
+        public void incCount() {
+            fCount++;
+        }
+    }
+
+    public Font getBoldFont(Display display, Font font) {
+        checkHashMaps();
+        FontIdentifier fid = new FontIdentifier(display, font);
+        FontReference result = cast(FontReference) fonts.get(fid);
+        if (result !is null && !result.getFont().isDisposed()) {
+            result.incCount();
+            return result.getFont();
+        }
+        Font boldFont = createBoldFont(display, font);
+        fonts.put(fid, new FontReference(boldFont));
+        ids.put(boldFont, fid);
+        return boldFont;
+    }
+
+    public bool markFinished(Font boldFont) {
+        checkHashMaps();
+        FontIdentifier id = cast(FontIdentifier)ids.get(boldFont);
+        if (id !is null) {
+            FontReference ref_ = cast(FontReference) fonts.get(id);
+            if (ref_ !is null) {
+                if (ref_.decCount()) {
+                    fonts.remove(id);
+                    ids.remove(ref_.getFont());
+                    ref_.getFont().dispose();
+                    validateHashMaps();
+                }
+                return true;
+            }
+        }
+        // if the image was not found, dispose of it for the caller
+        boldFont.dispose();
+        return false;
+    }
+
+    private Font createBoldFont(Display display, Font regularFont) {
+        FontData[] fontDatas = regularFont.getFontData();
+        for (int i = 0; i < fontDatas.length; i++) {
+            fontDatas[i].setStyle(fontDatas[i].getStyle() | DWT.BOLD);
+        }
+        return new Font(display, fontDatas);
+    }
+
+    private void checkHashMaps() {
+        if (fonts is null)
+            fonts = new HashMapStrToObj();
+        if (ids is null)
+            ids = new HashMapStrToObj();
+    }
+
+    private void validateHashMaps() {
+        if (fonts.size() is 0)
+            fonts = null;
+        if (ids.size() is 0)
+            ids = null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/FormHeading.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,1025 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.FormHeading;
+
+import dwtx.ui.internal.forms.widgets.TitleRegion;
+
+import dwt.DWT;
+import dwt.custom.CLabel;
+import dwt.dnd.DragSourceListener;
+import dwt.dnd.DropTargetListener;
+import dwt.dnd.Transfer;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.MouseEvent;
+import dwt.events.MouseMoveListener;
+import dwt.events.MouseTrackListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.FontMetrics;
+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.Event;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.widgets.ToolBar;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.ListenerList;
+import dwtx.jface.action.IMenuManager;
+import dwtx.jface.action.IToolBarManager;
+import dwtx.jface.action.ToolBarManager;
+import dwtx.jface.dialogs.Dialog;
+import dwtx.jface.dialogs.IMessageProvider;
+import dwtx.jface.resource.JFaceResources;
+import dwtx.ui.forms.IFormColors;
+import dwtx.ui.forms.IMessage;
+import dwtx.ui.forms.events.IHyperlinkListener;
+import dwtx.ui.forms.widgets.Hyperlink;
+import dwtx.ui.forms.widgets.ILayoutExtension;
+import dwtx.ui.forms.widgets.SizeCache;
+import dwtx.ui.internal.forms.IMessageToolTipManager;
+import dwtx.ui.internal.forms.MessageManager;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.HashMap;
+
+/**
+ * Form header moved out of the form class.
+ */
+public class FormHeading : Canvas {
+    private static const int TITLE_HMARGIN = 1;
+    private static const int SPACING = 5;
+    private static const int VSPACING = 5;
+    private static const int HMARGIN = 6;
+    private static const int VMARGIN = 1;
+    private static const int CLIENT_MARGIN = 1;
+
+    private static const int SEPARATOR = 1 << 1;
+    private static const int BOTTOM_TOOLBAR = 1 << 2;
+    private static const int BACKGROUND_IMAGE_TILED = 1 << 3;
+    private static const int SEPARATOR_HEIGHT = 2;
+    private static const int MESSAGE_AREA_LIMIT = 50;
+    static IMessage[] NULL_MESSAGE_ARRAY;
+
+    public static const String COLOR_BASE_BG = "baseBg"; //$NON-NLS-1$
+
+    private Image backgroundImage;
+
+    private Image gradientImage;
+
+    HashMap!(String,Object) colors;
+
+    private int flags;
+
+    private GradientInfo gradientInfo;
+
+    private ToolBarManager toolBarManager;
+
+    private SizeCache toolbarCache = new SizeCache();
+
+    private SizeCache clientCache = new SizeCache();
+
+    private SizeCache messageCache = new SizeCache();
+
+    private TitleRegion titleRegion;
+
+    private MessageRegion messageRegion;
+
+    private IMessageToolTipManager messageToolTipManager;
+
+    private Control headClient;
+
+    private class DefaultMessageToolTipManager :
+            IMessageToolTipManager {
+        public void createToolTip(Control control, bool imageLabel) {
+        }
+
+        public void update() {
+            String details = getMessageType() is 0 ? null : MessageManager
+                    .createDetails(getChildrenMessages());
+            if (messageRegion !is null)
+                messageRegion.updateToolTip(details);
+            if (getMessageType() > 0
+                    && (details is null || details.length() is 0))
+                details = getMessage();
+            titleRegion.updateToolTip(details);
+        }
+    }
+
+    private class GradientInfo {
+        Color[] gradientColors;
+
+        int[] percents;
+
+        bool vertical;
+    }
+
+    private class FormHeadingLayout : Layout, ILayoutExtension {
+        public int computeMinimumWidth(Composite composite, bool flushCache) {
+            return computeSize(composite, 5, DWT.DEFAULT, flushCache).x;
+        }
+
+        public int computeMaximumWidth(Composite composite, bool flushCache) {
+            return computeSize(composite, DWT.DEFAULT, DWT.DEFAULT, flushCache).x;
+        }
+
+        public Point computeSize(Composite composite, int wHint, int hHint,
+                bool flushCache) {
+            return layout(composite, false, 0, 0, wHint, hHint, flushCache);
+        }
+
+        protected void layout(Composite composite, bool flushCache) {
+            Rectangle rect = composite.getClientArea();
+            layout(composite, true, rect.x, rect.y, rect.width, rect.height,
+                    flushCache);
+        }
+
+        private Point layout(Composite composite, bool move, int x, int y,
+                int width, int height, bool flushCache) {
+            Point tsize = null;
+            Point msize = null;
+            Point tbsize = null;
+            Point clsize = null;
+
+            if (flushCache) {
+                clientCache.flush();
+                messageCache.flush();
+                toolbarCache.flush();
+            }
+            if (hasToolBar()) {
+                ToolBar tb = toolBarManager.getControl();
+                toolbarCache.setControl(tb);
+                tbsize = toolbarCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+            }
+            if (headClient !is null) {
+                clientCache.setControl(headClient);
+                int cwhint = width;
+                if (cwhint !is DWT.DEFAULT) {
+                    cwhint -= HMARGIN * 2;
+                    if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM)
+                        cwhint -= tbsize.x + SPACING;
+                }
+                clsize = clientCache.computeSize(cwhint, DWT.DEFAULT);
+            }
+            int totalFlexWidth = width;
+            int flexWidth = totalFlexWidth;
+            if (totalFlexWidth !is DWT.DEFAULT) {
+                totalFlexWidth -= TITLE_HMARGIN * 2;
+                // complete right margin
+                if (hasToolBar() && getToolBarAlignment() is DWT.TOP
+                        || hasMessageRegion())
+                    totalFlexWidth -= SPACING;
+                // subtract tool bar
+                if (hasToolBar() && getToolBarAlignment() is DWT.TOP)
+                    totalFlexWidth -= tbsize.x + SPACING;
+                flexWidth = totalFlexWidth;
+                if (hasMessageRegion()) {
+                    // remove message region spacing and divide by 2
+                    flexWidth -= SPACING;
+                    // flexWidth /= 2;
+                }
+            }
+            /*
+             * // compute text and message sizes tsize =
+             * titleRegion.computeSize(flexWidth, DWT.DEFAULT); if (flexWidth !is
+             * DWT.DEFAULT && tsize.x < flexWidth) flexWidth += flexWidth -
+             * tsize.x;
+             *
+             * if (hasMessageRegion()) {
+             * messageCache.setControl(messageRegion.getMessageControl()); msize =
+             * messageCache.computeSize(flexWidth, DWT.DEFAULT); int maxWidth =
+             * messageCache.computeSize(DWT.DEFAULT, DWT.DEFAULT).x; if
+             * (maxWidth < msize.x) { msize.x = maxWidth; // recompute title
+             * with the reclaimed width int tflexWidth = totalFlexWidth -
+             * SPACING - msize.x; tsize = titleRegion.computeSize(tflexWidth,
+             * DWT.DEFAULT); } }
+             */
+            if (!hasMessageRegion()) {
+                tsize = titleRegion.computeSize(flexWidth, DWT.DEFAULT);
+            } else {
+                // Total flexible area in the first row is flexWidth.
+                // Try natural widths of title and
+                Point tsizeNatural = titleRegion.computeSize(DWT.DEFAULT,
+                        DWT.DEFAULT);
+                messageCache.setControl(messageRegion.getMessageControl());
+                Point msizeNatural = messageCache.computeSize(DWT.DEFAULT,
+                        DWT.DEFAULT);
+                // try to fit all
+                tsize = tsizeNatural;
+                msize = msizeNatural;
+                if (flexWidth !is DWT.DEFAULT) {
+                    int needed = tsizeNatural.x + msizeNatural.x;
+                    if (needed > flexWidth) {
+                        // too big - try to limit the message
+                        int mwidth = flexWidth - tsizeNatural.x;
+                        if (mwidth >= MESSAGE_AREA_LIMIT) {
+                            msize.x = mwidth;
+                        } else {
+                            // message is squeezed to the limit
+                            int flex = flexWidth - MESSAGE_AREA_LIMIT;
+                            tsize = titleRegion.computeSize(flex, DWT.DEFAULT);
+                            msize.x = MESSAGE_AREA_LIMIT;
+                        }
+                    }
+                }
+            }
+
+            Point size = new Point(width, height);
+            if (!move) {
+                // compute sizes
+                int width1 = 2 * TITLE_HMARGIN;
+                width1 += tsize.x;
+                if (msize !is null)
+                    width1 += SPACING + msize.x;
+                if (tbsize !is null && getToolBarAlignment() is DWT.TOP)
+                    width1 += SPACING + tbsize.x;
+                if (msize !is null
+                        || (tbsize !is null && getToolBarAlignment() is DWT.TOP))
+                    width1 += SPACING;
+                size.x = width1;
+                if (clsize !is null) {
+                    int width2 = clsize.x;
+                    if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM)
+                        width2 += SPACING + tbsize.x;
+                    width2 += 2 * HMARGIN;
+                    size.x = Math.max(width1, width2);
+                }
+                // height, first row
+                size.y = tsize.y;
+                if (msize !is null)
+                    size.y = Math.max(msize.y, size.y);
+                if (tbsize !is null && getToolBarAlignment() is DWT.TOP)
+                    size.y = Math.max(tbsize.y, size.y);
+                if (size.y > 0)
+                    size.y += VMARGIN * 2;
+                // add second row
+                int height2 = 0;
+                if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM)
+                    height2 = tbsize.y;
+                if (clsize !is null)
+                    height2 = Math.max(height2, clsize.y);
+                if (height2 > 0)
+                    size.y += VSPACING + height2 + CLIENT_MARGIN;
+                // add separator
+                if (size.y > 0 && isSeparatorVisible())
+                    size.y += SEPARATOR_HEIGHT;
+            } else {
+                // position controls
+                int xloc = x;
+                int yloc = y + VMARGIN;
+                int row1Height = tsize.y;
+                if (hasMessageRegion())
+                    row1Height = Math.max(row1Height, msize.y);
+                if (hasToolBar() && getToolBarAlignment() is DWT.TOP)
+                    row1Height = Math.max(row1Height, tbsize.y);
+                titleRegion.setBounds(xloc,
+                // yloc + row1Height / 2 - tsize.y / 2,
+                        yloc, tsize.x, tsize.y);
+                xloc += tsize.x;
+
+                if (hasMessageRegion()) {
+                    xloc += SPACING;
+                    int messageOffset = 0;
+                    if (tsize.y > 0) {
+                        // space between title area and title text
+                        int titleLeadingSpace = (tsize.y - titleRegion.getFontHeight()) / 2;
+                        // space between message control and message text
+                        int messageLeadingSpace = (msize.y - messageRegion.getFontHeight()) / 2;
+                        // how much to offset the message so baselines align
+                        messageOffset = (titleLeadingSpace + titleRegion.getFontBaselineHeight())
+                            - (messageLeadingSpace + messageRegion.getFontBaselineHeight());
+                    }
+
+                    messageRegion
+                            .getMessageControl()
+                            .setBounds(
+                                    xloc,
+                                    tsize.y > 0 ? (yloc + messageOffset)
+                                            : (yloc + row1Height / 2 - msize.y / 2),
+                                    msize.x, msize.y);
+                    xloc += msize.x;
+                }
+                if (toolBarManager !is null)
+                    toolBarManager.getControl().setVisible(
+                            !toolBarManager.isEmpty());
+                if (tbsize !is null && getToolBarAlignment() is DWT.TOP) {
+                    ToolBar tbar = toolBarManager.getControl();
+                    tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc
+                            + row1Height - 1 - tbsize.y, tbsize.x, tbsize.y);
+                }
+                // second row
+                xloc = HMARGIN;
+                yloc += row1Height + VSPACING;
+                int tw = 0;
+
+                if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM) {
+                    ToolBar tbar = toolBarManager.getControl();
+                    tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc,
+                            tbsize.x, tbsize.y);
+                    tw = tbsize.x + SPACING;
+                }
+                if (headClient !is null) {
+                    int carea = width - HMARGIN * 2 - tw;
+                    headClient.setBounds(xloc, yloc, carea, clsize.y);
+                }
+            }
+            return size;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see dwt.widgets.Control#forceFocus()
+     */
+    public bool forceFocus() {
+        return false;
+    }
+
+    private bool hasToolBar() {
+        return toolBarManager !is null && !toolBarManager.isEmpty();
+    }
+
+    private bool hasMessageRegion() {
+        return messageRegion !is null && !messageRegion.isEmpty();
+    }
+
+    private class MessageRegion {
+        private String message;
+        private int messageType;
+        private CLabel messageLabel;
+        private IMessage[] messages;
+        private Hyperlink messageHyperlink;
+        private ListenerList listeners;
+        private Color fg;
+        private int fontHeight = -1;
+        private int fontBaselineHeight = -1;
+
+        public this() {
+        }
+
+        public bool isDisposed() {
+            Control c = getMessageControl();
+            return c !is null && c.isDisposed();
+        }
+
+        public bool isEmpty() {
+            Control c = getMessageControl();
+            if (c is null)
+                return true;
+            return !c.getVisible();
+        }
+
+        public int getFontHeight() {
+            if (fontHeight is -1) {
+                Control c = getMessageControl();
+                if (c is null)
+                    return 0;
+                GC gc = new GC(c.getDisplay());
+                gc.setFont(c.getFont());
+                fontHeight = gc.getFontMetrics().getHeight();
+                gc.dispose();
+            }
+            return fontHeight;
+        }
+
+        public int getFontBaselineHeight() {
+            if (fontBaselineHeight is -1) {
+                Control c = getMessageControl();
+                if (c is null)
+                    return 0;
+                GC gc = new GC(c.getDisplay());
+                gc.setFont(c.getFont());
+                FontMetrics fm = gc.getFontMetrics();
+                fontBaselineHeight = fm.getHeight() - fm.getDescent();
+                gc.dispose();
+            }
+            return fontBaselineHeight;
+        }
+
+        public void showMessage(String newMessage, int newType,
+                IMessage[] messages) {
+            Control oldControl = getMessageControl();
+            int oldType = messageType;
+            this.message = newMessage;
+            this.messageType = newType;
+            this.messages = messages;
+            if (newMessage is null) {
+                // clearing of the message
+                if (oldControl !is null && oldControl.getVisible())
+                    oldControl.setVisible(false);
+                return;
+            }
+            ensureControlExists();
+            if (needHyperlink()) {
+                messageHyperlink.setText(newMessage);
+                messageHyperlink.setHref(messages);
+            } else {
+                messageLabel.setText(newMessage);
+            }
+            if (oldType !is newType)
+                updateForeground();
+        }
+
+        public void updateToolTip(String toolTip) {
+            Control control = getMessageControl();
+            if (control !is null)
+                control.setToolTipText(toolTip);
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public int getMessageType() {
+            return messageType;
+        }
+
+        public IMessage[] getChildrenMessages() {
+            return messages;
+        }
+
+        public String getDetailedMessage() {
+            Control c = getMessageControl();
+            if (c !is null)
+                return c.getToolTipText();
+            return null;
+        }
+
+        public Control getMessageControl() {
+            if (needHyperlink() && messageHyperlink !is null)
+                return messageHyperlink;
+            return messageLabel;
+        }
+
+        public Image getMessageImage() {
+            switch (messageType) {
+            case IMessageProvider.INFORMATION:
+                return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO);
+            case IMessageProvider.WARNING:
+                return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING);
+            case IMessageProvider.ERROR:
+                return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_ERROR);
+            default:
+                return null;
+            }
+        }
+
+        public void addMessageHyperlinkListener(IHyperlinkListener listener) {
+            if (listeners is null)
+                listeners = new ListenerList();
+            listeners.add(listener);
+            ensureControlExists();
+            if (messageHyperlink !is null)
+                messageHyperlink.addHyperlinkListener(listener);
+            if (listeners.size() is 1)
+                updateForeground();
+        }
+
+        private void removeMessageHyperlinkListener(IHyperlinkListener listener) {
+            if (listeners !is null) {
+                listeners.remove(listener);
+                if (messageHyperlink !is null)
+                    messageHyperlink.removeHyperlinkListener(listener);
+                if (listeners.isEmpty())
+                    listeners = null;
+                ensureControlExists();
+                if (listeners is null && !isDisposed())
+                    updateForeground();
+            }
+        }
+
+        private void ensureControlExists() {
+            if (needHyperlink()) {
+                if (messageLabel !is null)
+                    messageLabel.setVisible(false);
+                if (messageHyperlink is null) {
+                    messageHyperlink = new Hyperlink(this.outer, DWT.NULL);
+                    messageHyperlink.setUnderlined(true);
+                    messageHyperlink.setText(message);
+                    messageHyperlink.setHref(messages);
+                    Object[] llist = listeners.getListeners();
+                    for (int i = 0; i < llist.length; i++)
+                        messageHyperlink
+                                .addHyperlinkListener(cast(IHyperlinkListener) llist[i]);
+                    if (messageToolTipManager !is null)
+                        messageToolTipManager.createToolTip(messageHyperlink, false);
+                } else if (!messageHyperlink.getVisible()) {
+                    messageHyperlink.setText(message);
+                    messageHyperlink.setHref(messages);
+                    messageHyperlink.setVisible(true);
+                }
+            } else {
+                // need a label
+                if (messageHyperlink !is null)
+                    messageHyperlink.setVisible(false);
+                if (messageLabel is null) {
+                    messageLabel = new CLabel(this.outer, DWT.NULL);
+                    messageLabel.setText(message);
+                    if (messageToolTipManager !is null)
+                        messageToolTipManager.createToolTip(messageLabel, false);
+                } else if (!messageLabel.getVisible()) {
+                    messageLabel.setText(message);
+                    messageLabel.setVisible(true);
+                }
+            }
+            layout(true);
+        }
+
+        private bool needHyperlink() {
+            return messageType > 0 && listeners !is null;
+        }
+
+        public void setBackground(Color bg) {
+            if (messageHyperlink !is null)
+                messageHyperlink.setBackground(bg);
+            if (messageLabel !is null)
+                messageLabel.setBackground(bg);
+        }
+
+        public void setForeground(Color fg) {
+            this.fg = fg;
+        }
+
+        private void updateForeground() {
+            Color theFg;
+
+            switch (messageType) {
+            case IMessageProvider.ERROR:
+                theFg = getDisplay().getSystemColor(DWT.COLOR_RED);
+                break;
+            case IMessageProvider.WARNING:
+                theFg = getDisplay().getSystemColor(DWT.COLOR_DARK_YELLOW);
+                break;
+            default:
+                theFg = fg;
+            }
+            getMessageControl().setForeground(theFg);
+        }
+    }
+
+    /**
+     * Creates the form content control as a child of the provided parent.
+     *
+     * @param parent
+     *            the parent widget
+     */
+    public this(Composite parent, int style) {
+        colors = new HashMap!(String,Object);
+        toolbarCache = new SizeCache();
+        clientCache = new SizeCache();
+        messageCache = new SizeCache();
+        messageToolTipManager = new DefaultMessageToolTipManager();
+
+        super(parent, style);
+        setBackgroundMode(DWT.INHERIT_DEFAULT);
+        addListener(DWT.Paint, new class Listener {
+            public void handleEvent(Event e) {
+                onPaint(e.gc);
+            }
+        });
+        addListener(DWT.Dispose, new class Listener {
+            public void handleEvent(Event e) {
+                if (gradientImage !is null) {
+                    FormImages.getInstance().markFinished(gradientImage);
+                    gradientImage = null;
+                }
+            }
+        });
+        addListener(DWT.Resize, new class Listener {
+            public void handleEvent(Event e) {
+                if (gradientInfo !is null
+                        || (backgroundImage !is null && !isBackgroundImageTiled()))
+                    updateGradientImage();
+            }
+        });
+        addMouseMoveListener(new class MouseMoveListener {
+            public void mouseMove(MouseEvent e) {
+                updateTitleRegionHoverState(e);
+            }
+        });
+        addMouseTrackListener(new class MouseTrackListener {
+            public void mouseEnter(MouseEvent e) {
+                updateTitleRegionHoverState(e);
+            }
+
+            public void mouseExit(MouseEvent e) {
+                titleRegion.setHoverState(TitleRegion.STATE_NORMAL);
+            }
+
+            public void mouseHover(MouseEvent e) {
+            }
+        });
+        super.setLayout(new FormHeadingLayout());
+        titleRegion = new TitleRegion(this);
+    }
+
+    /**
+     * Fully delegates the size computation to the internal layout manager.
+     */
+    public final Point computeSize(int wHint, int hHint, bool changed) {
+        return (cast(FormHeadingLayout) getLayout()).computeSize(this, wHint,
+                hHint, changed);
+    }
+
+    /**
+     * Prevents from changing the custom control layout.
+     */
+    public final void setLayout(Layout layout) {
+    }
+
+    /**
+     * Returns the title text that will be rendered at the top of the form.
+     *
+     * @return the title text
+     */
+    public String getText() {
+        return titleRegion.getText();
+    }
+
+    /**
+     * Returns the title image that will be rendered to the left of the title.
+     *
+     * @return the title image
+     * @since 3.2
+     */
+    public Image getImage() {
+        return titleRegion.getImage();
+    }
+
+    /**
+     * Sets the background color of the header.
+     */
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        internalSetBackground(bg);
+    }
+
+    private void internalSetBackground(Color bg) {
+        titleRegion.setBackground(bg);
+        if (messageRegion !is null)
+            messageRegion.setBackground(bg);
+        if (toolBarManager !is null)
+            toolBarManager.getControl().setBackground(bg);
+        putColor(COLOR_BASE_BG, bg);
+    }
+
+    /**
+     * Sets the foreground color of the header.
+     */
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        titleRegion.setForeground(fg);
+        if (messageRegion !is null)
+            messageRegion.setForeground(fg);
+    }
+
+    /**
+     * Sets the text to be rendered at the top of the form above the body as a
+     * title.
+     *
+     * @param text
+     *            the title text
+     */
+    public void setText(String text) {
+        titleRegion.setText(text);
+    }
+
+    public void setFont(Font font) {
+        super.setFont(font);
+        titleRegion.setFont(font);
+    }
+
+    /**
+     * Sets the image to be rendered to the left of the title.
+     *
+     * @param image
+     *            the title image or <code>null</code> to show no image.
+     * @since 3.2
+     */
+    public void setImage(Image image) {
+        titleRegion.setImage(image);
+        if (messageRegion !is null)
+            titleRegion.updateImage(messageRegion.getMessageImage(), true);
+        else
+            titleRegion.updateImage(null, true);
+    }
+
+    public void setTextBackground(Color[] gradientColors, int[] percents,
+            bool vertical) {
+        if (gradientColors !is null) {
+            gradientInfo = new GradientInfo();
+            gradientInfo.gradientColors = gradientColors;
+            gradientInfo.percents = percents;
+            gradientInfo.vertical = vertical;
+            setBackground(null);
+            updateGradientImage();
+        } else {
+            // reset
+            gradientInfo = null;
+            if (gradientImage !is null) {
+                FormImages.getInstance().markFinished(gradientImage);
+                gradientImage = null;
+                setBackgroundImage(null);
+            }
+        }
+    }
+
+    public void setHeadingBackgroundImage(Image image) {
+        this.backgroundImage = image;
+        if (image !is null)
+            setBackground(null);
+        if (isBackgroundImageTiled()) {
+            setBackgroundImage(image);
+        } else
+            updateGradientImage();
+    }
+
+    public Image getHeadingBackgroundImage() {
+        return backgroundImage;
+    }
+
+    public void setBackgroundImageTiled(bool tiled) {
+        if (tiled)
+            flags |= BACKGROUND_IMAGE_TILED;
+        else
+            flags &= ~BACKGROUND_IMAGE_TILED;
+        setHeadingBackgroundImage(this.backgroundImage);
+    }
+
+    public bool isBackgroundImageTiled() {
+        return (flags & BACKGROUND_IMAGE_TILED) !is 0;
+    }
+
+    public void setBackgroundImage(Image image) {
+        super.setBackgroundImage(image);
+        if (image !is null) {
+            internalSetBackground(null);
+        }
+    }
+
+    /**
+     * Returns the tool bar manager that is used to manage tool items in the
+     * form's title area.
+     *
+     * @return form tool bar manager
+     */
+    public IToolBarManager getToolBarManager() {
+        if (toolBarManager is null) {
+            toolBarManager = new ToolBarManager(DWT.FLAT);
+            ToolBar toolbar = toolBarManager.createControl(this);
+            toolbar.setBackground(getBackground());
+            toolbar.setForeground(getForeground());
+            toolbar.setCursor(FormsResources.getHandCursor());
+            addDisposeListener(new class DisposeListener {
+                public void widgetDisposed(DisposeEvent e) {
+                    if (toolBarManager !is null) {
+                        toolBarManager.dispose();
+                        toolBarManager = null;
+                    }
+                }
+            });
+        }
+        return toolBarManager;
+    }
+
+    /**
+     * Returns the menu manager that is used to manage tool items in the form's
+     * title area.
+     *
+     * @return form drop-down menu manager
+     * @since 3.3
+     */
+    public IMenuManager getMenuManager() {
+        return titleRegion.getMenuManager();
+    }
+
+    /**
+     * Updates the local tool bar manager if used. Does nothing if local tool
+     * bar manager has not been created yet.
+     */
+    public void updateToolBar() {
+        if (toolBarManager !is null)
+            toolBarManager.update(false);
+    }
+
+    private void onPaint(GC gc) {
+        if (!isSeparatorVisible() && getBackgroundImage() is null)
+            return;
+        Rectangle carea = getClientArea();
+        Image buffer = new Image(getDisplay(), carea.width, carea.height);
+        buffer.setBackground(getBackground());
+        GC igc = new GC(buffer);
+        igc.setBackground(getBackground());
+        igc.fillRectangle(0, 0, carea.width, carea.height);
+        if (getBackgroundImage() !is null) {
+            if (gradientInfo !is null)
+                drawBackground(igc, carea.x, carea.y, carea.width, carea.height);
+            else {
+                Image bgImage = getBackgroundImage();
+                Rectangle ibounds = bgImage.getBounds();
+                drawBackground(igc, carea.x, carea.y, ibounds.width,
+                        ibounds.height);
+            }
+        }
+
+        if (isSeparatorVisible()) {
+            // bg separator
+            if (hasColor(IFormColors.H_BOTTOM_KEYLINE1))
+                igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE1));
+            else
+                igc.setForeground(getBackground());
+            igc.drawLine(carea.x, carea.height - 2, carea.x + carea.width - 1,
+                    carea.height - 2);
+            if (hasColor(IFormColors.H_BOTTOM_KEYLINE2))
+                igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE2));
+            else
+                igc.setForeground(getForeground());
+            igc.drawLine(carea.x, carea.height - 1, carea.x + carea.width - 1,
+                    carea.height - 1);
+        }
+        igc.dispose();
+        gc.drawImage(buffer, carea.x, carea.y);
+        buffer.dispose();
+    }
+
+    private void updateTitleRegionHoverState(MouseEvent e) {
+        Rectangle titleRect = titleRegion.getBounds();
+        titleRect.width += titleRect.x + 15;
+        titleRect.height += titleRect.y + 15;
+        titleRect.x = 0;
+        titleRect.y = 0;
+        if (titleRect.contains(e.x, e.y))
+            titleRegion.setHoverState(TitleRegion.STATE_HOVER_LIGHT);
+        else
+            titleRegion.setHoverState(TitleRegion.STATE_NORMAL);
+    }
+
+    private void updateGradientImage() {
+        Rectangle rect = getBounds();
+        if (gradientImage !is null) {
+            FormImages.getInstance().markFinished(gradientImage);
+            gradientImage = null;
+        }
+        if (gradientInfo !is null) {
+            gradientImage = FormImages.getInstance().getGradient(getDisplay(), gradientInfo.gradientColors, gradientInfo.percents,
+                    gradientInfo.vertical ? rect.height : rect.width, gradientInfo.vertical, getColor(COLOR_BASE_BG));
+        } else if (backgroundImage !is null && !isBackgroundImageTiled()) {
+            gradientImage = new Image(getDisplay(), Math.max(rect.width, 1),
+                    Math.max(rect.height, 1));
+            gradientImage.setBackground(getBackground());
+            GC gc = new GC(gradientImage);
+            gc.drawImage(backgroundImage, 0, 0);
+            gc.dispose();
+        }
+        setBackgroundImage(gradientImage);
+    }
+
+    public bool isSeparatorVisible() {
+        return (flags & SEPARATOR) !is 0;
+    }
+
+    public void setSeparatorVisible(bool addSeparator) {
+        if (addSeparator)
+            flags |= SEPARATOR;
+        else
+            flags &= ~SEPARATOR;
+    }
+
+    public void setToolBarAlignment(int alignment) {
+        if (alignment is DWT.BOTTOM)
+            flags |= BOTTOM_TOOLBAR;
+        else
+            flags &= ~BOTTOM_TOOLBAR;
+    }
+
+    public int getToolBarAlignment() {
+        return (flags & BOTTOM_TOOLBAR) !is 0 ? DWT.BOTTOM : DWT.TOP;
+    }
+
+    public void addMessageHyperlinkListener(IHyperlinkListener listener) {
+        ensureMessageRegionExists();
+        messageRegion.addMessageHyperlinkListener(listener);
+    }
+
+    public void removeMessageHyperlinkListener(IHyperlinkListener listener) {
+        if (messageRegion !is null)
+            messageRegion.removeMessageHyperlinkListener(listener);
+    }
+
+    public String getMessage() {
+        return messageRegion !is null ? messageRegion.getMessage() : null;
+    }
+
+    public int getMessageType() {
+        return messageRegion !is null ? messageRegion.getMessageType() : 0;
+    }
+
+    public IMessage[] getChildrenMessages() {
+        return messageRegion !is null ? messageRegion.getChildrenMessages()
+                : NULL_MESSAGE_ARRAY;
+    }
+
+    private void ensureMessageRegionExists() {
+        // ensure message region exists
+        if (messageRegion is null)
+            messageRegion = new MessageRegion();
+    }
+
+    public void showMessage(String newMessage, int type, IMessage[] messages) {
+        if (messageRegion is null) {
+            // check the trivial case
+            if (newMessage is null)
+                return;
+        } else if (messageRegion.isDisposed())
+            return;
+        ensureMessageRegionExists();
+        messageRegion.showMessage(newMessage, type, messages);
+        titleRegion.updateImage(messageRegion.getMessageImage(), false);
+        if (messageToolTipManager !is null)
+            messageToolTipManager.update();
+        layout();
+        redraw();
+    }
+
+    /**
+     * Tests if the form is in the 'busy' state.
+     *
+     * @return <code>true</code> if busy, <code>false</code> otherwise.
+     */
+
+    public bool isBusy() {
+        return titleRegion.isBusy();
+    }
+
+    /**
+     * Sets the form's busy state. Busy form will display 'busy' animation in
+     * the area of the title image.
+     *
+     * @param busy
+     *            the form's busy state
+     */
+
+    public void setBusy(bool busy) {
+        if (titleRegion.setBusy(busy))
+            layout();
+    }
+
+    public Control getHeadClient() {
+        return headClient;
+    }
+
+    public void setHeadClient(Control headClient) {
+        if (headClient !is null)
+            Assert.isTrue(headClient.getParent() is this);
+        this.headClient = headClient;
+        layout();
+    }
+
+    public void putColor(String key, Color color) {
+        if (color is null)
+            colors.remove(key);
+        else
+            colors.put(key, color);
+    }
+
+    public Color getColor(String key) {
+        return cast(Color) colors.get(key);
+    }
+
+    public bool hasColor(String key) {
+        return colors.containsKey(key);
+    }
+
+    public void addDragSupport(int operations, Transfer[] transferTypes,
+            DragSourceListener listener) {
+        titleRegion.addDragSupport(operations, transferTypes, listener);
+    }
+
+    public void addDropSupport(int operations, Transfer[] transferTypes,
+            DropTargetListener listener) {
+        titleRegion.addDropSupport(operations, transferTypes, listener);
+    }
+
+    public IMessageToolTipManager getMessageToolTipManager() {
+        return messageToolTipManager;
+    }
+
+    public void setMessageToolTipManager(
+            IMessageToolTipManager messageToolTipManager) {
+        this.messageToolTipManager = messageToolTipManager;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/FormImages.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,303 @@
+/*******************************************************************************
+ * Copyright (c) 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 dwtx.ui.internal.forms.widgets.FormImages;
+
+//import dwtx.ui.internal.forms.widgets.FormImages;
+
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.widgets.Display;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.HashMap;
+
+public class FormImages {
+    private static FormImages instance;
+
+    public static FormImages getInstance() {
+        if (instance is null)
+            instance = new FormImages();
+        return instance;
+    }
+
+    private HashMap!(Object,Object) images;
+    private HashMap!(Object,Object) ids;
+
+    private this() {
+    }
+
+    private abstract class ImageIdentifier {
+        Display fDisplay;
+        Color[] fColors;
+        int fLength;
+
+        this(Display display, Color[] colors, int length) {
+            fDisplay = display;
+            fColors = colors;
+            fLength = length;
+        }
+
+        public bool equals(Object obj) {
+            if (null !is cast(ImageIdentifier)obj ) {
+                ImageIdentifier id = cast(ImageIdentifier)obj;
+                if (id.fColors.length is fColors.length) {
+                    bool result = id.fDisplay.equals(fDisplay) && id.fLength is fLength;
+                    for (int i = 0; i < fColors.length && result; i++) {
+                        result = result && id.fColors[i].equals(fColors[i]);
+                    }
+                    return result;
+                }
+            }
+            return false;
+        }
+
+        public override hash_t toHash() {
+            int hash = fDisplay.toHash();
+            for (int i = 0; i < fColors.length; i++)
+                hash = hash * 7 + fColors[i].toHash();
+            hash = hash * 7 + fLength;
+            return hash;
+        }
+    }
+
+    private class SimpleImageIdentifier : ImageIdentifier{
+        private int fTheight;
+        private int fMarginHeight;
+
+        this (Display display, Color color1, Color color2,
+                int realtheight, int theight, int marginHeight) {
+            super(display, [color1, color2], realtheight);
+            fTheight = theight;
+            fMarginHeight = marginHeight;
+        }
+
+        public bool equals(Object obj) {
+            if (null !is cast(SimpleImageIdentifier)obj ) {
+                SimpleImageIdentifier id = cast(SimpleImageIdentifier) obj;
+                if (super.equals(obj)  &&
+                        id.fTheight is fTheight && id.fMarginHeight is fMarginHeight)
+                    return true;
+            }
+            return false;
+        }
+
+        public override hash_t toHash() {
+            int hash = super.toHash();
+            hash = hash * 7 + (new Integer(fTheight)).toHash();
+            hash = hash * 7 + (new Integer(fMarginHeight)).toHash();
+            return hash;
+        }
+    }
+
+    private class ComplexImageIdentifier : ImageIdentifier {
+        Color fBg;
+        bool fVertical;
+        int[] fPercents;
+
+        public this(Display display, Color[] colors, int length,
+                int[] percents, bool vertical, Color bg) {
+            super(display, colors, length);
+            fBg = bg;
+            fVertical = vertical;
+            fPercents = percents;
+        }
+
+        public bool equals(Object obj) {
+            if (null !is cast(ComplexImageIdentifier)obj ) {
+                ComplexImageIdentifier id = cast(ComplexImageIdentifier) obj;
+                if (super.equals(obj)  &&
+                        id.fVertical is fVertical && Arrays.equals(id.fPercents, fPercents)) {
+                    if ((id.fBg is null && fBg is null) ||
+                            (id.fBg !is null && id.fBg.equals(fBg)))
+                        return true;
+                    // if the only thing that isn't the same is the background color
+                    // still return true if it does not matter (percents add up to 100)
+                    int sum = 0;
+                    for (int i = 0; i < fPercents.length; i++)
+                        sum += fPercents[i];
+                    if (sum >= 100)
+                        return true;
+                }
+            }
+            return false;
+        }
+
+        public override hash_t toHash() {
+            int hash = super.toHash();
+            hash = hash * 7 + (new Boolean(fVertical)).toHash();
+            for (int i = 0; i < fPercents.length; i++)
+                hash = hash * 7 + (new Integer(fPercents[i])).toHash();
+            return hash;
+        }
+    }
+
+    private class ImageReference {
+        private Image fImage;
+        private int fCount;
+
+        public this(Image image) {
+            fImage = image;
+            fCount = 1;
+        }
+
+        public Image getImage() {
+            return fImage;
+        }
+        // returns a bool indicating if all clients of this image are finished
+        // a true result indicates the underlying image should be disposed
+        public bool decCount() {
+            return --fCount is 0;
+        }
+        public void incCount() {
+            fCount++;
+        }
+    }
+
+    public Image getGradient(Display display, Color color1, Color color2,
+            int realtheight, int theight, int marginHeight) {
+        checkHashMaps();
+        ImageIdentifier id = new SimpleImageIdentifier(display, color1, color2, realtheight, theight, marginHeight);
+        ImageReference result = cast(ImageReference) images.get(id);
+        if (result !is null && !result.getImage().isDisposed()) {
+            result.incCount();
+            return result.getImage();
+        }
+        Image image = createGradient(display, color1, color2, realtheight, theight, marginHeight);
+        images.put(id, new ImageReference(image));
+        ids.put(image, id);
+        return image;
+    }
+
+    public Image getGradient(Display display, Color[] colors, int[] percents,
+            int length, bool vertical, Color bg) {
+        checkHashMaps();
+        ImageIdentifier id = new ComplexImageIdentifier(display, colors, length, percents, vertical, bg);
+        ImageReference result = cast(ImageReference) images.get(id);
+        if (result !is null && !result.getImage().isDisposed()) {
+            result.incCount();
+            return result.getImage();
+        }
+        Image image = createGradient(display, colors, percents, length, vertical, bg);
+        images.put(id, new ImageReference(image));
+        ids.put(image, id);
+        return image;
+    }
+
+    public bool markFinished(Image image) {
+        checkHashMaps();
+        ImageIdentifier id = cast(ImageIdentifier)ids.get(image);
+        if (id !is null) {
+            ImageReference ref_ = cast(ImageReference) images.get(id);
+            if (ref_ !is null) {
+                if (ref_.decCount()) {
+                    images.remove(id);
+                    ids.remove(ref_.getImage());
+                    ref_.getImage().dispose();
+                    validateHashMaps();
+                }
+                return true;
+            }
+        }
+        // if the image was not found, dispose of it for the caller
+        image.dispose();
+        return false;
+    }
+
+    private void checkHashMaps() {
+        if (images is null)
+            images = new HashMap!(Object,Object);
+        if (ids is null)
+            ids = new HashMap!(Object,Object);
+    }
+
+    private void validateHashMaps() {
+        if (images.size() is 0)
+            images = null;
+        if (ids.size() is 0)
+            ids = null;
+    }
+
+    private Image createGradient(Display display, Color color1, Color color2,
+            int realtheight, int theight, int marginHeight) {
+        Image image = new Image(display, 1, realtheight);
+        image.setBackground(color1);
+        GC gc = new GC(image);
+        gc.setBackground(color1);
+        gc.fillRectangle(0, 0, 1, realtheight);
+        gc.setForeground(color2);
+        gc.setBackground(color1);
+        gc.fillGradientRectangle(0, marginHeight + 2, 1, theight - 2, true);
+        gc.dispose();
+        return image;
+    }
+
+    private Image createGradient(Display display, Color[] colors, int[] percents,
+            int length, bool vertical, Color bg) {
+        int width = vertical ? 1 : length;
+        int height = vertical ? length : 1;
+        Image gradient = new Image(display, Math.max(width, 1), Math
+                .max(height, 1));
+        GC gc = new GC(gradient);
+        drawTextGradient(gc, width, height, colors, percents, vertical, bg);
+        gc.dispose();
+        return gradient;
+    }
+
+    private void drawTextGradient(GC gc, int width, int height, Color[] colors,
+            int[] percents, bool vertical, Color bg) {
+        final Color oldBackground = gc.getBackground();
+        if (colors.length is 1) {
+            if (colors[0] !is null)
+                gc.setBackground(colors[0]);
+            gc.fillRectangle(0, 0, width, height);
+        } else {
+            final Color oldForeground = gc.getForeground();
+            Color lastColor = colors[0];
+            if (lastColor is null)
+                lastColor = oldBackground;
+            int pos = 0;
+            for (int i = 0; i < percents.length; ++i) {
+                gc.setForeground(lastColor);
+                lastColor = colors[i + 1];
+                if (lastColor is null)
+                    lastColor = oldBackground;
+                gc.setBackground(lastColor);
+                if (vertical) {
+                    int gradientHeight = percents[i] * height / 100;
+
+                    gc.fillGradientRectangle(0, pos, width, gradientHeight,
+                            true);
+                    pos += gradientHeight;
+                } else {
+                    int gradientWidth = percents[i] * height / 100;
+
+                    gc.fillGradientRectangle(pos, 0, gradientWidth, height,
+                            false);
+                    pos += gradientWidth;
+                }
+            }
+            if (vertical && pos < height) {
+                if (bg !is null)
+                    gc.setBackground(bg);
+                gc.fillRectangle(0, pos, width, height - pos);
+            }
+            if (!vertical && pos < width) {
+                if (bg !is null)
+                    gc.setBackground(bg);
+                gc.fillRectangle(pos, 0, width - pos, height);
+            }
+            gc.setForeground(oldForeground);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/FormTextModel.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,782 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.FormTextModel;
+
+import dwtx.ui.internal.forms.widgets.Paragraph;
+import dwtx.ui.internal.forms.widgets.IFocusSelectable;
+import dwtx.ui.internal.forms.widgets.ParagraphSegment;
+import dwtx.ui.internal.forms.widgets.IHyperlinkSegment;
+import dwtx.ui.internal.forms.widgets.ControlSegment;
+import dwtx.ui.internal.forms.widgets.ImageSegment;
+import dwtx.ui.internal.forms.widgets.ObjectSegment;
+
+import dwt.DWT;
+import dwtx.ui.forms.HyperlinkSettings;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.InputStream;
+pragma(msg,"FIXME temp type "~__FILE__);
+public class FormTextModel {
+    public static const String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$
+    public this() ;
+    public Paragraph[] getParagraphs() ;
+    public String getAccessibleText() ;
+    public void parseTaggedText(String taggedText, bool expandURLs) ;
+    public void parseInputStream(InputStream is_, bool expandURLs) ;
+    public void parseRegularText(String regularText, bool convertURLs) ;
+    public HyperlinkSettings getHyperlinkSettings() ;
+    public void setHyperlinkSettings(HyperlinkSettings settings) ;
+    IFocusSelectable[] getFocusSelectableSegments() ;
+    public IHyperlinkSegment getHyperlink(int index) ;
+    public IHyperlinkSegment findHyperlinkAt(int x, int y) ;
+    public int getHyperlinkCount() ;
+    public int indexOf(IHyperlinkSegment link) ;
+    public ParagraphSegment findSegmentAt(int x, int y) ;
+    public void clearCache(String fontId) ;
+    public IFocusSelectable getSelectedSegment() ;
+    public int getSelectedSegmentIndex() ;
+    public bool linkExists(IHyperlinkSegment link) ;
+    public bool traverseFocusSelectableObjects(bool next) ;
+    public IFocusSelectable getNextFocusSegment(bool next) ;
+    public bool restoreSavedLink() ;
+    public void selectLink(IHyperlinkSegment link) ;
+    public void select(IFocusSelectable selectable) ;
+    public bool hasFocusSegments() ;
+    public void dispose() ;
+    public bool isWhitespaceNormalized() ;
+    public void setWhitespaceNormalized(bool whitespaceNormalized) ;
+}
+
+/++
+static import tango.text.xml.Document;
+import tango.util.collection.ArraySeq;
+public class FormTextModel {
+//     private static const DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+//             .newInstance();
+
+    alias tango.text.xml.Document.Document!(char) Document;
+    alias Document.NodeImpl Node;
+
+    private bool whitespaceNormalized = true;
+
+    private alias ArraySeq!(Paragraph) TArraySeqParagraph;
+    private TArraySeqParagraph paragraphs;
+
+    private IFocusSelectable[] selectableSegments;
+
+    private int selectedSegmentIndex = -1;
+
+    private int savedSelectedLinkIndex = -1;
+
+    private HyperlinkSettings hyperlinkSettings;
+
+    public static const String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$
+
+    //private static final int TEXT_ONLY_LINK = 1;
+
+    //private static final int IMG_ONLY_LINK = 2;
+
+    //private static final int TEXT_AND_IMAGES_LINK = 3;
+
+    public this() {
+        reset();
+    }
+
+    /*
+     * @see ITextModel#getParagraphs()
+     */
+    public Paragraph[] getParagraphs() {
+        if (paragraphs is null)
+            return new Paragraph[0];
+        return paragraphs
+                .toArray();
+    }
+
+    public String getAccessibleText() {
+        if (paragraphs is null)
+            return ""; //$NON-NLS-1$
+        StringBuffer sbuf = new StringBuffer();
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph paragraph = cast(Paragraph) paragraphs.get(i);
+            String text = paragraph.getAccessibleText();
+            sbuf.append(text);
+        }
+        return sbuf.toString();
+    }
+
+    /*
+     * @see ITextModel#parse(String)
+     */
+    public void parseTaggedText(String taggedText, bool expandURLs) {
+        if (taggedText is null) {
+            reset();
+            return;
+        }
+        try {
+            InputStream stream = new ByteArrayInputStream(taggedText
+                    .getBytes("UTF8")); //$NON-NLS-1$
+            parseInputStream(stream, expandURLs);
+        } catch (UnsupportedEncodingException e) {
+            DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT, e);
+        }
+    }
+
+    public void parseInputStream(InputStream is_, bool expandURLs) {
+
+        documentBuilderFactory.setNamespaceAware(true);
+        documentBuilderFactory.setIgnoringComments(true);
+
+        reset();
+        try {
+            DocumentBuilder parser = documentBuilderFactory
+                    .newDocumentBuilder();
+            InputSource source = new InputSource(is_);
+            Document doc = parser.parse(source);
+            processDocument(doc, expandURLs);
+        } catch (ParserConfigurationException e) {
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, e);
+        } catch (SAXException e) {
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, e);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+
+    private void processDocument(Document doc, bool expandURLs) {
+        Node root = doc.getDocumentElement();
+        NodeList children = root.getChildNodes();
+        processSubnodes(paragraphs, children, expandURLs);
+    }
+
+    private void processSubnodes(TArraySeqParagraph plist, NodeList children, bool expandURLs) {
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() is Node.TEXT_NODE) {
+                // Make an implicit paragraph
+                String text = getSingleNodeText(child);
+                if (text !is null && !isIgnorableWhiteSpace(text, true)) {
+                    Paragraph p = new Paragraph(true);
+                    p.parseRegularText(text, expandURLs, true,
+                            getHyperlinkSettings(), null);
+                    plist.add(p);
+                }
+            } else if (child.getNodeType() is Node.ELEMENT_NODE) {
+                String tag = child.getNodeName().toLowerCase();
+                if (tag.equals("p")) { //$NON-NLS-1$
+                    Paragraph p = processParagraph(child, expandURLs);
+                    if (p !is null)
+                        plist.add(p);
+                } else if (tag.equals("li")) { //$NON-NLS-1$
+                    Paragraph p = processListItem(child, expandURLs);
+                    if (p !is null)
+                        plist.add(p);
+                }
+            }
+        }
+    }
+
+    private Paragraph processParagraph(Node paragraph, bool expandURLs) {
+        NodeList children = paragraph.getChildNodes();
+        NamedNodeMap atts = paragraph.getAttributes();
+        Node addSpaceAtt = atts.getNamedItem("addVerticalSpace"); //$NON-NLS-1$
+        bool addSpace = true;
+
+        if (addSpaceAtt is null)
+            addSpaceAtt = atts.getNamedItem("vspace"); //$NON-NLS-1$
+
+        if (addSpaceAtt !is null) {
+            String value = addSpaceAtt.getNodeValue();
+            addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$
+        }
+        Paragraph p = new Paragraph(addSpace);
+
+        processSegments(p, children, expandURLs);
+        return p;
+    }
+
+    private Paragraph processListItem(Node listItem, bool expandURLs) {
+        NodeList children = listItem.getChildNodes();
+        NamedNodeMap atts = listItem.getAttributes();
+        Node addSpaceAtt = atts.getNamedItem("addVerticalSpace");//$NON-NLS-1$
+        Node styleAtt = atts.getNamedItem("style");//$NON-NLS-1$
+        Node valueAtt = atts.getNamedItem("value");//$NON-NLS-1$
+        Node indentAtt = atts.getNamedItem("indent");//$NON-NLS-1$
+        Node bindentAtt = atts.getNamedItem("bindent");//$NON-NLS-1$
+        int style = BulletParagraph.CIRCLE;
+        int indent = -1;
+        int bindent = -1;
+        String text = null;
+        bool addSpace = true;
+
+        if (addSpaceAtt !is null) {
+            String value = addSpaceAtt.getNodeValue();
+            addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$
+        }
+        if (styleAtt !is null) {
+            String value = styleAtt.getNodeValue();
+            if (value.equalsIgnoreCase("text")) { //$NON-NLS-1$
+                style = BulletParagraph.TEXT;
+            } else if (value.equalsIgnoreCase("image")) { //$NON-NLS-1$
+                style = BulletParagraph.IMAGE;
+            } else if (value.equalsIgnoreCase("bullet")) { //$NON-NLS-1$
+                style = BulletParagraph.CIRCLE;
+            }
+        }
+        if (valueAtt !is null) {
+            text = valueAtt.getNodeValue();
+            if (style is BulletParagraph.IMAGE)
+                text = "i." + text; //$NON-NLS-1$
+        }
+        if (indentAtt !is null) {
+            String value = indentAtt.getNodeValue();
+            try {
+                indent = Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+            }
+        }
+        if (bindentAtt !is null) {
+            String value = bindentAtt.getNodeValue();
+            try {
+                bindent = Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+            }
+        }
+
+        BulletParagraph p = new BulletParagraph(addSpace);
+        p.setIndent(indent);
+        p.setBulletIndent(bindent);
+        p.setBulletStyle(style);
+        p.setBulletText(text);
+
+        processSegments(p, children, expandURLs);
+        return p;
+    }
+
+    private void processSegments(Paragraph p, NodeList children,
+            bool expandURLs) {
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            ParagraphSegment segment = null;
+
+            if (child.getNodeType() is Node.TEXT_NODE) {
+                String value = getSingleNodeText(child);
+
+                if (value !is null && !isIgnorableWhiteSpace(value, false)) {
+                    p.parseRegularText(value, expandURLs, true,
+                            getHyperlinkSettings(), null);
+                }
+            } else if (child.getNodeType() is Node.ELEMENT_NODE) {
+                String name = child.getNodeName();
+                if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$
+                    segment = processImageSegment(child);
+                } else if (name.equalsIgnoreCase("a")) { //$NON-NLS-1$
+                    segment = processHyperlinkSegment(child,
+                            getHyperlinkSettings());
+                } else if (name.equalsIgnoreCase("span")) { //$NON-NLS-1$
+                    processTextSegment(p, expandURLs, child);
+                } else if (name.equalsIgnoreCase("b")) { //$NON-NLS-1$
+                    String text = getNodeText(child);
+                    String fontId = BOLD_FONT_ID;
+                    p.parseRegularText(text, expandURLs, true,
+                            getHyperlinkSettings(), fontId);
+                } else if (name.equalsIgnoreCase("br")) { //$NON-NLS-1$
+                    segment = new BreakSegment();
+                } else if (name.equalsIgnoreCase("control")) { //$NON-NLS-1$
+                    segment = processControlSegment(child);
+                }
+            }
+            if (segment !is null) {
+                p.addSegment(segment);
+            }
+        }
+    }
+
+    private bool isIgnorableWhiteSpace(String text, bool ignoreSpaces) {
+        for (int i = 0; i < text.length(); i++) {
+            char c = text.charAt(i);
+            if (ignoreSpaces && c is ' ')
+                continue;
+            if (c is '\n' || c is '\r' || c is '\f')
+                continue;
+            return false;
+        }
+        return true;
+    }
+
+    private ImageSegment processImageSegment(Node image) {
+        ImageSegment segment = new ImageSegment();
+        processObjectSegment(segment, image, "i."); //$NON-NLS-1$
+        return segment;
+    }
+
+    private ControlSegment processControlSegment(Node control) {
+        ControlSegment segment = new ControlSegment();
+        processObjectSegment(segment, control, "o."); //$NON-NLS-1$
+        Node fill = control.getAttributes().getNamedItem("fill"); //$NON-NLS-1$
+        if (fill !is null) {
+            String value = fill.getNodeValue();
+            bool doFill = value.equalsIgnoreCase("true"); //$NON-NLS-1$
+            segment.setFill(doFill);
+        }
+        try {
+            Node width = control.getAttributes().getNamedItem("width"); //$NON-NLS-1$
+            if (width !is null) {
+                String value = width.getNodeValue();
+                int doWidth = Integer.parseInt(value);
+                segment.setWidth(doWidth);
+            }
+            Node height = control.getAttributes().getNamedItem("height"); //$NON-NLS-1$
+            if (height !is null) {
+                String value = height.getNodeValue();
+                int doHeight = Integer.parseInt(value);
+                segment.setHeight(doHeight);
+            }
+        }
+        catch (NumberFormatException e) {
+            // ignore invalid width or height
+        }
+        return segment;
+    }
+
+    private void processObjectSegment(ObjectSegment segment, Node object, String prefix) {
+        NamedNodeMap atts = object.getAttributes();
+        Node id = atts.getNamedItem("href"); //$NON-NLS-1$
+        Node align_ = atts.getNamedItem("align"); //$NON-NLS-1$
+        if (id !is null) {
+            String value = id.getNodeValue();
+            segment.setObjectId(prefix + value);
+        }
+        if (align_ !is null) {
+            String value = align_.getNodeValue().toLowerCase();
+            if (value.equals("top")) //$NON-NLS-1$
+                segment.setVerticalAlignment(ImageSegment.TOP);
+            else if (value.equals("middle")) //$NON-NLS-1$
+                segment.setVerticalAlignment(ImageSegment.MIDDLE);
+            else if (value.equals("bottom")) //$NON-NLS-1$
+                segment.setVerticalAlignment(ImageSegment.BOTTOM);
+        }
+    }
+
+    private void appendText(String value, StringBuffer buf, int[] spaceCounter) {
+        if (!whitespaceNormalized)
+            buf.append(value);
+        else {
+            for (int j = 0; j < value.length(); j++) {
+                char c = value.charAt(j);
+                if (c is ' ' || c is '\t') {
+                    // space
+                    if (++spaceCounter[0] is 1) {
+                        buf.append(c);
+                    }
+                } else if (c is '\n' || c is '\r' || c is '\f') {
+                    // new line
+                    if (++spaceCounter[0] is 1) {
+                        buf.append(' ');
+                    }
+                } else {
+                    // other characters
+                    spaceCounter[0] = 0;
+                    buf.append(c);
+                }
+            }
+        }
+    }
+
+    private String getNormalizedText(String text) {
+        int[] spaceCounter = new int[1];
+        StringBuffer buf = new StringBuffer();
+
+        if (text is null)
+            return null;
+        appendText(text, buf, spaceCounter);
+        return buf.toString();
+    }
+
+    private String getSingleNodeText(Node node) {
+        return getNormalizedText(node.getNodeValue());
+    }
+
+    private String getNodeText(Node node) {
+        NodeList children = node.getChildNodes();
+        StringBuffer buf = new StringBuffer();
+        int[] spaceCounter = new int[1];
+
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() is Node.TEXT_NODE) {
+                String value = child.getNodeValue();
+                appendText(value, buf, spaceCounter);
+            }
+        }
+        return buf.toString().trim();
+    }
+
+    private ParagraphSegment processHyperlinkSegment(Node link,
+            HyperlinkSettings settings) {
+        NamedNodeMap atts = link.getAttributes();
+        String href = null;
+        bool wrapAllowed = true;
+        String boldFontId = null;
+
+        Node hrefAtt = atts.getNamedItem("href"); //$NON-NLS-1$
+        if (hrefAtt !is null) {
+            href = hrefAtt.getNodeValue();
+        }
+        Node boldAtt = atts.getNamedItem("bold"); //$NON-NLS-1$
+        if (boldAtt !is null) {
+            boldFontId = BOLD_FONT_ID;
+        }
+        Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$
+        if (nowrap !is null) {
+            String value = nowrap.getNodeValue();
+            if (value !is null && value.equalsIgnoreCase("true")) //$NON-NLS-1$
+                wrapAllowed = false;
+        }
+        Object status = checkChildren(link);
+        if ( auto child = cast(Node)status ) {
+            ImageHyperlinkSegment segment = new ImageHyperlinkSegment();
+            segment.setHref(href);
+            segment.setWordWrapAllowed(wrapAllowed);
+            Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$
+            if (alt !is null)
+                segment.setTooltipText(alt.getNodeValue());
+            Node text = child.getAttributes().getNamedItem("text"); //$NON-NLS-1$
+            if (text !is null)
+                segment.setText(text.getNodeValue());
+            processObjectSegment(segment, child, "i."); //$NON-NLS-1$
+            return segment;
+        }  else if ( auto textObj = cast(ArrayWrapperString)status ) {
+            String text = textObj.array;
+            TextHyperlinkSegment segment = new TextHyperlinkSegment(text,
+                    settings, null);
+            segment.setHref(href);
+            segment.setFontId(boldFontId);
+            Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$
+            if (alt !is null)
+                segment.setTooltipText(alt.getNodeValue());
+            segment.setWordWrapAllowed(wrapAllowed);
+            return segment;
+        } else {
+            AggregateHyperlinkSegment parent = new AggregateHyperlinkSegment();
+            parent.setHref(href);
+            NodeList children = link.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                if (child.getNodeType() is Node.TEXT_NODE) {
+                    String value = child.getNodeValue();
+                    TextHyperlinkSegment ts = new TextHyperlinkSegment(
+                            getNormalizedText(value), settings, null);
+                    Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$
+                    if (alt !is null)
+                        ts.setTooltipText(alt.getNodeValue());
+                    ts.setWordWrapAllowed(wrapAllowed);
+                    parent.add(ts);
+                } else if (child.getNodeType() is Node.ELEMENT_NODE) {
+                    String name = child.getNodeName();
+                    if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$
+                        ImageHyperlinkSegment is_ = new ImageHyperlinkSegment();
+                        processObjectSegment(is_, child, "i."); //$NON-NLS-1$
+                        Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$
+                        if (alt !is null)
+                            is_.setTooltipText(alt.getNodeValue());
+                        parent.add(is_);
+                        is_.setWordWrapAllowed(wrapAllowed);
+                    }
+                }
+            }
+            return parent;
+        }
+    }
+
+    private Object checkChildren(Node node) {
+        bool text = false;
+        Node imgNode = null;
+        //int status = 0;
+
+        NodeList children = node.getChildNodes();
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() is Node.TEXT_NODE)
+                text = true;
+            else if (child.getNodeType() is Node.ELEMENT_NODE
+                    && child.getNodeName().equalsIgnoreCase("img")) { //$NON-NLS-1$
+                imgNode = child;
+            }
+        }
+        if (text && imgNode is null)
+            return getNodeText(node);
+        else if (!text && imgNode !is null)
+            return imgNode;
+        else return null;
+    }
+
+    private void processTextSegment(Paragraph p, bool expandURLs,
+            Node textNode) {
+        String text = getNodeText(textNode);
+
+        NamedNodeMap atts = textNode.getAttributes();
+        Node font = atts.getNamedItem("font"); //$NON-NLS-1$
+        Node color = atts.getNamedItem("color"); //$NON-NLS-1$
+        bool wrapAllowed=true;
+        Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$
+        if (nowrap !is null) {
+            String value = nowrap.getNodeValue();
+            if (value !is null && value.equalsIgnoreCase("true")) //$NON-NLS-1$
+                wrapAllowed = false;
+        }
+        String fontId = null;
+        String colorId = null;
+        if (font !is null) {
+            fontId = "f." + font.getNodeValue(); //$NON-NLS-1$
+        }
+        if (color !is null) {
+            colorId = "c." + color.getNodeValue(); //$NON-NLS-1$
+        }
+        p.parseRegularText(text, expandURLs, wrapAllowed, getHyperlinkSettings(), fontId,
+                colorId);
+    }
+
+    public void parseRegularText(String regularText, bool convertURLs) {
+        reset();
+
+        if (regularText is null)
+            return;
+
+        regularText = getNormalizedText(regularText);
+
+        Paragraph p = new Paragraph(true);
+        paragraphs.append(p);
+        int pstart = 0;
+
+        for (int i = 0; i < regularText.length(); i++) {
+            char c = regularText.charAt(i);
+            if (p is null) {
+                p = new Paragraph(true);
+                paragraphs.append(p);
+            }
+            if (c is '\n') {
+                String text = regularText.substring(pstart, i);
+                pstart = i + 1;
+                p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(),
+                        null);
+                p = null;
+            }
+        }
+        if (p !is null) {
+            // no new line
+            String text = regularText.substring(pstart);
+            p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(), null);
+        }
+    }
+
+    public HyperlinkSettings getHyperlinkSettings() {
+        // #132723 cannot have null settings
+        if (hyperlinkSettingsisnull)
+            hyperlinkSettings = new HyperlinkSettings(SWTUtil.getStandardDisplay());
+        return hyperlinkSettings;
+    }
+
+    public void setHyperlinkSettings(HyperlinkSettings settings) {
+        this.hyperlinkSettings = settings;
+    }
+
+    private void reset() {
+        if (paragraphs is null)
+            paragraphs = new TArraySeqParagraph;
+        paragraphs.clear();
+        selectedSegmentIndex = -1;
+        savedSelectedLinkIndex = -1;
+        selectableSegments = null;
+    }
+
+    IFocusSelectable[] getFocusSelectableSegments() {
+        if (selectableSegments !is null || paragraphs is null)
+            return selectableSegments;
+        IFocusSelectable[] result;
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph p = cast(Paragraph) paragraphs.get(i);
+            ParagraphSegment[] segments = p.getSegments();
+            for (int j = 0; j < segments.length; j++) {
+                if (null !is cast(IFocusSelectable)segments[j] )
+                    result ~= segments[j];
+            }
+        }
+        selectableSegments = result;
+        return selectableSegments;
+    }
+
+    public IHyperlinkSegment getHyperlink(int index) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        if (selectables.length>index) {
+            IFocusSelectable link = selectables[index];
+            if (auto l = cast(IHyperlinkSegment)link )
+                return l;
+        }
+        return null;
+    }
+
+    public IHyperlinkSegment findHyperlinkAt(int x, int y) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        for (int i = 0; i < selectables.length; i++) {
+            IFocusSelectable segment = selectables[i];
+            if ( auto link = cast(IHyperlinkSegment)segment ) {
+                if (link.contains(x, y))
+                    return link;
+            }
+        }
+        return null;
+    }
+
+    public int getHyperlinkCount() {
+        return getFocusSelectableSegments().length;
+    }
+
+    public int indexOf(IHyperlinkSegment link) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        for (int i = 0; i < selectables.length; i++) {
+            IFocusSelectable segment = selectables[i];
+            if (auto l = cast(IHyperlinkSegment)segment ) {
+                if (link is l)
+                    return i;
+            }
+        }
+        return -1;
+    }
+
+    public ParagraphSegment findSegmentAt(int x, int y) {
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph p = cast(Paragraph) paragraphs.get(i);
+            ParagraphSegment segment = p.findSegmentAt(x, y);
+            if (segment !is null)
+                return segment;
+        }
+        return null;
+    }
+
+    public void clearCache(String fontId) {
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph p = cast(Paragraph) paragraphs.get(i);
+            p.clearCache(fontId);
+        }
+    }
+
+    public IFocusSelectable getSelectedSegment() {
+        if (selectableSegments is null || selectedSegmentIndex is -1)
+            return null;
+        return selectableSegments[selectedSegmentIndex];
+    }
+
+    public int getSelectedSegmentIndex() {
+        return selectedSegmentIndex;
+    }
+
+    public bool linkExists(IHyperlinkSegment link) {
+        if (selectableSegmentsisnull)
+            return false;
+        for (int i=0; i<selectableSegments.length; i++) {
+            if (selectableSegments[i] is link)
+                return true;
+        }
+        return false;
+    }
+
+    public bool traverseFocusSelectableObjects(bool next) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        if (selectables is null)
+            return false;
+        int size = selectables.length;
+        if (next) {
+            selectedSegmentIndex++;
+        } else
+            selectedSegmentIndex--;
+
+        if (selectedSegmentIndex < 0 || selectedSegmentIndex > size - 1) {
+            selectedSegmentIndex = -1;
+        }
+        return selectedSegmentIndex !is -1;
+    }
+
+    public IFocusSelectable getNextFocusSegment(bool next) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        if (selectables is null)
+            return null;
+        int nextIndex = next?selectedSegmentIndex+1:selectedSegmentIndex-1;
+
+        if (nextIndex < 0 || nextIndex > selectables.length - 1) {
+            return null;
+        }
+        return selectables[nextIndex];
+    }
+
+    public bool restoreSavedLink() {
+        if (savedSelectedLinkIndex!is -1) {
+            selectedSegmentIndex = savedSelectedLinkIndex;
+            return true;
+        }
+        return false;
+    }
+
+    public void selectLink(IHyperlinkSegment link) {
+        if (link is null) {
+            savedSelectedLinkIndex = selectedSegmentIndex;
+            selectedSegmentIndex = -1;
+        }
+        else {
+            select(link);
+
+        }
+    }
+
+    public void select(IFocusSelectable selectable) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        selectedSegmentIndex = -1;
+        if (selectables is null)
+            return;
+        for (int i = 0; i < selectables.length; i++) {
+            if (selectables[i].equals(selectable)) {
+                selectedSegmentIndex = i;
+                break;
+            }
+        }
+    }
+
+    public bool hasFocusSegments() {
+        IFocusSelectable[] segments = getFocusSelectableSegments();
+        if (segments.length > 0)
+            return true;
+        return false;
+    }
+
+    public void dispose() {
+        paragraphs = null;
+        selectedSegmentIndex = -1;
+        savedSelectedLinkIndex = -1;
+        selectableSegments = null;
+    }
+
+    /**
+     * @return Returns the whitespaceNormalized.
+     */
+    public bool isWhitespaceNormalized() {
+        return whitespaceNormalized;
+    }
+
+    /**
+     * @param whitespaceNormalized
+     *            The whitespaceNormalized to set.
+     */
+    public void setWhitespaceNormalized(bool whitespaceNormalized) {
+        this.whitespaceNormalized = whitespaceNormalized;
+    }
+}
+++/
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/FormUtil.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,554 @@
+/*******************************************************************************
+ * 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
+ *     Chriss Gross (schtoo@schtoo.com) - fix for 61670
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.internal.forms.widgets.FormUtil;
+
+
+import dwt.DWT;
+import dwt.custom.ScrolledComposite;
+import dwt.events.MouseEvent;
+import dwt.graphics.Device;
+import dwt.graphics.FontMetrics;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.ImageData;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.layout.GridData;
+import dwt.widgets.Combo;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Label;
+import dwt.widgets.Layout;
+import dwt.widgets.ScrollBar;
+import dwt.widgets.Text;
+import dwtx.ui.forms.widgets.ColumnLayout;
+import dwtx.ui.forms.widgets.Form;
+import dwtx.ui.forms.widgets.FormText;
+import dwtx.ui.forms.widgets.FormToolkit;
+import dwtx.ui.forms.widgets.ILayoutExtension;
+
+import dwt.dwthelper.utils;
+
+//import com.ibm.icu.text.BreakIterator;
+public class FormUtil {
+
+    //DWT_TODO temp type
+    static class BreakIterator{
+
+        public static const int DONE = 0;
+
+        public static BreakIterator getWordInstance() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        public void setText(String text) {
+            // TODO Auto-generated method stub
+
+        }
+
+        public int first() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int next() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+    }
+    public static const String PLUGIN_ID = "dwtx.ui.forms"; //$NON-NLS-1$
+
+    static const int H_SCROLL_INCREMENT = 5;
+
+    static const int V_SCROLL_INCREMENT = 64;
+
+    public static const String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$
+
+    public static const String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$
+    public static const String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$
+
+    public static const String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$
+
+    public static const String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$
+
+    public static const String IGNORE_BODY = "__ignore_body__"; //$NON-NLS-1$
+
+    public static Text createText(Composite parent, String label,
+            FormToolkit factory) {
+        return createText(parent, label, factory, 1);
+    }
+
+    public static Text createText(Composite parent, String label,
+            FormToolkit factory, int span) {
+        factory.createLabel(parent, label);
+        Text text = factory.createText(parent, ""); //$NON-NLS-1$
+        int hfill = span is 1 ? GridData.FILL_HORIZONTAL
+                : GridData.HORIZONTAL_ALIGN_FILL;
+        GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+        gd.horizontalSpan = span;
+        text.setLayoutData(gd);
+        return text;
+    }
+
+    public static Text createText(Composite parent, String label,
+            FormToolkit factory, int span, int style) {
+        Label l = factory.createLabel(parent, label);
+        if ((style & DWT.MULTI) !is 0) {
+            GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
+            l.setLayoutData(gd);
+        }
+        Text text = factory.createText(parent, "", style); //$NON-NLS-1$
+        int hfill = span is 1 ? GridData.FILL_HORIZONTAL
+                : GridData.HORIZONTAL_ALIGN_FILL;
+        GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+        gd.horizontalSpan = span;
+        text.setLayoutData(gd);
+        return text;
+    }
+
+    public static Text createText(Composite parent, FormToolkit factory,
+            int span) {
+        Text text = factory.createText(parent, ""); //$NON-NLS-1$
+        int hfill = span is 1 ? GridData.FILL_HORIZONTAL
+                : GridData.HORIZONTAL_ALIGN_FILL;
+        GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+        gd.horizontalSpan = span;
+        text.setLayoutData(gd);
+        return text;
+    }
+
+    public static int computeMinimumWidth(GC gc, String text) {
+        BreakIterator wb = BreakIterator.getWordInstance();
+        wb.setText(text);
+        int last = 0;
+
+        int width = 0;
+
+        for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) {
+            String word = text.substring(last, loc);
+            Point extent = gc.textExtent(word);
+            width = Math.max(width, extent.x);
+            last = loc;
+        }
+        String lastWord = text.substring(last);
+        Point extent = gc.textExtent(lastWord);
+        width = Math.max(width, extent.x);
+        return width;
+    }
+
+    public static Point computeWrapSize(GC gc, String text, int wHint) {
+        BreakIterator wb = BreakIterator.getWordInstance();
+        wb.setText(text);
+        FontMetrics fm = gc.getFontMetrics();
+        int lineHeight = fm.getHeight();
+
+        int saved = 0;
+        int last = 0;
+        int height = lineHeight;
+        int maxWidth = 0;
+        for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) {
+            String word = text.substring(saved, loc);
+            Point extent = gc.textExtent(word);
+            if (extent.x > wHint) {
+                // overflow
+                saved = last;
+                height += extent.y;
+                // switch to current word so maxWidth will accommodate very long single words
+                word = text.substring(last, loc);
+                extent = gc.textExtent(word);
+            }
+            maxWidth = Math.max(maxWidth, extent.x);
+            last = loc;
+        }
+        /*
+         * Correct the height attribute in case it was calculated wrong due to wHint being less than maxWidth.
+         * The recursive call proved to be the only thing that worked in all cases. Some attempts can be made
+         * to estimate the height, but the algorithm needs to be run again to be sure.
+         */
+        if (maxWidth > wHint)
+            return computeWrapSize(gc, text, maxWidth);
+        return new Point(maxWidth, height);
+    }
+
+    public static void paintWrapText(GC gc, String text, Rectangle bounds) {
+        paintWrapText(gc, text, bounds, false);
+    }
+
+    public static void paintWrapText(GC gc, String text, Rectangle bounds,
+            bool underline) {
+        BreakIterator wb = BreakIterator.getWordInstance();
+        wb.setText(text);
+        FontMetrics fm = gc.getFontMetrics();
+        int lineHeight = fm.getHeight();
+        int descent = fm.getDescent();
+
+        int saved = 0;
+        int last = 0;
+        int y = bounds.y;
+        int width = bounds.width;
+
+        for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) {
+            String line = text.substring(saved, loc);
+            Point extent = gc.textExtent(line);
+
+            if (extent.x > width) {
+                // overflow
+                String prevLine = text.substring(saved, last);
+                gc.drawText(prevLine, bounds.x, y, true);
+                if (underline) {
+                    Point prevExtent = gc.textExtent(prevLine);
+                    int lineY = y + lineHeight - descent + 1;
+                    gc
+                            .drawLine(bounds.x, lineY, bounds.x + prevExtent.x,
+                                    lineY);
+                }
+
+                saved = last;
+                y += lineHeight;
+            }
+            last = loc;
+        }
+        // paint the last line
+        String lastLine = text.substring(saved, last);
+        gc.drawText(lastLine, bounds.x, y, true);
+        if (underline) {
+            int lineY = y + lineHeight - descent + 1;
+            Point lastExtent = gc.textExtent(lastLine);
+            gc.drawLine(bounds.x, lineY, bounds.x + lastExtent.x, lineY);
+        }
+    }
+
+    public static ScrolledComposite getScrolledComposite(Control c) {
+        Composite parent = c.getParent();
+
+        while (parent !is null) {
+            if ( auto sc = cast(ScrolledComposite)parent ) {
+                return sc;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
+    public static void ensureVisible(Control c) {
+        ScrolledComposite scomp = getScrolledComposite(c);
+        if (scomp !is null) {
+            Object data = scomp.getData(FOCUS_SCROLLING);
+            if (data is null || !data.equals(Boolean.FALSE))
+                FormUtil.ensureVisible(scomp, c);
+        }
+    }
+
+    public static void ensureVisible(ScrolledComposite scomp, Control control) {
+        // if the control is a FormText we do not need to scroll since it will
+        // ensure visibility of its segments as necessary
+        if ( auto ft = cast(FormText)control )
+            return;
+        Point controlSize = control.getSize();
+        Point controlOrigin = getControlLocation(scomp, control);
+        ensureVisible(scomp, controlOrigin, controlSize);
+    }
+
+    public static void ensureVisible(ScrolledComposite scomp,
+            Point controlOrigin, Point controlSize) {
+        Rectangle area = scomp.getClientArea();
+        Point scompOrigin = scomp.getOrigin();
+
+        int x = scompOrigin.x;
+        int y = scompOrigin.y;
+
+        // horizontal right, but only if the control is smaller
+        // than the client area
+        if (controlSize.x < area.width
+                && (controlOrigin.x + controlSize.x > scompOrigin.x
+                        + area.width)) {
+            x = controlOrigin.x + controlSize.x - area.width;
+        }
+        // horizontal left - make sure the left edge of
+        // the control is showing
+        if (controlOrigin.x < x) {
+            if (controlSize.x < area.width)
+                x = controlOrigin.x + controlSize.x - area.width;
+            else
+                x = controlOrigin.x;
+        }
+        // vertical bottom
+        if (controlSize.y < area.height
+                && (controlOrigin.y + controlSize.y > scompOrigin.y
+                        + area.height)) {
+            y = controlOrigin.y + controlSize.y - area.height;
+        }
+        // vertical top - make sure the top of
+        // the control is showing
+        if (controlOrigin.y < y) {
+            if (controlSize.y < area.height)
+                y = controlOrigin.y + controlSize.y - area.height;
+            else
+                y = controlOrigin.y;
+        }
+
+        if (scompOrigin.x !is x || scompOrigin.y !is y) {
+            // scroll to reveal
+            scomp.setOrigin(x, y);
+        }
+    }
+
+    public static void ensureVisible(ScrolledComposite scomp, Control control,
+            MouseEvent e) {
+        Point controlOrigin = getControlLocation(scomp, control);
+        int rX = controlOrigin.x + e.x;
+        int rY = controlOrigin.y + e.y;
+        Rectangle area = scomp.getClientArea();
+        Point scompOrigin = scomp.getOrigin();
+
+        int x = scompOrigin.x;
+        int y = scompOrigin.y;
+        // System.out.println("Ensure: area="+area+", origin="+scompOrigin+",
+        // cloc="+controlOrigin+", csize="+controlSize+", x="+x+", y="+y);
+
+        // horizontal right
+        if (rX > scompOrigin.x + area.width) {
+            x = rX - area.width;
+        }
+        // horizontal left
+        else if (rX < x) {
+            x = rX;
+        }
+        // vertical bottom
+        if (rY > scompOrigin.y + area.height) {
+            y = rY - area.height;
+        }
+        // vertical top
+        else if (rY < y) {
+            y = rY;
+        }
+
+        if (scompOrigin.x !is x || scompOrigin.y !is y) {
+            // scroll to reveal
+            scomp.setOrigin(x, y);
+        }
+    }
+
+    public static Point getControlLocation(ScrolledComposite scomp,
+            Control control) {
+        int x = 0;
+        int y = 0;
+        Control content = scomp.getContent();
+        Control currentControl = control;
+        for (;;) {
+            if (currentControl is content)
+                break;
+            Point location = currentControl.getLocation();
+            // if (location.x > 0)
+            // x += location.x;
+            // if (location.y > 0)
+            // y += location.y;
+            x += location.x;
+            y += location.y;
+            currentControl = currentControl.getParent();
+        }
+        return new Point(x, y);
+    }
+
+    static void scrollVertical(ScrolledComposite scomp, bool up) {
+        scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT);
+    }
+
+    static void scrollHorizontal(ScrolledComposite scomp, bool left) {
+        scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0);
+    }
+
+    static void scrollPage(ScrolledComposite scomp, bool up) {
+        Rectangle clientArea = scomp.getClientArea();
+        int increment = up ? -clientArea.height : clientArea.height;
+        scroll(scomp, 0, increment);
+    }
+
+    static void scroll(ScrolledComposite scomp, int xoffset, int yoffset) {
+        Point origin = scomp.getOrigin();
+        Point contentSize = scomp.getContent().getSize();
+        int xorigin = origin.x + xoffset;
+        int yorigin = origin.y + yoffset;
+        xorigin = Math.max(xorigin, 0);
+        xorigin = Math.min(xorigin, contentSize.x - 1);
+        yorigin = Math.max(yorigin, 0);
+        yorigin = Math.min(yorigin, contentSize.y - 1);
+        scomp.setOrigin(xorigin, yorigin);
+    }
+
+    public static void updatePageIncrement(ScrolledComposite scomp) {
+        ScrollBar vbar = scomp.getVerticalBar();
+        if (vbar !is null) {
+            Rectangle clientArea = scomp.getClientArea();
+            int increment = clientArea.height - 5;
+            vbar.setPageIncrement(increment);
+        }
+        ScrollBar hbar = scomp.getHorizontalBar();
+        if (hbar !is null) {
+            Rectangle clientArea = scomp.getClientArea();
+            int increment = clientArea.width - 5;
+            hbar.setPageIncrement(increment);
+        }
+    }
+
+    public static void processKey(int keyCode, Control c) {
+        ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+        if (scomp !is null) {
+            if (null !is cast(Combo)c )
+                return;
+            switch (keyCode) {
+            case DWT.ARROW_DOWN:
+                if (scomp.getData("novarrows") is null) //$NON-NLS-1$
+                    FormUtil.scrollVertical(scomp, false);
+                break;
+            case DWT.ARROW_UP:
+                if (scomp.getData("novarrows") is null) //$NON-NLS-1$
+                    FormUtil.scrollVertical(scomp, true);
+                break;
+            case DWT.ARROW_LEFT:
+                FormUtil.scrollHorizontal(scomp, true);
+                break;
+            case DWT.ARROW_RIGHT:
+                FormUtil.scrollHorizontal(scomp, false);
+                break;
+            case DWT.PAGE_UP:
+                FormUtil.scrollPage(scomp, true);
+                break;
+            case DWT.PAGE_DOWN:
+                FormUtil.scrollPage(scomp, false);
+                break;
+            }
+        }
+    }
+
+    public static bool isWrapControl(Control c) {
+        if ((c.getStyle() & DWT.WRAP) !is 0)
+            return true;
+        if (auto comp = cast(Composite)c ) {
+            return ( null !is cast(ILayoutExtension)( comp.getLayout() ));
+        }
+        return false;
+    }
+
+    public static int getWidthHint(int wHint, Control c) {
+        bool wrap = isWrapControl(c);
+        return wrap ? wHint : DWT.DEFAULT;
+    }
+
+    public static int getHeightHint(int hHint, Control c) {
+        if ( auto comp = cast(Composite)c ) {
+            Layout layout = comp.getLayout();
+            if (null !is cast(ColumnLayout)layout )
+                return hHint;
+        }
+        return DWT.DEFAULT;
+    }
+
+    public static int computeMinimumWidth(Control c, bool changed) {
+        if ( auto comp = cast(Composite)c ) {
+            Layout layout = comp.getLayout();
+            if ( auto le = cast(ILayoutExtension)layout )
+                return le.computeMinimumWidth(
+                        comp, changed);
+        }
+        return c.computeSize(FormUtil.getWidthHint(5, c), DWT.DEFAULT, changed).x;
+    }
+
+    public static int computeMaximumWidth(Control c, bool changed) {
+        if ( auto comp = cast(Composite)c ) {
+            Layout layout = comp.getLayout();
+            if ( auto le = cast(ILayoutExtension)layout )
+                return le.computeMaximumWidth(
+                        comp, changed);
+        }
+        return c.computeSize(DWT.DEFAULT, DWT.DEFAULT, changed).x;
+    }
+
+    public static Form getForm(Control c) {
+        Composite parent = c.getParent();
+        while (parent !is null) {
+            if ( auto frm = cast(Form)parent ) {
+                return frm;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
+    public static Image createAlphaMashImage(Device device, Image srcImage) {
+        Rectangle bounds = srcImage.getBounds();
+        int alpha = 0;
+        int calpha = 0;
+        ImageData data = srcImage.getImageData();
+        // Create a new image with alpha values alternating
+        // between fully transparent (0) and fully opaque (255).
+        // This image will show the background through the
+        // transparent pixels.
+        for (int i = 0; i < bounds.height; i++) {
+            // scan line
+            alpha = calpha;
+            for (int j = 0; j < bounds.width; j++) {
+                // column
+                data.setAlpha(j, i, alpha);
+                alpha = alpha is 255 ? 0 : 255;
+            }
+            calpha = calpha is 255 ? 0 : 255;
+        }
+        return new Image(device, data);
+    }
+
+    public static bool mnemonicMatch(String text, char key) {
+        char mnemonic = findMnemonic(text);
+        if (mnemonic is '\0')
+            return false;
+        return Character.toUpperCase(key) is Character.toUpperCase(mnemonic);
+    }
+
+    private static char findMnemonic(String string) {
+        int index = 0;
+        int length = string.length();
+        do {
+            while (index < length && string.charAt(index) !is '&')
+                index++;
+            if (++index >= length)
+                return '\0';
+            if (string.charAt(index) !is '&')
+                return string.charAt(index);
+            index++;
+        } while (index < length);
+        return '\0';
+    }
+
+    public static void setFocusScrollingEnabled(Control c, bool enabled) {
+        ScrolledComposite scomp = null;
+
+        if ( auto sc = cast(ScrolledComposite)c )
+            scomp = sc;
+        else
+            scomp = getScrolledComposite(c);
+        if (scomp !is null)
+            scomp.setData(FormUtil.FOCUS_SCROLLING, enabled ? null : Boolean.FALSE);
+    }
+
+    public static void setAntialias(GC gc, int style) {
+        if (!gc.getAdvanced()) {
+            gc.setAdvanced(true);
+            if (!gc.getAdvanced())
+                return;
+        }
+        gc.setAntialias(style);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/FormsResources.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 dwtx.ui.internal.forms.widgets.FormsResources;
+
+import dwt.DWT;
+import dwt.graphics.Cursor;
+import dwt.widgets.Display;
+
+/**
+ * Utility methods to access shared form-specific resources.
+ * <p>
+ * All methods declared on this class are static. This
+ * class cannot be instantiated.
+ * </p>
+ * <p>
+ * </p>
+ */
+public class FormsResources {
+    private static Cursor busyCursor;
+    private static Cursor handCursor;
+    private static Cursor textCursor;
+
+    public static Cursor getBusyCursor() {
+        if (busyCursorisnull)
+            busyCursor = new Cursor(Display.getCurrent(), DWT.CURSOR_WAIT);
+        return busyCursor;
+    }
+    public static Cursor getHandCursor() {
+        if (handCursorisnull)
+            handCursor = new Cursor(Display.getCurrent(), DWT.CURSOR_HAND);
+        return handCursor;
+    }
+    public static Cursor getTextCursor() {
+        if (textCursorisnull)
+            textCursor = new Cursor(Display.getCurrent(), DWT.CURSOR_IBEAM);
+        return textCursor;
+    }
+
+    public static int getProgressDelay(int index) {
+        /*
+        if (progressDelaysisnull)
+            return 0;
+        return progressDelays[index];
+        */
+        return 100;
+    }
+
+    public static void shutdown() {
+        if (busyCursor !is null)
+            busyCursor.dispose();
+        if (handCursor !is null)
+            handCursor.dispose();
+        if (textCursor !is null)
+            textCursor.dispose();
+        busyCursor=null;
+        handCursor=null;
+        textCursor=null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/IFocusSelectable.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 dwtx.ui.internal.forms.widgets.IFocusSelectable;
+
+import tango.util.collection.HashMap;
+
+import dwt.graphics.Rectangle;
+import dwt.dwthelper.utils;
+
+public interface IFocusSelectable {
+    alias HashMap!(String,Object) Hashtable;
+    bool isFocusSelectable(Hashtable resourceTable);
+    bool setFocus(Hashtable resourceTable, bool direction);
+    Rectangle getBounds();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/IHyperlinkSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * 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
+ *     Chriss Gross (schtoo@schtoo.com) - fix for 61670
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.internal.forms.widgets.IHyperlinkSegment;
+
+import dwtx.ui.internal.forms.widgets.IFocusSelectable;
+
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+
+import dwt.dwthelper.utils;
+
+public interface IHyperlinkSegment : IFocusSelectable {
+    String getHref();
+    String getText();
+    void paintFocus(GC gc, Color bg, Color fg, bool selected, Rectangle repaintRegion);
+    bool contains(int x, int y);
+    bool intersects(Rectangle rect);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/ImageHyperlinkSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 dwtx.ui.internal.forms.widgets.ImageHyperlinkSegment;
+
+import java.util.Hashtable;
+
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+
+public class ImageHyperlinkSegment extends ImageSegment implements
+        IHyperlinkSegment {
+    private String href;
+    private String text;
+
+    private String tooltipText;
+
+    public ImageHyperlinkSegment() {
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#setHref(java.lang.String)
+     */
+    public void setHref(String href) {
+        this.href = href;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#getHref()
+     */
+    public String getHref() {
+        return href;
+    }
+
+    public void paintFocus(GC gc, Color bg, Color fg, bool selected,
+            Rectangle repaintRegion) {
+        Rectangle bounds = getBounds();
+        if (bounds is null)
+            return;
+        if (selected) {
+            gc.setBackground(bg);
+            gc.setForeground(fg);
+            gc.drawFocus(bounds.x, bounds.y, bounds.width, bounds.height);
+        } else {
+            gc.setForeground(bg);
+            gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1,
+                    bounds.height - 1);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#isWordWrapAllowed()
+     */
+    public bool isWordWrapAllowed() {
+        return !isNowrap();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#setWordWrapAllowed(bool)
+     */
+    public void setWordWrapAllowed(bool value) {
+        setNowrap(!value);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#getText()
+     */
+    public String getText() {
+        return text!isnull?text:""; //$NON-NLS-1$
+    }
+    
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    /**
+     * @return Returns the tooltipText.
+     */
+    public String getTooltipText() {
+        return tooltipText;
+    }
+
+    /**
+     * @param tooltipText
+     *            The tooltipText to set.
+     */
+    public void setTooltipText(String tooltipText) {
+        this.tooltipText = tooltipText;
+    }
+    
+    public bool isSelectable() {
+        return true;
+    }
+
+    public bool isFocusSelectable(Hashtable resourceTable) {
+        return true;
+    }
+
+    public bool setFocus(Hashtable resourceTable, bool direction) {
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/ImageSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.ImageSegment;
+
+import dwtx.ui.internal.forms.widgets.ObjectSegment;
+import dwtx.ui.internal.forms.widgets.SelectionData;
+
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.HashMap;
+
+/**
+ * @version 1.0
+ * @author
+ */
+public class ImageSegment : ObjectSegment {
+    alias HashMap!(String,Object) Hashtable;
+    public static const String SEL_IMAGE_PREFIX = "isel."; //$NON-NLS-1$
+
+    public Image getImage(Hashtable objectTable) {
+        return getImage(getObjectId(), objectTable);
+    }
+
+    private Image getImage(String key, Hashtable objectTable) {
+        if (key is null)
+            return null;
+        Object obj = objectTable.get(key);
+        if (obj is null)
+            return null;
+        if ( auto image = cast(Image)obj )
+            return image;
+        return null;
+    }
+
+    private Image getSelectedImage(Hashtable objectTable, SelectionData selData) {
+        String key = SEL_IMAGE_PREFIX + getObjectId();
+        Image image = getImage(key, objectTable);
+        if (image is null) {
+            image = FormUtil.createAlphaMashImage(selData.display, getImage(objectTable));
+            if (image !is null)
+                objectTable.put(key, image);
+        }
+        return image;
+    }
+/*
+    private String getSelectedImageId() {
+        if (getObjectId() is null)
+            return null;
+        return SEL_IMAGE_PREFIX + getObjectId();
+    }
+*/
+
+    public void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle repaintRegion) {
+        Image image = getImage(resourceTable);
+        int iwidth = 0;
+        int iheight = 0;
+        if (image !is null) {
+            Rectangle rect = image.getBounds();
+            iwidth = rect.width + (isSelectable()?2:0);
+            iheight = rect.height + (isSelectable()?2:0);
+        } else
+            return;
+        Rectangle bounds = getBounds();
+        int ix = bounds.x+(isSelectable()?1:0);
+        int iy = bounds.y+(isSelectable()?1:0);
+
+        if (selData !is null) {
+            int leftOffset = selData.getLeftOffset(bounds.height);
+            int rightOffset = selData.getRightOffset(bounds.height);
+            bool firstRow = selData.isFirstSelectionRow(bounds.y,
+                    bounds.height);
+            bool lastRow = selData.isLastSelectionRow(bounds.y,
+                    bounds.height);
+            bool selectedRow = selData
+                    .isSelectedRow(bounds.y, bounds.height);
+            if (selectedRow) {
+                if ((firstRow && leftOffset > ix) ||
+                    (lastRow && rightOffset < ix + iwidth/2)) {
+                    drawClipImage(gc, image, ix, iy, repaintRegion);
+                }
+                else {
+                    Color savedBg = gc.getBackground();
+                    gc.setBackground(selData.bg);
+                    int sx = ix;
+                    int sy = iy;
+                    if (repaintRegion !is null) {
+                        sx -= repaintRegion.x;
+                        sy -= repaintRegion.y;
+                    }
+                    gc.fillRectangle(sx, sy, iwidth, iheight);
+                    Image selImage = getSelectedImage(resourceTable, selData);
+                    gc.drawImage(selImage, sx, sy);
+                    gc.setBackground(savedBg);
+                }
+            }
+            else
+                drawClipImage(gc, image, ix, iy, repaintRegion);
+        } else
+            drawClipImage(gc, image, ix, iy, repaintRegion);
+        if (selected) {
+            int fx = bounds.x;
+            int fy = bounds.y;
+            if (repaintRegion !is null) {
+                fx -= repaintRegion.x;
+                fy -= repaintRegion.y;
+            }
+            Color fg = gc.getForeground();
+            gc.setForeground(gc.getBackground());
+            // Clean up to avoid canceling out XOR if it is already
+            // selected.
+            gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1,
+                    bounds.height - 1);
+            gc.setForeground(fg);
+            gc.drawFocus(fx, fy, bounds.width, bounds.height);
+        }
+    }
+    private void drawClipImage(GC gc, Image image, int ix, int iy, Rectangle repaintRegion) {
+        if (repaintRegion !is null) {
+            ix -= repaintRegion.x;
+            iy -= repaintRegion.y;
+        }
+        gc.drawImage(image, ix, iy);
+    }
+
+    protected Point getObjectSize(Hashtable resourceTable, int wHint) {
+        Image image = getImage(resourceTable);
+        if (image is null)
+            return new Point(0, 0);
+        Rectangle ibounds = image.getBounds();
+        return new Point(ibounds.width, ibounds.height);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/Locator.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.Locator;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+
+public class Locator : Cloneable {
+    public int indent;
+    public int x, y;
+    public int width;
+    public int leading;
+    public int rowHeight;
+    public int marginWidth;
+    public int marginHeight;
+    public int rowCounter;
+    public ArraySeq!( ArrayWrapperInt ) heights;
+
+    public Locator clone(){
+        auto res = new Locator();
+        res.indent = indent;
+        res.x = x;
+        res.y = y;
+        res.width = width;
+        res.leading = leading;
+        res.rowHeight = rowHeight;
+        res.marginWidth = marginWidth;
+        res.marginHeight = marginHeight;
+        res.rowCounter = rowCounter;
+        res.heights = heights;
+        return res;
+    }
+
+    public void newLine() {
+        resetCaret();
+        y += rowHeight;
+        rowHeight = 0;
+    }
+
+    public Locator create() {
+        try {
+            return cast(Locator)clone();
+        }
+        catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+    public void collectHeights() {
+        heights.add(new ArrayWrapperInt( [ rowHeight, leading ] ) );
+        rowCounter++;
+    }
+    public int getBaseline(int segmentHeight) {
+        return getBaseline(segmentHeight, true);
+
+    }
+    public int getMiddle(int segmentHeight, bool text) {
+        if (heights !is null && heights.size() > rowCounter) {
+            int [] rdata = heights.get(rowCounter).array;
+            int rheight = rdata[0];
+            int rleading = rdata[1];
+            if (text)
+                return y + rheight/2 - segmentHeight/2 - rleading;
+            return y + rheight/2 - segmentHeight/2;
+        }
+        return y;
+    }
+    public int getBaseline(int segmentHeight, bool text) {
+        if (heights !is null && heights.size()>rowCounter) {
+            int [] rdata = heights.get(rowCounter).array;
+            int rheight = rdata[0];
+            int rleading = rdata[1];
+            if (text)
+                return y + rheight - segmentHeight - rleading;
+            return y + rheight - segmentHeight;
+        }
+        return y;
+    }
+
+    public void resetCaret() {
+        x = getStartX();
+    }
+    public int getStartX() {
+        return marginWidth + indent;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/ObjectSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.ObjectSegment;
+
+import dwtx.ui.internal.forms.widgets.ParagraphSegment;
+import dwtx.ui.internal.forms.widgets.Locator;
+import dwtx.ui.internal.forms.widgets.SelectionData;
+
+import dwt.DWT;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+
+import dwt.dwthelper.utils;
+
+public abstract class ObjectSegment : ParagraphSegment {
+    public static const int TOP = 1;
+
+    public static const int MIDDLE = 2;
+
+    public static const int BOTTOM = 3;
+
+    private int alignment = BOTTOM;
+    private bool nowrap=false;
+    private Rectangle bounds;
+    private String objectId;
+
+    public int getVerticalAlignment() {
+        return alignment;
+    }
+
+    void setVerticalAlignment(int alignment) {
+        this.alignment = alignment;
+    }
+
+    public String getObjectId() {
+        return objectId;
+    }
+
+    void setObjectId(String objectId) {
+        this.objectId = objectId;
+    }
+
+    protected abstract Point getObjectSize(Hashtable resourceTable, int wHint);
+
+    public bool advanceLocator(GC gc, int wHint, Locator loc,
+            Hashtable objectTable, bool computeHeightOnly) {
+        Point objectSize = getObjectSize(objectTable, wHint);
+        int iwidth = 0;
+        int iheight = 0;
+        bool newLine = false;
+
+        if (objectSize !is null) {
+            iwidth = objectSize.x + (isSelectable()?2:0);
+            iheight = objectSize.y + (isSelectable()?2:0);
+        }
+        if (wHint !is DWT.DEFAULT && !nowrap && loc.x + iwidth > wHint) {
+            // new line
+            if (computeHeightOnly)
+                loc.collectHeights();
+            loc.x = loc.indent;
+            loc.x += iwidth;
+            loc.y += loc.rowHeight;
+            loc.width = loc.indent + iwidth;
+            loc.rowHeight = iheight;
+            loc.leading = 0;
+            newLine = true;
+        } else {
+            loc.x += iwidth;
+            loc.width += iwidth;
+            loc.rowHeight = Math.max(loc.rowHeight, iheight);
+        }
+        return newLine;
+    }
+
+    public bool contains(int x, int y) {
+        if (boundsisnull)
+            return false;
+        return bounds.contains(x, y);
+    }
+    public bool intersects(Rectangle rect) {
+        if (boundsisnull)
+            return false;
+        return bounds.intersects(rect);
+    }
+
+    public Rectangle getBounds() {
+        return bounds;
+    }
+
+    public bool isSelectable() {
+        return false;
+    }
+    /**
+     * @return Returns the nowrap.
+     */
+    public bool isNowrap() {
+        return nowrap;
+    }
+    /**
+     * @param nowrap The nowrap to set.
+     */
+    public void setNowrap(bool nowrap) {
+        this.nowrap = nowrap;
+    }
+    public void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle repaintRegion) {
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC, int, dwtx.ui.internal.forms.widgets.Locator, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData)
+     */
+    public void layout(GC gc, int width, Locator loc, Hashtable resourceTable,
+            bool selected) {
+        Point size = getObjectSize(resourceTable, width);
+
+        int objWidth = 0;
+        int objHeight = 0;
+        if (size !is null) {
+            objWidth = size.x + (isSelectable()?2:0);
+            objHeight = size.y + (isSelectable()?2:0);
+        } else
+            return;
+        loc.width = objWidth;
+
+        if (!nowrap && loc.x + objWidth > width) {
+            // new row
+            loc.newLine();
+            loc.rowCounter++;
+        }
+        int ix = loc.x;
+        int iy = loc.y;
+
+        if (alignmentisMIDDLE)
+            iy = loc.getMiddle(objHeight, false);
+        else if (alignmentisBOTTOM)
+            iy = loc.getBaseline(objHeight, false);
+        loc.x += objWidth;
+        loc.rowHeight = Math.max(loc.rowHeight, objHeight);
+        bounds = new Rectangle(ix, iy, objWidth, objHeight);
+    }
+    /* (non-Javadoc)
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#computeSelection(dwt.graphics.GC, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData)
+     */
+    public void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData) {
+        // TODO we should add this to the selection
+        // if we want to support rich text
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/Paragraph.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.Paragraph;
+
+import dwtx.ui.internal.forms.widgets.ParagraphSegment;
+import dwtx.ui.internal.forms.widgets.Locator;
+import dwtx.ui.internal.forms.widgets.IHyperlinkSegment;
+import dwtx.ui.internal.forms.widgets.SelectionData;
+
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+import dwtx.ui.forms.HyperlinkSettings;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+import tango.util.collection.HashMap;
+
+/**
+ * @version 1.0
+ * @author
+ */
+public class Paragraph {
+    public static const String HTTP = "http://"; //$NON-NLS-1$
+
+    alias ArraySeq!(ParagraphSegment) TArraySeqParagraphSegment;
+    alias HashMap!(String,Object) Hashtable;
+
+    private TArraySeqParagraphSegment segments;
+
+    private bool addVerticalSpace = true;
+
+    public this(bool addVerticalSpace) {
+        this.addVerticalSpace = addVerticalSpace;
+    }
+
+    public int getIndent() {
+        return 0;
+    }
+
+    public bool getAddVerticalSpace() {
+        return addVerticalSpace;
+    }
+
+    /*
+     * @see IParagraph#getSegments()
+     */
+    public ParagraphSegment[] getSegments() {
+        if (segments is null)
+            return new TArraySeqParagraphSegment;
+        return segments
+                .toArray();
+    }
+
+    public void addSegment(ParagraphSegment segment) {
+        if (segments is null)
+            segments = new TArraySeqParagraphSegment;
+        segments.append(segment);
+    }
+
+    public void parseRegularText(String text, bool expandURLs, bool wrapAllowed,
+            HyperlinkSettings settings, String fontId) {
+        parseRegularText(text, expandURLs, wrapAllowed, settings, fontId, null);
+    }
+
+    public void parseRegularText(String text, bool expandURLs, bool wrapAllowed,
+            HyperlinkSettings settings, String fontId, String colorId) {
+        if (text.length() is 0)
+            return;
+        if (expandURLs) {
+            int loc = text.indexOf(HTTP);
+
+            if (loc is -1)
+                addSegment(new TextSegment(text, fontId, colorId, wrapAllowed));
+            else {
+                int textLoc = 0;
+                while (loc !is -1) {
+                    addSegment(new TextSegment(text.substring(textLoc, loc),
+                            fontId, colorId, wrapAllowed));
+                    bool added = false;
+                    for (textLoc = loc; textLoc < text.length(); textLoc++) {
+                        char c = text.charAt(textLoc);
+                        if (Character.isSpaceChar(c)) {
+                            addHyperlinkSegment(text.substring(loc, textLoc),
+                                    settings, fontId);
+                            added = true;
+                            break;
+                        }
+                    }
+                    if (!added) {
+                        // there was no space - just end of text
+                        addHyperlinkSegment(text.substring(loc), settings,
+                                fontId);
+                        break;
+                    }
+                    loc = text.indexOf(HTTP, textLoc);
+                }
+                if (textLoc < text.length()) {
+                    addSegment(new TextSegment(text.substring(textLoc), fontId,
+                            colorId, wrapAllowed));
+                }
+            }
+        } else {
+            addSegment(new TextSegment(text, fontId, colorId, wrapAllowed));
+        }
+    }
+
+    private void addHyperlinkSegment(String text, HyperlinkSettings settings,
+            String fontId) {
+        TextHyperlinkSegment hs = new TextHyperlinkSegment(text, settings,
+                fontId);
+        hs.setWordWrapAllowed(false);
+        hs.setHref(text);
+        addSegment(hs);
+    }
+
+    protected void computeRowHeights(GC gc, int width, Locator loc,
+            int lineHeight, Hashtable resourceTable) {
+        ParagraphSegment[] segments = getSegments();
+        // compute heights
+        Locator hloc = loc.create();
+        ArrayList heights = new ArrayList();
+        hloc.heights = heights;
+        hloc.rowCounter = 0;
+        int innerWidth = width - loc.marginWidth*2;
+        for (int j = 0; j < segments.length; j++) {
+            ParagraphSegment segment = segments[j];
+            segment.advanceLocator(gc, innerWidth, hloc, resourceTable, true);
+        }
+        hloc.collectHeights();
+        loc.heights = heights;
+        loc.rowCounter = 0;
+    }
+
+    public void layout(GC gc, int width, Locator loc, int lineHeight,
+            Hashtable resourceTable, IHyperlinkSegment selectedLink) {
+        ParagraphSegment[] segments = getSegments();
+        //int height;
+        if (segments.length > 0) {
+            /*
+            if (segments[0] instanceof TextSegment
+                    && ((TextSegment) segments[0]).isSelectable())
+                loc.x += 1;
+            */
+            // compute heights
+            if (loc.heights is null)
+                computeRowHeights(gc, width, loc, lineHeight, resourceTable);
+            for (int j = 0; j < segments.length; j++) {
+                ParagraphSegment segment = segments[j];
+                bool doSelect = false;
+                if (selectedLink !is null && segment.equals(selectedLink))
+                    doSelect = true;
+                segment.layout(gc, width, loc, resourceTable, doSelect);
+            }
+            loc.heights = null;
+            loc.y += loc.rowHeight;
+        } else {
+            loc.y += lineHeight;
+        }
+    }
+
+    public void paint(GC gc, Rectangle repaintRegion,
+            Hashtable resourceTable, IHyperlinkSegment selectedLink,
+            SelectionData selData) {
+        ParagraphSegment[] segments = getSegments();
+
+        for (int i = 0; i < segments.length; i++) {
+            ParagraphSegment segment = segments[i];
+            if (!segment.intersects(repaintRegion))
+                continue;
+            bool doSelect = false;
+            if (selectedLink !is null && segment.equals(selectedLink))
+                doSelect = true;
+            segment.paint(gc, false, resourceTable, doSelect, selData, repaintRegion);
+        }
+    }
+
+    public void computeSelection(GC gc, Hashtable resourceTable, IHyperlinkSegment selectedLink,
+            SelectionData selData) {
+        ParagraphSegment[] segments = getSegments();
+
+        for (int i = 0; i < segments.length; i++) {
+            ParagraphSegment segment = segments[i];
+            //bool doSelect = false;
+            //if (selectedLink !is null && segment.equals(selectedLink))
+                //doSelect = true;
+            segment.computeSelection(gc, resourceTable, selData);
+        }
+    }
+
+    public String getAccessibleText() {
+        ParagraphSegment[] segments = getSegments();
+        StringWriter swriter = new StringWriter();
+        PrintWriter writer = new PrintWriter(swriter);
+        for (int i = 0; i < segments.length; i++) {
+            ParagraphSegment segment = segments[i];
+            if ( auto ts = cast(TextSegment)segment ) {
+                String text = ts.getText();
+                writer.print(text);
+            }
+        }
+        writer.println();
+        swriter.flush();
+        return swriter.toString();
+    }
+
+    public ParagraphSegment findSegmentAt(int x, int y) {
+        if (segments !is null) {
+            for (int i = 0; i < segments.size(); i++) {
+                ParagraphSegment segment = cast(ParagraphSegment) segments.get(i);
+                if (segment.contains(x, y))
+                    return segment;
+            }
+        }
+        return null;
+    }
+    public void clearCache(String fontId) {
+        if (segments !is null) {
+            for (int i = 0; i < segments.size(); i++) {
+                ParagraphSegment segment = cast(ParagraphSegment) segments.get(i);
+                segment.clearCache(fontId);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/ParagraphSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.ParagraphSegment;
+
+import dwtx.ui.internal.forms.widgets.Locator;
+import dwtx.ui.internal.forms.widgets.SelectionData;
+
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+
+import dwt.dwthelper.utils;
+
+import tango.util.collection.HashMap;
+
+/**
+ * @version     1.0
+ * @author
+ */
+public abstract class ParagraphSegment {
+
+    alias HashMap!(String,Object) Hashtable;
+    /**
+     * Moves the locator according to the content of this segment.
+     * @param gc
+     * @param wHint
+     * @param loc
+     * @param objectTable
+     * @param computeHeightOnly
+     * @return <code>true</code> if text wrapped to the new line, <code>false</code> otherwise.
+     */
+    public abstract bool advanceLocator(GC gc, int wHint, Locator loc, Hashtable objectTable, bool computeHeightOnly);
+    /**
+     * Computes bounding rectangles and row heights of this segments.
+     * @param gc
+     * @param width
+     * @param loc
+     * @param resourceTable
+     * @param selected
+     */
+    public abstract void layout(GC gc, int width, Locator loc, Hashtable resourceTable, bool selected);
+    /**
+     * Paints this segment.
+     * @param gc
+     * @param hover
+     * @param resourceTable
+     * @param selected
+     * @param selData
+     * @param region
+     */
+    public abstract void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle region);
+    /**
+     * Paints this segment.
+     * @param gc
+     * @param resourceTable
+     * @param selData
+     */
+    public abstract void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData);
+    /**
+     * Tests if the coordinates are contained in one of the
+     * bounding rectangles of this segment.
+     * @param x
+     * @param y
+     * @return true if inside the bounding rectangle, false otherwise.
+     */
+    public abstract bool contains(int x, int y);
+    /**
+     * Tests if the source rectangle intersects with
+     * one of the bounding rectangles of this segment.
+     * @param rect
+     * @return true if the two rectangles intersect, false otherwise.
+     */
+    public abstract bool intersects(Rectangle rect);
+    /**
+     * Returns the tool tip of this segment or <code>null</code>
+     * if not defined.
+     * @return tooltip or <code>null</code>.
+     */
+    public String getTooltipText() {
+        return null;
+    }
+    /**
+     * Clears the text metrics cache for the provided font id.
+     * @param fontId the id of the font that the cache is kept for.
+     *
+     */
+    public void clearCache(String fontId) {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/PixelConverter.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.PixelConverter;
+
+import dwt.graphics.FontMetrics;
+import dwt.graphics.GC;
+import dwt.widgets.Control;
+
+public class PixelConverter {
+    /**
+     * Number of horizontal dialog units per character, value <code>4</code>.
+     */
+    private static final int HORIZONTAL_DIALOG_UNIT_PER_CHAR = 4;
+
+    private FontMetrics fFontMetrics;
+
+    public PixelConverter(Control control) {
+        GC gc = new GC(control);
+        gc.setFont(control.getFont());
+        fFontMetrics = gc.getFontMetrics();
+        gc.dispose();
+    }
+
+    public int convertHorizontalDLUsToPixels(int dlus) {
+        // round to the nearest pixel
+        return (fFontMetrics.getAverageCharWidth() * dlus + HORIZONTAL_DIALOG_UNIT_PER_CHAR / 2)
+                / HORIZONTAL_DIALOG_UNIT_PER_CHAR;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/SWTUtil.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.SWTUtil;
+import dwt.dnd.DragSource;
+import dwt.dnd.DropTarget;
+import dwt.widgets.Caret;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Menu;
+import dwt.widgets.ScrollBar;
+import dwt.widgets.Shell;
+import dwt.widgets.Widget;
+
+/**
+ * Utility class to simplify access to some DWT resources. 
+ */
+public class SWTUtil {
+
+    /**
+     * Returns the standard display to be used. The method first checks, if
+     * the thread calling this method has an associated disaply. If so, this
+     * display is returned. Otherwise the method returns the default display.
+     */
+    public static Display getStandardDisplay() {
+        Display display;
+        display = Display.getCurrent();
+        if (display is null)
+            display = Display.getDefault();
+        return display;
+    }
+
+    /**
+     * Returns the shell for the given widget. If the widget doesn't represent
+     * a DWT object that manage a shell, <code>null</code> is returned.
+     * 
+     * @return the shell for the given widget
+     */
+    public static Shell getShell(Widget widget) {
+        if (widget instanceof Control)
+            return ((Control) widget).getShell();
+        if (widget instanceof Caret)
+            return ((Caret) widget).getParent().getShell();
+        if (widget instanceof DragSource)
+            return ((DragSource) widget).getControl().getShell();
+        if (widget instanceof DropTarget)
+            return ((DropTarget) widget).getControl().getShell();
+        if (widget instanceof Menu)
+            return ((Menu) widget).getParent().getShell();
+        if (widget instanceof ScrollBar)
+            return ((ScrollBar) widget).getParent().getShell();
+
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/SelectionData.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.SelectionData;
+
+import dwtx.ui.internal.forms.widgets.Locator;
+
+import dwt.DWT;
+import dwt.events.MouseEvent;
+import dwt.graphics.Color;
+import dwt.graphics.Point;
+import dwt.widgets.Display;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+static import tango.text.Text;
+
+public class SelectionData {
+    public Display display;
+    public Color bg;
+    public Color fg;
+    private Point start;
+    private Point stop;
+    private ArraySeq!(String) segments;
+    private bool newLineNeeded;
+
+    public this(MouseEvent e) {
+        display = e.display;
+        segments = new ArraySeq!(String);
+        start = new Point(e.x, e.y);
+        stop = new Point(e.x, e.y);
+        bg = e.display.getSystemColor(DWT.COLOR_LIST_SELECTION);
+        fg = e.display.getSystemColor(DWT.COLOR_LIST_SELECTION_TEXT);
+    }
+
+    public void markNewLine() {
+        newLineNeeded=true;
+    }
+    public void addSegment(String text) {
+        if (newLineNeeded) {
+            segments.append(System.getProperty("line.separator")); //$NON-NLS-1$
+            newLineNeeded=false;
+        }
+        segments.append(text);
+    }
+
+    public void update(MouseEvent e) {
+        //Control c = (Control)e.widget;
+        stop.x = e.x;
+        stop.y = e.y;
+    }
+    public void reset() {
+        segments.clear();
+    }
+    public String getSelectionText() {
+        auto buf = new tango.text.Text.Text!(char);
+        for (int i=0; i<segments.size(); i++) {
+            buf.append(segments.get(i));
+        }
+        return buf.toString();
+    }
+    public bool canCopy() {
+        return segments.size()>0;
+    }
+
+    private int getTopOffset() {
+        return start.y<stop.y?start.y:stop.y;
+    }
+    private int getBottomOffset() {
+        return start.y>stop.y?start.y:stop.y;
+    }
+    public int getLeftOffset(Locator locator) {
+        return isInverted(locator)? stop.x:start.x;
+    }
+    public int getLeftOffset(int rowHeight) {
+        return isInverted(rowHeight) ? stop.x:start.x;
+    }
+    public int getRightOffset(Locator locator) {
+        return isInverted(locator)? start.x: stop.x;
+    }
+    public int getRightOffset(int rowHeight) {
+        return isInverted(rowHeight) ? start.x:stop.x;
+    }
+    private bool isInverted(Locator locator) {
+        int rowHeight = locator.heights.get(locator.rowCounter).array[0];
+        return isInverted(rowHeight);
+    }
+    private bool isInverted(int rowHeight) {
+        int deltaY = start.y - stop.y;
+        if (Math.abs(deltaY) > rowHeight) {
+            // inter-row selection
+            return deltaY>0;
+        }
+        // intra-row selection
+        return start.x > stop.x;
+    }
+    public bool isEnclosed() {
+        return !start.equals(stop);
+    }
+
+    public bool isSelectedRow(Locator locator) {
+        if (!isEnclosed())
+            return false;
+        int rowHeight = locator.heights.get(locator.rowCounter).array[0];
+        return isSelectedRow(locator.y, rowHeight);
+    }
+    public bool isSelectedRow(int y, int rowHeight) {
+        if (!isEnclosed())
+            return false;
+        return (y + rowHeight >= getTopOffset() &&
+                y <= getBottomOffset());
+    }
+    public bool isFirstSelectionRow(Locator locator) {
+        if (!isEnclosed())
+            return false;
+        int rowHeight = locator.heights.get(locator.rowCounter).array[0];
+        return (locator.y + rowHeight >= getTopOffset() &&
+                locator.y <= getTopOffset());
+    }
+    public bool isFirstSelectionRow(int y, int rowHeight) {
+        if (!isEnclosed())
+            return false;
+        return (y + rowHeight >= getTopOffset() &&
+                y <= getTopOffset());
+    }
+    public bool isLastSelectionRow(Locator locator) {
+        if (!isEnclosed())
+            return false;
+        int rowHeight = locator.heights.get(locator.rowCounter).array[0];
+        return (locator.y + rowHeight >=getBottomOffset() &&
+                locator.y <= getBottomOffset());
+    }
+    public bool isLastSelectionRow(int y, int rowHeight) {
+        if (!isEnclosed())
+            return false;
+        return (y + rowHeight >=getBottomOffset() &&
+                y <= getBottomOffset());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/TextHyperlinkSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.TextHyperlinkSegment;
+
+import java.util.Hashtable;
+
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.Rectangle;
+import dwtx.ui.forms.HyperlinkSettings;
+
+/**
+ * @version 1.0
+ * @author
+ */
+public class TextHyperlinkSegment extends TextSegment implements
+        IHyperlinkSegment {
+    private String href;
+
+    private String tooltipText;
+
+    //private static final String LINK_FG = "c.___link_fg";
+
+    private HyperlinkSettings settings;
+
+    public TextHyperlinkSegment(String text, HyperlinkSettings settings,
+            String fontId) {
+        super(text, fontId);
+        this.settings = settings;
+        underline = settings.getHyperlinkUnderlineMode() is HyperlinkSettings.UNDERLINE_ALWAYS;
+    }
+
+    /*
+     * @see IObjectReference#getObjectId()
+     */
+    public String getHref() {
+        return href;
+    }
+
+    public void setHref(String href) {
+        this.href = href;
+    }
+
+    /*
+     * public void paint(GC gc, int width, Locator locator, Hashtable
+     * resourceTable, bool selected, SelectionData selData) {
+     * resourceTable.put(LINK_FG, settings.getForeground());
+     * setColorId(LINK_FG); super.paint(gc, width, locator, resourceTable,
+     * selected, selData); }
+     */
+
+    public void paint(GC gc, bool hover, Hashtable resourceTable,
+            bool selected, SelectionData selData, Rectangle repaintRegion) {
+        bool rolloverMode = settings.getHyperlinkUnderlineMode() is HyperlinkSettings.UNDERLINE_HOVER;
+        Color savedFg = gc.getForeground();
+        Color newFg = hover ? settings.getActiveForeground() : settings
+                .getForeground();
+        if (newFg!isnull)
+            gc.setForeground(newFg); 
+        super.paint(gc, hover, resourceTable, selected, rolloverMode, selData,
+                repaintRegion);
+        gc.setForeground(savedFg);
+    }
+
+    public String getTooltipText() {
+        return tooltipText;
+    }
+
+    public void setTooltipText(String tooltip) {
+        this.tooltipText = tooltip;
+    }
+    
+    public bool isSelectable() {
+        return true;
+    }
+
+    public bool isFocusSelectable(Hashtable resourceTable) {
+        return true;
+    }
+
+    public bool setFocus(Hashtable resourceTable, bool direction) {
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/TextSegment.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,755 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.TextSegment;
+
+import dwtx.ui.internal.forms.widgets.ParagraphSegment;
+import dwtx.ui.internal.forms.widgets.Locator;
+import dwtx.ui.internal.forms.widgets.SelectionData;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.FontMetrics;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+
+import dwt.dwthelper.utils;
+import tango.util.collection.ArraySeq;
+
+/**
+ * @version 1.0
+ * @author
+ */
+public class TextSegment : ParagraphSegment {
+
+    //DWT_TODO temp type
+    static class BreakIterator{
+
+        public static const int DONE = 0;
+
+        public static BreakIterator getLineInstance() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        public void setText(String text) {
+            // TODO Auto-generated method stub
+
+        }
+
+        public int first() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int next() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+    }
+    private String colorId;
+
+    private String fontId;
+
+    private String text;
+
+    protected bool underline;
+
+    private bool wrapAllowed = true;
+
+    protected ArraySeq!(Object) areaRectangles;
+
+    private TextFragment[] textFragments;
+
+    class AreaRectangle {
+        Rectangle rect;
+
+        int from, to;
+
+        public this(Rectangle rect, int from, int to) {
+            this.rect = rect;
+            this.from = from;
+            this.to = to;
+        }
+
+        public bool contains(int x, int y) {
+            return rect.contains(x, y);
+        }
+
+        public bool intersects(Rectangle region) {
+            return rect.intersects(region);
+        }
+
+        public String getText() {
+            if (from is 0 && to is -1)
+                return this.outer.getText();
+            if (from > 0 && to is -1)
+                return this.outer.getText().substring(from);
+            return this.outer.getText().substring(from, to);
+        }
+    }
+
+    static class SelectionRange {
+        public int start;
+
+        public int stop;
+
+        public this() {
+            reset();
+        }
+
+        public void reset() {
+            start = -1;
+            stop = -1;
+        }
+    }
+
+    static class TextFragment {
+        short index;
+
+        short length;
+
+        public this(short index, short length) {
+            this.index = index;
+            this.length = length;
+        }
+    }
+
+    public this(String text, String fontId) {
+        this(text, fontId, null, true);
+    }
+
+    public this(String text, String fontId, String colorId) {
+        this(text, fontId, colorId, true);
+    }
+
+    public this(String text, String fontId, String colorId, bool wrapAllowed) {
+        areaRectangles = new ArraySeq!(Object);
+        this.text = cleanup(text);
+        this.fontId = fontId;
+        this.colorId = colorId;
+        this.wrapAllowed = wrapAllowed;
+    }
+
+    private String cleanup(String text) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < text.length(); i++) {
+            char c = text.charAt(i);
+            if (c is '\n' || c is '\r' || c is '\f') {
+                if (i > 0)
+                    buf.append(' ');
+            } else
+                buf.append(c);
+        }
+        return buf.toString();
+    }
+
+    public void setWordWrapAllowed(bool value) {
+        wrapAllowed = value;
+    }
+
+    public bool isWordWrapAllowed() {
+        return wrapAllowed;
+    }
+
+    public bool isSelectable() {
+        return false;
+    }
+
+    public String getColorId() {
+        return colorId;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    void setText(String text) {
+        this.text = cleanup(text);
+        textFragments = null;
+    }
+
+    void setColorId(String colorId) {
+        this.colorId = colorId;
+    }
+
+    void setFontId(String fontId) {
+        this.fontId = fontId;
+        textFragments = null;
+    }
+
+    public bool contains(int x, int y) {
+        for (int i = 0; i < areaRectangles.size(); i++) {
+            AreaRectangle ar = cast(AreaRectangle) areaRectangles.get(i);
+            if (ar.contains(x, y))
+                return true;
+            if (i<areaRectangles.size()-1) {
+                // test the gap
+                Rectangle top = ar.rect;
+                Rectangle bot = (cast(AreaRectangle)areaRectangles.get(i+1)).rect;
+                if (y >= top.y+top.height && y < bot.y) {
+                    // in the gap
+                    int left = Math.max(top.x, bot.x);
+                    int right = Math.min(top.x+top.width, bot.x+bot.width);
+                    if (x>=left && x<=right) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    public bool intersects(Rectangle rect) {
+        for (int i = 0; i < areaRectangles.size(); i++) {
+            AreaRectangle ar = cast(AreaRectangle) areaRectangles.get(i);
+            if (ar.intersects(rect))
+                return true;
+            if (i<areaRectangles.size()-1) {
+                // test the gap
+                Rectangle top = ar.rect;
+                Rectangle bot = (cast(AreaRectangle)areaRectangles.get(i+1)).rect;
+                if (top.y+top.height < bot.y) {
+                    int y = top.y+top.height;
+                    int height = bot.y-y;
+                    int left = Math.max(top.x, bot.x);
+                    int right = Math.min(top.x+top.width, bot.x+bot.width);
+                    Rectangle gap = new Rectangle(left, y, right-left, height);
+                    if (gap.intersects(rect))
+                        return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public Rectangle getBounds() {
+        int x = 0, y = 0;
+        int width = 0, height = 0;
+
+        for (int i = 0; i < areaRectangles.size(); i++) {
+            AreaRectangle ar = cast(AreaRectangle) areaRectangles.get(i);
+            if (i is 0) {
+                x = ar.rect.x;
+                y = ar.rect.y;
+            } else
+                x = Math.min(ar.rect.x, x);
+            width = Math.max(ar.rect.width, width);
+            height += ar.rect.height;
+        }
+        return new Rectangle(x, y, width, height);
+    }
+
+    public bool advanceLocator(GC gc, int wHint, Locator locator,
+            Hashtable objectTable, bool computeHeightOnly) {
+        Font oldFont = null;
+        if (fontId !is null) {
+            oldFont = gc.getFont();
+            Font newFont = cast(Font) objectTable.get(fontId);
+            if (newFont !is null)
+                gc.setFont(newFont);
+        }
+        FontMetrics fm = gc.getFontMetrics();
+        int lineHeight = fm.getHeight();
+        bool newLine = false;
+
+        if (wHint is DWT.DEFAULT || !wrapAllowed) {
+            Point extent = gc.textExtent(text);
+            int totalExtent = locator.x+extent.x;
+            if (isSelectable())
+                totalExtent+=1;
+
+            if (wHint !is DWT.DEFAULT && totalExtent > wHint) {
+                // new line
+                locator.x = locator.indent;
+                locator.y += locator.rowHeight;
+                if (computeHeightOnly)
+                    locator.collectHeights();
+                locator.rowHeight = 0;
+                locator.leading = 0;
+                newLine = true;
+            }
+            int width = extent.x;
+            if (isSelectable())
+                width += 1;
+            locator.x += width;
+            locator.width = locator.indent + width;
+            locator.rowHeight = Math.max(locator.rowHeight, extent.y);
+            locator.leading = Math.max(locator.leading, fm.getLeading());
+            return newLine;
+        }
+
+        computeTextFragments(gc);
+
+        int width = 0;
+        Point lineExtent = new Point(0, 0);
+
+        for (int i = 0; i < textFragments.length; i++) {
+            TextFragment textFragment = textFragments[i];
+            int currentExtent = locator.x + lineExtent.x;
+
+            if (isSelectable())
+                currentExtent += 1;
+
+            if (i !is 0 && currentExtent + textFragment.length > wHint) {
+                // overflow
+                int lineWidth = currentExtent;
+                locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y);
+                locator.leading = Math.max(locator.leading, fm.getLeading());
+                if (computeHeightOnly)
+                    locator.collectHeights();
+                locator.x = locator.indent;
+                locator.y += locator.rowHeight;
+                locator.rowHeight = 0;
+                locator.leading = 0;
+                lineExtent.x = 0;
+                lineExtent.y = 0;
+                width = Math.max(width, lineWidth);
+                newLine = true;
+            }
+            lineExtent.x += textFragment.length;
+            lineExtent.y = Math.max(lineHeight, lineExtent.y);
+        }
+        int lineWidth = lineExtent.x;
+        if (isSelectable())
+            lineWidth += 1;
+        locator.x += lineWidth;
+        locator.width = width;
+        locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y);
+        locator.leading = Math.max(locator.leading, fm.getLeading());
+        if (oldFont !is null) {
+            gc.setFont(oldFont);
+        }
+        return newLine;
+    }
+
+    /**
+     * @param gc
+     * @param width
+     * @param locator
+     * @param selected
+     * @param selData
+     * @param color
+     * @param fm
+     * @param lineHeight
+     * @param descent
+     */
+    private void layoutWithoutWrapping(GC gc, int width, Locator locator,
+            bool selected, FontMetrics fm, int lineHeight, int descent) {
+        Point extent = gc.textExtent(text);
+        int ewidth = extent.x;
+        if (isSelectable())
+            ewidth += 1;
+        if (locator.x + ewidth > width-locator.marginWidth) {
+            // new line
+            locator.resetCaret();
+            locator.y += locator.rowHeight;
+            locator.rowHeight = 0;
+            locator.rowCounter++;
+        }
+        int ly = locator.getBaseline(fm.getHeight() - fm.getLeading());
+        //int lineY = ly + lineHeight - descent + 1;
+        Rectangle br = new Rectangle(locator.x, ly, ewidth,
+                lineHeight - descent + 3);
+        areaRectangles.add(new AreaRectangle(br, 0, -1));
+        locator.x += ewidth;
+        locator.width = ewidth;
+        locator.rowHeight = Math.max(locator.rowHeight, extent.y);
+    }
+
+    protected int convertOffsetToStringIndex(GC gc, String s, int x,
+            int swidth, int selOffset) {
+        int index = s.length();
+        while (index > 0 && x + swidth > selOffset) {
+            index--;
+            String ss = s.substring(0, index);
+            swidth = gc.textExtent(ss).x;
+        }
+        return index;
+    }
+
+    public void paintFocus(GC gc, Color bg, Color fg, bool selected,
+            Rectangle repaintRegion) {
+        if (areaRectangles is null)
+            return;
+        for (int i = 0; i < areaRectangles.size(); i++) {
+            AreaRectangle areaRectangle = cast(AreaRectangle) areaRectangles.get(i);
+            Rectangle br = areaRectangle.rect;
+            int bx = br.x;
+            int by = br.y;
+            if (repaintRegion !is null) {
+                bx -= repaintRegion.x;
+                by -= repaintRegion.y;
+            }
+            if (selected) {
+                gc.setBackground(bg);
+                gc.setForeground(fg);
+                gc.drawFocus(bx, by, br.width, br.height);
+            } else {
+                gc.setForeground(bg);
+                gc.drawRectangle(bx, by, br.width - 1, br.height - 1);
+            }
+        }
+    }
+
+    public void paint(GC gc, bool hover, Hashtable resourceTable,
+            bool selected, SelectionData selData, Rectangle repaintRegion) {
+        this.paint(gc, hover, resourceTable, selected, false, selData,
+                repaintRegion);
+    }
+
+    protected void paint(GC gc, bool hover, Hashtable resourceTable,
+            bool selected, bool rollover, SelectionData selData,
+            Rectangle repaintRegion) {
+        Font oldFont = null;
+        Color oldColor = null;
+        Color oldBg = null;
+
+        // apply segment-specific font, color and background
+        if (fontId !is null) {
+            oldFont = gc.getFont();
+            Font newFont = cast(Font) resourceTable.get(fontId);
+            if (newFont !is null)
+                gc.setFont(newFont);
+        }
+        if (!hover && colorId !is null) {
+            oldColor = gc.getForeground();
+            Color newColor = cast(Color) resourceTable.get(colorId);
+            if (newColor !is null)
+                gc.setForeground(newColor);
+        }
+        oldBg = gc.getBackground();
+
+        FontMetrics fm = gc.getFontMetrics();
+        int lineHeight = fm.getHeight();
+        int descent = fm.getDescent();
+
+        // paint area rectangles of the segment
+        for (int i = 0; i < areaRectangles.size(); i++) {
+            AreaRectangle areaRectangle = cast(AreaRectangle) areaRectangles.get(i);
+            Rectangle rect = areaRectangle.rect;
+            String text = areaRectangle.getText();
+            Point extent = gc.textExtent(text);
+            int textX = rect.x + (isSelectable()?1:0);
+            int lineY = rect.y + lineHeight - descent + 1;
+            paintString(gc, text, extent.x, textX, rect.y, lineY, selData,
+                    rect, hover, rollover, repaintRegion);
+            if (selected) {
+                int fx = rect.x;
+                int fy = rect.y;
+                if (repaintRegion !is null) {
+                    fx -= repaintRegion.x;
+                    fy -= repaintRegion.y;
+                }
+                //To avoid partially cancelling the focus by painting over
+                //X-ORed pixels, first cancel it yourself
+                Color fg = gc.getForeground();
+                gc.setForeground(oldBg);
+                gc.drawRectangle(fx, fy, rect.width - 1, rect.height - 1);
+                gc.setForeground(fg);
+                gc.drawFocus(fx, fy, rect.width, rect.height);
+            }
+        }
+        // restore GC resources
+        if (oldFont !is null) {
+            gc.setFont(oldFont);
+        }
+        if (oldColor !is null) {
+            gc.setForeground(oldColor);
+        }
+        if (oldBg !is null) {
+            gc.setBackground(oldBg);
+        }
+    }
+
+    public void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData) {
+        Font oldFont = null;
+
+        if (fontId !is null) {
+            oldFont = gc.getFont();
+            Font newFont = cast(Font) resourceTable.get(fontId);
+            if (newFont !is null)
+                gc.setFont(newFont);
+        }
+
+        for (int i = 0; i < areaRectangles.size(); i++) {
+            AreaRectangle areaRectangle = cast(AreaRectangle) areaRectangles.get(i);
+            Rectangle rect = areaRectangle.rect;
+            String text = areaRectangle.getText();
+            Point extent = gc.textExtent(text);
+            computeSelection(gc, text, extent.x, selData,
+                    rect);
+        }
+        // restore GC resources
+        if (oldFont !is null) {
+            gc.setFont(oldFont);
+        }
+    }
+
+    private void paintString(GC gc, String s, int swidth, int x, int y,
+            int lineY, SelectionData selData, Rectangle bounds, bool hover,
+            bool rolloverMode, Rectangle repaintRegion) {
+        // repaints one area rectangle
+        if (selData !is null && selData.isEnclosed()) {
+            Color savedBg = gc.getBackground();
+            Color savedFg = gc.getForeground();
+            int leftOffset = selData.getLeftOffset(bounds.height);
+            int rightOffset = selData.getRightOffset(bounds.height);
+            bool firstRow = selData.isFirstSelectionRow(bounds.y,
+                    bounds.height);
+            bool lastRow = selData.isLastSelectionRow(bounds.y,
+                    bounds.height);
+            bool selectedRow = selData
+                    .isSelectedRow(bounds.y, bounds.height);
+
+            int sstart = -1;
+            int sstop = -1;
+
+            if ((firstRow && x + swidth < leftOffset)
+                    || (lastRow && x > rightOffset)) {
+                paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY,
+                        hover, rolloverMode, repaintRegion);
+                return;
+            }
+
+            if (firstRow && bounds.x + swidth > leftOffset) {
+                sstart = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
+                        leftOffset);
+            }
+            if (lastRow && bounds.x + swidth > rightOffset) {
+                sstop = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
+                        rightOffset);
+            }
+
+            if (firstRow && sstart !is -1) {
+                String left = s.substring(0, sstart);
+                int width = gc.textExtent(left).x;
+                paintStringSegment(gc, left, width, x, y, lineY, hover,
+                        rolloverMode, repaintRegion);
+                x += width;
+            }
+            if (selectedRow) {
+                int lindex = sstart !is -1 ? sstart : 0;
+                int rindex = sstop !is -1 ? sstop : s.length();
+                String mid = s.substring(lindex, rindex);
+                Point extent = gc.textExtent(mid);
+                gc.setForeground(selData.fg);
+                gc.setBackground(selData.bg);
+                gc.fillRectangle(x, y, extent.x, extent.y);
+                paintStringSegment(gc, mid, extent.x, x, y, lineY, hover,
+                        rolloverMode, repaintRegion);
+                x += extent.x;
+                gc.setForeground(savedFg);
+                gc.setBackground(savedBg);
+            } else {
+                paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY,
+                        hover, rolloverMode, repaintRegion);
+            }
+            if (lastRow && sstop !is -1) {
+                String right = s.substring(sstop);
+                paintStringSegment(gc, right, gc.textExtent(right).x, x, y,
+                        lineY, hover, rolloverMode, repaintRegion);
+            }
+        } else {
+            paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY, hover,
+                    rolloverMode, repaintRegion);
+        }
+    }
+
+    private void computeSelection(GC gc, String s, int swidth, SelectionData selData, Rectangle bounds) {
+        int leftOffset = selData.getLeftOffset(bounds.height);
+        int rightOffset = selData.getRightOffset(bounds.height);
+        bool firstRow = selData.isFirstSelectionRow(bounds.y, bounds.height);
+        bool lastRow = selData.isLastSelectionRow(bounds.y, bounds.height);
+        bool selectedRow = selData.isSelectedRow(bounds.y, bounds.height);
+
+        int sstart = -1;
+        int sstop = -1;
+
+        if (firstRow && bounds.x + swidth > leftOffset) {
+            sstart = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
+                    leftOffset);
+        }
+        if (lastRow && bounds.x + swidth > rightOffset) {
+            sstop = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
+                    rightOffset);
+        }
+
+        if (selectedRow) {
+            int lindex = sstart !is -1 ? sstart : 0;
+            int rindex = sstop !is -1 ? sstop : s.length();
+            String mid = s.substring(lindex, rindex);
+            selData.addSegment(mid);
+        }
+    }
+
+    /**
+     * @param gc
+     * @param s
+     * @param x
+     * @param y
+     * @param lineY
+     * @param hover
+     * @param rolloverMode
+     */
+    private void paintStringSegment(GC gc, String s, int swidth, int x, int y,
+            int lineY, bool hover, bool rolloverMode,
+            Rectangle repaintRegion) {
+        bool reverse = false;
+        int clipX = x;
+        int clipY = y;
+        int clipLineY = lineY;
+        if (repaintRegion !is null) {
+            clipX -= repaintRegion.x;
+            clipY -= repaintRegion.y;
+            clipLineY -= repaintRegion.y;
+        }
+        if (underline || hover || rolloverMode) {
+            if (rolloverMode && !hover)
+                reverse = true;
+        }
+        if (reverse) {
+            drawUnderline(gc, swidth, clipX, clipLineY, hover, rolloverMode);
+            gc.drawString(s, clipX, clipY, false);
+        } else {
+            gc.drawString(s, clipX, clipY, false);
+            drawUnderline(gc, swidth, clipX, clipLineY, hover, rolloverMode);
+        }
+    }
+
+    private void drawUnderline(GC gc, int swidth, int x, int y, bool hover,
+            bool rolloverMode) {
+        if (underline || hover || rolloverMode) {
+            Color saved = null;
+            if (rolloverMode && !hover) {
+                saved = gc.getForeground();
+                gc.setForeground(gc.getBackground());
+            }
+            gc.drawLine(x, y, x + swidth-1, y);
+            if (saved !is null)
+                gc.setForeground(saved);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC,
+     *      int, dwtx.ui.internal.forms.widgets.Locator,
+     *      java.util.Hashtable, bool,
+     *      dwtx.ui.internal.forms.widgets.SelectionData)
+     */
+    public void layout(GC gc, int width, Locator locator,
+            Hashtable resourceTable, bool selected) {
+        Font oldFont = null;
+
+        areaRectangles.clear();
+
+        if (fontId !is null) {
+            oldFont = gc.getFont();
+            Font newFont = cast(Font) resourceTable.get(fontId);
+            if (newFont !is null)
+                gc.setFont(newFont);
+        }
+        FontMetrics fm = gc.getFontMetrics();
+        int lineHeight = fm.getHeight();
+        int descent = fm.getDescent();
+
+        if (!wrapAllowed) {
+            layoutWithoutWrapping(gc, width, locator, selected, fm, lineHeight,
+                    descent);
+        } else {
+            int lineStart = 0;
+            int lastLoc = 0;
+            Point lineExtent = new Point(0, 0);
+            computeTextFragments(gc);
+            int rightEdge = width-locator.marginWidth;
+            for (int i = 0; i < textFragments.length; i++) {
+                TextFragment fragment = textFragments[i];
+                int breakLoc = fragment.index;
+                if (breakLoc is 0)
+                    continue;
+                if (i !is 0 && locator.x + lineExtent.x + fragment.length > rightEdge) {
+                    // overflow
+                    int lineWidth = locator.x + lineExtent.x;
+                    if (isSelectable())
+                        lineWidth += 1;
+                    int ly = locator.getBaseline(lineHeight - fm.getLeading());
+                    Rectangle br = new Rectangle(isSelectable()?
+                            locator.x - 1:locator.x, ly,
+                            isSelectable()?lineExtent.x + 1:lineExtent.x, lineHeight - descent + 3);
+                    areaRectangles
+                            .add(new AreaRectangle(br, lineStart, lastLoc));
+
+                    locator.rowHeight = Math.max(locator.rowHeight,
+                            lineExtent.y);
+                    locator.resetCaret();
+                    if (isSelectable())
+                        locator.x += 1;
+                    locator.y += locator.rowHeight;
+                    locator.rowCounter++;
+                    locator.rowHeight = 0;
+                    lineStart = lastLoc;
+                    lineExtent.x = 0;
+                    lineExtent.y = 0;
+                }
+                lastLoc = breakLoc;
+                lineExtent.x += fragment.length;
+                lineExtent.y = Math.max(lineHeight, lineExtent.y);
+            }
+            //String lastLine = text.substring(lineStart, lastLoc);
+            int ly = locator.getBaseline(lineHeight - fm.getLeading());
+            int lastWidth = lineExtent.x;
+            if (isSelectable())
+                lastWidth += 1;
+            Rectangle br = new Rectangle(isSelectable()?locator.x - 1:locator.x, ly,
+                    isSelectable()?lineExtent.x + 1:lineExtent.x,
+                    lineHeight - descent + 3);
+            //int lineY = ly + lineHeight - descent + 1;
+            areaRectangles.add(new AreaRectangle(br, lineStart, lastLoc));
+            locator.x += lastWidth;
+            locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y);
+        }
+        if (oldFont !is null) {
+            gc.setFont(oldFont);
+        }
+    }
+
+    private void computeTextFragments(GC gc) {
+        if (textFragments !is null)
+            return;
+        ArrayList list = new ArrayList();
+        BreakIterator wb = BreakIterator.getLineInstance();
+        wb.setText(getText());
+        int cursor = 0;
+        for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) {
+            if (loc is 0)
+                continue;
+            String word = text.substring(cursor, loc);
+            Point extent = gc.textExtent(word);
+            list.add(new TextFragment(cast(short) loc, cast(short) extent.x));
+            cursor = loc;
+        }
+        textFragments = cast(TextFragment[]) list.toArray(new TextFragment[list
+                .size()]);
+    }
+
+    public void clearCache(String fontId) {
+        if (fontIdisnull && (this.fontId is null||this.fontId.equals(FormTextModel.BOLD_FONT_ID)))
+            textFragments = null;
+        else if (fontId !is null && this.fontId !is null && fontId.equals(this.fontId))
+            textFragments = null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/TitleRegion.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,544 @@
+/*******************************************************************************
+ * 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 dwtx.ui.internal.forms.widgets.TitleRegion;
+
+import dwtx.ui.internal.forms.widgets.BusyIndicator;
+
+import dwt.DWT;
+import dwt.dnd.DragSource;
+import dwt.dnd.DragSourceEffect;
+import dwt.dnd.DragSourceEvent;
+import dwt.dnd.DragSourceListener;
+import dwt.dnd.DropTarget;
+import dwt.dnd.DropTargetListener;
+import dwt.dnd.Transfer;
+import dwt.events.MouseEvent;
+import dwt.events.MouseMoveListener;
+import dwt.events.MouseTrackListener;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.FontMetrics;
+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.Event;
+import dwt.widgets.Label;
+import dwt.widgets.Layout;
+import dwt.widgets.Listener;
+import dwt.widgets.Menu;
+import dwtx.jface.action.IMenuManager;
+import dwtx.jface.action.MenuManager;
+import dwtx.ui.forms.IFormColors;
+import dwtx.ui.forms.widgets.ILayoutExtension;
+import dwtx.ui.forms.widgets.SizeCache;
+import dwtx.ui.forms.widgets.Twistie;
+import dwtx.ui.internal.forms.IMessageToolTipManager;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Form heading title.
+ */
+public class TitleRegion : Canvas {
+    public static const int STATE_NORMAL = 0;
+    public static const int STATE_HOVER_LIGHT = 1;
+    public static const int STATE_HOVER_FULL = 2;
+    private int hoverState;
+    private static const int HMARGIN = 1;
+    private static const int VMARGIN = 5;
+    private static const int SPACING = 5;
+    private static const int ARC_WIDTH = 20;
+    private static const int ARC_HEIGHT = 20;
+    private Image image;
+    private BusyIndicator busyLabel;
+    private Label titleLabel;
+    private SizeCache titleCache;
+    private int fontHeight = -1;
+    private int fontBaselineHeight = -1;
+    private MenuHyperlink menuHyperlink;
+    private MenuManager menuManager;
+    private bool dragSupport;
+    private int dragOperations;
+    private Transfer[] dragTransferTypes;
+    private DragSourceListener dragListener;
+    private DragSource dragSource;
+    private Image dragImage;
+
+    private class HoverListener : MouseTrackListener,
+            MouseMoveListener {
+
+        public void mouseEnter(MouseEvent e) {
+            setHoverState(STATE_HOVER_FULL);
+        }
+
+        public void mouseExit(MouseEvent e) {
+            setHoverState(STATE_NORMAL);
+        }
+
+        public void mouseHover(MouseEvent e) {
+        }
+
+        public void mouseMove(MouseEvent e) {
+            if (e.button > 0)
+                setHoverState(STATE_NORMAL);
+            else
+                setHoverState(STATE_HOVER_FULL);
+        }
+    }
+
+    private class MenuHyperlink : Twistie {
+        private bool firstTime = true;
+
+        public this(Composite parent, int style) {
+            super(parent, style);
+            setExpanded(true);
+        }
+
+        public void setExpanded(bool expanded) {
+            if (firstTime) {
+                super.setExpanded(expanded);
+                firstTime = false;
+            } else {
+                Menu menu = menuManager.createContextMenu(menuHyperlink);
+                menu.setVisible(true);
+            }
+        }
+    }
+
+    private class TitleRegionLayout : Layout, ILayoutExtension {
+
+        protected Point computeSize(Composite composite, int wHint, int hHint,
+                bool flushCache) {
+            return layout(composite, false, 0, 0, wHint, hHint, flushCache);
+        }
+
+        protected void layout(Composite composite, bool flushCache) {
+            Rectangle carea = composite.getClientArea();
+            layout(composite, true, carea.x, carea.y, carea.width,
+                    carea.height, flushCache);
+        }
+
+        private Point layout(Composite composite, bool move, int x, int y,
+                int width, int height, bool flushCache) {
+            int iwidth = width is DWT.DEFAULT ? DWT.DEFAULT : width - HMARGIN
+                    * 2;
+            Point bsize = null;
+            Point tsize = null;
+            Point msize = null;
+
+            if (busyLabel !is null) {
+                bsize = busyLabel.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+            }
+            if (menuManager !is null) {
+                menuHyperlink.setVisible(!menuManager.isEmpty()
+                        && titleLabel.getVisible());
+                if (menuHyperlink.getVisible())
+                    msize = menuHyperlink.computeSize(DWT.DEFAULT, DWT.DEFAULT);
+            }
+            if (flushCache)
+                titleCache.flush();
+            titleCache.setControl(titleLabel);
+            int twidth = iwidth is DWT.DEFAULT ? iwidth : iwidth - SPACING * 2;
+            if (bsize !is null && twidth !is DWT.DEFAULT)
+                twidth -= bsize.x + SPACING;
+            if (msize !is null && twidth !is DWT.DEFAULT)
+                twidth -= msize.x + SPACING;
+            if (titleLabel.getVisible()) {
+                tsize = titleCache.computeSize(twidth, DWT.DEFAULT);
+                if (twidth !is DWT.DEFAULT) {
+                    // correct for the case when width hint is larger
+                    // than the maximum width - this is when the text
+                    // can be rendered on one line with width to spare
+                    int maxWidth = titleCache.computeSize(DWT.DEFAULT,
+                            DWT.DEFAULT).x;
+                    tsize.x = Math.min(tsize.x, maxWidth);
+                    // System.out.println("twidth="+twidth+",
+                    // tsize.x="+tsize.x); //$NON-NLS-1$//$NON-NLS-2$
+                }
+            } else
+                tsize = new Point(0, 0);
+            Point size = new Point(width, height);
+            if (!move) {
+                // compute size
+                size.x = tsize.x > 0 ? HMARGIN * 2 + SPACING * 2 + tsize.x : 0;
+                size.y = tsize.y;
+                if (bsize !is null) {
+                    size.x += bsize.x + SPACING;
+                    size.y = Math.max(size.y, bsize.y);
+                }
+                if (msize !is null) {
+                    size.x += msize.x + SPACING;
+                    size.y = Math.max(size.y, msize.y);
+                }
+                if (size.y > 0)
+                    size.y += VMARGIN * 2;
+                // System.out.println("Compute size: width="+width+",
+                // size.x="+size.x); //$NON-NLS-1$ //$NON-NLS-2$
+            } else {
+                // position controls
+                int xloc = x + HMARGIN + SPACING;
+                int yloc = y + VMARGIN;
+                if (bsize !is null) {
+                    busyLabel.setBounds(xloc,
+                            // yloc + height / 2 - bsize.y / 2,
+                            yloc + (getFontHeight() >= bsize.y ? getFontHeight() : bsize.y) - 1 - bsize.y,
+                            bsize.x, bsize.y);
+                    xloc += bsize.x + SPACING;
+                }
+                if (titleLabel.getVisible()) {
+                    int tw = width - HMARGIN * 2 - SPACING * 2;
+                    if (bsize !is null)
+                        tw -= bsize.x + SPACING;
+                    if (msize !is null)
+                        tw -= msize.x + SPACING;
+                    titleLabel.setBounds(xloc,
+                    // yloc + height / 2 - tsize.y / 2,
+                            yloc, tw, tsize.y);
+                    // System.out.println("tw="+tw); //$NON-NLS-1$
+                    xloc += tw + SPACING;
+                }
+                if (msize !is null) {
+                    menuHyperlink.setBounds(xloc, yloc
+                            + getFontHeight() / 2 - msize.y / 2,
+                            msize.x, msize.y);
+                }
+            }
+            return size;
+        }
+
+        public int computeMaximumWidth(Composite parent, bool changed) {
+            return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x;
+        }
+
+        public int computeMinimumWidth(Composite parent, bool changed) {
+            return computeSize(parent, 0, DWT.DEFAULT, changed).x;
+        }
+    }
+
+    public this(Composite parent) {
+        super(parent, DWT.NULL);
+        titleLabel = new Label(this, DWT.WRAP);
+        titleLabel.setVisible(false);
+        titleCache = new SizeCache();
+        super.setLayout(new TitleRegionLayout());
+        hookHoverListeners();
+        addListener(DWT.Dispose, new class Listener {
+            public void handleEvent(Event e) {
+                if (dragImage !is null) {
+                    dragImage.dispose();
+                    dragImage = null;
+                }
+            }
+        });
+    }
+
+    /* (non-Javadoc)
+     * @see dwt.widgets.Control#forceFocus()
+     */
+    public bool forceFocus() {
+        return false;
+    }
+
+    private Color getColor(String key) {
+        return cast(Color) (cast(FormHeading) getParent()).colors.get(key);
+    }
+
+    private void hookHoverListeners() {
+        HoverListener listener = new HoverListener();
+        addMouseTrackListener(listener);
+        addMouseMoveListener(listener);
+        titleLabel.addMouseTrackListener(listener);
+        titleLabel.addMouseMoveListener(listener);
+        addPaintListener(new class PaintListener {
+            public void paintControl(PaintEvent e) {
+                onPaint(e);
+            }
+        });
+    }
+
+    private void onPaint(PaintEvent e) {
+        if (hoverState is STATE_NORMAL)
+            return;
+        GC gc = e.gc;
+        Rectangle carea = getClientArea();
+        gc.setBackground(getHoverBackground());
+        int savedAntialias = gc.getAntialias();
+        FormUtil.setAntialias(gc, DWT.ON);
+        gc.fillRoundRectangle(carea.x + HMARGIN, carea.y + 2, carea.width
+                - HMARGIN * 2, carea.height - 4, ARC_WIDTH, ARC_HEIGHT);
+        FormUtil.setAntialias(gc, savedAntialias);
+    }
+
+    private Color getHoverBackground() {
+        if (hoverState is STATE_NORMAL)
+            return null;
+        Color color = getColor(hoverState is STATE_HOVER_FULL ? IFormColors.H_HOVER_FULL
+                : IFormColors.H_HOVER_LIGHT);
+        if (color is null)
+            color = getDisplay()
+                    .getSystemColor(
+                            hoverState is STATE_HOVER_FULL ? DWT.COLOR_WIDGET_BACKGROUND
+                                    : DWT.COLOR_WIDGET_LIGHT_SHADOW);
+        return color;
+    }
+
+    public void setHoverState(int state) {
+        if (dragSource is null || this.hoverState is state)
+            return;
+        this.hoverState = state;
+        Color color = getHoverBackground();
+        titleLabel.setBackground(color !is null ? color
+                : getColor(FormHeading.COLOR_BASE_BG));
+        if (busyLabel !is null)
+            busyLabel.setBackground(color !is null ? color
+                    : getColor(FormHeading.COLOR_BASE_BG));
+        if (menuHyperlink !is null)
+            menuHyperlink.setBackground(color !is null ? color
+                    : getColor(FormHeading.COLOR_BASE_BG));
+        redraw();
+    }
+
+    /**
+     * Fully delegates the size computation to the internal layout manager.
+     */
+    public final Point computeSize(int wHint, int hHint, bool changed) {
+        return (cast(TitleRegionLayout) getLayout()).computeSize(this, wHint,
+                hHint, changed);
+    }
+
+    public final void setLayout(Layout layout) {
+        // do nothing
+    }
+
+    public Image getImage() {
+        return image;
+    }
+
+    public void setImage(Image image) {
+        this.image = image;
+    }
+
+    public void updateImage(Image newImage, bool doLayout) {
+        Image theImage = newImage !is null ? newImage : this.image;
+
+        if (theImage !is null) {
+            ensureBusyLabelExists();
+        } else if (busyLabel !is null) {
+            if (!busyLabel.isBusy()) {
+                busyLabel.dispose();
+                busyLabel = null;
+            }
+        }
+        if (busyLabel !is null) {
+            busyLabel.setImage(theImage);
+        }
+        if (doLayout)
+            layout();
+    }
+
+    public void updateToolTip(String toolTip) {
+        if (busyLabel !is null)
+            busyLabel.setToolTipText(toolTip);
+    }
+
+    public void setBackground(Color bg) {
+        super.setBackground(bg);
+        titleLabel.setBackground(bg);
+        if (busyLabel !is null)
+            busyLabel.setBackground(bg);
+        if (menuHyperlink !is null)
+            menuHyperlink.setBackground(bg);
+    }
+
+    public void setForeground(Color fg) {
+        super.setForeground(fg);
+        titleLabel.setForeground(fg);
+        if (menuHyperlink !is null)
+            menuHyperlink.setForeground(fg);
+    }
+
+    public void setText(String text) {
+        if (text !is null)
+            titleLabel.setText(text);
+        titleLabel.setVisible(text !is null);
+        layout();
+        redraw();
+    }
+
+    public String getText() {
+        return titleLabel.getText();
+    }
+
+    public void setFont(Font font) {
+        super.setFont(font);
+        titleLabel.setFont(font);
+        fontHeight = -1;
+        fontBaselineHeight = -1;
+        layout();
+    }
+
+    private void ensureBusyLabelExists() {
+        if (busyLabel is null) {
+            busyLabel = new BusyIndicator(this, DWT.NULL);
+            busyLabel.setBackground(getColor(FormHeading.COLOR_BASE_BG));
+            HoverListener listener = new HoverListener();
+            busyLabel.addMouseTrackListener(listener);
+            busyLabel.addMouseMoveListener(listener);
+            if (menuManager !is null)
+                busyLabel.setMenu(menuManager.createContextMenu(this));
+            if (dragSupport)
+                addDragSupport(busyLabel, dragOperations, dragTransferTypes, dragListener);
+            IMessageToolTipManager mng = (cast(FormHeading) getParent())
+                    .getMessageToolTipManager();
+            if (mng !is null)
+                mng.createToolTip(busyLabel, true);
+        }
+    }
+
+    private void createMenuHyperlink() {
+        menuHyperlink = new MenuHyperlink(this, DWT.NULL);
+        menuHyperlink.setBackground(getColor(FormHeading.COLOR_BASE_BG));
+        menuHyperlink.setDecorationColor(getForeground());
+        menuHyperlink.setHoverDecorationColor(getDisplay().getSystemColor(DWT.COLOR_LIST_FOREGROUND));
+        HoverListener listener = new HoverListener();
+        menuHyperlink.addMouseTrackListener(listener);
+        menuHyperlink.addMouseMoveListener(listener);
+        if (dragSupport)
+            addDragSupport(menuHyperlink, dragOperations, dragTransferTypes, dragListener);
+    }
+
+    /**
+     * Sets the form's busy state. Busy form will display 'busy' animation in
+     * the area of the title image.
+     *
+     * @param busy
+     *            the form's busy state
+     */
+
+    public bool setBusy(bool busy) {
+        if (busy)
+            ensureBusyLabelExists();
+        else if (busyLabel is null)
+            return false;
+        if (busy is busyLabel.isBusy())
+            return false;
+        busyLabel.setBusy(busy);
+        if (busyLabel.getImage() is null) {
+            layout();
+            return true;
+        }
+        return false;
+    }
+
+    public bool isBusy() {
+        return busyLabel !is null && busyLabel.isBusy();
+    }
+
+    /*
+     * Returns the complete height of the font.
+     */
+    public int getFontHeight() {
+        if (fontHeight is -1) {
+            Font font = getFont();
+            GC gc = new GC(getDisplay());
+            gc.setFont(font);
+            FontMetrics fm = gc.getFontMetrics();
+            fontHeight = fm.getHeight();
+            gc.dispose();
+        }
+        return fontHeight;
+    }
+
+    /*
+     * Returns the height of the font starting at the baseline,
+     * i.e. without the descent.
+     */
+    public int getFontBaselineHeight() {
+        if (fontBaselineHeight is -1) {
+            Font font = getFont();
+            GC gc = new GC(getDisplay());
+            gc.setFont(font);
+            FontMetrics fm = gc.getFontMetrics();
+            fontBaselineHeight = fm.getHeight() - fm.getDescent();
+            gc.dispose();
+        }
+        return fontBaselineHeight;
+    }
+
+    public IMenuManager getMenuManager() {
+        if (menuManager is null) {
+            menuManager = new MenuManager();
+            Menu menu = menuManager.createContextMenu(this);
+            setMenu(menu);
+            titleLabel.setMenu(menu);
+            if (busyLabel !is null)
+                busyLabel.setMenu(menu);
+            createMenuHyperlink();
+        }
+        return menuManager;
+    }
+
+    public void addDragSupport(int operations, Transfer[] transferTypes,
+            DragSourceListener listener) {
+        dragSupport = true;
+        dragOperations = operations;
+        dragTransferTypes = transferTypes;
+        dragListener = listener;
+        dragSource = addDragSupport(titleLabel, operations, transferTypes,
+                listener);
+        addDragSupport(this, operations, transferTypes, listener);
+        if (busyLabel !is null)
+            addDragSupport(busyLabel, operations, transferTypes, listener);
+        if (menuHyperlink !is null)
+            addDragSupport(menuHyperlink, operations, transferTypes, listener);
+    }
+
+    private DragSource addDragSupport(Control control, int operations,
+            Transfer[] transferTypes, DragSourceListener listener) {
+        DragSource source = new DragSource(control, operations);
+        source.setTransfer(transferTypes);
+        source.addDragListener(listener);
+        source.setDragSourceEffect(new class(control) DragSourceEffect {
+            this(Control c){
+                super(c);
+            }
+            public void dragStart(DragSourceEvent event) {
+                event.image = createDragEffectImage();
+            }
+        });
+        return source;
+    }
+
+    private Image createDragEffectImage() {
+        /*
+         * if (dragImage !is null) { dragImage.dispose(); } GC gc = new GC(this);
+         * Point size = getSize(); dragImage = new Image(getDisplay(), size.x,
+         * size.y); gc.copyArea(dragImage, 0, 0); gc.dispose(); return
+         * dragImage;
+         */
+        return null;
+    }
+
+    public void addDropSupport(int operations, Transfer[] transferTypes,
+            DropTargetListener listener) {
+        final DropTarget target = new DropTarget(this, operations);
+        target.setTransfer(transferTypes);
+        target.addDropListener(listener);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/WrappedPageBook.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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 dwtx.ui.internal.forms.widgets.WrappedPageBook;
+
+import dwt.DWT;
+import dwt.graphics.Point;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Layout;
+import dwtx.ui.forms.widgets.ILayoutExtension;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A pagebook is a composite control where only a single control is visible at
+ * a time. It is similar to a notebook, but without tabs.
+ * <p>
+ * This class may be instantiated; it is not intended to be subclassed.
+ * </p>
+ */
+public class WrappedPageBook : Composite {
+    class PageBookLayout : Layout, ILayoutExtension {
+        protected Point computeSize(Composite composite, int wHint, int hHint,
+                bool flushCache) {
+            if (wHint !is DWT.DEFAULT && hHint !is DWT.DEFAULT)
+                return new Point(wHint, hHint);
+            Point result = null;
+            if (currentPage !is null) {
+                result = currentPage.computeSize(wHint, hHint, flushCache);
+            } else {
+                result = new Point(0, 0);
+            }
+            return result;
+        }
+        protected void layout(Composite composite, bool flushCache) {
+            if (currentPage !is null) {
+                currentPage.setBounds(composite.getClientArea());
+            }
+        }
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMaximumWidth(dwt.widgets.Composite,
+         *      bool)
+         */
+        public int computeMaximumWidth(Composite parent, bool changed) {
+            return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x;
+        }
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite,
+         *      bool)
+         */
+        public int computeMinimumWidth(Composite parent, bool changed) {
+            return computeSize(parent, 0, DWT.DEFAULT, changed).x;
+        }
+    }
+    /**
+     * The current control; <code>null</code> if none.
+     */
+    private Control currentPage = null;
+    /**
+     * Creates a new empty pagebook.
+     *
+     * @param parent
+     *            the parent composite
+     * @param style
+     *            the DWT style bits
+     */
+    public this(Composite parent, int style) {
+        super(parent, style);
+        setLayout(new PageBookLayout());
+    }
+    /**
+     * Shows the given page. This method has no effect if the given page is not
+     * contained in this pagebook.
+     *
+     * @param page
+     *            the page to show
+     */
+    public void showPage(Control page) {
+        if (page is currentPage)
+            return;
+        if (page.getParent() !is this)
+            return;
+        Control oldPage = currentPage;
+        currentPage = page;
+        // show new page
+        if (page !is null) {
+            if (!page.isDisposed()) {
+                //page.setVisible(true);
+                layout(true);
+                page.setVisible(true);
+            }
+        }
+        // hide old *after* new page has been made visible in order to avoid
+        // flashing
+        if (oldPage !is null && !oldPage.isDisposed())
+            oldPage.setVisible(false);
+    }
+    public Point computeSize(int wHint, int hHint, bool changed) {
+        return (cast(PageBookLayout) getLayout()).computeSize(this, wHint, hHint,
+                changed);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/res/dwtx.ui.internal.forms.Messages.properties	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,24 @@
+###############################################################################
+# 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
+###############################################################################
+FormText_copy=&Copy
+Form_tooltip_minimize=Minimize
+FormDialog_defaultTitle=Form Dialog
+Form_tooltip_restore=Restore
+
+#Message manager
+MessageManager_sMessageSummary = 1 message detected
+MessageManager_sWarningSummary = 1 warning detected
+MessageManager_sErrorSummary = 1 error detected
+MessageManager_pMessageSummary = {0} messages detected 
+MessageManager_pWarningSummary = {0} warnings detected
+MessageManager_pErrorSummary = {0} errors detected
+ToggleHyperlink_accessibleName=Expandable
+ToggleHyperlink_accessibleColumn=: