diff dwtx/ui/forms/widgets/ExpandableComposite.d @ 75:5d489b9f966c

Fix continue porting
author Frank Benoit <benoit@tionex.de>
date Sat, 24 May 2008 05:11:16 +0200
parents
children 56fea7e5f0f9
line wrap: on
line diff
--- /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);
+    }
+}