changeset 16:e0f0aaf75edd

PopupDialog, bindings and actions
author Frank Benoit <benoit@tionex.de>
date Tue, 01 Apr 2008 08:00:31 +0200
parents db8940420ed8
children f459f9147650
files dwtx/jface/action/AbstractAction.d dwtx/jface/action/AbstractGroupMarker.d dwtx/jface/action/Action.d dwtx/jface/action/ActionContributionItem.d dwtx/jface/action/ContributionItem.d dwtx/jface/action/ContributionManager.d dwtx/jface/action/ExternalActionManager.d dwtx/jface/action/GroupMarker.d dwtx/jface/action/IAction.d dwtx/jface/action/IContributionItem.d dwtx/jface/action/IContributionManager.d dwtx/jface/action/IContributionManagerOverrides.d dwtx/jface/action/IMenuListener.d dwtx/jface/action/IMenuListener2.d dwtx/jface/action/IMenuManager.d dwtx/jface/action/LegacyActionTools.d dwtx/jface/action/MenuManager.d dwtx/jface/action/Separator.d dwtx/jface/action/SubContributionItem.d dwtx/jface/bindings/Binding.d dwtx/jface/bindings/BindingManager.d dwtx/jface/bindings/BindingManagerEvent.d dwtx/jface/bindings/CachedBindingSet.d dwtx/jface/bindings/IBindingManagerListener.d dwtx/jface/bindings/ISchemeListener.d dwtx/jface/bindings/Scheme.d dwtx/jface/bindings/SchemeEvent.d dwtx/jface/bindings/Trigger.d dwtx/jface/bindings/TriggerSequence.d dwtx/jface/bindings/keys/IKeyLookup.d dwtx/jface/bindings/keys/KeyBinding.d dwtx/jface/bindings/keys/KeyLookupFactory.d dwtx/jface/bindings/keys/KeySequence.d dwtx/jface/bindings/keys/KeySequenceText.d dwtx/jface/bindings/keys/KeyStroke.d dwtx/jface/bindings/keys/ParseException.d dwtx/jface/bindings/keys/SWTKeyLookup.d dwtx/jface/bindings/keys/SWTKeySupport.d dwtx/jface/bindings/keys/formatting/AbstractKeyFormatter.d dwtx/jface/bindings/keys/formatting/EmacsKeyFormatter.d dwtx/jface/bindings/keys/formatting/FormalKeyFormatter.d dwtx/jface/bindings/keys/formatting/IKeyFormatter.d dwtx/jface/bindings/keys/formatting/KeyFormatterFactory.d dwtx/jface/bindings/keys/formatting/NativeKeyFormatter.d dwtx/jface/dialogs/PopupDialog.d dwtx/jface/dialogs/StatusDialog.d dwtx/jface/util/Util.d
diffstat 47 files changed, 15279 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/AbstractAction.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * 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.jface.action.AbstractAction;
+
+import dwtx.jface.action.IAction;
+
+import dwtx.core.commands.common.EventManager;
+import dwtx.jface.util.IPropertyChangeListener;
+import dwtx.jface.util.PropertyChangeEvent;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * Some common functionality to share between implementations of
+ * <code>IAction</code>. This functionality deals with the property change
+ * event mechanism.
+ * </p>
+ * <p>
+ * Clients may neither instantiate nor extend this class.
+ * </p>
+ *
+ * @since 3.2
+ */
+public abstract class AbstractAction : EventManager, IAction {
+
+    public void addPropertyChangeListener(IPropertyChangeListener listener) {
+        addListenerObject(cast(Object)listener);
+    }
+
+    /**
+     * Notifies any property change listeners that a property has changed. Only
+     * listeners registered at the time this method is called are notified.
+     *
+     * @param event
+     *            the property change event
+     *
+     * @see dwtx.jface.util.IPropertyChangeListener#propertyChange(PropertyChangeEvent)
+     */
+    protected final void firePropertyChange(PropertyChangeEvent event) {
+        final Object[] list = getListeners();
+        for (int i = 0; i < list.length; ++i) {
+            (cast(IPropertyChangeListener) list[i]).propertyChange(event);
+        }
+    }
+
+    /**
+     * Notifies any property change listeners that a property has changed. Only
+     * listeners registered at the time this method is called are notified. This
+     * method avoids creating an event object if there are no listeners
+     * registered, but calls
+     * <code>firePropertyChange(PropertyChangeEvent)</code> if there are.
+     *
+     * @param propertyName
+     *            the name of the property that has changed
+     * @param oldValue
+     *            the old value of the property, or <code>null</code> if none
+     * @param newValue
+     *            the new value of the property, or <code>null</code> if none
+     *
+     * @see dwtx.jface.util.IPropertyChangeListener#propertyChange(PropertyChangeEvent)
+     */
+    protected final void firePropertyChange(String propertyName,
+            Object oldValue, Object newValue) {
+        if (isListenerAttached()) {
+            firePropertyChange(new PropertyChangeEvent(this, propertyName,
+                    oldValue, newValue));
+        }
+    }
+
+    public void removePropertyChangeListener(
+            IPropertyChangeListener listener) {
+        removeListenerObject(cast(Object)listener);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/AbstractGroupMarker.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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.jface.action.AbstractGroupMarker;
+
+import dwtx.jface.action.ContributionItem;
+
+import dwtx.core.runtime.Assert;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Abstract superclass for group marker classes.
+ * <p>
+ * This class is not intended to be subclassed outside the framework.
+ * </p>
+ */
+public abstract class AbstractGroupMarker : ContributionItem {
+    /**
+     * Constructor for use by subclasses.
+     */
+    protected this() {
+    }
+
+    /**
+     * Create a new group marker with the given name.
+     * The group name must not be <code>null</code> or the empty string.
+     * The group name is also used as the item id.
+     *
+     * @param groupName the name of the group
+     */
+    protected this(String groupName) {
+        super(groupName);
+        Assert.isTrue(groupName !is null && groupName.length > 0);
+    }
+
+    /**
+     * Returns the group name.
+     *
+     * @return the group name
+     */
+    public String getGroupName() {
+        return getId();
+    }
+
+    /**
+     * The <code>AbstractGroupMarker</code> implementation of this <code>IContributionItem</code>
+     * method returns <code>true</code> iff the id is not <code>null</code>. Subclasses may override.
+     */
+    public bool isGroupMarker() {
+        return getId() !is null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/Action.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,721 @@
+/*******************************************************************************
+ * 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.jface.action.Action;
+
+import dwtx.jface.action.AbstractAction;
+import dwtx.jface.action.IAction;
+import dwtx.jface.action.IMenuCreator;
+import dwtx.jface.action.LegacyActionTools;
+
+import dwtx.jface.util.IPropertyChangeListener;
+
+import dwt.events.HelpListener;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Menu;
+import dwtx.jface.resource.ImageDescriptor;
+
+import dwt.dwthelper.utils;
+
+/**
+ * The standard abstract implementation of an action.
+ * <p>
+ * Subclasses must implement the <code>IAction.run</code> method to carry out
+ * the action's semantics.
+ * </p>
+ */
+public abstract class Action : AbstractAction, IAction {
+
+    public void addPropertyChangeListener(IPropertyChangeListener listener) {
+        super.addPropertyChangeListener(listener);
+    }
+    public void removePropertyChangeListener( IPropertyChangeListener listener) {
+        super.removePropertyChangeListener(listener);
+    }
+
+
+    private static const IMenuCreator VAL_DROP_DOWN_MENU;
+    static this(){
+        VAL_DROP_DOWN_MENU = new class IMenuCreator {
+            public void dispose() {
+                // do nothing
+            }
+
+            public Menu getMenu(Control parent) {
+                // do nothing
+                return null;
+            }
+
+            public Menu getMenu(Menu parent) {
+                // do nothing
+                return null;
+            }
+        };
+        VAL_RADIO_BTN_OFF = new ValueWrapperInt(0);
+        VAL_RADIO_BTN_ON = new ValueWrapperInt(1);
+        VAL_TOGGLE_BTN_OFF = new ValueWrapperBool(false);
+        VAL_TOGGLE_BTN_ON = new ValueWrapperBool(true);
+    }
+
+    /*
+     * The list of default values the action can have. These values will
+     * determine the style of the action.
+     */
+    private static const String VAL_PUSH_BTN = "PUSH_BTN"; //$NON-NLS-1$
+
+    private static const ValueWrapperInt VAL_RADIO_BTN_OFF;
+
+    private static const ValueWrapperInt VAL_RADIO_BTN_ON;
+
+    private static const ValueWrapperBool VAL_TOGGLE_BTN_OFF;
+
+    private static const ValueWrapperBool VAL_TOGGLE_BTN_ON;
+
+    /**
+     * Converts an accelerator key code to a string representation.
+     *
+     * @param keyCode
+     *            the key code to be translated
+     * @return a string representation of the key code
+     */
+    public static String convertAccelerator(int keyCode) {
+        return LegacyActionTools.convertAccelerator(keyCode);
+    }
+
+    /**
+     * Parses the given accelerator text, and converts it to an accelerator key
+     * code.
+     *
+     * @param acceleratorText
+     *            the accelerator text
+     * @return the DWT key code, or 0 if there is no accelerator
+     */
+    public static int convertAccelerator(String acceleratorText) {
+        return LegacyActionTools.convertAccelerator(acceleratorText);
+    }
+
+    /**
+     * Maps a standard keyboard key name to an DWT key code. Key names are
+     * converted to upper case before comparison. If the key name is a single
+     * letter, for example "S", its character code is returned.
+     * <p>
+     * The following key names are known (case is ignored):
+     * <ul>
+     * <li><code>"BACKSPACE"</code></li>
+     * <li><code>"TAB"</code></li>
+     * <li><code>"RETURN"</code></li>
+     * <li><code>"ENTER"</code></li>
+     * <li><code>"ESC"</code></li>
+     * <li><code>"ESCAPE"</code></li>
+     * <li><code>"DELETE"</code></li>
+     * <li><code>"SPACE"</code></li>
+     * <li><code>"ARROW_UP"</code>, <code>"ARROW_DOWN"</code>,
+     * <code>"ARROW_LEFT"</code>, and <code>"ARROW_RIGHT"</code></li>
+     * <li><code>"PAGE_UP"</code> and <code>"PAGE_DOWN"</code></li>
+     * <li><code>"HOME"</code></li>
+     * <li><code>"END"</code></li>
+     * <li><code>"INSERT"</code></li>
+     * <li><code>"F1"</code>, <code>"F2"</code> through <code>"F12"</code></li>
+     * </ul>
+     * </p>
+     *
+     * @param token
+     *            the key name
+     * @return the DWT key code, <code>-1</code> if no match was found
+     * @see dwt.DWT
+     */
+    public static int findKeyCode(String token) {
+        return LegacyActionTools.findKeyCode(token);
+    }
+
+    /**
+     * Maps an DWT key code to a standard keyboard key name. The key code is
+     * stripped of modifiers (DWT.CTRL, DWT.ALT, DWT.SHIFT, and DWT.COMMAND). If
+     * the key code is not an DWT code (for example if it a key code for the key
+     * 'S'), a string containing a character representation of the key code is
+     * returned.
+     *
+     * @param keyCode
+     *            the key code to be translated
+     * @return the string representation of the key code
+     * @see dwt.DWT
+     * @since 2.0
+     */
+    public static String findKeyString(int keyCode) {
+        return LegacyActionTools.findKeyString(keyCode);
+    }
+
+    /**
+     * Maps standard keyboard modifier key names to the corresponding DWT
+     * modifier bit. The following modifier key names are recognized (case is
+     * ignored): <code>"CTRL"</code>, <code>"SHIFT"</code>,
+     * <code>"ALT"</code>, and <code>"COMMAND"</code>. The given modifier
+     * key name is converted to upper case before comparison.
+     *
+     * @param token
+     *            the modifier key name
+     * @return the DWT modifier bit, or <code>0</code> if no match was found
+     * @see dwt.DWT
+     */
+    public static int findModifier(String token) {
+        return LegacyActionTools.findModifier(token);
+    }
+
+    /**
+     * Returns a string representation of an DWT modifier bit (DWT.CTRL,
+     * DWT.ALT, DWT.SHIFT, and DWT.COMMAND). Returns <code>null</code> if the
+     * key code is not an DWT modifier bit.
+     *
+     * @param keyCode
+     *            the DWT modifier bit to be translated
+     * @return the string representation of the DWT modifier bit, or
+     *         <code>null</code> if the key code was not an DWT modifier bit
+     * @see dwt.DWT
+     * @since 2.0
+     */
+    public static String findModifierString(int keyCode) {
+        return LegacyActionTools.findModifierString(keyCode);
+    }
+
+    /**
+     * Convenience method for removing any optional accelerator text from the
+     * given string. The accelerator text appears at the end of the text, and is
+     * separated from the main part by a single tab character <code>'\t'</code>.
+     *
+     * @param text
+     *            the text
+     * @return the text sans accelerator
+     */
+    public static String removeAcceleratorText(String text) {
+        return LegacyActionTools.removeAcceleratorText(text);
+    }
+
+    /**
+     * Convenience method for removing any mnemonics from the given string. For
+     * example, <code>removeMnemonics("&Open")</code> will return
+     * <code>"Open"</code>.
+     *
+     * @param text
+     *            the text
+     * @return the text sans mnemonics
+     *
+     * @since 3.0
+     */
+    public static String removeMnemonics(String text) {
+        return LegacyActionTools.removeMnemonics(text);
+    }
+
+    /**
+     * This action's accelerator; <code>0</code> means none.
+     */
+    private int accelerator = 0;
+
+    /**
+     * This action's action definition id, or <code>null</code> if none.
+     */
+    private String actionDefinitionId;
+
+    /**
+     * This action's description, or <code>null</code> if none.
+     */
+    private String description;
+
+    /**
+     * This action's disabled image, or <code>null</code> if none.
+     */
+    private ImageDescriptor disabledImage;
+
+    /**
+     * Indicates this action is enabled.
+     */
+    private bool enabled = true;
+
+    /**
+     * An action's help listener, or <code>null</code> if none.
+     */
+    private HelpListener helpListener;
+
+    /**
+     * This action's hover image, or <code>null</code> if none.
+     */
+    private ImageDescriptor hoverImage;
+
+    /**
+     * This action's id, or <code>null</code> if none.
+     */
+    private String id;
+
+    /**
+     * This action's image, or <code>null</code> if none.
+     */
+    private ImageDescriptor image;
+
+    /**
+     * This action's text, or <code>null</code> if none.
+     */
+    private String text;
+
+    /**
+     * This action's tool tip text, or <code>null</code> if none.
+     */
+    private String toolTipText;
+
+    /**
+     * Holds the action's menu creator (an IMenuCreator) or checked state (a
+     * bool for toggle button, or an Integer for radio button), or
+     * <code>null</code> if neither have been set.
+     * <p>
+     * The value of this field affects the value of <code>getStyle()</code>.
+     * </p>
+     */
+    private Object value = null;
+
+    /**
+     * Creates a new action with no text and no image.
+     * <p>
+     * Configure the action later using the set methods.
+     * </p>
+     */
+    protected this() {
+        // do nothing
+    }
+
+    /**
+     * Creates a new action with the given text and no image. Calls the zero-arg
+     * constructor, then <code>setText</code>.
+     *
+     * @param text
+     *            the string used as the text for the action, or
+     *            <code>null</code> if there is no text
+     * @see #setText
+     */
+    protected this(String text) {
+        this();
+        setText(text);
+    }
+
+    /**
+     * Creates a new action with the given text and image. Calls the zero-arg
+     * constructor, then <code>setText</code> and
+     * <code>setImageDescriptor</code>.
+     *
+     * @param text
+     *            the action's text, or <code>null</code> if there is no text
+     * @param image
+     *            the action's image, or <code>null</code> if there is no
+     *            image
+     * @see #setText
+     * @see #setImageDescriptor
+     */
+    protected this(String text, ImageDescriptor image) {
+        this(text);
+        setImageDescriptor(image);
+    }
+
+    /**
+     * Creates a new action with the given text and style.
+     *
+     * @param text
+     *            the action's text, or <code>null</code> if there is no text
+     * @param style
+     *            one of <code>AS_PUSH_BUTTON</code>,
+     *            <code>AS_CHECK_BOX</code>, <code>AS_DROP_DOWN_MENU</code>,
+     *            <code>AS_RADIO_BUTTON</code>, and
+     *            <code>AS_UNSPECIFIED</code>.
+     */
+    protected this(String text, int style) {
+        this(text);
+        switch (style) {
+        case AS_PUSH_BUTTON:
+            value = stringcast(VAL_PUSH_BTN);
+            break;
+        case AS_CHECK_BOX:
+            value = VAL_TOGGLE_BTN_OFF;
+            break;
+        case AS_DROP_DOWN_MENU:
+            value = cast(Object)VAL_DROP_DOWN_MENU;
+            break;
+        case AS_RADIO_BUTTON:
+            value = VAL_RADIO_BTN_OFF;
+            break;
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public int getAccelerator() {
+        return accelerator;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     *
+     */
+    public String getActionDefinitionId() {
+        return actionDefinitionId;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public String getDescription() {
+        if (description !is null) {
+            return description;
+        }
+        return getToolTipText();
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public ImageDescriptor getDisabledImageDescriptor() {
+        return disabledImage;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public HelpListener getHelpListener() {
+        return helpListener;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public ImageDescriptor getHoverImageDescriptor() {
+        return hoverImage;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public String getId() {
+        return id;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public ImageDescriptor getImageDescriptor() {
+        return image;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public IMenuCreator getMenuCreator() {
+        // The default drop down menu value is only used
+        // to mark this action requested style. So do not
+        // return it. For backward compatibility reasons.
+        if (value is cast(Object)VAL_DROP_DOWN_MENU) {
+            return null;
+        }
+        if ( auto v = cast(IMenuCreator) value ) {
+            return v;
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public int getStyle() {
+        // Infer the style from the value field.
+        if (value is stringcast(VAL_PUSH_BTN) || value is null) {
+            return AS_PUSH_BUTTON;
+        }
+        if (value is VAL_TOGGLE_BTN_ON || value is VAL_TOGGLE_BTN_OFF) {
+            return AS_CHECK_BOX;
+        }
+        if (value is VAL_RADIO_BTN_ON || value is VAL_RADIO_BTN_OFF) {
+            return AS_RADIO_BUTTON;
+        }
+        if (cast(IMenuCreator)value ) {
+            return AS_DROP_DOWN_MENU;
+        }
+
+        // We should never get to this line...
+        return AS_PUSH_BUTTON;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public String getText() {
+        return text;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public String getToolTipText() {
+        return toolTipText;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public bool isChecked() {
+        return value is VAL_TOGGLE_BTN_ON || value is VAL_RADIO_BTN_ON;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public bool isEnabled() {
+        return enabled;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public bool isHandled() {
+        return true;
+    }
+
+    /**
+     * Reports the outcome of the running of this action via the
+     * {@link IAction#RESULT} property.
+     *
+     * @param success
+     *            <code>true</code> if the action succeeded and
+     *            <code>false</code> if the action failed or was not completed
+     * @see IAction#RESULT
+     * @since 3.0
+     */
+    public final void notifyResult(bool success) {
+        // avoid bool.valueOf(bool) to allow compilation against JCL
+        // Foundation (bug 80059)
+        firePropertyChange(RESULT, null, new ValueWrapperBool(success));
+    }
+
+    /**
+     * The default implementation of this <code>IAction</code> method does
+     * nothing. Subclasses should override this method if they do not need
+     * information from the triggering event, or override
+     * <code>runWithEvent(Event)</code> if they do.
+     */
+    public void run() {
+        // do nothing
+    }
+
+    /**
+     * The default implementation of this <code>IAction</code> method ignores
+     * the event argument, and simply calls <code>run()</code>. Subclasses
+     * should override this method if they need information from the triggering
+     * event, or override <code>run()</code> if not.
+     *
+     * @param event
+     *            the DWT event which triggered this action being run
+     * @since 2.0
+     */
+    public void runWithEvent(Event event) {
+        run();
+    }
+
+    /*
+     * @see IAction#setAccelerator(int)
+     */
+    public void setAccelerator(int keycode) {
+        this.accelerator = keycode;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setActionDefinitionId(String id) {
+        actionDefinitionId = id;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setChecked(bool checked) {
+        Object newValue = null;
+
+        // For backward compatibility, if the style is not
+        // set yet, then convert it to a toggle button.
+        if (value is null || value is VAL_TOGGLE_BTN_ON
+                || value is VAL_TOGGLE_BTN_OFF) {
+            newValue = checked ? VAL_TOGGLE_BTN_ON : VAL_TOGGLE_BTN_OFF;
+        } else if (value is VAL_RADIO_BTN_ON || value is VAL_RADIO_BTN_OFF) {
+            newValue = checked ? VAL_RADIO_BTN_ON : VAL_RADIO_BTN_OFF;
+        } else {
+            // Some other style already, so do nothing.
+            return;
+        }
+
+        if (newValue !is value) {
+            value = newValue;
+            if (checked) {
+                firePropertyChange(CHECKED, new ValueWrapperBool(false), new ValueWrapperBool(true));
+            } else {
+                firePropertyChange(CHECKED, new ValueWrapperBool(true), new ValueWrapperBool(false));
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setDescription(String text) {
+
+        if ((description is null && text !is null)
+                || (description !is null && text is null)
+                || (description !is null && text !is null && !text
+                        .equals(description))) {
+            String oldDescription = description;
+            description = text;
+            firePropertyChange(DESCRIPTION, stringcast(oldDescription), stringcast(description));
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setDisabledImageDescriptor(ImageDescriptor newImage) {
+        if (disabledImage !is newImage) {
+            ImageDescriptor oldImage = disabledImage;
+            disabledImage = newImage;
+            firePropertyChange(IMAGE, oldImage, newImage);
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setEnabled(bool enabled) {
+        if (enabled !is this.enabled) {
+            this.enabled = enabled;
+            firePropertyChange(ENABLED, new ValueWrapperBool(this.enabled), new ValueWrapperBool(enabled));
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setHelpListener(HelpListener listener) {
+        helpListener = listener;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setHoverImageDescriptor(ImageDescriptor newImage) {
+        if (hoverImage !is newImage) {
+            ImageDescriptor oldImage = hoverImage;
+            hoverImage = newImage;
+            firePropertyChange(IMAGE, oldImage, newImage);
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IAction.
+     */
+    public void setImageDescriptor(ImageDescriptor newImage) {
+        if (image !is newImage) {
+            ImageDescriptor oldImage = image;
+            image = newImage;
+            firePropertyChange(IMAGE, oldImage, newImage);
+        }
+    }
+
+    /**
+     * Sets the menu creator for this action.
+     * <p>
+     * Note that if this method is called, it overrides the check status.
+     * </p>
+     *
+     * @param creator
+     *            the menu creator, or <code>null</code> if none
+     */
+    public void setMenuCreator(IMenuCreator creator) {
+        // For backward compatibility, if the style is not
+        // set yet, then convert it to a drop down menu.
+        if (value is null) {
+            value = cast(Object)creator;
+            return;
+        }
+
+        if ( cast(IMenuCreator)value ) {
+            value = creator is null ? cast(Object)VAL_DROP_DOWN_MENU : cast(Object)creator;
+        }
+    }
+
+    /**
+     * Sets the text for this action.
+     * <p>
+     * Fires a property change event for the <code>TEXT</code> property if the
+     * text actually changes as a consequence.
+     * </p>
+     * <p>
+     * The accelerator is identified by the last index of a tab character. If
+     * there are no tab characters, then it is identified by the last index of a
+     * '@' character. If neither, then there is no accelerator text. Note that
+     * if you want to insert a '@' character into the text (but no accelerator,
+     * you can simply insert a '@' or a tab at the end of the text.
+     * </p>
+     *
+     * @param text
+     *            the text, or <code>null</code> if none
+     */
+    public void setText(String text) {
+        String oldText = this.text;
+        int oldAccel = this.accelerator;
+        this.text = text;
+        if (text !is null) {
+            String acceleratorText = LegacyActionTools
+                    .extractAcceleratorText(text);
+            if (acceleratorText !is null) {
+                int newAccelerator = LegacyActionTools
+                        .convertLocalizedAccelerator(acceleratorText);
+                // Be sure to not wipe out the accelerator if nothing found
+                if (newAccelerator > 0) {
+                    setAccelerator(newAccelerator);
+                }
+            }
+        }
+        if (!(this.accelerator is oldAccel && (oldText is null ? this.text is null
+                : oldText.equals(this.text)))) {
+            firePropertyChange(TEXT, stringcast(oldText), stringcast(this.text));
+        }
+    }
+
+    /**
+     * Sets the tool tip text for this action.
+     * <p>
+     * Fires a property change event for the <code>TOOL_TIP_TEXT</code>
+     * property if the tool tip text actually changes as a consequence.
+     * </p>
+     *
+     * @param toolTipText
+     *            the tool tip text, or <code>null</code> if none
+     */
+    public void setToolTipText(String toolTipText) {
+        String oldToolTipText = this.toolTipText;
+        if (!(oldToolTipText is null ? toolTipText is null : oldToolTipText
+                .equals(toolTipText))) {
+            this.toolTipText = toolTipText;
+            firePropertyChange(TOOL_TIP_TEXT, stringcast(oldToolTipText), stringcast(toolTipText));
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/ActionContributionItem.d	Tue Apr 01 08:00:31 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.action.ActionContributionItem;
+
+import dwtx.jface.action.ContributionItem;
+import dwtx.jface.action.IAction;
+import dwtx.jface.action.LegacyActionTools;
+import dwtx.jface.action.Action;
+import dwtx.jface.action.IMenuCreator;
+import dwtx.jface.action.IContributionManagerOverrides;
+
+import dwt.DWT;
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Button;
+import dwt.widgets.Composite;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Item;
+import dwt.widgets.Listener;
+import dwt.widgets.Menu;
+import dwt.widgets.MenuItem;
+import dwt.widgets.ToolBar;
+import dwt.widgets.ToolItem;
+import dwt.widgets.Widget;
+import dwtx.jface.action.ExternalActionManager;
+import dwtx.jface.bindings.Trigger;
+import dwtx.jface.bindings.TriggerSequence;
+import dwtx.jface.bindings.keys.IKeyLookup;
+import dwtx.jface.bindings.keys.KeyLookupFactory;
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.resource.ImageDescriptor;
+import dwtx.jface.resource.JFaceResources;
+import dwtx.jface.resource.LocalResourceManager;
+import dwtx.jface.resource.ResourceManager;
+import dwtx.jface.util.IPropertyChangeListener;
+import dwtx.jface.util.Policy;
+import dwtx.jface.util.PropertyChangeEvent;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+import tango.core.Thread;
+import tango.io.Stdout;
+
+/**
+ * A contribution item which delegates to an action.
+ * <p>
+ * This class may be instantiated; it is not intended to be subclassed.
+ * </p>
+ */
+public class ActionContributionItem : ContributionItem {
+
+    /**
+     * Mode bit: Show text on tool items, even if an image is present. If this
+     * mode bit is not set, text is only shown on tool items if there is no
+     * image present.
+     *
+     * @since 3.0
+     */
+    public static int MODE_FORCE_TEXT = 1;
+
+    /** a string inserted in the middle of text that has been shortened */
+    private static const String ellipsis = "..."; //$NON-NLS-1$
+
+    private static bool USE_COLOR_ICONS = true;
+
+    /**
+     * Returns whether color icons should be used in toolbars.
+     *
+     * @return <code>true</code> if color icons should be used in toolbars,
+     *         <code>false</code> otherwise
+     */
+    public static bool getUseColorIconsInToolbars() {
+        return USE_COLOR_ICONS;
+    }
+
+    /**
+     * Sets whether color icons should be used in toolbars.
+     *
+     * @param useColorIcons
+     *            <code>true</code> if color icons should be used in toolbars,
+     *            <code>false</code> otherwise
+     */
+    public static void setUseColorIconsInToolbars(bool useColorIcons) {
+        USE_COLOR_ICONS = useColorIcons;
+    }
+
+    /**
+     * The presentation mode.
+     */
+    private int mode = 0;
+
+    /**
+     * The action.
+     */
+    private IAction action;
+
+    /**
+     * The listener for changes to the text of the action contributed by an
+     * external source.
+     */
+    private const IPropertyChangeListener actionTextListener;
+
+    /**
+     * Remembers all images in use by this contribution item
+     */
+    private LocalResourceManager imageManager;
+
+    /**
+     * Listener for DWT button widget events.
+     */
+    private Listener buttonListener;
+
+    /**
+     * Listener for DWT menu item widget events.
+     */
+    private Listener menuItemListener;
+
+    /**
+     * Listener for action property change notifications.
+     */
+    private const IPropertyChangeListener propertyListener;
+
+    /**
+     * Listener for DWT tool item widget events.
+     */
+    private Listener toolItemListener;
+
+    /**
+     * The widget created for this item; <code>null</code> before creation and
+     * after disposal.
+     */
+    private Widget widget = null;
+
+    /**
+     * Creates a new contribution item from the given action. The id of the
+     * action is used as the id of the item.
+     *
+     * @param action
+     *            the action
+     */
+    public this(IAction action) {
+        super(action.getId());
+        this.action = action;
+        actionTextListener = new class IPropertyChangeListener {
+            /**
+            * @see IPropertyChangeListener#propertyChange(PropertyChangeEvent)
+            */
+            public void propertyChange(PropertyChangeEvent event) {
+                update(event.getProperty());
+            }
+        };
+        propertyListener = new class IPropertyChangeListener {
+            public void propertyChange(PropertyChangeEvent event) {
+                actionPropertyChange(event);
+            }
+        };
+    }
+
+    /**
+     * Handles a property change event on the action (forwarded by nested
+     * listener).
+     */
+    private void actionPropertyChange(PropertyChangeEvent e) {
+        // This code should be removed. Avoid using free asyncExec
+
+        if (isVisible() && widget !is null) {
+            Display display = widget.getDisplay();
+            if (display.getThread() is Thread.getThis()) {
+                update(e.getProperty());
+            } else {
+                display.asyncExec(new class Runnable {
+                    PropertyChangeEvent e_;
+                    this(){
+                        e_=e;
+                    }
+                    public void run() {
+                        update(e_.getProperty());
+                    }
+                });
+            }
+
+        }
+    }
+
+    /**
+     * Compares this action contribution item with another object. Two action
+     * contribution items are equal if they refer to the identical Action.
+     */
+    public override int opEquals(Object o) {
+        if (!(cast(ActionContributionItem)o )) {
+            return false;
+        }
+        return (cast(Object)action).opEquals(cast(Object)(cast(ActionContributionItem) o).action);
+    }
+
+    /**
+     * The <code>ActionContributionItem</code> implementation of this
+     * <code>IContributionItem</code> method creates an DWT
+     * <code>Button</code> for the action using the action's style. If the
+     * action's checked property has been set, the button is created and primed
+     * to the value of the checked property.
+     */
+    public void fill(Composite parent) {
+        if (widget is null && parent !is null) {
+            int flags = DWT.PUSH;
+            if (action !is null) {
+                if (action.getStyle() is IAction.AS_CHECK_BOX) {
+                    flags = DWT.TOGGLE;
+                }
+                if (action.getStyle() is IAction.AS_RADIO_BUTTON) {
+                    flags = DWT.RADIO;
+                }
+            }
+
+            Button b = new Button(parent, flags);
+            b.setData(this);
+            b.addListener(DWT.Dispose, getButtonListener());
+            // Don't hook a dispose listener on the parent
+            b.addListener(DWT.Selection, getButtonListener());
+            if (action.getHelpListener() !is null) {
+                b.addHelpListener(action.getHelpListener());
+            }
+            widget = b;
+
+            update(null);
+
+            // Attach some extra listeners.
+            action.addPropertyChangeListener(propertyListener);
+            if (action !is null) {
+                String commandId = action.getActionDefinitionId();
+                ExternalActionManager.ICallback callback = ExternalActionManager
+                        .getInstance().getCallback();
+
+                if ((callback !is null) && (commandId !is null)) {
+                    callback.addPropertyChangeListener(commandId,
+                            actionTextListener);
+                }
+            }
+        }
+    }
+
+    /**
+     * The <code>ActionContributionItem</code> implementation of this
+     * <code>IContributionItem</code> method creates an DWT
+     * <code>MenuItem</code> for the action using the action's style. If the
+     * action's checked property has been set, a button is created and primed to
+     * the value of the checked property. If the action's menu creator property
+     * has been set, a cascading submenu is created.
+     */
+    public void fill(Menu parent, int index) {
+        if (widget is null && parent !is null) {
+            Menu subMenu = null;
+            int flags = DWT.PUSH;
+            if (action !is null) {
+                int style = action.getStyle();
+                if (style is IAction.AS_CHECK_BOX) {
+                    flags = DWT.CHECK;
+                } else if (style is IAction.AS_RADIO_BUTTON) {
+                    flags = DWT.RADIO;
+                } else if (style is IAction.AS_DROP_DOWN_MENU) {
+                    IMenuCreator mc = action.getMenuCreator();
+                    if (mc !is null) {
+                        subMenu = mc.getMenu(parent);
+                        flags = DWT.CASCADE;
+                    }
+                }
+            }
+
+            MenuItem mi = null;
+            if (index >= 0) {
+                mi = new MenuItem(parent, flags, index);
+            } else {
+                mi = new MenuItem(parent, flags);
+            }
+            widget = mi;
+
+            mi.setData(this);
+            mi.addListener(DWT.Dispose, getMenuItemListener());
+            mi.addListener(DWT.Selection, getMenuItemListener());
+            if (action.getHelpListener() !is null) {
+                mi.addHelpListener(action.getHelpListener());
+            }
+
+            if (subMenu !is null) {
+                mi.setMenu(subMenu);
+            }
+
+            update(null);
+
+            // Attach some extra listeners.
+            action.addPropertyChangeListener(propertyListener);
+            if (action !is null) {
+                String commandId = action.getActionDefinitionId();
+                ExternalActionManager.ICallback callback = ExternalActionManager
+                        .getInstance().getCallback();
+
+                if ((callback !is null) && (commandId !is null)) {
+                    callback.addPropertyChangeListener(commandId,
+                            actionTextListener);
+                }
+            }
+        }
+    }
+
+    /**
+     * The <code>ActionContributionItem</code> implementation of this ,
+     * <code>IContributionItem</code> method creates an DWT
+     * <code>ToolItem</code> for the action using the action's style. If the
+     * action's checked property has been set, a button is created and primed to
+     * the value of the checked property. If the action's menu creator property
+     * has been set, a drop-down tool item is created.
+     */
+    public void fill(ToolBar parent, int index) {
+        if (widget is null && parent !is null) {
+            int flags = DWT.PUSH;
+            if (action !is null) {
+                int style = action.getStyle();
+                if (style is IAction.AS_CHECK_BOX) {
+                    flags = DWT.CHECK;
+                } else if (style is IAction.AS_RADIO_BUTTON) {
+                    flags = DWT.RADIO;
+                } else if (style is IAction.AS_DROP_DOWN_MENU) {
+                    flags = DWT.DROP_DOWN;
+                }
+            }
+
+            ToolItem ti = null;
+            if (index >= 0) {
+                ti = new ToolItem(parent, flags, index);
+            } else {
+                ti = new ToolItem(parent, flags);
+            }
+            ti.setData(this);
+            ti.addListener(DWT.Selection, getToolItemListener());
+            ti.addListener(DWT.Dispose, getToolItemListener());
+
+            widget = ti;
+
+            update(null);
+
+            // Attach some extra listeners.
+            action.addPropertyChangeListener(propertyListener);
+            if (action !is null) {
+                String commandId = action.getActionDefinitionId();
+                ExternalActionManager.ICallback callback = ExternalActionManager
+                        .getInstance().getCallback();
+
+                if ((callback !is null) && (commandId !is null)) {
+                    callback.addPropertyChangeListener(commandId,
+                            actionTextListener);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the action associated with this contribution item.
+     *
+     * @return the action
+     */
+    public IAction getAction() {
+        return action;
+    }
+
+    /**
+     * Returns the listener for DWT button widget events.
+     *
+     * @return a listener for button events
+     */
+    private Listener getButtonListener() {
+        if (buttonListener is null) {
+            buttonListener = new class Listener {
+                public void handleEvent(Event event) {
+                    switch (event.type) {
+                    case DWT.Dispose:
+                        handleWidgetDispose(event);
+                        break;
+                    case DWT.Selection:
+                        Widget ew = event.widget;
+                        if (ew !is null) {
+                            handleWidgetSelection(event, (cast(Button) ew)
+                                    .getSelection());
+                        }
+                        break;
+                    }
+                }
+            };
+        }
+        return buttonListener;
+    }
+
+    /**
+     * Returns the listener for DWT menu item widget events.
+     *
+     * @return a listener for menu item events
+     */
+    private Listener getMenuItemListener() {
+        if (menuItemListener is null) {
+            menuItemListener = new class Listener {
+                public void handleEvent(Event event) {
+                    switch (event.type) {
+                    case DWT.Dispose:
+                        handleWidgetDispose(event);
+                        break;
+                    case DWT.Selection:
+                        Widget ew = event.widget;
+                        if (ew !is null) {
+                            handleWidgetSelection(event, (cast(MenuItem) ew)
+                                    .getSelection());
+                        }
+                        break;
+                    }
+                }
+            };
+        }
+        return menuItemListener;
+    }
+
+    /**
+     * Returns the presentation mode, which is the bitwise-or of the
+     * <code>MODE_*</code> constants. The default mode setting is 0, meaning
+     * that for menu items, both text and image are shown (if present), but for
+     * tool items, the text is shown only if there is no image.
+     *
+     * @return the presentation mode settings
+     *
+     * @since 3.0
+     */
+    public int getMode() {
+        return mode;
+    }
+
+    /**
+     * Returns the listener for DWT tool item widget events.
+     *
+     * @return a listener for tool item events
+     */
+    private Listener getToolItemListener() {
+        if (toolItemListener is null) {
+            toolItemListener = new class Listener {
+                public void handleEvent(Event event) {
+                    switch (event.type) {
+                    case DWT.Dispose:
+                        handleWidgetDispose(event);
+                        break;
+                    case DWT.Selection:
+                        Widget ew = event.widget;
+                        if (ew !is null) {
+                            handleWidgetSelection(event, (cast(ToolItem) ew)
+                                    .getSelection());
+                        }
+                        break;
+                    }
+                }
+            };
+        }
+        return toolItemListener;
+    }
+
+    /**
+     * Handles a widget dispose event for the widget corresponding to this item.
+     */
+    private void handleWidgetDispose(Event e) {
+        // Check if our widget is the one being disposed.
+        if (e.widget is widget) {
+            // Dispose of the menu creator.
+            if (action.getStyle() is IAction.AS_DROP_DOWN_MENU) {
+                IMenuCreator mc = action.getMenuCreator();
+                if (mc !is null) {
+                    mc.dispose();
+                }
+            }
+
+            // Unhook all of the listeners.
+            action.removePropertyChangeListener(propertyListener);
+            if (action !is null) {
+                String commandId = action.getActionDefinitionId();
+                ExternalActionManager.ICallback callback = ExternalActionManager
+                        .getInstance().getCallback();
+
+                if ((callback !is null) && (commandId !is null)) {
+                    callback.removePropertyChangeListener(commandId,
+                            actionTextListener);
+                }
+            }
+
+            // Clear the widget field.
+            widget = null;
+
+            disposeOldImages();
+        }
+    }
+
+    /**
+     * Handles a widget selection event.
+     */
+    private void handleWidgetSelection(Event e, bool selection) {
+
+        Widget item = e.widget;
+        if (item !is null) {
+            int style = item.getStyle();
+
+            if ((style & (DWT.TOGGLE | DWT.CHECK)) !is 0) {
+                if (action.getStyle() is IAction.AS_CHECK_BOX) {
+                    action.setChecked(selection);
+                }
+            } else if ((style & DWT.RADIO) !is 0) {
+                if (action.getStyle() is IAction.AS_RADIO_BUTTON) {
+                    action.setChecked(selection);
+                }
+            } else if ((style & DWT.DROP_DOWN) !is 0) {
+                if (e.detail is 4) { // on drop-down button
+                    if (action.getStyle() is IAction.AS_DROP_DOWN_MENU) {
+                        IMenuCreator mc = action.getMenuCreator();
+                        ToolItem ti = cast(ToolItem) item;
+                        // we create the menu as a sub-menu of "dummy" so that
+                        // we can use
+                        // it in a cascading menu too.
+                        // If created on a DWT control we would get an DWT
+                        // error...
+                        // Menu dummy= new Menu(ti.getParent());
+                        // Menu m= mc.getMenu(dummy);
+                        // dummy.dispose();
+                        if (mc !is null) {
+                            Menu m = mc.getMenu(ti.getParent());
+                            if (m !is null) {
+                                // position the menu below the drop down item
+                                Rectangle b = ti.getBounds();
+                                Point p = ti.getParent().toDisplay(
+                                        new Point(b.x, b.y + b.height));
+                                m.setLocation(p.x, p.y); // waiting for DWT
+                                                            // 0.42
+                                m.setVisible(true);
+                                return; // we don't fire the action
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Ensure action is enabled first.
+            // See 1GAN3M6: ITPUI:WINNT - Any IAction in the workbench can be
+            // executed while disabled.
+            if (action.isEnabled()) {
+                bool trace = Policy.TRACE_ACTIONS;
+
+                long ms = 0L;
+                if (trace) {
+                    ms = System.currentTimeMillis();
+                    Stdout.formatln("Running action: {}", action.getText()); //$NON-NLS-1$
+                }
+
+                action.runWithEvent(e);
+
+                if (trace) {
+                    Stdout.formatln("{} ms to run action: {}",(System.currentTimeMillis() - ms), action.getText()); //$NON-NLS-1$
+                }
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on Object.
+     */
+    public override hash_t toHash() {
+        return (cast(Object)action).toHash();
+    }
+
+    /**
+     * Returns whether the given action has any images.
+     *
+     * @param actionToCheck
+     *            the action
+     * @return <code>true</code> if the action has any images,
+     *         <code>false</code> if not
+     */
+    private bool hasImages(IAction actionToCheck) {
+        return actionToCheck.getImageDescriptor() !is null
+                || actionToCheck.getHoverImageDescriptor() !is null
+                || actionToCheck.getDisabledImageDescriptor() !is null;
+    }
+
+    /**
+     * Returns whether the command corresponding to this action is active.
+     */
+    private bool isCommandActive() {
+        IAction actionToCheck = getAction();
+
+        if (actionToCheck !is null) {
+            String commandId = actionToCheck.getActionDefinitionId();
+            ExternalActionManager.ICallback callback = ExternalActionManager
+                    .getInstance().getCallback();
+
+            if (callback !is null) {
+                return callback.isActive(commandId);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * The action item implementation of this <code>IContributionItem</code>
+     * method returns <code>true</code> for menu items and <code>false</code>
+     * for everything else.
+     */
+    public bool isDynamic() {
+        if (cast(MenuItem)widget ) {
+            // Optimization. Only recreate the item is the check or radio style
+            // has changed.
+            bool itemIsCheck = (widget.getStyle() & DWT.CHECK) !is 0;
+            bool actionIsCheck = getAction() !is null
+                    && getAction().getStyle() is IAction.AS_CHECK_BOX;
+            bool itemIsRadio = (widget.getStyle() & DWT.RADIO) !is 0;
+            bool actionIsRadio = getAction() !is null
+                    && getAction().getStyle() is IAction.AS_RADIO_BUTTON;
+            return (itemIsCheck !is actionIsCheck)
+                    || (itemIsRadio !is actionIsRadio);
+        }
+        return false;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionItem.
+     */
+    public bool isEnabled() {
+        return action !is null && action.isEnabled();
+    }
+
+    /**
+     * Returns <code>true</code> if this item is allowed to enable,
+     * <code>false</code> otherwise.
+     *
+     * @return if this item is allowed to be enabled
+     * @since 2.0
+     */
+    protected bool isEnabledAllowed() {
+        if (getParent() is null) {
+            return true;
+        }
+        auto value = getParent().getOverrides().getEnabled(this);
+        return (value is null) ? true : value.value;
+    }
+
+    /**
+     * The <code>ActionContributionItem</code> implementation of this
+     * <code>ContributionItem</code> method extends the super implementation
+     * by also checking whether the command corresponding to this action is
+     * active.
+     */
+    public bool isVisible() {
+        return super.isVisible() && isCommandActive();
+    }
+
+    /**
+     * Sets the presentation mode, which is the bitwise-or of the
+     * <code>MODE_*</code> constants.
+     *
+     * @param mode
+     *            the presentation mode settings
+     *
+     * @since 3.0
+     */
+    public void setMode(int mode) {
+        this.mode = mode;
+        update();
+    }
+
+    /**
+     * The action item implementation of this <code>IContributionItem</code>
+     * method calls <code>update(null)</code>.
+     */
+    public final void update() {
+        update(null);
+    }
+
+    /**
+     * Synchronizes the UI with the given property.
+     *
+     * @param propertyName
+     *            the name of the property, or <code>null</code> meaning all
+     *            applicable properties
+     */
+    public void update(String propertyName) {
+        if (widget !is null) {
+            // determine what to do
+            bool textChanged = propertyName is null
+                    || propertyName.equals(IAction.TEXT);
+            bool imageChanged = propertyName is null
+                    || propertyName.equals(IAction.IMAGE);
+            bool tooltipTextChanged = propertyName is null
+                    || propertyName.equals(IAction.TOOL_TIP_TEXT);
+            bool enableStateChanged = propertyName is null
+                    || propertyName.equals(IAction.ENABLED)
+                    || propertyName
+                            .equals(IContributionManagerOverrides.P_ENABLED);
+            bool checkChanged = (action.getStyle() is IAction.AS_CHECK_BOX || action
+                    .getStyle() is IAction.AS_RADIO_BUTTON)
+                    && (propertyName is null || propertyName
+                            .equals(IAction.CHECKED));
+
+            if (cast(ToolItem)widget ) {
+                ToolItem ti = cast(ToolItem) widget;
+                String text = action.getText();
+                // the set text is shown only if there is no image or if forced
+                // by MODE_FORCE_TEXT
+                bool showText = text !is null
+                        && ((getMode() & MODE_FORCE_TEXT) !is 0 || !hasImages(action));
+
+                // only do the trimming if the text will be used
+                if (showText && text !is null) {
+                    text = Action.removeAcceleratorText(text);
+                    text = Action.removeMnemonics(text);
+                }
+
+                if (textChanged) {
+                    String textToSet = showText ? text : ""; //$NON-NLS-1$
+                    bool rightStyle = (ti.getParent().getStyle() & DWT.RIGHT) !is 0;
+                    if (rightStyle || !ti.getText().equals(textToSet)) {
+                        // In addition to being required to update the text if
+                        // it
+                        // gets nulled out in the action, this is also a
+                        // workaround
+                        // for bug 50151: Using DWT.RIGHT on a ToolBar leaves
+                        // blank space
+                        ti.setText(textToSet);
+                    }
+                }
+
+                if (imageChanged) {
+                    // only substitute a missing image if it has no text
+                    updateImages(!showText);
+                }
+
+                if (tooltipTextChanged || textChanged) {
+                    String toolTip = action.getToolTipText();
+                    if ((toolTip is null) || (toolTip.length is 0)) {
+                        toolTip = text;
+                    }
+
+                    ExternalActionManager.ICallback callback = ExternalActionManager
+                            .getInstance().getCallback();
+                    String commandId = action.getActionDefinitionId();
+                    if ((callback !is null) && (commandId !is null) && (toolTip !is null)) {
+                        String acceleratorText = callback.getAcceleratorText(commandId);
+                        if (acceleratorText !is null && acceleratorText.length !is 0) {
+                            toolTip = JFaceResources.format(
+                                    "Toolbar_Tooltip_Accelerator", //$NON-NLS-1$
+                                    [ toolTip, acceleratorText ]);
+                        }
+                    }
+
+                    // if the text is showing, then only set the tooltip if
+                    // different
+                    if (!showText || toolTip !is null && !toolTip.equals(text)) {
+                        ti.setToolTipText(toolTip);
+                    } else {
+                        ti.setToolTipText(null);
+                    }
+                }
+
+                if (enableStateChanged) {
+                    bool shouldBeEnabled = action.isEnabled()
+                            && isEnabledAllowed();
+
+                    if (ti.getEnabled() !is shouldBeEnabled) {
+                        ti.setEnabled(shouldBeEnabled);
+                    }
+                }
+
+                if (checkChanged) {
+                    bool bv = action.isChecked();
+
+                    if (ti.getSelection() !is bv) {
+                        ti.setSelection(bv);
+                    }
+                }
+                return;
+            }
+
+            if (cast(MenuItem)widget ) {
+                MenuItem mi = cast(MenuItem) widget;
+
+                if (textChanged) {
+                    int accelerator = 0;
+                    String acceleratorText = null;
+                    IAction updatedAction = getAction();
+                    String text = null;
+                    accelerator = updatedAction.getAccelerator();
+                    ExternalActionManager.ICallback callback = ExternalActionManager
+                            .getInstance().getCallback();
+
+                    // Block accelerators that are already in use.
+                    if ((accelerator !is 0) && (callback !is null)
+                            && (callback.isAcceleratorInUse(accelerator))) {
+                        accelerator = 0;
+                    }
+
+                    /*
+                     * Process accelerators on GTK in a special way to avoid Bug
+                     * 42009. We will override the native input method by
+                     * allowing these reserved accelerators to be placed on the
+                     * menu. We will only do this for "Ctrl+Shift+[0-9A-FU]".
+                     */
+                    String commandId = updatedAction
+                            .getActionDefinitionId();
+                    if (("gtk".equals(DWT.getPlatform())) && (cast(ExternalActionManager.IBindingManagerCallback)callback ) //$NON-NLS-1$
+                            && (commandId !is null)) {
+                        ExternalActionManager.IBindingManagerCallback bindingManagerCallback = cast(ExternalActionManager.IBindingManagerCallback) callback;
+                        IKeyLookup lookup = KeyLookupFactory.getDefault();
+                        TriggerSequence[] triggerSequences = bindingManagerCallback
+                                .getActiveBindingsFor(commandId);
+                        for (int i = 0; i < triggerSequences.length; i++) {
+                            TriggerSequence triggerSequence = triggerSequences[i];
+                            Trigger[] triggers = triggerSequence
+                                    .getTriggers();
+                            if (triggers.length is 1) {
+                                Trigger trigger = triggers[0];
+                                if (cast(KeyStroke)trigger ) {
+                                    KeyStroke currentKeyStroke = cast(KeyStroke) trigger;
+                                    int currentNaturalKey = currentKeyStroke
+                                            .getNaturalKey();
+                                    if ((currentKeyStroke.getModifierKeys() is (lookup
+                                            .getCtrl() | lookup.getShift()))
+                                            && ((currentNaturalKey >= '0' && currentNaturalKey <= '9')
+                                                    || (currentNaturalKey >= 'A' && currentNaturalKey <= 'F') || (currentNaturalKey is 'U'))) {
+                                        accelerator = currentKeyStroke
+                                                .getModifierKeys()
+                                                | currentNaturalKey;
+                                        acceleratorText = triggerSequence
+                                                .format();
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    if (accelerator is 0) {
+                        if ((callback !is null) && (commandId !is null)) {
+                            acceleratorText = callback
+                                    .getAcceleratorText(commandId);
+                        }
+                    }
+
+                    IContributionManagerOverrides overrides = null;
+
+                    if (getParent() !is null) {
+                        overrides = getParent().getOverrides();
+                    }
+
+                    if (overrides !is null) {
+                        text = getParent().getOverrides().getText(this);
+                    }
+
+                    mi.setAccelerator(accelerator);
+
+                    if (text is null) {
+                        text = updatedAction.getText();
+                    }
+
+                    if (text !is null && acceleratorText is null) {
+                        // use extracted accelerator text in case accelerator cannot be fully represented in one int (e.g. multi-stroke keys)
+                        acceleratorText = LegacyActionTools.extractAcceleratorText(text);
+                        if (acceleratorText is null && accelerator !is 0) {
+                            acceleratorText= Action.convertAccelerator(accelerator);
+                        }
+                    }
+
+                    if (text is null) {
+                        text = ""; //$NON-NLS-1$
+                    } else {
+                        text = Action.removeAcceleratorText(text);
+                    }
+
+                    if (acceleratorText is null) {
+                        mi.setText(text);
+                    } else {
+                        mi.setText(text ~ '\t' ~ acceleratorText);
+                    }
+                }
+
+                if (imageChanged) {
+                    updateImages(false);
+                }
+
+                if (enableStateChanged) {
+                    bool shouldBeEnabled = action.isEnabled()
+                            && isEnabledAllowed();
+
+                    if (mi.getEnabled() !is shouldBeEnabled) {
+                        mi.setEnabled(shouldBeEnabled);
+                    }
+                }
+
+                if (checkChanged) {
+                    bool bv = action.isChecked();
+
+                    if (mi.getSelection() !is bv) {
+                        mi.setSelection(bv);
+                    }
+                }
+
+                return;
+            }
+
+            if (cast(Button)widget ) {
+                Button button = cast(Button) widget;
+
+                if (imageChanged && updateImages(false)) {
+                    textChanged = false; // don't update text if it has an
+                                            // image
+                }
+
+                if (textChanged) {
+                    String text = action.getText();
+                    if (text is null) {
+                        text = ""; //$NON-NLS-1$
+                    } else {
+                        text = Action.removeAcceleratorText(text);
+                    }
+                    button.setText(text);
+                }
+
+                if (tooltipTextChanged) {
+                    button.setToolTipText(action.getToolTipText());
+                }
+
+                if (enableStateChanged) {
+                    bool shouldBeEnabled = action.isEnabled()
+                            && isEnabledAllowed();
+
+                    if (button.getEnabled() !is shouldBeEnabled) {
+                        button.setEnabled(shouldBeEnabled);
+                    }
+                }
+
+                if (checkChanged) {
+                    bool bv = action.isChecked();
+
+                    if (button.getSelection() !is bv) {
+                        button.setSelection(bv);
+                    }
+                }
+                return;
+            }
+        }
+    }
+
+    /**
+     * Updates the images for this action.
+     *
+     * @param forceImage
+     *            <code>true</code> if some form of image is compulsory, and
+     *            <code>false</code> if it is acceptable for this item to have
+     *            no image
+     * @return <code>true</code> if there are images for this action,
+     *         <code>false</code> if not
+     */
+    private bool updateImages(bool forceImage) {
+
+        ResourceManager parentResourceManager = JFaceResources.getResources();
+
+        if (cast(ToolItem)widget ) {
+            if (USE_COLOR_ICONS) {
+                ImageDescriptor image = action.getHoverImageDescriptor();
+                if (image is null) {
+                    image = action.getImageDescriptor();
+                }
+                ImageDescriptor disabledImage = action
+                        .getDisabledImageDescriptor();
+
+                // Make sure there is a valid image.
+                if (image is null && forceImage) {
+                    image = ImageDescriptor.getMissingImageDescriptor();
+                }
+
+                LocalResourceManager localManager = new LocalResourceManager(
+                        parentResourceManager);
+
+                // performance: more efficient in DWT to set disabled and hot
+                // image before regular image
+                (cast(ToolItem) widget)
+                        .setDisabledImage(disabledImage is null ? null
+                                : localManager
+                                        .createImageWithDefault(disabledImage));
+                (cast(ToolItem) widget).setImage(image is null ? null
+                        : localManager.createImageWithDefault(image));
+
+                disposeOldImages();
+                imageManager = localManager;
+
+                return image !is null;
+            }
+            ImageDescriptor image = action.getImageDescriptor();
+            ImageDescriptor hoverImage = action.getHoverImageDescriptor();
+            ImageDescriptor disabledImage = action.getDisabledImageDescriptor();
+
+            // If there is no regular image, but there is a hover image,
+            // convert the hover image to gray and use it as the regular image.
+            if (image is null && hoverImage !is null) {
+                image = ImageDescriptor.createWithFlags(action
+                        .getHoverImageDescriptor(), DWT.IMAGE_GRAY);
+            } else {
+                // If there is no hover image, use the regular image as the
+                // hover image,
+                // and convert the regular image to gray
+                if (hoverImage is null && image !is null) {
+                    hoverImage = image;
+                    image = ImageDescriptor.createWithFlags(action
+                            .getImageDescriptor(), DWT.IMAGE_GRAY);
+                }
+            }
+
+            // Make sure there is a valid image.
+            if (hoverImage is null && image is null && forceImage) {
+                image = ImageDescriptor.getMissingImageDescriptor();
+            }
+
+            // Create a local resource manager to remember the images we've
+            // allocated for this tool item
+            LocalResourceManager localManager = new LocalResourceManager(
+                    parentResourceManager);
+
+            // performance: more efficient in DWT to set disabled and hot image
+            // before regular image
+            (cast(ToolItem) widget).setDisabledImage(disabledImage is null ? null
+                    : localManager.createImageWithDefault(disabledImage));
+            (cast(ToolItem) widget).setHotImage(hoverImage is null ? null
+                    : localManager.createImageWithDefault(hoverImage));
+            (cast(ToolItem) widget).setImage(image is null ? null : localManager
+                    .createImageWithDefault(image));
+
+            // Now that we're no longer referencing the old images, clear them
+            // out.
+            disposeOldImages();
+            imageManager = localManager;
+
+            return image !is null;
+        } else if (cast(Item)widget  || cast(Button)widget ) {
+
+            // Use hover image if there is one, otherwise use regular image.
+            ImageDescriptor image = action.getHoverImageDescriptor();
+            if (image is null) {
+                image = action.getImageDescriptor();
+            }
+            // Make sure there is a valid image.
+            if (image is null && forceImage) {
+                image = ImageDescriptor.getMissingImageDescriptor();
+            }
+
+            // Create a local resource manager to remember the images we've
+            // allocated for this widget
+            LocalResourceManager localManager = new LocalResourceManager(
+                    parentResourceManager);
+
+            if (cast(Item)widget) {
+                (cast(Item) widget).setImage(image is null ? null : localManager
+                        .createImageWithDefault(image));
+            } else if (cast(Button)widget) {
+                (cast(Button) widget).setImage(image is null ? null : localManager
+                        .createImageWithDefault(image));
+            }
+
+            // Now that we're no longer referencing the old images, clear them
+            // out.
+            disposeOldImages();
+            imageManager = localManager;
+
+            return image !is null;
+        }
+        return false;
+    }
+
+    /**
+     * Dispose any images allocated for this contribution item
+     */
+    private void disposeOldImages() {
+        if (imageManager !is null) {
+            imageManager.dispose();
+            imageManager = null;
+        }
+    }
+
+    /**
+     * Shorten the given text <code>t</code> so that its length doesn't exceed
+     * the width of the given ToolItem.The default implementation replaces
+     * characters in the center of the original string with an ellipsis ("...").
+     * Override if you need a different strategy.
+     *
+     * @param textValue
+     *            the text to shorten
+     * @param item
+     *            the tool item the text belongs to
+     * @return the shortened string
+     *
+     */
+    protected String shortenText(String textValue, ToolItem item) {
+        if (textValue is null) {
+            return null;
+        }
+
+        GC gc = new GC(item.getParent());
+
+        int maxWidth = item.getImage().getBounds().width * 4;
+
+        if (gc.textExtent(textValue).x < maxWidth) {
+            gc.dispose();
+            return textValue;
+        }
+
+        for (int i = textValue.length; i > 0; i--) {
+            String test = textValue.substring(0, i);
+            test = test ~ ellipsis;
+            if (gc.textExtent(test).x < maxWidth) {
+                gc.dispose();
+                return test;
+            }
+
+        }
+        gc.dispose();
+        // If for some reason we fall through abort
+        return textValue;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/ContributionItem.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * 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.jface.action.ContributionItem;
+
+import dwtx.jface.action.IContributionItem;
+import dwtx.jface.action.IContributionManager;
+
+import dwt.widgets.Composite;
+import dwt.widgets.CoolBar;
+import dwt.widgets.Menu;
+import dwt.widgets.ToolBar;
+
+import dwt.dwthelper.utils;
+
+/**
+ * An abstract base implementation for contribution items.
+ */
+public abstract class ContributionItem : IContributionItem {
+
+    /**
+     * The identifier for this contribution item, of <code>null</code> if none.
+     */
+    private String id = null;
+
+    /**
+     * Indicates this item is visible in its manager; <code>true</code>
+     * by default.
+     */
+    private bool visible = true;
+
+    /**
+     * The parent contribution manager for this item
+     */
+    private IContributionManager parent;
+
+    /**
+     * Creates a contribution item with a <code>null</code> id.
+     * Calls <code>this(String)</code> with <code>null</code>.
+     */
+    protected this() {
+        this(null);
+    }
+
+    /**
+     * Creates a contribution item with the given (optional) id.
+     * The given id is used to find items in a contribution manager,
+     * and for positioning items relative to other items.
+     *
+     * @param id the contribution item identifier, or <code>null</code>
+     */
+    protected this(String id) {
+        this.id = id;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method does nothing. Subclasses may override.
+     */
+    public void dispose() {
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method does nothing. Subclasses may override.
+     */
+    public void fill(Composite parent) {
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method does nothing. Subclasses may override.
+     */
+    public void fill(Menu menu, int index) {
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method does nothing. Subclasses may override.
+     */
+    public void fill(ToolBar parent, int index) {
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method does nothing. Subclasses may override.
+     *
+     * @since 3.0
+     */
+    public void fill(CoolBar parent, int index) {
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method does nothing. Subclasses may override.
+     *
+     * @since 3.0
+     */
+    public void saveWidgetState() {
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Returns the parent contribution manager, or <code>null</code> if this
+     * contribution item is not currently added to a contribution manager.
+     *
+     * @return the parent contribution manager, or <code>null</code>
+     * @since 2.0
+     */
+    public IContributionManager getParent() {
+        return parent;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method returns <code>false</code>. Subclasses may override.
+     */
+    public bool isDirty() {
+        // @issue should this be false instead of calling isDynamic()?
+        return isDynamic();
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method returns <code>true</code>. Subclasses may override.
+     */
+    public bool isEnabled() {
+        return true;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method returns <code>false</code>. Subclasses may override.
+     */
+    public bool isDynamic() {
+        return false;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method returns <code>false</code>. Subclasses may override.
+     */
+    public bool isGroupMarker() {
+        return false;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method returns <code>false</code>. Subclasses may override.
+     */
+    public bool isSeparator() {
+        return false;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method returns the value recorded in an internal state variable,
+     * which is <code>true</code> by default. <code>setVisible</code>
+     * should be used to change this setting.
+     */
+    public bool isVisible() {
+        return visible;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method stores the value in an internal state variable,
+     * which is <code>true</code> by default.
+     */
+    public void setVisible(bool visible) {
+        this.visible = visible;
+    }
+
+    /**
+     * Returns a string representation of this contribution item
+     * suitable only for debugging.
+     */
+    public String toString() {
+        return this.classinfo.name ~ "(id=" ~ getId() ~ ")";//$NON-NLS-2$//$NON-NLS-1$
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * method does nothing. Subclasses may override.
+     */
+    public void update() {
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void setParent(IContributionManager parent) {
+        this.parent = parent;
+    }
+
+    /**
+     * The <code>ContributionItem</code> implementation of this
+     * method declared on <code>IContributionItem</code> does nothing.
+     * Subclasses should override to update their state.
+     */
+    public void update(String id) {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/ContributionManager.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,594 @@
+/*******************************************************************************
+ * 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.jface.action.ContributionManager;
+
+import dwtx.jface.action.ActionContributionItem;
+
+import dwtx.jface.action.IContributionManager;
+import dwtx.jface.action.IContributionItem;
+import dwtx.jface.action.IContributionManagerOverrides;
+import dwtx.jface.action.IAction;
+
+import tango.util.collection.ArraySeq;
+import tango.util.collection.model.Seq;
+
+import dwtx.jface.util.Policy;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Integer;
+import tango.io.Stdout;
+import tango.core.Exception;
+import tango.text.convert.Format;
+
+/**
+ * Abstract base class for all contribution managers, and standard
+ * implementation of <code>IContributionManager</code>. This class provides
+ * functionality common across the specific managers defined by this framework.
+ * <p>
+ * This class maintains a list of contribution items and a dirty flag, both as
+ * internal state. In addition to providing implementations of most
+ * <code>IContributionManager</code> methods, this class automatically
+ * coalesces adjacent separators, hides beginning and ending separators, and
+ * deals with dynamically changing sets of contributions. When the set of
+ * contributions does change dynamically, the changes are propagated to the
+ * control via the <code>update</code> method, which subclasses must
+ * implement.
+ * </p>
+ * <p>
+ * Note: A <code>ContributionItem</code> cannot be shared between different
+ * <code>ContributionManager</code>s.
+ * </p>
+ */
+public abstract class ContributionManager : IContributionManager {
+
+    // Internal debug flag.
+    // protected static final bool DEBUG = false;
+
+    /**
+     * The list of contribution items.
+     */
+    private Seq!(IContributionItem) contributions;
+
+    /**
+     * Indicates whether the widgets are in sync with the contributions.
+     */
+    private bool isDirty_ = true;
+
+    /**
+     * Number of dynamic contribution items.
+     */
+    private int dynamicItems = 0;
+
+    /**
+     * The overrides for items of this manager
+     */
+    private IContributionManagerOverrides overrides;
+
+    /**
+     * Creates a new contribution manager.
+     */
+    protected this() {
+        contributions = new ArraySeq!(IContributionItem);
+        // Do nothing.
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void add(IAction action) {
+        add(new ActionContributionItem(action));
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void add(IContributionItem item) {
+        if (allowItem(item)) {
+            contributions.append(item);
+            itemAdded(item);
+        }
+    }
+
+    /**
+     * Adds a contribution item to the start or end of the group with the given
+     * name.
+     *
+     * @param groupName
+     *            the name of the group
+     * @param item
+     *            the contribution item
+     * @param append
+     *            <code>true</code> to add to the end of the group, and
+     *            <code>false</code> to add the beginning of the group
+     * @exception IllegalArgumentException
+     *                if there is no group with the given name
+     */
+    private void addToGroup(String groupName, IContributionItem item,
+            bool append) {
+        int i;
+        auto items = contributions.elements();
+        for (i = 0; items.more(); i++) {
+            IContributionItem o = cast(IContributionItem) items.get();
+            if (o.isGroupMarker()) {
+                String id = o.getId();
+                if (id !is null && id.equalsIgnoreCase(groupName)) {
+                    i++;
+                    if (append) {
+                        for (; items.more(); i++) {
+                            IContributionItem ci = cast(IContributionItem) items
+                                    .get();
+                            if (ci.isGroupMarker()) {
+                                break;
+                            }
+                        }
+                    }
+                    if (allowItem(item)) {
+                        //TODO: does this corrupt the iterator?
+                        contributions.addAt(i, item);
+                        itemAdded(item);
+                    }
+                    return;
+                }
+            }
+        }
+        throw new IllegalArgumentException("Group not found: " ~ groupName);//$NON-NLS-1$
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void appendToGroup(String groupName, IAction action) {
+        addToGroup(groupName, new ActionContributionItem(action), true);
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void appendToGroup(String groupName, IContributionItem item) {
+        addToGroup(groupName, item, true);
+    }
+
+    /**
+     * This method allows subclasses of <code>ContributionManager</code> to
+     * prevent certain items in the contributions list.
+     * <code>ContributionManager</code> will either block or allow an addition
+     * based on the result of this method call. This can be used to prevent
+     * duplication, for example.
+     *
+     * @param itemToAdd
+     *            The contribution item to be added; may be <code>null</code>.
+     * @return <code>true</code> if the addition should be allowed;
+     *         <code>false</code> otherwise. The default implementation allows
+     *         all items.
+     * @since 3.0
+     */
+    protected bool allowItem(IContributionItem itemToAdd) {
+        return true;
+    }
+
+    /**
+     * Internal debug method for printing statistics about this manager to
+     * <code>System.out</code>.
+     */
+    protected void dumpStatistics() {
+        int size = 0;
+        if (contributions !is null) {
+            size = contributions.size();
+        }
+
+        Stdout.formatln(this.toString());
+        Stdout.formatln("   Number of elements: {}", size);//$NON-NLS-1$
+        int sum = 0;
+        for (int i = 0; i < size; i++) {
+            if ((cast(IContributionItem) contributions.get(i)).isVisible()) {
+                sum++;
+            }
+        }
+        Stdout.formatln("   Number of visible elements: {}", sum);//$NON-NLS-1$
+        Stdout.formatln("   Is dirty: {}", isDirty()); //$NON-NLS-1$
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public IContributionItem find(String id) {
+        auto e = contributions.elements();
+        while (e.more()) {
+            IContributionItem item = cast(IContributionItem) e.get();
+            String itemId = item.getId();
+            if (itemId !is null && itemId.equalsIgnoreCase(id)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public IContributionItem[] getItems() {
+        return contributions.toArray();
+    }
+
+    /**
+     * Return the number of contributions in this manager.
+     *
+     * @return the number of contributions in this manager
+     * @since 3.3
+     */
+    public int getSize() {
+        return contributions.size();
+    }
+
+    /**
+     * The <code>ContributionManager</code> implementation of this method
+     * declared on <code>IContributionManager</code> returns the current
+     * overrides. If there is no overrides it lazily creates one which overrides
+     * no item state.
+     *
+     * @since 2.0
+     */
+    public IContributionManagerOverrides getOverrides() {
+        if (overrides is null) {
+            overrides = new class IContributionManagerOverrides {
+                public ValueWrapperBool getEnabled(IContributionItem item) {
+                    return null;
+                }
+
+                public ValueWrapperInt getAccelerator(IContributionItem item) {
+                    return null;
+                }
+
+                public String getAcceleratorText(IContributionItem item) {
+                    return null;
+                }
+
+                public String getText(IContributionItem item) {
+                    return null;
+                }
+            };
+        }
+        return overrides;
+    }
+
+    /**
+     * Returns whether this contribution manager contains dynamic items. A
+     * dynamic contribution item contributes items conditionally, dependent on
+     * some internal state.
+     *
+     * @return <code>true</code> if this manager contains dynamic items, and
+     *         <code>false</code> otherwise
+     */
+    protected bool hasDynamicItems() {
+        return (dynamicItems > 0);
+    }
+
+    /**
+     * Returns the index of the item with the given id.
+     *
+     * @param id
+     *            The id of the item whose index is requested.
+     *
+     * @return <code>int</code> the index or -1 if the item is not found
+     */
+    public int indexOf(String id) {
+        for (int i = 0; i < contributions.size(); i++) {
+            IContributionItem item = cast(IContributionItem) contributions.get(i);
+            String itemId = item.getId();
+            if (itemId !is null && itemId.equalsIgnoreCase(id)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the index of the object in the internal structure. This is
+     * different from <code>indexOf(String id)</code> since some contribution
+     * items may not have an id.
+     *
+     * @param item
+     *            The contribution item
+     * @return the index, or -1 if the item is not found
+     * @since 3.0
+     */
+    protected int indexOf(IContributionItem item) {
+        int res = -1;
+        int idx = 0;
+        foreach( e; contributions ){
+            if( e == item ) {
+                res = idx;
+                break;
+            }
+            idx++;
+        }
+        return res;
+    }
+
+    /**
+     * Insert the item at the given index.
+     *
+     * @param index
+     *            The index to be used for insertion
+     * @param item
+     *            The item to be inserted
+     */
+    public void insert(int index, IContributionItem item) {
+        if (index > contributions.size()) {
+            throw new IndexOutOfBoundsException( Format(
+                    "inserting {} at {}", item.getId(), index)); //$NON-NLS-1$ //$NON-NLS-2$
+        }
+        if (allowItem(item)) {
+            contributions.addAt(index, item);
+            itemAdded(item);
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void insertAfter(String ID, IAction action) {
+        insertAfter(ID, new ActionContributionItem(action));
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void insertAfter(String ID, IContributionItem item) {
+        IContributionItem ci = find(ID);
+        if (ci is null) {
+            throw new IllegalArgumentException(Format("can't find ID{}", ID));//$NON-NLS-1$
+        }
+        int ix = SeqIndexOf!(IContributionItem)( contributions, ci );
+        if (ix >= 0) {
+            // System.out.println("insert after: " + ix);
+            if (allowItem(item)) {
+                contributions.addAt(ix + 1, item);
+                itemAdded(item);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void insertBefore(String ID, IAction action) {
+        insertBefore(ID, new ActionContributionItem(action));
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void insertBefore(String ID, IContributionItem item) {
+        IContributionItem ci = find(ID);
+        if (ci is null) {
+            throw new IllegalArgumentException(Format("can't find ID {}", ID));//$NON-NLS-1$
+        }
+        int ix = SeqIndexOf!(IContributionItem)(contributions,ci);
+        if (ix >= 0) {
+            // System.out.println("insert before: " + ix);
+            if (allowItem(item)) {
+                contributions.addAt(ix, item);
+                itemAdded(item);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public bool isDirty() {
+        if (isDirty_) {
+            return true;
+        }
+        if (hasDynamicItems()) {
+            foreach( e; contributions ){
+                IContributionItem item = cast(IContributionItem) e;
+                if (item.isDirty()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public bool isEmpty() {
+        return contributions.drained();
+    }
+
+    /**
+     * The given item was added to the list of contributions. Marks the manager
+     * as dirty and updates the number of dynamic items, and the memento.
+     *
+     * @param item
+     *            the item to be added
+     *
+     */
+    protected void itemAdded(IContributionItem item) {
+        item.setParent(this);
+        markDirty();
+        if (item.isDynamic()) {
+            dynamicItems++;
+        }
+    }
+
+    /**
+     * The given item was removed from the list of contributions. Marks the
+     * manager as dirty and updates the number of dynamic items.
+     *
+     * @param item
+     *            remove given parent from list of contributions
+     */
+    protected void itemRemoved(IContributionItem item) {
+        item.setParent(null);
+        markDirty();
+        if (item.isDynamic()) {
+            dynamicItems--;
+        }
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void markDirty() {
+        setDirty(true);
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void prependToGroup(String groupName, IAction action) {
+        addToGroup(groupName, new ActionContributionItem(action), false);
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void prependToGroup(String groupName, IContributionItem item) {
+        addToGroup(groupName, item, false);
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public IContributionItem remove(String ID) {
+        IContributionItem ci = find(ID);
+        if (ci is null) {
+            return null;
+        }
+        return remove(ci);
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public IContributionItem remove(IContributionItem item) {
+        bool contained = contributions.contains(item);
+        contributions.remove(item);
+        if (contained) {
+            itemRemoved(item);
+            return item;
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc) Method declared on IContributionManager.
+     */
+    public void removeAll() {
+        IContributionItem[] items = getItems();
+        contributions.clear();
+        for (int i = 0; i < items.length; i++) {
+            IContributionItem item = items[i];
+            itemRemoved(item);
+        }
+        dynamicItems = 0;
+        markDirty();
+    }
+
+    /**
+     * Replaces the item of the given identifier with another contribution item.
+     * This can be used, for example, to replace large contribution items with
+     * placeholders to avoid memory leaks. If the identifier cannot be found in
+     * the current list of items, then this does nothing. If multiple
+     * occurrences are found, then the replacement items is put in the first
+     * position and the other positions are removed.
+     *
+     * @param identifier
+     *            The identifier to look for in the list of contributions;
+     *            should not be <code>null</code>.
+     * @param replacementItem
+     *            The contribution item to replace the old item; must not be
+     *            <code>null</code>. Use
+     *            {@link dwtx.jface.action.ContributionManager#remove(java.lang.String) remove}
+     *            if that is what you want to do.
+     * @return <code>true</code> if the given identifier can be; <code>
+     * @since 3.0
+     */
+    public bool replaceItem(String identifier,
+            IContributionItem replacementItem) {
+        if (identifier is null) {
+            return false;
+        }
+
+        int index = indexOf(identifier);
+        if (index < 0) {
+            return false; // couldn't find the item.
+        }
+
+        // Remove the old item.
+        IContributionItem oldItem = cast(IContributionItem) contributions
+                .get(index);
+        itemRemoved(oldItem);
+
+        // Add the new item.
+        contributions.replaceAt(index, replacementItem);
+        itemAdded(replacementItem); // throws NPE if (replacementItem is null)
+
+        // Go through and remove duplicates.
+        for (int i = contributions.size() - 1; i > index; i--) {
+            IContributionItem item = cast(IContributionItem) contributions.get(i);
+            if ((item !is null) && (identifier.equals(item.getId()))) {
+                if (Policy.TRACE_TOOLBAR) {
+                    Stdout.formatln("Removing duplicate on replace: {}", identifier); //$NON-NLS-1$
+                }
+                contributions.removeAt(i);
+                itemRemoved(item);
+            }
+        }
+
+        return true; // success
+    }
+
+    /**
+     * Sets whether this manager is dirty. When dirty, the list of contributions
+     * is not accurately reflected in the corresponding widgets.
+     *
+     * @param dirty
+     *            <code>true</code> if this manager is dirty, and
+     *            <code>false</code> if it is up-to-date
+     */
+    protected void setDirty(bool dirty) {
+        isDirty_ = dirty;
+    }
+
+    /**
+     * Sets the overrides for this contribution manager
+     *
+     * @param newOverrides
+     *            the overrides for the items of this manager
+     * @since 2.0
+     */
+    public void setOverrides(IContributionManagerOverrides newOverrides) {
+        overrides = newOverrides;
+    }
+
+    /**
+     * An internal method for setting the order of the contribution items.
+     *
+     * @param items
+     *            the contribution items in the specified order
+     * @since 3.0
+     */
+    protected void internalSetItems(IContributionItem[] items) {
+        contributions.clear();
+        for (int i = 0; i < items.length; i++) {
+            if (allowItem(items[i])) {
+                contributions.append(items[i]);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/ExternalActionManager.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,564 @@
+/*******************************************************************************
+ * 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.jface.action.ExternalActionManager;
+
+import dwtx.jface.action.IAction;
+
+import tango.util.collection.HashMap;
+import tango.util.collection.HashSet;
+import tango.util.collection.model.Map;
+import tango.util.collection.model.Set;
+
+import dwtx.core.commands.Command;
+import dwtx.core.commands.CommandEvent;
+import dwtx.core.commands.CommandManager;
+import dwtx.core.commands.ICommandListener;
+import dwtx.core.commands.ParameterizedCommand;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.Status;
+import dwtx.jface.bindings.BindingManager;
+import dwtx.jface.bindings.BindingManagerEvent;
+import dwtx.jface.bindings.IBindingManagerListener;
+import dwtx.jface.bindings.Trigger;
+import dwtx.jface.bindings.TriggerSequence;
+import dwtx.jface.bindings.keys.KeySequence;
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.bindings.keys.SWTKeySupport;
+import dwtx.jface.util.IPropertyChangeListener;
+import dwtx.jface.util.Policy;
+import dwtx.jface.util.PropertyChangeEvent;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.ResourceBundle;
+import dwt.dwthelper.Integer;
+import tango.text.convert.Format;
+
+/**
+ * <p>
+ * A manager for a callback facility which is capable of querying external
+ * interfaces for additional information about actions and action contribution
+ * items. This information typically includes things like accelerators and
+ * textual representations.
+ * </p>
+ * <p>
+ * <em>It is only necessary to use this mechanism if you will be using a mix of
+ * actions and commands, and wish the interactions to work properly.</em>
+ * </p>
+ * <p>
+ * For example, in the Eclipse workbench, this mechanism is used to allow the
+ * command architecture to override certain values in action contribution items.
+ * </p>
+ * <p>
+ * This class is not intended to be called or extended by any external clients.
+ * </p>
+ *
+ * @since 3.0
+ */
+public final class ExternalActionManager {
+
+    /**
+     * A simple implementation of the <code>ICallback</code> mechanism that
+     * simply takes a <code>BindingManager</code> and a
+     * <code>CommandManager</code>.
+     *
+     * @since 3.1
+     */
+    public static final class CommandCallback :
+            IBindingManagerListener, IBindingManagerCallback {
+
+        /**
+         * The internationalization bundle for text produced by this class.
+         */
+        private static const ResourceBundle RESOURCE_BUNDLE;
+
+        /**
+         * The callback capable of responding to whether a command is active.
+         */
+        private const IActiveChecker activeChecker;
+
+        /**
+         * The binding manager for your application. Must not be
+         * <code>null</code>.
+         */
+        private const BindingManager bindingManager;
+
+        /**
+         * Whether a listener has been attached to the binding manager yet.
+         */
+        private bool bindingManagerListenerAttached = false;
+
+        /**
+         * The command manager for your application. Must not be
+         * <code>null</code>.
+         */
+        private const CommandManager commandManager;
+
+        /**
+         * A set of all the command identifiers that have been logged as broken
+         * so far. For each of these, there will be a listener on the
+         * corresponding command. If the command ever becomes defined, the item
+         * will be removed from this set and the listener removed. This value
+         * may be empty, but never <code>null</code>.
+         */
+        private const Set!(String) loggedCommandIds;
+
+        /**
+         * The list of listeners that have registered for property change
+         * notification. This is a map of command identifiers (<code>String</code>)
+         * to listeners (<code>IPropertyChangeListener</code>).
+         */
+        private const Map!(String,IPropertyChangeListener) registeredListeners;
+
+        static this(){
+            RESOURCE_BUNDLE = ResourceBundle.getBundle(ExternalActionManager.classinfo.name);
+        }
+        /**
+         * Constructs a new instance of <code>CommandCallback</code> with the
+         * workbench it should be using. All commands will be considered active.
+         *
+         * @param bindingManager
+         *            The binding manager which will provide the callback; must
+         *            not be <code>null</code>.
+         * @param commandManager
+         *            The command manager which will provide the callback; must
+         *            not be <code>null</code>.
+         *
+         * @since 3.1
+         */
+        public this(BindingManager bindingManager,
+                CommandManager commandManager) {
+            this(bindingManager, commandManager, new class IActiveChecker {
+                public bool isActive(String commandId) {
+                    return true;
+                }
+
+            });
+        }
+
+        /**
+         * Constructs a new instance of <code>CommandCallback</code> with the
+         * workbench it should be using.
+         *
+         * @param bindingManager
+         *            The binding manager which will provide the callback; must
+         *            not be <code>null</code>.
+         * @param commandManager
+         *            The command manager which will provide the callback; must
+         *            not be <code>null</code>.
+         * @param activeChecker
+         *            The callback mechanism for checking whether a command is
+         *            active; must not be <code>null</code>.
+         *
+         * @since 3.1
+         */
+        public this(BindingManager bindingManager,
+                CommandManager commandManager,
+                IActiveChecker activeChecker) {
+            loggedCommandIds = new HashSet!(String);
+            registeredListeners = new HashMap!(String,IPropertyChangeListener);
+            if (bindingManager is null) {
+                throw new NullPointerException(
+                        "The callback needs a binding manager"); //$NON-NLS-1$
+            }
+
+            if (commandManager is null) {
+                throw new NullPointerException(
+                        "The callback needs a command manager"); //$NON-NLS-1$
+            }
+
+            if (activeChecker is null) {
+                throw new NullPointerException(
+                        "The callback needs an active callback"); //$NON-NLS-1$
+            }
+
+            this.activeChecker = activeChecker;
+            this.bindingManager = bindingManager;
+            this.commandManager = commandManager;
+        }
+
+        /**
+         * @see dwtx.jface.action.ExternalActionManager.ICallback#addPropertyChangeListener(String,
+         *      IPropertyChangeListener)
+         */
+        public final void addPropertyChangeListener(String commandId,
+                IPropertyChangeListener listener) {
+            registeredListeners.add(commandId, listener);
+            if (!bindingManagerListenerAttached) {
+                bindingManager.addBindingManagerListener(this);
+                bindingManagerListenerAttached = true;
+            }
+        }
+
+        public final void bindingManagerChanged(BindingManagerEvent event) {
+            if (event.isActiveBindingsChanged()) {
+                foreach( k,v; registeredListeners ){
+//                 Iterator listenerItr = registeredListeners.entrySet()
+//                         .iterator();
+//                 while (listenerItr.hasNext()) {
+//                     Map.Entry entry = cast(Map.Entry) listenerItr.next();
+                    String commandId = k;//stringcast(k);// entry.getKey();
+                    Command command = commandManager.getCommand(commandId);
+                    ParameterizedCommand parameterizedCommand = new ParameterizedCommand(
+                            command, null);
+                    if (event.isActiveBindingsChangedFor(parameterizedCommand)) {
+                        IPropertyChangeListener listener = cast(IPropertyChangeListener) v;
+                        listener.propertyChange(new PropertyChangeEvent(event
+                                .getManager(), IAction.TEXT, null, null));
+                    }
+                }
+            }
+        }
+
+        /**
+         * @see dwtx.jface.action.ExternalActionManager.ICallback#getAccelerator(String)
+         */
+        public ValueWrapperInt getAccelerator(String commandId) {
+            TriggerSequence triggerSequence = bindingManager
+                    .getBestActiveBindingFor(commandId);
+            if (triggerSequence !is null) {
+                Trigger[] triggers = triggerSequence.getTriggers();
+                if (triggers.length is 1) {
+                    Trigger trigger = triggers[0];
+                    if ( auto keyStroke = cast(KeyStroke) trigger ) {
+                        int accelerator = SWTKeySupport
+                                .convertKeyStrokeToAccelerator(keyStroke);
+                        return new ValueWrapperInt(accelerator);
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        /**
+         * @see dwtx.jface.action.ExternalActionManager.ICallback#getAcceleratorText(String)
+         */
+        public final String getAcceleratorText(String commandId) {
+            TriggerSequence triggerSequence = bindingManager
+                    .getBestActiveBindingFor(commandId);
+            if (triggerSequence is null) {
+                return null;
+            }
+
+            return triggerSequence.format();
+        }
+
+        /**
+         * Returns the active bindings for a particular command identifier.
+         *
+         * @param commandId
+         *            The identifier of the command whose bindings are
+         *            requested. This argument may be <code>null</code>. It
+         *            is assumed that the command has no parameters.
+         * @return The array of active triggers (<code>TriggerSequence</code>)
+         *         for a particular command identifier. This value is guaranteed
+         *         not to be <code>null</code>, but it may be empty.
+         * @since 3.2
+         */
+        public final TriggerSequence[] getActiveBindingsFor(
+                String commandId) {
+            return bindingManager.getActiveBindingsFor(commandId);
+        }
+
+        /**
+         * @see dwtx.jface.action.ExternalActionManager.ICallback#isAcceleratorInUse(int)
+         */
+        public final bool isAcceleratorInUse(int accelerator) {
+            KeySequence keySequence = KeySequence
+                    .getInstance(SWTKeySupport
+                            .convertAcceleratorToKeyStroke(accelerator));
+            return bindingManager.isPerfectMatch(keySequence)
+                    || bindingManager.isPartialMatch(keySequence);
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * Calling this method with an undefined command id will generate a log
+         * message.
+         */
+        public final bool isActive(String commandId) {
+            if (commandId !is null) {
+                Command command = commandManager.getCommand(commandId);
+
+                if (!command.isDefined()
+                        && (!loggedCommandIds.contains(commandId))) {
+                    // The command is not yet defined, so we should log this.
+                    String message = Format(Util
+                            .translateString(RESOURCE_BUNDLE,
+                                    "undefinedCommand.WarningMessage", null), //$NON-NLS-1$
+                            [ command.getId() ]);
+                    IStatus status = new Status(IStatus.ERROR,
+                            "dwtx.jface", //$NON-NLS-1$
+                            0, message, new Exception(null));
+                    Policy.getLog().log(status);
+
+                    // And remember this item so we don't log it again.
+                    loggedCommandIds.add(commandId);
+                    command.addCommandListener(new class ICommandListener {
+                        Command command_;
+                        String commandId_;
+                        this(){
+                            command_=command;
+                            commandId_=commandId;
+                        }
+                        /*
+                         * (non-Javadoc)
+                         *
+                         * @see dwtx.ui.commands.ICommandListener#commandChanged(dwtx.ui.commands.CommandEvent)
+                         */
+                        public final void commandChanged(
+                                CommandEvent commandEvent) {
+                            if (command_.isDefined()) {
+                                command_.removeCommandListener(this);
+                                loggedCommandIds.remove(commandId_);
+                            }
+                        }
+                    });
+
+                    return true;
+                }
+
+                return activeChecker.isActive(commandId);
+            }
+
+            return true;
+        }
+
+        /**
+         * @see dwtx.jface.action.ExternalActionManager.ICallback#removePropertyChangeListener(String,
+         *      IPropertyChangeListener)
+         */
+        public final void removePropertyChangeListener(String commandId,
+                IPropertyChangeListener listener) {
+            IPropertyChangeListener existingListener = cast(IPropertyChangeListener) registeredListeners
+                    .get(commandId);
+            if (existingListener is listener) {
+                registeredListeners.removeKey(commandId);
+                if (registeredListeners.drained()) {
+                    bindingManager.removeBindingManagerListener(this);
+                    bindingManagerListenerAttached = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * Defines a callback mechanism for developer who wish to further control
+     * the visibility of legacy action-based contribution items.
+     *
+     * @since 3.1
+     */
+    public static interface IActiveChecker {
+        /**
+         * Checks whether the command with the given identifier should be
+         * considered active. This can be used in systems using some kind of
+         * user interface filtering (e.g., activities in the Eclipse workbench).
+         *
+         * @param commandId
+         *            The identifier for the command; must not be
+         *            <code>null</code>
+         * @return <code>true</code> if the command is active;
+         *         <code>false</code> otherwise.
+         */
+        public bool isActive(String commandId);
+    }
+
+    /**
+     * <p>
+     * A callback which communicates with the applications binding manager. This
+     * interface provides more information from the binding manager, which
+     * allows greater integration. Implementing this interface is preferred over
+     * {@link ExternalActionManager.ICallback}.
+     * </p>
+     * <p>
+     * Clients may implement this interface, but must not extend.
+     * </p>
+     *
+     * @since 3.2
+     */
+    public static interface IBindingManagerCallback : ICallback {
+
+        /**
+         * <p>
+         * Returns the active bindings for a particular command identifier.
+         * </p>
+         *
+         * @param commandId
+         *            The identifier of the command whose bindings are
+         *            requested. This argument may be <code>null</code>. It
+         *            is assumed that the command has no parameters.
+         * @return The array of active triggers (<code>TriggerSequence</code>)
+         *         for a particular command identifier. This value is guaranteed
+         *         not to be <code>null</code>, but it may be empty.
+         */
+        public TriggerSequence[] getActiveBindingsFor(String commandId);
+    }
+
+    /**
+     * A callback mechanism for some external tool to communicate extra
+     * information to actions and action contribution items.
+     *
+     * @since 3.0
+     */
+    public static interface ICallback {
+
+        /**
+         * <p>
+         * Adds a listener to the object referenced by <code>identifier</code>.
+         * This listener will be notified if a property of the item is to be
+         * changed. This identifier is specific to mechanism being used. In the
+         * case of the Eclipse workbench, this is the command identifier.
+         * </p>
+         * <p>
+         * A single instance of the listener may only ever be associated with
+         * one identifier. Attempts to add the listener twice (without a removal
+         * in between) has undefined behaviour.
+         * </p>
+         *
+         * @param identifier
+         *            The identifier of the item to which the listener should be
+         *            attached; must not be <code>null</code>.
+         * @param listener
+         *            The listener to be added; must not be <code>null</code>.
+         */
+        public void addPropertyChangeListener(String identifier,
+                IPropertyChangeListener listener);
+
+        /**
+         * An accessor for the accelerator associated with the item indicated by
+         * the identifier. This identifier is specific to mechanism being used.
+         * In the case of the Eclipse workbench, this is the command identifier.
+         *
+         * @param identifier
+         *            The identifier of the item from which the accelerator
+         *            should be obtained ; must not be <code>null</code>.
+         * @return An integer representation of the accelerator. This is the
+         *         same accelerator format used by DWT.
+         */
+        public Integer getAccelerator(String identifier);
+
+        /**
+         * An accessor for the accelerator text associated with the item
+         * indicated by the identifier. This identifier is specific to mechanism
+         * being used. In the case of the Eclipse workbench, this is the command
+         * identifier.
+         *
+         * @param identifier
+         *            The identifier of the item from which the accelerator text
+         *            should be obtained ; must not be <code>null</code>.
+         * @return A string representation of the accelerator. This is the
+         *         string representation that should be displayed to the user.
+         */
+        public String getAcceleratorText(String identifier);
+
+        /**
+         * Checks to see whether the given accelerator is being used by some
+         * other mechanism (outside of the menus controlled by JFace). This is
+         * used to keep JFace from trying to grab accelerators away from someone
+         * else.
+         *
+         * @param accelerator
+         *            The accelerator to check -- in DWT's internal accelerator
+         *            format.
+         * @return <code>true</code> if the accelerator is already being used
+         *         and shouldn't be used again; <code>false</code> otherwise.
+         */
+        public bool isAcceleratorInUse(int accelerator);
+
+        /**
+         * Checks whether the item matching this identifier is active. This is
+         * used to decide whether a contribution item with this identifier
+         * should be made visible. An inactive item is not visible.
+         *
+         * @param identifier
+         *            The identifier of the item from which the active state
+         *            should be retrieved; must not be <code>null</code>.
+         * @return <code>true</code> if the item is active; <code>false</code>
+         *         otherwise.
+         */
+        public bool isActive(String identifier);
+
+        /**
+         * Removes a listener from the object referenced by
+         * <code>identifier</code>. This identifier is specific to mechanism
+         * being used. In the case of the Eclipse workbench, this is the command
+         * identifier.
+         *
+         * @param identifier
+         *            The identifier of the item to from the listener should be
+         *            removed; must not be <code>null</code>.
+         * @param listener
+         *            The listener to be removed; must not be <code>null</code>.
+         */
+        public void removePropertyChangeListener(String identifier,
+                IPropertyChangeListener listener);
+    }
+
+    /**
+     * The singleton instance of this class. This value may be <code>null</code>--
+     * if it has not yet been initialized.
+     */
+    private static ExternalActionManager instance;
+
+    /**
+     * Retrieves the current singleton instance of this class.
+     *
+     * @return The singleton instance; this value is never <code>null</code>.
+     */
+    public static ExternalActionManager getInstance() {
+        if (instance is null) {
+            instance = new ExternalActionManager();
+        }
+
+        return instance;
+    }
+
+    /**
+     * The callback mechanism to use to retrieve extra information.
+     */
+    private ICallback callback;
+
+    /**
+     * Constructs a new instance of <code>ExternalActionManager</code>.
+     */
+    private this() {
+        // This is a singleton class. Only this class should create an instance.
+    }
+
+    /**
+     * An accessor for the current call back.
+     *
+     * @return The current callback mechanism being used. This is the callback
+     *         that should be queried for extra information about actions and
+     *         action contribution items. This value may be <code>null</code>
+     *         if there is no extra information.
+     */
+    public ICallback getCallback() {
+        return callback;
+    }
+
+    /**
+     * A mutator for the current call back
+     *
+     * @param callbackToUse
+     *            The new callback mechanism to use; this value may be
+     *            <code>null</code> if the default is acceptable (i.e., no
+     *            extra information will provided to actions).
+     */
+    public void setCallback(ICallback callbackToUse) {
+        callback = callbackToUse;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/GroupMarker.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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.jface.action.GroupMarker;
+
+import dwtx.jface.action.AbstractGroupMarker;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A group marker is a special kind of contribution item denoting
+ * the beginning of a group. These groups are used to structure
+ * the list of items. Unlike regular contribution items and
+ * separators, group markers have no visual representation.
+ * The name of the group is synonymous with the contribution item id.
+ * <p>
+ * This class may be instantiated; it is not intended to be
+ * subclassed outside the framework.
+ * </p>
+ */
+public class GroupMarker : AbstractGroupMarker {
+    /**
+     * Create a new group marker with the given name.
+     * The group name must not be <code>null</code> or the empty string.
+     * The group name is also used as the item id.
+     *
+     * @param groupName the name of the group
+     */
+    public this(String groupName) {
+        super(groupName);
+    }
+
+    /**
+     * The <code>GroupMarker</code> implementation of this method
+     * returns <code>false</code> since group markers are always invisible.
+     */
+    public bool isVisible() {
+        return false;
+    }
+}
--- a/dwtx/jface/action/IAction.d	Mon Mar 31 02:00:41 2008 +0200
+++ b/dwtx/jface/action/IAction.d	Tue Apr 01 08:00:31 2008 +0200
@@ -57,25 +57,25 @@
      *
      * @since 2.1
      */
-    public static int AS_UNSPECIFIED = 0x00;
+    public static const int AS_UNSPECIFIED = 0x00;
 
     /**
      * Action style constant (value <code>1</code>) indicating action is
      * a simple push button.
      */
-    public static int AS_PUSH_BUTTON = 0x01;
+    public static const int AS_PUSH_BUTTON = 0x01;
 
     /**
      * Action style constant (value <code>2</code>) indicating action is
      * a check box (or a toggle button).
      */
-    public static int AS_CHECK_BOX = 0x02;
+    public static const int AS_CHECK_BOX = 0x02;
 
     /**
      * Action style constant (value <code>4</code>) indicating action is
      * a drop down menu.
      */
-    public static int AS_DROP_DOWN_MENU = 0x04;
+    public static const int AS_DROP_DOWN_MENU = 0x04;
 
     /**
      * Action style constant (value <code>8</code>) indicating action is
@@ -83,7 +83,7 @@
      *
      * @since 2.1
      */
-    public static int AS_RADIO_BUTTON = 0x08;
+    public static const int AS_RADIO_BUTTON = 0x08;
 
     /**
      * Property name of an action's text (value <code>"text"</code>).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/IContributionItem.d	Tue Apr 01 08:00:31 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.jface.action.IContributionItem;
+
+import dwtx.jface.action.IContributionManager;
+
+import dwt.widgets.Composite;
+import dwt.widgets.CoolBar;
+import dwt.widgets.Menu;
+import dwt.widgets.ToolBar;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A contribution item represents a contribution to a shared UI resource such as a
+ * menu or tool bar. More generally, contribution items are managed by a contribution
+ * manager.
+ * For instance, in a tool bar a contribution item is a tool bar button or a separator.
+ * In a menu bar a contribution item is a menu, and in a menu a contribution item
+ * is a menu item or separator.
+ * <p>
+ * A contribution item can realize itself in different DWT widgets, using the different
+ * <code>fill</code> methods.  The same type of contribution item can be used with a
+ * <code>MenuBarManager</code>, <code>ToolBarManager</code>, <code>CoolBarManager</code>,
+ * </code>or a <code>StatusLineManager</code>.
+ * </p>
+ * <p>
+ * This interface is internal to the framework; it should not be implemented outside
+ * the framework.
+ * </p>
+ *
+ * @see IContributionManager
+ */
+public interface IContributionItem {
+
+    /**
+     * Disposes of this contribution item. Called by the parent
+     * contribution manager when the manager is being disposed.
+     * Clients should not call this method directly, unless they
+     * have removed this contribution item from the containing
+     * IContributionManager before the contribution lifecycle
+     * has ended.
+     *
+     * @since 2.1
+     */
+    public void dispose();
+
+    /**
+     * Fills the given composite control with controls representing this
+     * contribution item.  Used by <code>StatusLineManager</code>.
+     *
+     * @param parent the parent control
+     */
+    public void fill(Composite parent);
+
+    /**
+     * Fills the given menu with controls representing this contribution item.
+     * Used by <code>MenuManager</code>.
+     *
+     * @param parent the parent menu
+     * @param index the index where the controls are inserted,
+     *   or <code>-1</code> to insert at the end
+     */
+    public void fill(Menu parent, int index);
+
+    /**
+     * Fills the given tool bar with controls representing this contribution item.
+     * Used by <code>ToolBarManager</code>.
+     *
+     * @param parent the parent tool bar
+     * @param index the index where the controls are inserted,
+     *   or <code>-1</code> to insert at the end
+     */
+    public void fill(ToolBar parent, int index);
+
+    /**
+     * Fills the given cool bar with controls representing this contribution item.
+     * Used by <code>CoolBarManager</code>.
+     *
+     * @param parent the parent cool bar
+     * @param index the index where the controls are inserted,
+     *   or <code>-1</code> to insert at the end
+     * @since 3.0
+     */
+    public void fill(CoolBar parent, int index);
+
+    /**
+     * Returns the identifier of this contribution item.
+     * The id is used for retrieving an item from its manager.
+     *
+     * @return the contribution item identifier, or <code>null</code>
+     *   if none
+     */
+    public String getId();
+
+    /**
+     * Returns whether this contribution item is enabled.
+     *
+     * @return <code>true</code> if this item is enabled
+     */
+    public bool isEnabled();
+
+    /**
+     * Returns whether this contribution item is dirty. A dirty item will be
+     * recreated when the action bar is updated.
+     *
+     * @return <code>true</code> if this item is dirty
+     */
+    public bool isDirty();
+
+    /**
+     * Returns whether this contribution item is dynamic. A dynamic contribution
+     * item contributes items conditionally, dependent on some internal state.
+     *
+     * @return <code>true</code> if this item is dynamic, and
+     *  <code>false</code> for normal items
+     */
+    public bool isDynamic();
+
+    /**
+     * Returns whether this contribution item is a group marker.
+     * This information is used when adding items to a group.
+     *
+     * @return <code>true</code> if this item is a group marker, and
+     *  <code>false</code> for normal items
+     *
+     * @see GroupMarker
+     * @see IContributionManager#appendToGroup(String, IContributionItem)
+     * @see IContributionManager#prependToGroup(String, IContributionItem)
+     */
+    public bool isGroupMarker();
+
+    /**
+     * Returns whether this contribution item is a separator.
+     * This information is used to enable hiding of unnecessary separators.
+     *
+     * @return <code>true</code> if this item is a separator, and
+     *  <code>false</code> for normal items
+     * @see Separator
+     */
+    public bool isSeparator();
+
+    /**
+     * Returns whether this contribution item is visibile within its manager.
+     *
+     * @return <code>true</code> if this item is visible, and
+     *  <code>false</code> otherwise
+     */
+    public bool isVisible();
+
+    /**
+     * Saves any state information of the control(s) owned by this contribution item.
+     * The contribution manager calls this method before disposing of the controls.
+     *
+     * @since 3.0
+     */
+    public void saveWidgetState();
+
+    /**
+     * Sets the parent manager of this item
+     *
+     * @param parent the parent contribution manager
+     * @since 2.0
+     */
+    public void setParent(IContributionManager parent);
+
+    /**
+     * Sets whether this contribution item is visibile within its manager.
+     *
+     * @param visible <code>true</code> if this item should be visible, and
+     *  <code>false</code> otherwise
+     */
+    public void setVisible(bool visible);
+
+    /**
+     * Updates any DWT controls cached by this contribution item with any
+     * changes which have been made to this contribution item since the last update.
+     * Called by contribution manager update methods.
+     */
+    public void update();
+
+    /**
+     * Updates any DWT controls cached by this contribution item with changes
+     * for the the given property.
+     *
+     * @param id the id of the changed property
+     * @since 2.0
+     */
+    public void update(String id);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/IContributionManager.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * 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.jface.action.IContributionManager;
+
+import dwtx.jface.action.IAction;
+import dwtx.jface.action.IContributionItem;
+import dwtx.jface.action.IContributionManagerOverrides;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A contribution manager organizes contributions to such UI components
+ * as menus, toolbars and status lines.
+ * <p>
+ * A contribution manager keeps track of a list of contribution
+ * items. Each contribution item may has an optional identifier, which can be used
+ * to retrieve items from a manager, and for positioning items relative to
+ * each other. The list of contribution items can be subdivided into named groups
+ * using special contribution items that serve as group markers.
+ * </p>
+ * <p>
+ * The <code>IContributionManager</code> interface provides general
+ * protocol for adding, removing, and retrieving contribution items.
+ * It also provides convenience methods that make it convenient
+ * to contribute actions. This interface should be implemented
+ * by all objects that wish to manage contributions.
+ * </p>
+ * <p>
+ * There are several implementions of this interface in this package,
+ * including ones for menus ({@link MenuManager <code>MenuManager</code>}),
+ * tool bars ({@link ToolBarManager <code>ToolBarManager</code>}),
+ * and status lines ({@link StatusLineManager <code>StatusLineManager</code>}).
+ * </p>
+ */
+public interface IContributionManager {
+    /**
+     * Adds an action as a contribution item to this manager.
+     * Equivalent to <code>add(new ActionContributionItem(action))</code>.
+     *
+     * @param action the action
+     */
+    public void add(IAction action);
+
+    /**
+     * Adds a contribution item to this manager.
+     *
+     * @param item the contribution item
+     */
+    public void add(IContributionItem item);
+
+    /**
+     * Adds a contribution item for the given action at the end of the group
+     * with the given name.
+     * Equivalent to
+     * <code>appendToGroup(groupName,new ActionContributionItem(action))</code>.
+     *
+     * @param groupName the name of the group
+     * @param action the action
+     * @exception IllegalArgumentException if there is no group with
+     *   the given name
+     */
+    public void appendToGroup(String groupName, IAction action);
+
+    /**
+     * Adds a contribution item to this manager at the end of the group
+     * with the given name.
+     *
+     * @param groupName the name of the group
+     * @param item the contribution item
+     * @exception IllegalArgumentException if there is no group with
+     *   the given name
+     */
+    public void appendToGroup(String groupName, IContributionItem item);
+
+    /**
+     * Finds the contribution item with the given id.
+     *
+     * @param id the contribution item id
+     * @return the contribution item, or <code>null</code> if
+     *   no item with the given id can be found
+     */
+    public IContributionItem find(String id);
+
+    /**
+     * Returns all contribution items known to this manager.
+     *
+     * @return a list of contribution items
+     */
+    public IContributionItem[] getItems();
+
+    /**
+     * Returns the overrides for the items of this manager.
+     *
+     * @return the overrides for the items of this manager
+     * @since 2.0
+     */
+    public IContributionManagerOverrides getOverrides();
+
+    /**
+     * Inserts a contribution item for the given action after the item
+     * with the given id.
+     * Equivalent to
+     * <code>insertAfter(id,new ActionContributionItem(action))</code>.
+     *
+     * @param id the contribution item id
+     * @param action the action to insert
+     * @exception IllegalArgumentException if there is no item with
+     *   the given id
+     */
+    public void insertAfter(String id, IAction action);
+
+    /**
+     * Inserts a contribution item after the item with the given id.
+     *
+     * @param id the contribution item id
+     * @param item the contribution item to insert
+     * @exception IllegalArgumentException if there is no item with
+     *   the given id
+     */
+    public void insertAfter(String id, IContributionItem item);
+
+    /**
+     * Inserts a contribution item for the given action before the item
+     * with the given id.
+     * Equivalent to
+     * <code>insertBefore(id,new ActionContributionItem(action))</code>.
+     *
+     * @param id the contribution item id
+     * @param action the action to insert
+     * @exception IllegalArgumentException if there is no item with
+     *   the given id
+     */
+    public void insertBefore(String id, IAction action);
+
+    /**
+     * Inserts a contribution item before the item with the given id.
+     *
+     * @param id the contribution item id
+     * @param item the contribution item to insert
+     * @exception IllegalArgumentException if there is no item with
+     *   the given id
+     */
+    public void insertBefore(String id, IContributionItem item);
+
+    /**
+     * Returns whether the list of contributions has recently changed and
+     * has yet to be reflected in the corresponding widgets.
+     *
+     * @return <code>true</code> if this manager is dirty, and <code>false</code>
+     *   if it is up-to-date
+     */
+    public bool isDirty();
+
+    /**
+     * Returns whether this manager has any contribution items.
+     *
+     * @return <code>true</code> if there are no items, and
+     *   <code>false</code> otherwise
+     */
+    public bool isEmpty();
+
+    /**
+     * Marks this contribution manager as dirty.
+     */
+    public void markDirty();
+
+    /**
+     * Adds a contribution item for the given action at the beginning of the
+     * group with the given name.
+     * Equivalent to
+     * <code>prependToGroup(groupName,new ActionContributionItem(action))</code>.
+     *
+     * @param groupName the name of the group
+     * @param action the action
+     * @exception IllegalArgumentException if there is no group with
+     *   the given name
+     */
+    public void prependToGroup(String groupName, IAction action);
+
+    /**
+     * Adds a contribution item to this manager at the beginning of the
+     * group with the given name.
+     *
+     * @param groupName the name of the group
+     * @param item the contribution item
+     * @exception IllegalArgumentException if there is no group with
+     *   the given name
+     */
+    public void prependToGroup(String groupName, IContributionItem item);
+
+    /**
+     * Removes and returns the contribution item with the given id from this manager.
+     * Returns <code>null</code> if this manager has no contribution items
+     * with the given id.
+     *
+     * @param id the contribution item id
+     * @return the item that was found and removed, or <code>null</code> if none
+     */
+    public IContributionItem remove(String id);
+
+    /**
+     * Removes the given contribution item from the contribution items
+     * known to this manager.
+     *
+     * @param item the contribution item
+     * @return the <code>item</code> parameter if the item was removed,
+     *   and <code>null</code> if it was not found
+     */
+    public IContributionItem remove(IContributionItem item);
+
+    /**
+     * Removes all contribution items from this manager.
+     */
+    public void removeAll();
+
+    /**
+     * Updates this manager's underlying widget(s) with any changes which
+     * have been made to it or its items.  Normally changes to a contribution
+     * manager merely mark it as dirty, without updating the underlying widgets.
+     * This brings the underlying widgets up to date with any changes.
+     *
+     * @param force <code>true</code> means update even if not dirty,
+     *   and <code>false</code> for normal incremental updating
+     */
+    public void update(bool force);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/IContributionManagerOverrides.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * 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.jface.action.IContributionManagerOverrides;
+
+import dwtx.jface.action.IContributionItem;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This interface is used by instances of <code>IContributionItem</code>
+ * to determine if the values for certain properties have been overriden
+ * by their manager.
+ * <p>
+ * This interface is internal to the framework; it should not be implemented outside
+ * the framework.
+ * </p>
+ *
+ * @since 2.0
+ */
+public interface IContributionManagerOverrides {
+    /**
+     * Id for the enabled property. Value is <code>"enabled"</code>.
+     *
+     * @since 2.0
+     */
+    public const static String P_ENABLED = "enabled"; //$NON-NLS-1$
+
+    /**
+     * Find out the enablement of the item
+     * @param item the contribution item for which the enable override value is
+     * determined
+     * @return <ul>
+     *              <li><code>bool.TRUE</code> if the given contribution item should be enabled</li>
+     *              <li><code>bool.FALSE</code> if the item should be disabled</li>
+     *              <li><code>null</code> if the item may determine its own enablement</li>
+     *          </ul>
+     * @since 2.0
+     */
+    public ValueWrapperBool getEnabled(IContributionItem item);
+
+    /**
+     * This is not intended to be called outside of the workbench. This method
+     * is intended to be deprecated in 3.1.
+     *
+     * TODO deprecate for 3.1
+     * @param item the contribution item for which the accelerator value is determined
+     * @return the accelerator
+     */
+    public ValueWrapperInt getAccelerator(IContributionItem item);
+
+    /**
+     * This is not intended to be called outside of the workbench. This method
+     * is intended to be deprecated in 3.1.
+     *
+     * TODO deprecate for 3.1
+     * @param item the contribution item for which the accelerator text is determined
+     * @return the text for the accelerator
+     */
+    public String getAcceleratorText(IContributionItem item);
+
+    /**
+     * This is not intended to be called outside of the workbench. This method
+     * is intended to be deprecated in 3.1.
+     *
+     * TODO deprecate for 3.1
+     * @param item the contribution item for which the text is determined
+     * @return the text
+     */
+    public String getText(IContributionItem item);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/IMenuListener.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * 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.jface.action.IMenuListener;
+
+import dwtx.jface.action.IMenuManager;
+
+/**
+ * A menu listener that gets informed when a menu is about to show.
+ *
+ * @see MenuManager#addMenuListener
+ */
+public interface IMenuListener {
+    /**
+     * Notifies this listener that the menu is about to be shown by
+     * the given menu manager.
+     *
+     * @param manager the menu manager
+     */
+    public void menuAboutToShow(IMenuManager manager);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/IMenuListener2.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 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.jface.action.IMenuListener2;
+
+import dwtx.jface.action.IMenuListener;
+import dwtx.jface.action.IMenuManager;
+
+/**
+ * A menu listener that gets informed when a menu is about to hide.
+ *
+ * @see MenuManager#addMenuListener
+ * @since 3.2
+ */
+public interface IMenuListener2 : IMenuListener {
+    /**
+     * Notifies this listener that the menu is about to be hidden by
+     * the given menu manager.
+     *
+     * @param manager the menu manager
+     */
+    public void menuAboutToHide(IMenuManager manager);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/IMenuManager.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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.jface.action.IMenuManager;
+
+import dwtx.jface.action.IContributionItem;
+import dwtx.jface.action.IContributionManager;
+import dwtx.jface.action.IMenuListener;
+
+import dwt.dwthelper.utils;
+
+/**
+ * The <code>IMenuManager</code> interface provides protocol for managing
+ * contributions to a menu bar and its sub menus.
+ * An <code>IMenuManager</code> is also an <code>IContributionItem</code>,
+ * allowing sub-menus to be nested in parent menus.
+ * <p>
+ * This interface is internal to the framework; it should not be implemented outside
+ * the framework.
+ * </p>
+ * <p>
+ * This package provides a concrete menu manager implementation,
+ * {@link MenuManager <code>MenuManager</code>}.
+ * </p>
+ */
+public interface IMenuManager : IContributionManager, IContributionItem {
+    /**
+     * Adds a menu listener to this menu.
+     * Has no effect if an identical listener is already registered.
+     *
+     * @param listener a menu listener
+     */
+    public void addMenuListener(IMenuListener listener);
+
+    /**
+     * Finds the manager for the menu at the given path. A path
+     * consists of contribution item ids separated by the separator
+     * character.  The path separator character is <code>'/'</code>.
+     * <p>
+     * Convenience for <code>findUsingPath(path)</code> which
+     * extracts an <code>IMenuManager</code> if possible.
+     * </p>
+     *
+     * @param path the path string
+     * @return the menu contribution item, or <code>null</code>
+     *   if there is no such contribution item or if the item does
+     *   not have an associated menu manager
+     */
+    public IMenuManager findMenuUsingPath(String path);
+
+    /**
+     * Finds the contribution item at the given path. A path
+     * consists of contribution item ids separated by the separator
+     * character. The path separator character is <code>'/'</code>.
+     *
+     * @param path the path string
+     * @return the contribution item, or <code>null</code> if there is no
+     *   such contribution item
+     */
+    public IContributionItem findUsingPath(String path);
+
+    /**
+     * Returns whether all items should be removed when the menu is about to
+     * show, but before notifying menu listeners. The default is
+     * <code>false</code>.
+     *
+     * @return <code>true</code> if all items should be removed when shown,
+     *         <code>false</code> if not
+     */
+    public bool getRemoveAllWhenShown();
+
+    /**
+     * Returns whether this menu should be enabled or not.
+     *
+     * @return <code>true</code> if enabled, and
+     *   <code>false</code> if disabled
+     */
+    public bool isEnabled();
+
+    /**
+     * Removes the given menu listener from this menu.
+     * Has no effect if an identical listener is not registered.
+     *
+     * @param listener the menu listener
+     */
+    public void removeMenuListener(IMenuListener listener);
+
+    /**
+     * Sets whether all items should be removed when the menu is about to show,
+     * but before notifying menu listeners.
+     *
+     * @param removeAll
+     *            <code>true</code> if all items should be removed when shown,
+     *            <code>false</code> if not
+     */
+    public void setRemoveAllWhenShown(bool removeAll);
+
+    /**
+     * Incrementally builds the menu from the contribution items, and
+     * does so recursively for all submenus.
+     *
+     * @param force <code>true</code> means update even if not dirty,
+     *   and <code>false</code> for normal incremental updating
+     */
+    public void updateAll(bool force);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/LegacyActionTools.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,746 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.jface.action.LegacyActionTools;
+
+import tango.util.collection.HashMap;
+import tango.util.collection.model.Map;
+
+import dwt.DWT;
+import dwtx.jface.resource.JFaceResources;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Integer;
+static import tango.text.Text;
+alias tango.text.Text.Text!(char) StringBuffer;
+
+/**
+ * <p>
+ * Some static utility methods for handling labels on actions. This includes
+ * mnemonics and accelerators.
+ * </p>
+ * <p>
+ * Clients may neither instantiate this class nor extend.
+ * </p>
+ *
+ * @since 3.2
+ */
+public final class LegacyActionTools {
+
+    /**
+     * Table of key codes (key type: <code>String</code>, value type:
+     * <code>Integer</code>); <code>null</code> if not yet initialized.
+     *
+     * @see #findKeyCode
+     */
+    private static Map!(String,Object) keyCodes = null;
+
+    /**
+     * Table of string representations of keys (key type: <code>Integer</code>,
+     * value type: <code>String</code>); <code>null</code>> if not yet
+     * initialized.
+     *
+     * @see #findKeyString
+     */
+    private static Map!(Object,String) keyStrings = null;
+
+    /**
+     * The localized uppercase version of ALT
+     */
+    private static String localizedAlt;
+
+    /**
+     * The localized uppercase version of COMMAND
+     */
+    private static String localizedCommand;
+
+    /**
+     * The localized uppercase version of CTRL
+     */
+    private static String localizedCtrl;
+
+    /**
+     * Table of key codes (key type: <code>String</code>, value type:
+     * <code>Integer</code>); <code>null</code> if not yet initialized. The
+     * key is the localalized name of the key as it appears in menus.
+     *
+     * @see #findLocalizedKeyCode
+     */
+    private static Map!(String,Object) localizedKeyCodes = null;
+
+    /**
+     * The localized uppercase version of SHIFT
+     */
+    private static String localizedShift;
+
+    /**
+     * The constant to use if there is no mnemonic for this location.
+     */
+    public static const char MNEMONIC_NONE = 0;
+
+    /**
+     * Converts an accelerator key code to a string representation.
+     *
+     * @param keyCode
+     *            the key code to be translated
+     * @return a string representation of the key code
+     */
+    public static final String convertAccelerator(int keyCode) {
+        String modifier = getModifierString(keyCode);
+        String fullKey;
+        if (modifier.equals("")) { //$NON-NLS-1$
+            fullKey = findKeyString(keyCode);
+        } else {
+            fullKey = modifier ~ "+" ~ findKeyString(keyCode); //$NON-NLS-1$
+        }
+        return fullKey;
+    }
+
+    /**
+     * Parses the given accelerator text, and converts it to an accelerator key
+     * code.
+     *
+     * @param acceleratorText
+     *            the accelerator text
+     * @return the DWT key code, or 0 if there is no accelerator
+     */
+    public static final int convertAccelerator(String acceleratorText) {
+        int accelerator = 0;
+        auto tokens = tango.text.Util.delimit( acceleratorText, "+" );
+
+        int keyCode = -1;
+        foreach( idx, token; tokens ){
+//         bool hasMoreTokens = stok.hasMoreTokens();
+//         while (hasMoreTokens) {
+//             String token = stok.nextToken();
+//             bool hasMoreTokens = idx+1 < tokens.length;//stok.hasMoreTokens();
+            // Every token except the last must be one of the modifiers
+            // Ctrl, Shift, Alt, or Command
+            if (idx+1 < tokens.length) {
+                int modifier = findModifier(token);
+                if (modifier !is 0) {
+                    accelerator |= modifier;
+                } else { // Leave if there are none
+                    return 0;
+                }
+            } else {
+                keyCode = findKeyCode(token);
+            }
+        }
+        if (keyCode !is -1) {
+            accelerator |= keyCode;
+        }
+        return accelerator;
+    }
+
+    /**
+     * Parses the given accelerator text, and converts it to an accelerator key
+     * code.
+     *
+     * Support for localized modifiers is for backwards compatibility with 1.0.
+     * Use setAccelerator(int) to set accelerators programatically or the
+     * <code>accelerator</code> tag in action definitions in plugin.xml.
+     *
+     * @param acceleratorText
+     *            the accelerator text localized to the current locale
+     * @return the DWT key code, or 0 if there is no accelerator
+     */
+    static final int convertLocalizedAccelerator(String acceleratorText) {
+        int accelerator = 0;
+        auto tokens = tango.text.Util.delimit( acceleratorText, "+" );
+//         StringTokenizer stok = new StringTokenizer(acceleratorText, "+"); //$NON-NLS-1$
+
+        int keyCode = -1;
+
+        foreach( idx, token; tokens ){
+//         bool hasMoreTokens = stok.hasMoreTokens();
+//         while (hasMoreTokens) {
+//             String token = stok.nextToken();
+//             hasMoreTokens = stok.hasMoreTokens();
+            // Every token except the last must be one of the modifiers
+            // Ctrl, Shift, Alt, or Command
+            if (idx+1 < tokens.length) {
+                int modifier = findLocalizedModifier(token);
+                if (modifier !is 0) {
+                    accelerator |= modifier;
+                } else { // Leave if there are none
+                    return 0;
+                }
+            } else {
+                keyCode = findLocalizedKeyCode(token);
+            }
+        }
+        if (keyCode !is -1) {
+            accelerator |= keyCode;
+        }
+        return accelerator;
+    }
+
+    /**
+     * Extracts the accelerator text from the given text. Returns
+     * <code>null</code> if there is no accelerator text, and the empty string
+     * if there is no text after the accelerator delimeter (tab or '@').
+     *
+     * @param text
+     *            the text for the action; may be <code>null</code>.
+     * @return the accelerator text, or <code>null</code>
+     */
+    public static final String extractAcceleratorText(String text) {
+        if (text is null) {
+            return null;
+        }
+
+        int index = text.lastIndexOf('\t');
+        if (index is -1) {
+            index = text.lastIndexOf('@');
+        }
+        if (index >= 0) {
+            return text.substring(index + 1);
+        }
+        return null;
+    }
+
+    /**
+     * Extracts the mnemonic text from the given string.
+     *
+     * @param text
+     *            The text from which the mnemonic should be extracted; may be
+     *            <code>null</code>
+     * @return The text of the mnemonic; will be {@link #MNEMONIC_NONE} if there
+     *         is no mnemonic;
+     */
+    public static final char extractMnemonic(String text) {
+        if (text is null) {
+            return MNEMONIC_NONE;
+        }
+
+        int index = text.indexOf('&');
+        if (index is -1) {
+            return MNEMONIC_NONE;
+        }
+
+        int textLength = text.length;
+
+        // Ignore '&' at the end of the string.
+        if (index is textLength - 1) {
+            return MNEMONIC_NONE;
+        }
+
+        // Ignore two consecutive ampersands.
+        while (text.charAt(index + 1) is '&') {
+            index = text.indexOf('&', ++index);
+            if (index is textLength - 1) {
+                return MNEMONIC_NONE;
+            }
+        }
+
+        return text.charAt(index + 1);
+    }
+
+    /**
+     * Maps a standard keyboard key name to an DWT key code. Key names are
+     * converted to upper case before comparison. If the key name is a single
+     * letter, for example "S", its character code is returned.
+     * <p>
+     * The following key names are known (case is ignored):
+     * <ul>
+     * <li><code>"BACKSPACE"</code></li>
+     * <li><code>"TAB"</code></li>
+     * <li><code>"RETURN"</code></li>
+     * <li><code>"ENTER"</code></li>
+     * <li><code>"ESC"</code></li>
+     * <li><code>"ESCAPE"</code></li>
+     * <li><code>"DELETE"</code></li>
+     * <li><code>"SPACE"</code></li>
+     * <li><code>"ARROW_UP"</code>, <code>"ARROW_DOWN"</code>,
+     * <code>"ARROW_LEFT"</code>, and <code>"ARROW_RIGHT"</code></li>
+     * <li><code>"PAGE_UP"</code> and <code>"PAGE_DOWN"</code></li>
+     * <li><code>"HOME"</code></li>
+     * <li><code>"END"</code></li>
+     * <li><code>"INSERT"</code></li>
+     * <li><code>"F1"</code>, <code>"F2"</code> through <code>"F12"</code></li>
+     * </ul>
+     * </p>
+     *
+     * @param token
+     *            the key name
+     * @return the DWT key code, <code>-1</code> if no match was found
+     * @see DWT
+     */
+    public static final int findKeyCode(String token) {
+        if (keyCodes is null) {
+            initKeyCodes();
+        }
+        token = token.toUpperCase();
+        Integer i = cast(Integer) keyCodes.get(token);
+        if (i !is null) {
+            return i.intValue();
+        }
+        if (token.length is 1) {
+            return token.charAt(0);
+        }
+        return -1;
+    }
+
+    /**
+     * Maps an DWT key code to a standard keyboard key name. The key code is
+     * stripped of modifiers (DWT.CTRL, DWT.ALT, DWT.SHIFT, and DWT.COMMAND). If
+     * the key code is not an DWT code (for example if it a key code for the key
+     * 'S'), a string containing a character representation of the key code is
+     * returned.
+     *
+     * @param keyCode
+     *            the key code to be translated
+     * @return the string representation of the key code
+     * @see DWT
+     * @since 2.0
+     */
+    public static final String findKeyString( int keyCode) {
+        if (keyStrings is null) {
+            initKeyStrings();
+        }
+        int i = keyCode & ~(DWT.CTRL | DWT.ALT | DWT.SHIFT | DWT.COMMAND);
+        Integer integer = new Integer(i);
+        String result = keyStrings.get(integer);
+        if (result !is null) {
+            return result;
+        }
+        result = dcharToString( cast(dchar) i );
+        return result;
+    }
+
+    /**
+     * Find the supplied code for a localized key. As #findKeyCode but localized
+     * to the current locale.
+     *
+     * Support for localized modifiers is for backwards compatibility with 1.0.
+     * Use setAccelerator(int) to set accelerators programatically or the
+     * <code>accelerator</code> tag in action definitions in plugin.xml.
+     *
+     * @param token
+     *            the localized key name
+     * @return the DWT key code, <code>-1</code> if no match was found
+     * @see #findKeyCode
+     */
+    private static final int findLocalizedKeyCode(String token) {
+        if (localizedKeyCodes is null) {
+            initLocalizedKeyCodes();
+        }
+        token = token.toUpperCase();
+        Integer i = cast(Integer) localizedKeyCodes.get(token);
+        if (i !is null) {
+            return i.intValue();
+        }
+        if (token.length is 1) {
+            return token.charAt(0);
+        }
+        return -1;
+    }
+
+    /**
+     * Maps the localized modifier names to a code in the same manner as
+     * #findModifier.
+     *
+     * Support for localized modifiers is for backwards compatibility with 1.0.
+     * Use setAccelerator(int) to set accelerators programatically or the
+     * <code>accelerator</code> tag in action definitions in plugin.xml.
+     *
+     * @see #findModifier
+     */
+    private static final int findLocalizedModifier(String token) {
+        if (localizedCtrl is null) {
+            initLocalizedModifiers();
+        }
+
+        token = token.toUpperCase();
+        if (token.equals(localizedCtrl)) {
+            return DWT.CTRL;
+        }
+        if (token.equals(localizedShift)) {
+            return DWT.SHIFT;
+        }
+        if (token.equals(localizedAlt)) {
+            return DWT.ALT;
+        }
+        if (token.equals(localizedCommand)) {
+            return DWT.COMMAND;
+        }
+        return 0;
+    }
+
+    /**
+     * Maps standard keyboard modifier key names to the corresponding DWT
+     * modifier bit. The following modifier key names are recognized (case is
+     * ignored): <code>"CTRL"</code>, <code>"SHIFT"</code>,
+     * <code>"ALT"</code>, and <code>"COMMAND"</code>. The given modifier
+     * key name is converted to upper case before comparison.
+     *
+     * @param token
+     *            the modifier key name
+     * @return the DWT modifier bit, or <code>0</code> if no match was found
+     * @see DWT
+     */
+    public static final int findModifier(String token) {
+        token = token.toUpperCase();
+        if (token.equals("CTRL")) { //$NON-NLS-1$
+            return DWT.CTRL;
+        }
+        if (token.equals("SHIFT")) { //$NON-NLS-1$
+            return DWT.SHIFT;
+        }
+        if (token.equals("ALT")) { //$NON-NLS-1$
+            return DWT.ALT;
+        }
+        if (token.equals("COMMAND")) { //$NON-NLS-1$
+            return DWT.COMMAND;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns a string representation of an DWT modifier bit (DWT.CTRL,
+     * DWT.ALT, DWT.SHIFT, and DWT.COMMAND). Returns <code>null</code> if the
+     * key code is not an DWT modifier bit.
+     *
+     * @param keyCode
+     *            the DWT modifier bit to be translated
+     * @return the string representation of the DWT modifier bit, or
+     *         <code>null</code> if the key code was not an DWT modifier bit
+     * @see DWT
+     */
+    public static final String findModifierString(int keyCode) {
+        if (keyCode is DWT.CTRL) {
+            return JFaceResources.getString("Ctrl"); //$NON-NLS-1$
+        }
+        if (keyCode is DWT.ALT) {
+            return JFaceResources.getString("Alt"); //$NON-NLS-1$
+        }
+        if (keyCode is DWT.SHIFT) {
+            return JFaceResources.getString("Shift"); //$NON-NLS-1$
+        }
+        if (keyCode is DWT.COMMAND) {
+            return JFaceResources.getString("Command"); //$NON-NLS-1$
+        }
+        return null;
+    }
+
+    /**
+     * Returns the string representation of the modifiers (Ctrl, Alt, Shift,
+     * Command) of the key event.
+     *
+     * @param keyCode
+     *            The key code for which the modifier string is desired.
+     * @return The string representation of the key code; never
+     *         <code>null</code>.
+     */
+    private static String getModifierString(int keyCode) {
+        String modString = ""; //$NON-NLS-1$
+
+        if ((keyCode & DWT.CTRL) !is 0) {
+            modString = findModifierString(keyCode & DWT.CTRL);
+        }
+
+        if ((keyCode & DWT.ALT) !is 0) {
+            if (modString.equals("")) { //$NON-NLS-1$
+                modString = findModifierString(keyCode & DWT.ALT);
+            } else {
+                modString = modString
+                        ~ "+" ~ findModifierString(keyCode & DWT.ALT); //$NON-NLS-1$
+            }
+        }
+
+        if ((keyCode & DWT.SHIFT) !is 0) {
+            if (modString.equals("")) { //$NON-NLS-1$
+                modString = findModifierString(keyCode & DWT.SHIFT);
+            } else {
+                modString = modString
+                        ~ "+" ~ findModifierString(keyCode & DWT.SHIFT); //$NON-NLS-1$
+            }
+        }
+
+        if ((keyCode & DWT.COMMAND) !is 0) {
+            if (modString.equals("")) { //$NON-NLS-1$
+                modString = findModifierString(keyCode & DWT.COMMAND);
+            } else {
+                modString = modString
+                        ~ "+" ~ findModifierString(keyCode & DWT.COMMAND); //$NON-NLS-1$
+            }
+        }
+
+        return modString;
+    }
+
+    /**
+     * Initializes the internal key code table.
+     */
+    private static final void initKeyCodes() {
+        keyCodes = new HashMap!(String,Object);
+
+        keyCodes.add("BACKSPACE", new Integer(8)); //$NON-NLS-1$
+        keyCodes.add("TAB", new Integer(9)); //$NON-NLS-1$
+        keyCodes.add("RETURN", new Integer(13)); //$NON-NLS-1$
+        keyCodes.add("ENTER", new Integer(13)); //$NON-NLS-1$
+        keyCodes.add("ESCAPE", new Integer(27)); //$NON-NLS-1$
+        keyCodes.add("ESC", new Integer(27)); //$NON-NLS-1$
+        keyCodes.add("DELETE", new Integer(127)); //$NON-NLS-1$
+
+        keyCodes.add("SPACE", new Integer(' ')); //$NON-NLS-1$
+        keyCodes.add("ARROW_UP", new Integer(DWT.ARROW_UP)); //$NON-NLS-1$
+        keyCodes.add("ARROW_DOWN", new Integer(DWT.ARROW_DOWN)); //$NON-NLS-1$
+        keyCodes.add("ARROW_LEFT", new Integer(DWT.ARROW_LEFT)); //$NON-NLS-1$
+        keyCodes.add("ARROW_RIGHT", new Integer(DWT.ARROW_RIGHT)); //$NON-NLS-1$
+        keyCodes.add("PAGE_UP", new Integer(DWT.PAGE_UP)); //$NON-NLS-1$
+        keyCodes.add("PAGE_DOWN", new Integer(DWT.PAGE_DOWN)); //$NON-NLS-1$
+        keyCodes.add("HOME", new Integer(DWT.HOME)); //$NON-NLS-1$
+        keyCodes.add("END", new Integer(DWT.END)); //$NON-NLS-1$
+        keyCodes.add("INSERT", new Integer(DWT.INSERT)); //$NON-NLS-1$
+        keyCodes.add("F1", new Integer(DWT.F1)); //$NON-NLS-1$
+        keyCodes.add("F2", new Integer(DWT.F2)); //$NON-NLS-1$
+        keyCodes.add("F3", new Integer(DWT.F3)); //$NON-NLS-1$
+        keyCodes.add("F4", new Integer(DWT.F4)); //$NON-NLS-1$
+        keyCodes.add("F5", new Integer(DWT.F5)); //$NON-NLS-1$
+        keyCodes.add("F6", new Integer(DWT.F6)); //$NON-NLS-1$
+        keyCodes.add("F7", new Integer(DWT.F7)); //$NON-NLS-1$
+        keyCodes.add("F8", new Integer(DWT.F8)); //$NON-NLS-1$
+        keyCodes.add("F9", new Integer(DWT.F9)); //$NON-NLS-1$
+        keyCodes.add("F10", new Integer(DWT.F10)); //$NON-NLS-1$
+        keyCodes.add("F11", new Integer(DWT.F11)); //$NON-NLS-1$
+        keyCodes.add("F12", new Integer(DWT.F12)); //$NON-NLS-1$
+    }
+
+    /**
+     * Initializes the internal key string table.
+     */
+    private static void initKeyStrings() {
+        keyStrings = new HashMap!(Object,String);
+
+        keyStrings.add(new Integer(8), JFaceResources.getString("Backspace")); //$NON-NLS-1$
+        keyStrings.add(new Integer(9), JFaceResources.getString("Tab")); //$NON-NLS-1$
+        keyStrings.add(new Integer(13), JFaceResources.getString("Return")); //$NON-NLS-1$
+        keyStrings.add(new Integer(13), JFaceResources.getString("Enter")); //$NON-NLS-1$
+        keyStrings.add(new Integer(27), JFaceResources.getString("Escape")); //$NON-NLS-1$
+        keyStrings.add(new Integer(27), JFaceResources.getString("Esc")); //$NON-NLS-1$
+        keyStrings.add(new Integer(127), JFaceResources.getString("Delete")); //$NON-NLS-1$
+
+        keyStrings.add(new Integer(' '), JFaceResources.getString("Space")); //$NON-NLS-1$
+
+        keyStrings.add(new Integer(DWT.ARROW_UP), JFaceResources
+                .getString("Arrow_Up")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.ARROW_DOWN), JFaceResources
+                .getString("Arrow_Down")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.ARROW_LEFT), JFaceResources
+                .getString("Arrow_Left")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.ARROW_RIGHT), JFaceResources
+                .getString("Arrow_Right")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.PAGE_UP), JFaceResources
+                .getString("Page_Up")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.PAGE_DOWN), JFaceResources
+                .getString("Page_Down")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.HOME), JFaceResources.getString("Home")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.END), JFaceResources.getString("End")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.INSERT), JFaceResources
+                .getString("Insert")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F1), JFaceResources.getString("F1")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F2), JFaceResources.getString("F2")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F3), JFaceResources.getString("F3")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F4), JFaceResources.getString("F4")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F5), JFaceResources.getString("F5")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F6), JFaceResources.getString("F6")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F7), JFaceResources.getString("F7")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F8), JFaceResources.getString("F8")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F9), JFaceResources.getString("F9")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F10), JFaceResources.getString("F10")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F11), JFaceResources.getString("F11")); //$NON-NLS-1$
+        keyStrings.add(new Integer(DWT.F12), JFaceResources.getString("F12")); //$NON-NLS-1$
+    }
+
+    /**
+     * Initializes the localized internal key code table.
+     */
+    private static void initLocalizedKeyCodes() {
+        localizedKeyCodes = new HashMap!(String,Object);
+
+        localizedKeyCodes.add(JFaceResources
+                .getString("Backspace").toUpperCase(), new Integer(8)); //$NON-NLS-1$
+        localizedKeyCodes.add(
+                JFaceResources.getString("Tab").toUpperCase(), new Integer(9)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Return").toUpperCase(), new Integer(13)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Enter").toUpperCase(), new Integer(13)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Escape").toUpperCase(), new Integer(27)); //$NON-NLS-1$
+        localizedKeyCodes.add(
+                JFaceResources.getString("Esc").toUpperCase(), new Integer(27)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Delete").toUpperCase(), new Integer(127)); //$NON-NLS-1$
+
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Space").toUpperCase(), new Integer(' ')); //$NON-NLS-1$
+
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Arrow_Up").toUpperCase(), new Integer(DWT.ARROW_UP)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Arrow_Down").toUpperCase(), new Integer(DWT.ARROW_DOWN)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Arrow_Left").toUpperCase(), new Integer(DWT.ARROW_LEFT)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Arrow_Right").toUpperCase(), new Integer(DWT.ARROW_RIGHT)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Page_Up").toUpperCase(), new Integer(DWT.PAGE_UP)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Page_Down").toUpperCase(), new Integer(DWT.PAGE_DOWN)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Home").toUpperCase(), new Integer(DWT.HOME)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("End").toUpperCase(), new Integer(DWT.END)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("Insert").toUpperCase(), new Integer(DWT.INSERT)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F1").toUpperCase(), new Integer(DWT.F1)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F2").toUpperCase(), new Integer(DWT.F2)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F3").toUpperCase(), new Integer(DWT.F3)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F4").toUpperCase(), new Integer(DWT.F4)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F5").toUpperCase(), new Integer(DWT.F5)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F6").toUpperCase(), new Integer(DWT.F6)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F7").toUpperCase(), new Integer(DWT.F7)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F8").toUpperCase(), new Integer(DWT.F8)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F9").toUpperCase(), new Integer(DWT.F9)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F10").toUpperCase(), new Integer(DWT.F10)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F11").toUpperCase(), new Integer(DWT.F11)); //$NON-NLS-1$
+        localizedKeyCodes
+                .add(
+                        JFaceResources.getString("F12").toUpperCase(), new Integer(DWT.F12)); //$NON-NLS-1$
+    }
+
+    /**
+     * Initialize the list of localized modifiers
+     */
+    private static void initLocalizedModifiers() {
+        localizedCtrl = JFaceResources.getString("Ctrl").toUpperCase(); //$NON-NLS-1$
+        localizedShift = JFaceResources.getString("Shift").toUpperCase(); //$NON-NLS-1$
+        localizedAlt = JFaceResources.getString("Alt").toUpperCase(); //$NON-NLS-1$
+        localizedCommand = JFaceResources.getString("Command").toUpperCase(); //$NON-NLS-1$
+    }
+
+    /**
+     * Convenience method for removing any optional accelerator text from the
+     * given string. The accelerator text appears at the end of the text, and is
+     * separated from the main part by a single tab character <code>'\t'</code>.
+     *
+     * @param text
+     *            the text
+     * @return the text sans accelerator
+     */
+    public static final String removeAcceleratorText(String text) {
+        int index = text.lastIndexOf('\t');
+        if (index is -1) {
+            index = text.lastIndexOf('@');
+        }
+        if (index >= 0) {
+            return text.substring(0, index);
+        }
+        return text;
+    }
+
+    /**
+     * Convenience method for removing any mnemonics from the given string. For
+     * example, <code>removeMnemonics("&Open")</code> will return
+     * <code>"Open"</code>.
+     *
+     * @param text
+     *            the text
+     * @return the text sans mnemonics
+     */
+    public static final String removeMnemonics(String text) {
+        int index = text.indexOf('&');
+        if (index is -1) {
+            return text;
+        }
+        int len = text.length;
+        StringBuffer sb = new StringBuffer(len);
+        int lastIndex = 0;
+        while (index !is -1) {
+            // ignore & at the end
+            if (index is len - 1) {
+                break;
+            }
+            // handle the && case
+            if (text.charAt(index + 1) is '&') {
+                ++index;
+            }
+
+            // DBCS languages use "(&X)" format
+            if (index > 0 && text.charAt(index - 1) is '('
+                    && text.length >= index + 3
+                    && text.charAt(index + 2) is ')') {
+                sb.append(text.substring(lastIndex, index - 1));
+                index += 3;
+            } else {
+                sb.append(text.substring(lastIndex, index));
+                // skip the &
+                ++index;
+            }
+
+            lastIndex = index;
+            index = text.indexOf('&', index);
+        }
+        if (lastIndex < len) {
+            sb.append(text.substring(lastIndex, len));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * This class cannot be instantiated.
+     */
+    private this() {
+        // Does nothing
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/MenuManager.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,830 @@
+/*******************************************************************************
+ * 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.jface.action.MenuManager;
+
+import dwtx.jface.action.ContributionManager;
+import dwtx.jface.action.IMenuManager;
+import dwtx.jface.action.IContributionManagerOverrides;
+import dwtx.jface.action.IContributionManager;
+import dwtx.jface.action.IMenuListener;
+import dwtx.jface.action.IMenuListener2;
+import dwtx.jface.action.IContributionItem;
+import dwtx.jface.action.SubContributionItem;
+import dwtx.jface.action.IAction;
+import dwtx.jface.action.ExternalActionManager;
+
+import tango.util.collection.ArraySeq;
+import tango.util.collection.model.Seq;
+
+import dwt.DWT;
+import dwt.events.MenuAdapter;
+import dwt.events.MenuEvent;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.CoolBar;
+import dwt.widgets.Decorations;
+import dwt.widgets.Menu;
+import dwt.widgets.MenuItem;
+import dwt.widgets.Shell;
+import dwt.widgets.ToolBar;
+import dwtx.core.runtime.ListenerList;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A menu manager is a contribution manager which realizes itself and its items
+ * in a menu control; either as a menu bar, a sub-menu, or a context menu.
+ * <p>
+ * This class may be instantiated; it may also be subclassed.
+ * </p>
+ */
+public class MenuManager : ContributionManager, IMenuManager {
+
+    public bool isDirty(){
+        return super.isDirty();
+    }
+
+    /**
+     * The menu id.
+     */
+    private String id;
+
+    /**
+     * List of registered menu listeners (element type: <code>IMenuListener</code>).
+     */
+    private ListenerList listeners;
+
+    /**
+     * The menu control; <code>null</code> before
+     * creation and after disposal.
+     */
+    private Menu menu = null;
+
+    /**
+     * The menu item widget; <code>null</code> before
+     * creation and after disposal. This field is used
+     * when this menu manager is a sub-menu.
+     */
+    private MenuItem menuItem;
+
+    /**
+     * The text for a sub-menu.
+     */
+    private String menuText;
+
+    /**
+     * The overrides for items of this manager
+     */
+    private IContributionManagerOverrides overrides;
+
+    /**
+     * The parent contribution manager.
+     */
+    private IContributionManager parent;
+
+    /**
+     * Indicates whether <code>removeAll</code> should be
+     * called just before the menu is displayed.
+     */
+    private bool removeAllWhenShown = false;
+
+    /**
+     * Indicates this item is visible in its manager; <code>true</code>
+     * by default.
+     * @since 3.3
+     */
+    protected bool visible = true;
+
+    /**
+     * Creates a menu manager.  The text and id are <code>null</code>.
+     * Typically used for creating a context menu, where it doesn't need to be referred to by id.
+     */
+    public this() {
+        this(null, null);
+    }
+
+    /**
+     * Creates a menu manager with the given text. The id of the menu
+     * is <code>null</code>.
+     * Typically used for creating a sub-menu, where it doesn't need to be referred to by id.
+     *
+     * @param text the text for the menu, or <code>null</code> if none
+     */
+    public this(String text) {
+        this(text, null);
+    }
+
+    /**
+     * Creates a menu manager with the given text and id.
+     * Typically used for creating a sub-menu, where it needs to be referred to by id.
+     *
+     * @param text the text for the menu, or <code>null</code> if none
+     * @param id the menu id, or <code>null</code> if it is to have no id
+     */
+    public this(String text, String id) {
+        listeners = new ListenerList();
+        this.menuText = text;
+        this.id = id;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IMenuManager#addMenuListener(dwtx.jface.action.IMenuListener)
+     */
+    public void addMenuListener(IMenuListener listener) {
+        listeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Creates and returns an DWT context menu control for this menu,
+     * and installs all registered contributions.
+     * Does not create a new control if one already exists.
+     * <p>
+     * Note that the menu is not expected to be dynamic.
+     * </p>
+     *
+     * @param parent the parent control
+     * @return the menu control
+     */
+    public Menu createContextMenu(Control parent) {
+        if (!menuExist()) {
+            menu = new Menu(parent);
+            initializeMenu();
+        }
+        return menu;
+    }
+
+    /**
+     * Creates and returns an DWT menu bar control for this menu,
+     * for use in the given <code>Decorations</code>, and installs all registered
+     * contributions. Does not create a new control if one already exists.
+     *
+     * @param parent the parent decorations
+     * @return the menu control
+     * @since 2.1
+     */
+    public Menu createMenuBar(Decorations parent) {
+        if (!menuExist()) {
+            menu = new Menu(parent, DWT.BAR);
+            update(false);
+        }
+        return menu;
+    }
+
+    /**
+     * Creates and returns an DWT menu bar control for this menu, for use in the
+     * given <code>Shell</code>, and installs all registered contributions. Does not
+     * create a new control if one already exists. This implementation simply calls
+     * the <code>createMenuBar(Decorations)</code> method
+     *
+     * @param parent the parent decorations
+     * @return the menu control
+     * @deprecated use <code>createMenuBar(Decorations)</code> instead.
+     */
+    public Menu createMenuBar(Shell parent) {
+        return createMenuBar(cast(Decorations) parent);
+    }
+
+    /**
+     * Disposes of this menu manager and frees all allocated DWT resources.
+     * Notifies all contribution items of the dispose. Note that this method does
+     * not clean up references between this menu manager and its associated
+     * contribution items. Use <code>removeAll</code> for that purpose.
+     */
+    public void dispose() {
+        if (menuExist()) {
+            menu.dispose();
+        }
+        menu = null;
+
+        if (menuItem !is null) {
+            menuItem.dispose();
+            menuItem = null;
+        }
+
+        IContributionItem[] items = getItems();
+        for (int i = 0; i < items.length; i++) {
+            items[i].dispose();
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.Composite)
+     */
+    public void fill(Composite parent) {
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.CoolBar, int)
+     */
+    public void fill(CoolBar parent, int index) {
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.Menu, int)
+     */
+    public void fill(Menu parent, int index) {
+        if (menuItem is null || menuItem.isDisposed()) {
+            if (index >= 0) {
+                menuItem = new MenuItem(parent, DWT.CASCADE, index);
+            } else {
+                menuItem = new MenuItem(parent, DWT.CASCADE);
+            }
+
+            menuItem.setText(getMenuText());
+
+            if (!menuExist()) {
+                menu = new Menu(parent);
+            }
+
+            menuItem.setMenu(menu);
+
+            initializeMenu();
+
+            // populate the submenu, in order to enable accelerators
+            // and to set enabled state on the menuItem properly
+            update(true);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.ToolBar, int)
+     */
+    public void fill(ToolBar parent, int index) {
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IMenuManager#findMenuUsingPath(java.lang.String)
+     */
+    public IMenuManager findMenuUsingPath(String path) {
+        IContributionItem item = findUsingPath(path);
+        if (auto mm = cast(IMenuManager)item ) {
+            return mm;
+        }
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IMenuManager#findUsingPath(java.lang.String)
+     */
+    public IContributionItem findUsingPath(String path) {
+        String id = path;
+        String rest = null;
+        int separator = dwt.dwthelper.utils.indexOf( path, '/');
+        if (separator !is -1) {
+            id = path.substring(0, separator);
+            rest = path.substring(separator + 1);
+        } else {
+            return super.find(path);
+        }
+
+        IContributionItem item = super.find(id);
+        if (auto mm = cast(IMenuManager)item ) {
+            return mm.findUsingPath(rest);
+        }
+        return null;
+    }
+
+    /**
+     * Notifies any menu listeners that a menu is about to show.
+     * Only listeners registered at the time this method is called are notified.
+     *
+     * @param manager the menu manager
+     *
+     * @see IMenuListener#menuAboutToShow
+     */
+    private void fireAboutToShow(IMenuManager manager) {
+        Object[] listeners = this.listeners.getListeners();
+        for (int i = 0; i < listeners.length; ++i) {
+            (cast(IMenuListener) listeners[i]).menuAboutToShow(manager);
+        }
+    }
+
+    /**
+     * Notifies any menu listeners that a menu is about to hide.
+     * Only listeners registered at the time this method is called are notified.
+     *
+     * @param manager the menu manager
+     *
+     */
+    private void fireAboutToHide(IMenuManager manager) {
+        final Object[] listeners = this.listeners.getListeners();
+        for (int i = 0; i < listeners.length; ++i) {
+            final Object listener = listeners[i];
+            if (auto listener2 = cast(IMenuListener2)listener) {
+                listener2.menuAboutToHide(manager);
+            }
+        }
+    }
+
+    /**
+     * Returns the menu id. The menu id is used when creating a contribution
+     * item for adding this menu as a sub menu of another.
+     *
+     * @return the menu id
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Returns the DWT menu control for this menu manager.
+     *
+     * @return the menu control
+     */
+    public Menu getMenu() {
+        return menu;
+    }
+
+    /**
+     * Returns the text shown in the menu.
+     *
+     * @return the menu text
+     */
+    public String getMenuText() {
+        return menuText;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionManager#getOverrides()
+     */
+    public IContributionManagerOverrides getOverrides() {
+        if (overrides is null) {
+            if (parent is null) {
+                overrides = new class IContributionManagerOverrides {
+                    public ValueWrapperInt getAccelerator(IContributionItem item) {
+                        return null;
+                    }
+
+                    public String getAcceleratorText(IContributionItem item) {
+                        return null;
+                    }
+
+                    public ValueWrapperBool getEnabled(IContributionItem item) {
+                        return null;
+                    }
+
+                    public String getText(IContributionItem item) {
+                        return null;
+                    }
+                };
+            } else {
+                overrides = parent.getOverrides();
+            }
+            super.setOverrides(overrides);
+        }
+        return overrides;
+    }
+
+    /**
+     * Returns the parent contribution manager of this manger.
+     *
+     * @return the parent contribution manager
+     * @since 2.0
+     */
+    public IContributionManager getParent() {
+        return parent;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IMenuManager#getRemoveAllWhenShown()
+     */
+    public bool getRemoveAllWhenShown() {
+        return removeAllWhenShown;
+    }
+
+    /**
+     * Notifies all listeners that this menu is about to appear.
+     */
+    private void handleAboutToShow() {
+        if (removeAllWhenShown) {
+            removeAll();
+        }
+        fireAboutToShow(this);
+        update(false, true);
+    }
+
+    /**
+     * Notifies all listeners that this menu is about to disappear.
+     */
+    private void handleAboutToHide() {
+        fireAboutToHide(this);
+    }
+
+    /**
+     * Initializes the menu control.
+     */
+    private void initializeMenu() {
+        menu.addMenuListener(new class MenuAdapter {
+            public void menuHidden(MenuEvent e) {
+                //          ApplicationWindow.resetDescription(e.widget);
+                handleAboutToHide();
+            }
+
+            public void menuShown(MenuEvent e) {
+                handleAboutToShow();
+            }
+        });
+        // Don't do an update(true) here, in case menu is never opened.
+        // Always do it lazily in handleAboutToShow().
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#isDynamic()
+     */
+    public bool isDynamic() {
+        return false;
+    }
+
+    /**
+     * Returns whether this menu should be enabled or not.
+     * Used to enable the menu item containing this menu when it is realized as a sub-menu.
+     * <p>
+     * The default implementation of this framework method
+     * returns <code>true</code>. Subclasses may reimplement.
+     * </p>
+     *
+     * @return <code>true</code> if enabled, and
+     *   <code>false</code> if disabled
+     */
+    public bool isEnabled() {
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#isGroupMarker()
+     */
+    public bool isGroupMarker() {
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#isSeparator()
+     */
+    public bool isSeparator() {
+        return false;
+    }
+
+    /**
+     * Check if the contribution is item is a subsitute for ourselves
+     *
+     * @param item the contribution item
+     * @return <code>true</code> if give item is a substitution for ourselves
+     * @deprecated this method is no longer a part of the
+     *   {@link dwtx.jface.action.IContributionItem} API.
+     */
+    public bool isSubstituteFor(IContributionItem item) {
+        return this.opEquals(cast(Object)item) !is 0;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#isVisible()
+     */
+    public bool isVisible() {
+        if (!visible) {
+            return false; // short circut calculations in this case
+        }
+
+        // menus arent visible if all of its children are invisible (or only contains visible separators).
+        IContributionItem[] childItems = getItems();
+        bool visibleChildren = false;
+        for (int j = 0; j < childItems.length; j++) {
+            if (childItems[j].isVisible() && !childItems[j].isSeparator()) {
+                visibleChildren = true;
+                break;
+            }
+        }
+
+        return visibleChildren;
+    }
+
+
+    /**
+     * The <code>MenuManager</code> implementation of this <code>ContributionManager</code> method
+     * also propagates the dirty flag up the parent chain.
+     *
+     * @since 3.1
+     */
+    public void markDirty() {
+        super.markDirty();
+        // Can't optimize by short-circuiting when the first dirty manager is encountered,
+        // since non-visible children are not even processed.
+        // That is, it's possible to have a dirty sub-menu under a non-dirty parent menu
+        // even after the parent menu has been updated.
+        // If items are added/removed in the sub-menu, we still need to propagate the dirty flag up,
+        // even if the sub-menu is already dirty, since the result of isVisible() may change
+        // due to the added/removed items.
+        IContributionManager parent = getParent();
+        if (parent !is null) {
+            parent.markDirty();
+        }
+    }
+
+    /**
+     * Returns whether the menu control is created
+     * and not disposed.
+     *
+     * @return <code>true</code> if the control is created
+     *  and not disposed, <code>false</code> otherwise
+     */
+    private bool menuExist() {
+        return menu !is null && !menu.isDisposed();
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IMenuManager#removeMenuListener(dwtx.jface.action.IMenuListener)
+     */
+    public void removeMenuListener(IMenuListener listener) {
+        listeners.remove(cast(Object)listener);
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#saveWidgetState()
+     */
+    public void saveWidgetState() {
+    }
+
+    /**
+     * Sets the overrides for this contribution manager
+     *
+     * @param newOverrides the overrides for the items of this manager
+     * @since 2.0
+     */
+    public void setOverrides(IContributionManagerOverrides newOverrides) {
+        overrides = newOverrides;
+        super.setOverrides(overrides);
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#setParent(dwtx.jface.action.IContributionManager)
+     */
+    public void setParent(IContributionManager manager) {
+        parent = manager;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IMenuManager#setRemoveAllWhenShown(bool)
+     */
+    public void setRemoveAllWhenShown(bool removeAll) {
+        this.removeAllWhenShown = removeAll;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#setVisible(bool)
+     */
+    public void setVisible(bool visible) {
+        this.visible = visible;
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#update()
+     */
+    public void update() {
+        updateMenuItem();
+    }
+
+    /**
+     * The <code>MenuManager</code> implementation of this <code>IContributionManager</code>
+     * updates this menu, but not any of its submenus.
+     *
+     * @see #updateAll
+     */
+    public void update(bool force) {
+        update(force, false);
+    }
+
+    /**
+     * Incrementally builds the menu from the contribution items.
+     * This method leaves out double separators and separators in the first
+     * or last position.
+     *
+     * @param force <code>true</code> means update even if not dirty,
+     *   and <code>false</code> for normal incremental updating
+     * @param recursive <code>true</code> means recursively update
+     *   all submenus, and <code>false</code> means just this menu
+     */
+    protected void update(bool force, bool recursive) {
+        if (isDirty() || force) {
+            if (menuExist()) {
+                // clean contains all active items without double separators
+                IContributionItem[] items = getItems();
+                auto clean = new ArraySeq!(IContributionItem);
+                clean.capacity(items.length);
+                IContributionItem separator = null;
+                for (int i = 0; i < items.length; ++i) {
+                    IContributionItem ci = items[i];
+                    if (!ci.isVisible()) {
+                        continue;
+                    }
+                    if (ci.isSeparator()) {
+                        // delay creation until necessary
+                        // (handles both adjacent separators, and separator at end)
+                        separator = ci;
+                    } else {
+                        if (separator !is null) {
+                            if (clean.size() > 0) {
+                                clean.append(separator);
+                            }
+                            separator = null;
+                        }
+                        clean.append(ci);
+                    }
+                }
+
+                // remove obsolete (removed or non active)
+                MenuItem[] mi = menu.getItems();
+
+                for (int i = 0; i < mi.length; i++) {
+                    Object data = mi[i].getData();
+
+                    bool clean_contains = false;
+                    foreach( ci; clean ){
+                        if( cast(Object)ci == data ) clean_contains = true;
+                    }
+                    if (data is null || !clean_contains) {
+                        mi[i].dispose();
+                    } else if (cast(IContributionItem)data
+                            && (cast(IContributionItem) data).isDynamic()
+                            && (cast(IContributionItem) data).isDirty()) {
+                        mi[i].dispose();
+                    }
+                }
+
+                // add new
+                mi = menu.getItems();
+                int srcIx = 0;
+                int destIx = 0;
+
+                foreach( src; clean ){
+                    IContributionItem dest;
+
+                    // get corresponding item in DWT widget
+                    if (srcIx < mi.length) {
+                        dest = cast(IContributionItem) mi[srcIx].getData();
+                    } else {
+                        dest = null;
+                    }
+
+                    if (dest !is null && (cast(Object)src).opEquals(cast(Object)dest)) {
+                        srcIx++;
+                        destIx++;
+                    } else if (dest !is null && dest.isSeparator()
+                            && src.isSeparator()) {
+                        mi[srcIx].setData(cast(Object)src);
+                        srcIx++;
+                        destIx++;
+                    } else {
+                        int start = menu.getItemCount();
+                        src.fill(menu, destIx);
+                        int newItems = menu.getItemCount() - start;
+                        for (int i = 0; i < newItems; i++) {
+                            MenuItem item = menu.getItem(destIx++);
+                            item.setData(cast(Object)src);
+                        }
+                    }
+
+                    // May be we can optimize this call. If the menu has just
+                    // been created via the call src.fill(fMenuBar, destIx) then
+                    // the menu has already been updated with update(true)
+                    // (see MenuManager). So if force is true we do it again. But
+                    // we can't set force to false since then information for the
+                    // sub sub menus is lost.
+                    if (recursive) {
+                        IContributionItem item = src;
+                        if ( auto sub = cast(SubContributionItem)item ) {
+                            item = sub.getInnerItem();
+                        }
+                        if (auto mm = cast(IMenuManager)item ) {
+                            mm.updateAll(force);
+                        }
+                    }
+
+                }
+
+                // remove any old menu items not accounted for
+                for (; srcIx < mi.length; srcIx++) {
+                    mi[srcIx].dispose();
+                }
+
+                setDirty(false);
+            }
+        } else {
+            // I am not dirty. Check if I must recursivly walk down the hierarchy.
+            if (recursive) {
+                IContributionItem[] items = getItems();
+                for (int i = 0; i < items.length; ++i) {
+                    IContributionItem ci = items[i];
+                    if ( auto mm = cast(IMenuManager) ci ) {
+                        if (mm.isVisible()) {
+                            mm.updateAll(force);
+                        }
+                    }
+                }
+            }
+        }
+        updateMenuItem();
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#update(java.lang.String)
+     */
+    public void update(String property) {
+        IContributionItem items[] = getItems();
+
+        for (int i = 0; i < items.length; i++) {
+            items[i].update(property);
+        }
+
+        if (menu !is null && !menu.isDisposed() && menu.getParentItem() !is null
+                && IAction.TEXT.equals(property)) {
+            String text = getOverrides().getText(this);
+
+            if (text is null) {
+                text = getMenuText();
+            }
+
+            if (text !is null) {
+                ExternalActionManager.ICallback callback = ExternalActionManager
+                        .getInstance().getCallback();
+
+                if (callback !is null) {
+                    int index = dwt.dwthelper.utils.indexOf( text, '&' );
+
+                    if (index >= 0 && index < text.length - 1) {
+                        char character = CharacterToUpper(text
+                                .charAt(index + 1));
+
+                        if (callback.isAcceleratorInUse(DWT.ALT | character)) {
+                            if (index is 0) {
+                                text = text.substring(1);
+                            } else {
+                                text = text.substring(0, index)
+                                        ~ text.substring(index + 1);
+                            }
+                        }
+                    }
+                }
+
+                menu.getParentItem().setText(text);
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IMenuManager#updateAll(bool)
+     */
+    public void updateAll(bool force) {
+        update(force, true);
+    }
+
+    /**
+     * Updates the menu item for this sub menu.
+     * The menu item is disabled if this sub menu is empty.
+     * Does nothing if this menu is not a submenu.
+     */
+    private void updateMenuItem() {
+        /*
+         * Commented out until proper solution to enablement of
+         * menu item for a sub-menu is found. See bug 30833 for
+         * more details.
+         *
+         if (menuItem !is null && !menuItem.isDisposed() && menuExist()) {
+         IContributionItem items[] = getItems();
+         bool enabled = false;
+         for (int i = 0; i < items.length; i++) {
+         IContributionItem item = items[i];
+         enabled = item.isEnabled();
+         if(enabled) break;
+         }
+         // Workaround for 1GDDCN2: DWT:Linux - MenuItem.setEnabled() always causes a redraw
+         if (menuItem.getEnabled() !is enabled)
+         menuItem.setEnabled(enabled);
+         }
+         */
+        // Partial fix for bug #34969 - diable the menu item if no
+        // items in sub-menu (for context menus).
+        if (menuItem !is null && !menuItem.isDisposed() && menuExist()) {
+            bool enabled = menu.getItemCount() > 0;
+            // Workaround for 1GDDCN2: DWT:Linux - MenuItem.setEnabled() always causes a redraw
+            if (menuItem.getEnabled() !is enabled) {
+                // We only do this for context menus (for bug #34969)
+                Menu topMenu = menu;
+                while (topMenu.getParentMenu() !is null) {
+                    topMenu = topMenu.getParentMenu();
+                }
+                if ((topMenu.getStyle() & DWT.BAR) is 0) {
+                    menuItem.setEnabled(enabled);
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/Separator.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.action.Separator;
+
+import dwtx.jface.action.AbstractGroupMarker;
+
+import dwt.DWT;
+import dwt.widgets.Menu;
+import dwt.widgets.MenuItem;
+import dwt.widgets.ToolBar;
+import dwt.widgets.ToolItem;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A separator is a special kind of contribution item which acts
+ * as a visual separator and, optionally, acts as a group marker.
+ * Unlike group markers, separators do have a visual representation for
+ * menus and toolbars.
+ * <p>
+ * This class may be instantiated; it is not intended to be
+ * subclassed outside the framework.
+ * </p>
+ */
+public class Separator : AbstractGroupMarker {
+    /**
+     * Creates a separator which does not start a new group.
+     */
+    public this() {
+        super();
+    }
+
+    /**
+     * Creates a new separator which also defines a new group having the given group name.
+     * The group name must not be <code>null</code> or the empty string.
+     * The group name is also used as the item id.
+     *
+     * @param groupName the group name of the separator
+     */
+    public this(String groupName) {
+        super(groupName);
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     * Fills the given menu with a DWT separator MenuItem.
+     */
+    public void fill(Menu menu, int index) {
+        if (index >= 0) {
+            new MenuItem(menu, DWT.SEPARATOR, index);
+        } else {
+            new MenuItem(menu, DWT.SEPARATOR);
+        }
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     * Fills the given tool bar with a DWT separator ToolItem.
+     */
+    public void fill(ToolBar toolbar, int index) {
+        if (index >= 0) {
+            new ToolItem(toolbar, DWT.SEPARATOR, index);
+        } else {
+            new ToolItem(toolbar, DWT.SEPARATOR);
+        }
+    }
+
+    /**
+     * The <code>Separator</code> implementation of this <code>IContributionItem</code>
+     * method returns <code>true</code>
+     */
+    public bool isSeparator() {
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/action/SubContributionItem.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * 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.jface.action.SubContributionItem;
+
+import dwtx.jface.action.IContributionItem;
+import dwtx.jface.action.IContributionManager;
+
+import dwt.widgets.Composite;
+import dwt.widgets.CoolBar;
+import dwt.widgets.Menu;
+import dwt.widgets.ToolBar;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A <code>SubContributionItem</code> is a wrapper for an <code>IContributionItem</code>.
+ * It is used within a <code>SubContributionManager</code> to control the visibility
+ * of items.
+ * <p>
+ * This class is not intended to be subclassed.
+ * </p>
+ */
+public class SubContributionItem : IContributionItem {
+    /**
+     * The visibility of the item.
+     */
+    private bool visible;
+
+    /**
+     * The inner item for this contribution.
+     */
+    private IContributionItem innerItem;
+
+    /**
+     * Creates a new <code>SubContributionItem</code>.
+     * @param item the contribution item to be wrapped
+     */
+    public this(IContributionItem item) {
+        innerItem = item;
+    }
+
+    /**
+     * The default implementation of this <code>IContributionItem</code>
+     * delegates to the inner item. Subclasses may override.
+     */
+    public void dispose() {
+        innerItem.dispose();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void fill(Composite parent) {
+        if (visible) {
+            innerItem.fill(parent);
+        }
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void fill(Menu parent, int index) {
+        if (visible) {
+            innerItem.fill(parent, index);
+        }
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void fill(ToolBar parent, int index) {
+        if (visible) {
+            innerItem.fill(parent, index);
+        }
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public String getId() {
+        return innerItem.getId();
+    }
+
+    /**
+     * Returns the inner contribution item.
+     *
+     * @return the inner contribution item
+     */
+    public IContributionItem getInnerItem() {
+        return innerItem;
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public bool isEnabled() {
+        return innerItem.isEnabled();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public bool isDirty() {
+        return innerItem.isDirty();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public bool isDynamic() {
+        return innerItem.isDynamic();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public bool isGroupMarker() {
+        return innerItem.isGroupMarker();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public bool isSeparator() {
+        return innerItem.isSeparator();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public bool isVisible() {
+        return visible && innerItem.isVisible();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void setParent(IContributionManager parent) {
+        // do nothing, the parent of our inner item
+        // is its SubContributionManager
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void setVisible(bool visible) {
+        this.visible = visible;
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void update() {
+        innerItem.update();
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IContributionItem.
+     */
+    public void update(String id) {
+        innerItem.update(id);
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.CoolBar, int)
+     */
+    public void fill(CoolBar parent, int index) {
+        if (visible) {
+            innerItem.fill(parent, index);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.jface.action.IContributionItem#saveWidgetState()
+     */
+    public void saveWidgetState() {
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/Binding.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,397 @@
+/*******************************************************************************
+ * 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.jface.bindings.Binding;
+
+import dwtx.jface.bindings.TriggerSequence;
+// import java.io.BufferedWriter;
+// import java.io.IOException;
+// import java.io.StringWriter;
+
+import dwtx.core.commands.ParameterizedCommand;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+import tango.text.convert.Format;
+
+/**
+ * <p>
+ * A binding is a link between user input and the triggering of a particular
+ * command. The most common example of a binding is a keyboard shortcut, but
+ * there are also mouse and gesture bindings.
+ * </p>
+ * <p>
+ * Bindings are linked to particular conditions within the application. Some of
+ * these conditions change infrequently (e.g., locale, scheme), while some will
+ * tend to change quite frequently (e.g., context). This allows the bindings to
+ * be tailored to particular situations. For example, a set of bindings may be
+ * appropriate only inside a text editor.  Or, perhaps, a set of bindings might
+ * be appropriate only for a given locale, such as bindings that coexist with
+ * the Input Method Editor (IME) on Chinese locales.
+ * </p>
+ * <p>
+ * It is also possible to remove a particular binding. This is typically done as
+ * part of user configuration (e.g., user changing keyboard shortcuts). However,
+ * it can also be helpful when trying to change a binding on a particular locale
+ * or platform. An "unbinding" is really just a binding with no command
+ * identifier. For it to unbind a particular binding, it must match that binding
+ * in its context identifier and scheme identifier. Subclasses (e.g.,
+ * <code>KeyBinding</code>) may require other properties to match (e.g.,
+ * <code>keySequence</code>). If these properties match, then this is an
+ * unbinding. Note: the locale and platform can be different.
+ * </p>
+ * <p>
+ * For example, imagine you have a key binding that looks like this:
+ * </p>
+ * <code><pre>
+ * KeyBinding(command, scheme, context, &quot;Ctrl+Shift+F&quot;)
+ * </pre></code>
+ * <p>
+ * On GTK+, the "Ctrl+Shift+F" interferes with some native behaviour. To change
+ * the binding, we first unbind the "Ctrl+Shift+F" key sequence by
+ * assigning it a null command on the gtk platform.  We then create a new binding
+ * that maps the command to the "Esc Ctrl+F" key sequence.
+ * </p>
+ * <code><pre>
+ *     KeyBinding("Ctrl+Shift+F",null,scheme,context,null,gtk,null,SYSTEM)
+ *     KeyBinding("Esc Ctrl+F",parameterizedCommand,scheme,context,null,gtk,SYSTEM)
+ * </pre></code>
+ * <p>
+ * Bindings are intended to be immutable objects.
+ * </p>
+ *
+ * @since 3.1
+ */
+public abstract class Binding {
+
+    /**
+     * The constant integer hash code value meaning the hash code has not yet
+     * been computed.
+     */
+    private static const int HASH_CODE_NOT_COMPUTED = -1;
+
+    /**
+     * A factor for computing the hash code for all key bindings.
+     */
+    private final const int HASH_FACTOR = 89;
+
+    /**
+     * The type of binding that is defined by the system (i.e., by the
+     * application developer). In the case of an application based on the
+     * Eclipse workbench, this is the registry.
+     */
+    public static const int SYSTEM = 0;
+
+    /**
+     * The type of binding that is defined by the user (i.e., by the end user of
+     * the application). In the case of an application based on the Eclipse
+     * workbench, this is the preference store.
+     */
+    public static const int USER = 1;
+
+    /**
+     * The parameterized command to which this binding applies. This value may
+     * be <code>null</code> if this binding is meant to "unbind" an existing
+     * binding.
+     */
+    private const ParameterizedCommand command;
+
+    /**
+     * The context identifier to which this binding applies. This context must
+     * be active before this key binding becomes active. This value will never
+     * be <code>null</code>.
+     */
+    private const String contextId;
+
+    /**
+     * The hash code for this key binding. This value is computed lazily, and
+     * marked as invalid when one of the values on which it is based changes.
+     */
+    private /+transient+/ int hashCode = HASH_CODE_NOT_COMPUTED;
+
+    /**
+     * The locale in which this binding applies. This value may be
+     * <code>null</code> if this binding is meant to apply to all locales.
+     * This string should be in the same format returned by
+     * <code>Locale.getDefault().toString()</code>.
+     */
+    private const String locale;
+
+    /**
+     * The platform on which this binding applies. This value may be
+     * <code>null</code> if this binding is meant to apply to all platforms.
+     * This string should be in the same format returned by
+     * <code>DWT.getPlatform</code>.
+     */
+    private const String platform;
+
+    /**
+     * The identifier of the scheme in which this binding applies. This value
+     * will never be <code>null</code>.
+     */
+    private const String schemeId;
+
+    /**
+     * The string representation of this binding. This string is for debugging
+     * purposes only, and is not meant to be displayed to the user. This value
+     * is computed lazily.
+     */
+    protected /+transient+/ String string = null;
+
+    /**
+     * The type of binding this represents. This is used to distinguish between
+     * different priority levels for bindings. For example, in our case,
+     * <code>USER</code> bindings override <code>SYSTEM</code> bindings.
+     */
+    private const int type;
+
+    /**
+     * Constructs a new instance of <code>Binding</code>.
+     *
+     * @param command
+     *            The parameterized command to which this binding applies; this
+     *            value may be <code>null</code> if the binding is meant to
+     *            "unbind" a previously defined binding.
+     * @param schemeId
+     *            The scheme to which this binding belongs; this value must not
+     *            be <code>null</code>.
+     * @param contextId
+     *            The context to which this binding applies; this value must not
+     *            be <code>null</code>.
+     * @param locale
+     *            The locale to which this binding applies; this value may be
+     *            <code>null</code> if it applies to all locales.
+     * @param platform
+     *            The platform to which this binding applies; this value may be
+     *            <code>null</code> if it applies to all platforms.
+     * @param windowManager
+     *            The window manager to which this binding applies; this value
+     *            may be <code>null</code> if it applies to all window
+     *            managers. This value is currently ignored.
+     * @param type
+     *            The type of binding. This should be either <code>SYSTEM</code>
+     *            or <code>USER</code>.
+     */
+    protected this(ParameterizedCommand command,
+            String schemeId, String contextId, String locale,
+            String platform, String windowManager, int type) {
+        if (schemeId is null) {
+            throw new NullPointerException("The scheme cannot be null"); //$NON-NLS-1$
+        }
+
+        if (contextId is null) {
+            throw new NullPointerException("The context cannot be null"); //$NON-NLS-1$
+        }
+
+        if ((type !is SYSTEM) && (type !is USER)) {
+            throw new IllegalArgumentException(
+                    "The type must be SYSTEM or USER"); //$NON-NLS-1$
+        }
+
+        this.command = command;
+        this.schemeId = schemeId.intern();
+        this.contextId = contextId.intern();
+        this.locale = (locale is null) ? null : locale.intern();
+        this.platform = (platform is null) ? null : platform.intern();
+        this.type = type;
+    }
+
+    /**
+     * Tests whether this binding is intended to delete another binding. The
+     * receiver must have a <code>null</code> command identifier.
+     *
+     * @param binding
+     *            The binding to test; must not be <code>null</code>.
+     *            This binding must be a <code>SYSTEM</code> binding.
+     * @return <code>true</code> if the receiver deletes the binding defined by
+     *          the argument.
+     */
+    final bool deletes(Binding binding) {
+        bool deletes = true;
+        deletes &= Util.opEquals(getContextId(), binding.getContextId());
+        deletes &= Util.opEquals(getTriggerSequence(), binding
+                .getTriggerSequence());
+        if (getLocale() !is null) {
+            deletes &= !Util.opEquals(getLocale(), binding.getLocale());
+        }
+        if (getPlatform() !is null) {
+            deletes &= !Util.opEquals(getPlatform(), binding.getPlatform());
+        }
+        deletes &= (binding.getType() is SYSTEM);
+        deletes &= Util.opEquals(getParameterizedCommand(), null);
+
+        return deletes;
+    }
+
+    /**
+     * Tests whether this binding is equal to another object. Bindings are only
+     * equal to other bindings with equivalent values.
+     *
+     * @param object
+     *            The object with which to compare; may be <code>null</code>.
+     * @return <code>true</code> if the object is a binding with equivalent
+     *         values for all of its properties; <code>false</code> otherwise.
+     */
+    public final override int opEquals( Object object) {
+        if (this is object) {
+            return true;
+
+        }
+        if (!(cast(Binding)object )) {
+            return false;
+        }
+
+        Binding binding = cast(Binding) object;
+        if (!Util.opEquals(getParameterizedCommand(), binding
+                .getParameterizedCommand())) {
+            return false;
+        }
+        if (!Util.opEquals(getContextId(), binding.getContextId())) {
+            return false;
+        }
+        if (!Util.opEquals(getTriggerSequence(), binding.getTriggerSequence())) {
+            return false;
+        }
+        if (!Util.opEquals(getLocale(), binding.getLocale())) {
+            return false;
+        }
+        if (!Util.opEquals(getPlatform(), binding.getPlatform())) {
+            return false;
+        }
+        if (!Util.opEquals(getSchemeId(), binding.getSchemeId())) {
+            return false;
+        }
+        return (getType() !is binding.getType());
+    }
+
+    /**
+     * Returns the parameterized command to which this binding applies. If the
+     * identifier is <code>null</code>, then this binding is "unbinding" an
+     * existing binding.
+     *
+     * @return The fully-parameterized command; may be <code>null</code>.
+     */
+    public final ParameterizedCommand getParameterizedCommand() {
+        return command;
+    }
+
+    /**
+     * Returns the identifier of the context in which this binding applies.
+     *
+     * @return The context identifier; never <code>null</code>.
+     */
+    public final String getContextId() {
+        return contextId;
+    }
+
+    /**
+     * Returns the locale in which this binding applies. If the locale is
+     * <code>null</code>, then this binding applies to all locales. This
+     * string is the same format as returned by
+     * <code>Locale.getDefault().toString()</code>.
+     *
+     * @return The locale; may be <code>null</code>.
+     */
+    public final String getLocale() {
+        return locale;
+    }
+
+    /**
+     * Returns the platform on which this binding applies. If the platform is
+     * <code>null</code>, then this binding applies to all platforms. This
+     * string is the same format as returned by <code>DWT.getPlatform()</code>.
+     *
+     * @return The platform; may be <code>null</code>.
+     */
+    public final String getPlatform() {
+        return platform;
+    }
+
+    /**
+     * Returns the identifier of the scheme in which this binding applies.
+     *
+     * @return The scheme identifier; never <code>null</code>.
+     */
+    public final String getSchemeId() {
+        return schemeId;
+    }
+
+    /**
+     * Returns the sequence of trigger for a given binding. The triggers can be
+     * anything, but above all it must be hashable. This trigger sequence is
+     * used by the binding manager to distinguish between different bindings.
+     *
+     * @return The object representing an input event that will trigger this
+     *         binding; must not be <code>null</code>.
+     */
+    public abstract TriggerSequence getTriggerSequence();
+
+    /**
+     * Returns the type for this binding. As it stands now, this value will
+     * either be <code>SYSTEM</code> or <code>USER</code>. In the future,
+     * more types might be added.
+     *
+     * @return The type for this binding.
+     */
+    public final int getType() {
+        return type;
+    }
+
+    /**
+     * Computes the hash code for this key binding based on all of its
+     * attributes.
+     *
+     * @return The hash code for this key binding.
+     */
+    public final override hash_t toHash() {
+        if (hashCode is HASH_CODE_NOT_COMPUTED) {
+            auto HASH_INITIAL = dwt.dwthelper.utils.toHash(Binding.classinfo.name);
+            hashCode = HASH_INITIAL;
+            hashCode = hashCode * HASH_FACTOR
+                    + Util.toHash(getParameterizedCommand());
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(getContextId());
+            hashCode = hashCode * HASH_FACTOR
+                    + Util.toHash(getTriggerSequence());
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(getLocale());
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(getPlatform());
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(getSchemeId());
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(getType());
+            if (hashCode is HASH_CODE_NOT_COMPUTED) {
+                hashCode++;
+            }
+        }
+
+        return hashCode;
+    }
+
+    /**
+     * The string representation of this binding -- for debugging purposes only.
+     * This string should not be shown to an end user. This should be overridden
+     * by subclasses that add properties.
+     *
+     * @return The string representation; never <code>null</code>.
+     */
+    public override String toString() {
+        if (string is null) {
+            string = Format("Binding({},\n\t{},\n\t{},\n\t{},{},{},{})",
+                getTriggerSequence().toString(),
+                command is null?"":command.toString(),
+                schemeId,
+                contextId,
+                locale is null?"":locale,
+                platform is null?"":platform,
+                (type is SYSTEM) ? "system" : "user");
+        }
+
+        return string;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/BindingManager.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,2330 @@
+/*******************************************************************************
+ * 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.jface.bindings.BindingManager;
+
+import dwtx.jface.bindings.Binding;
+import dwtx.jface.bindings.BindingManagerEvent;
+import dwtx.jface.bindings.CachedBindingSet;
+import dwtx.jface.bindings.IBindingManagerListener;
+import dwtx.jface.bindings.ISchemeListener;
+import dwtx.jface.bindings.Scheme;
+import dwtx.jface.bindings.SchemeEvent;
+import dwtx.jface.bindings.Trigger;
+import dwtx.jface.bindings.TriggerSequence;
+
+// import java.io.BufferedWriter;
+// import java.io.IOException;
+// import java.io.StringWriter;
+import tango.util.collection.HashMap;
+import tango.util.collection.HashSet;
+import tango.util.collection.ArraySeq;
+import tango.util.collection.LinkSeq;
+import tango.util.collection.model.Seq;
+import tango.util.collection.model.Map;
+import tango.util.collection.model.Set;
+import tango.util.collection.model.View;
+
+import dwt.DWT;
+import dwtx.core.commands.CommandManager;
+import dwtx.core.commands.ParameterizedCommand;
+import dwtx.core.commands.common.HandleObjectManager;
+import dwtx.core.commands.common.NotDefinedException;
+import dwtx.core.commands.contexts.Context;
+import dwtx.core.commands.contexts.ContextManager;
+import dwtx.core.commands.contexts.ContextManagerEvent;
+import dwtx.core.commands.contexts.IContextManagerListener;
+import dwtx.core.commands.util.Tracing;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.MultiStatus;
+import dwtx.core.runtime.Status;
+import dwtx.jface.bindings.keys.IKeyLookup;
+import dwtx.jface.bindings.keys.KeyLookupFactory;
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.contexts.IContextIds;
+import dwtx.jface.internal.InternalPolicy;
+import dwtx.jface.util.Policy;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+static import tango.text.Text;
+alias tango.text.Text.Text!(char) StringBuffer;
+import tango.text.convert.Format;
+import tango.text.locale.Core;
+
+/**
+ * <p>
+ * A central repository for bindings -- both in the defined and undefined
+ * states. Schemes and bindings can be created and retrieved using this manager.
+ * It is possible to listen to changes in the collection of schemes and bindings
+ * by adding a listener to the manager.
+ * </p>
+ * <p>
+ * The binding manager is very sensitive to performance. Misusing the manager
+ * can render an application unenjoyable to use. As such, each of the public
+ * methods states the current run-time performance. In future releases, it is
+ * guaranteed that the method will run in at least the stated time constraint --
+ * though it might get faster. Where possible, we have also tried to be memory
+ * efficient.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class BindingManager : HandleObjectManager,
+        IContextManagerListener, ISchemeListener {
+
+    private static Map!(Object,Object) EMPTY_MAP;
+    /**
+     * This flag can be set to <code>true</code> if the binding manager should
+     * print information to <code>System.out</code> when certain boundary
+     * conditions occur.
+     */
+    public static bool DEBUG = false;
+
+    /**
+     * Returned for optimized lookup.
+     */
+    private static const TriggerSequence[] EMPTY_TRIGGER_SEQUENCE = null;
+
+    /**
+     * The separator character used in locales.
+     */
+    private static const String LOCALE_SEPARATOR = "_"; //$NON-NLS-1$
+
+    /**
+     * </p>
+     * A utility method for adding entries to a map. The map is checked for
+     * entries at the key. If such an entry exists, it is expected to be a
+     * <code>Seq!(Object)</code>. The value is then appended to the collection.
+     * If no such entry exists, then a collection is created, and the value
+     * added to the collection.
+     * </p>
+     *
+     * @param map
+     *            The map to modify; if this value is <code>null</code>, then
+     *            this method simply returns.
+     * @param key
+     *            The key to look up in the map; may be <code>null</code>.
+     * @param value
+     *            The value to look up in the map; may be <code>null</code>.
+     */
+    private static final void addReverseLookup(Map!(Object,Object) map, Object key,
+            Object value) {
+        if (map is null) {
+            return;
+        }
+
+        Object currentValue = map.get(key);
+        if (currentValue !is null) {
+            auto values = cast(Seq!(Object)) currentValue;
+            values.append(value);
+        } else { // currentValue is null
+            auto values = new ArraySeq!(Object);
+            values.append(value);
+            map.add(key, values);
+        }
+    }
+
+    /**
+     * <p>
+     * Takes a fully-specified string, and converts it into an array of
+     * increasingly less-specific strings. So, for example, "en_GB" would become
+     * ["en_GB", "en", "", null].
+     * </p>
+     * <p>
+     * This method runs in linear time (O(n)) over the length of the string.
+     * </p>
+     *
+     * @param string
+     *            The string to break apart into its less specific components;
+     *            should not be <code>null</code>.
+     * @param separator
+     *            The separator that indicates a separation between a degrees of
+     *            specificity; should not be <code>null</code>.
+     * @return An array of strings from the most specific (i.e.,
+     *         <code>string</code>) to the least specific (i.e.,
+     *         <code>null</code>).
+     */
+    private static final String[] expand(String string, String separator) {
+        // Test for boundary conditions.
+        if (string is null || separator is null) {
+            return new String[0];
+        }
+
+        auto strings = new LinkSeq!(String);
+        auto stringBuffer = new StringBuffer();
+        string = string.trim(); // remove whitespace
+        if (string.length > 0) {
+
+            auto tokens = tango.text.Util.delimit(string, separator);
+            foreach( tok; tokens ){
+                if (stringBuffer.length() > 0) {
+                    stringBuffer.append(separator);
+                }
+                stringBuffer.append(tok.trim());
+                strings.prepend(stringBuffer.toString());
+            }
+        }
+        strings.append(Util.ZERO_LENGTH_STRING);
+        strings.append(cast(char[])null);
+        return strings.toArray();
+    }
+
+    /**
+     * The active bindings. This is a map of triggers (
+     * <code>TriggerSequence</code>) to bindings (<code>Binding</code>).
+     * This value will only be <code>null</code> if the active bindings have
+     * not yet been computed. Otherwise, this value may be empty.
+     */
+    private Map!(Object,Object) activeBindings = null;
+
+    /**
+     * The active bindings indexed by fully-parameterized commands. This is a
+     * map of fully-parameterized commands (<code>ParameterizedCommand</code>)
+     * to triggers ( <code>TriggerSequence</code>). This value will only be
+     * <code>null</code> if the active bindings have not yet been computed.
+     * Otherwise, this value may be empty.
+     */
+    private Map!(Object,Object) activeBindingsByParameterizedCommand = null;
+
+    private Set!(Object) triggerConflicts;
+
+    /**
+     * The scheme that is currently active. An active scheme is the one that is
+     * currently dictating which bindings will actually work. This value may be
+     * <code>null</code> if there is no active scheme. If the active scheme
+     * becomes undefined, then this should automatically revert to
+     * <code>null</code>.
+     */
+    private Scheme activeScheme = null;
+
+    /**
+     * The array of scheme identifiers, starting with the active scheme and
+     * moving up through its parents. This value may be <code>null</code> if
+     * there is no active scheme.
+     */
+    private String[] activeSchemeIds = null;
+
+    /**
+     * The number of bindings in the <code>bindings</code> array.
+     */
+    private int bindingCount = 0;
+
+    /**
+     * A cache of context IDs that weren't defined.
+     */
+    private Set!(Object) bindingErrors;
+
+    /**
+     * The array of all bindings currently handled by this manager. This array
+     * is the raw list of bindings, as provided to this manager. This value may
+     * be <code>null</code> if there are no bindings. The size of this array
+     * is not necessarily the number of bindings.
+     */
+    private Binding[] bindings = null;
+
+    /**
+     * A cache of the bindings previously computed by this manager. This value
+     * may be empty, but it is never <code>null</code>. This is a map of
+     * <code>CachedBindingSet</code> to <code>CachedBindingSet</code>.
+     */
+    private Map!(Object,Object) cachedBindings;
+
+    /**
+     * The command manager for this binding manager. This manager is only needed
+     * for the <code>getActiveBindingsFor(String)</code> method. This value is
+     * guaranteed to never be <code>null</code>.
+     */
+    private const CommandManager commandManager;
+
+    /**
+     * The context manager for this binding manager. For a binding manager to
+     * function, it needs to listen for changes to the contexts. This value is
+     * guaranteed to never be <code>null</code>.
+     */
+    private const ContextManager contextManager;
+
+    /**
+     * The locale for this manager. This defaults to the current locale. The
+     * value will never be <code>null</code>.
+     */
+    private String locale;
+
+    /**
+     * The array of locales, starting with the active locale and moving up
+     * through less specific representations of the locale. For example,
+     * ["en_US", "en", "", null]. This value will never be <code>null</code>.
+     */
+    private String[] locales;
+
+    /**
+     * The platform for this manager. This defaults to the current platform. The
+     * value will never be <code>null</code>.
+     */
+    private String platform;
+
+    /**
+     * The array of platforms, starting with the active platform and moving up
+     * through less specific representations of the platform. For example,
+     * ["gtk", "", null]. This value will never be <code>null,/code>.
+     */
+    private String[] platforms;
+
+    /**
+     * A map of prefixes (<code>TriggerSequence</code>) to a map of
+     * available completions (possibly <code>null</code>, which means there
+     * is an exact match). The available completions is a map of trigger (<code>TriggerSequence</code>)
+     * to bindings (<code>Binding</code>). This value may be
+     * <code>null</code> if there is no existing solution.
+     */
+    private Map!(Object,Object) prefixTable = null;
+
+    /**
+     * <p>
+     * Constructs a new instance of <code>BindingManager</code>.
+     * </p>
+     * <p>
+     * This method completes in amortized constant time (O(1)).
+     * </p>
+     *
+     * @param contextManager
+     *            The context manager that will support this binding manager.
+     *            This value must not be <code>null</code>.
+     * @param commandManager
+     *            The command manager that will support this binding manager.
+     *            This value must not be <code>null</code>.
+     */
+    public this(ContextManager contextManager,
+            CommandManager commandManager) {
+        triggerConflicts = new HashSet!(Object);
+        bindingErrors = new HashSet!(Object);
+        cachedBindings = new HashMap!(Object,Object);
+        locale = /+Locale+/Culture.current().toString();
+        locales = expand(locale, LOCALE_SEPARATOR);
+        platform = DWT.getPlatform();
+        platforms = expand(platform, Util.ZERO_LENGTH_STRING);
+        if (contextManager is null) {
+            throw new NullPointerException(
+                    "A binding manager requires a context manager"); //$NON-NLS-1$
+        }
+
+        if (commandManager is null) {
+            throw new NullPointerException(
+                    "A binding manager requires a command manager"); //$NON-NLS-1$
+        }
+
+        this.contextManager = contextManager;
+        contextManager.addContextManagerListener(this);
+        this.commandManager = commandManager;
+    }
+
+    /**
+     * <p>
+     * Adds a single new binding to the existing array of bindings. If the array
+     * is currently <code>null</code>, then a new array is created and this
+     * binding is added to it. This method does not detect duplicates.
+     * </p>
+     * <p>
+     * This method completes in amortized <code>O(1)</code>.
+     * </p>
+     *
+     * @param binding
+     *            The binding to be added; must not be <code>null</code>.
+     */
+    public final void addBinding(Binding binding) {
+        if (binding is null) {
+            throw new NullPointerException("Cannot add a null binding"); //$NON-NLS-1$
+        }
+
+        if (bindings is null) {
+            bindings = new Binding[1];
+        } else if (bindingCount >= bindings.length) {
+            Binding[] oldBindings = bindings;
+            bindings = new Binding[oldBindings.length * 2];
+            System.arraycopy(oldBindings, 0, bindings, 0, oldBindings.length);
+        }
+        bindings[bindingCount++] = binding;
+        clearCache();
+    }
+
+    /**
+     * <p>
+     * Adds a listener to this binding manager. The listener will be notified
+     * when the set of defined schemes or bindings changes. This can be used to
+     * track the global appearance and disappearance of bindings.
+     * </p>
+     * <p>
+     * This method completes in amortized constant time (<code>O(1)</code>).
+     * </p>
+     *
+     * @param listener
+     *            The listener to attach; must not be <code>null</code>.
+     */
+    public final void addBindingManagerListener(
+            IBindingManagerListener listener) {
+        addListenerObject(cast(Object)listener);
+    }
+
+    /**
+     * <p>
+     * Builds a prefix table look-up for a map of active bindings.
+     * </p>
+     * <p>
+     * This method takes <code>O(mn)</code>, where <code>m</code> is the
+     * length of the trigger sequences and <code>n</code> is the number of
+     * bindings.
+     * </p>
+     *
+     * @param activeBindings
+     *            The map of triggers (<code>TriggerSequence</code>) to
+     *            command ids (<code>String</code>) which are currently
+     *            active. This value may be <code>null</code> if there are no
+     *            active bindings, and it may be empty. It must not be
+     *            <code>null</code>.
+     * @return A map of prefixes (<code>TriggerSequence</code>) to a map of
+     *         available completions (possibly <code>null</code>, which means
+     *         there is an exact match). The available completions is a map of
+     *         trigger (<code>TriggerSequence</code>) to command identifier (<code>String</code>).
+     *         This value will never be <code>null</code>, but may be empty.
+     */
+    private final Map!(Object,Object) buildPrefixTable(Map!(Object,Object) activeBindings) {
+        auto prefixTable = new HashMap!(Object,Object);
+        foreach( k, v; activeBindings ){
+            TriggerSequence triggerSequence = cast(TriggerSequence)k;
+
+            // Add the perfect match.
+            if (!prefixTable.containsKey(triggerSequence)) {
+                prefixTable.add(triggerSequence, null);
+            }
+
+            TriggerSequence[] prefixes = triggerSequence.getPrefixes();
+            int prefixesLength = prefixes.length;
+            if (prefixesLength is 0) {
+                continue;
+            }
+
+            // Break apart the trigger sequence.
+            Binding binding = cast(Binding) v;
+            for (int i = 0; i < prefixesLength; i++) {
+                TriggerSequence prefix = prefixes[i];
+                Object value = prefixTable.get(prefix);
+                if ((prefixTable.containsKey(prefix)) && (cast(Map!(Object,Object))value )) {
+                    (cast(Map!(Object,Object)) value).add(triggerSequence, binding);
+                } else {
+                    auto map = new HashMap!(Object,Object);
+                    prefixTable.add(prefix, map);
+                    map.add(triggerSequence, binding);
+                }
+            }
+        }
+
+        return prefixTable;
+    }
+
+    /**
+     * <p>
+     * Clears the cache, and the existing solution. If debugging is turned on,
+     * then this will also print a message to standard out.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     */
+    private final void clearCache() {
+        if (DEBUG) {
+            Tracing.printTrace("BINDINGS", "Clearing cache"); //$NON-NLS-1$ //$NON-NLS-2$
+        }
+        cachedBindings.clear();
+        clearSolution();
+    }
+
+    /**
+     * <p>
+     * Clears the existing solution.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     */
+    private final void clearSolution() {
+        setActiveBindings(null, null, null, null);
+    }
+
+    /**
+     * Compares the identifier of two schemes, and decides which scheme is the
+     * youngest (i.e., the child) of the two. Both schemes should be active
+     * schemes.
+     *
+     * @param schemeId1
+     *            The identifier of the first scheme; must not be
+     *            <code>null</code>.
+     * @param schemeId2
+     *            The identifier of the second scheme; must not be
+     *            <code>null</code>.
+     * @return <code>0</code> if the two schemes are equal of if neither
+     *         scheme is active; <code>1</code> if the second scheme is the
+     *         youngest; and <code>-1</code> if the first scheme is the
+     *         youngest.
+     * @since 3.2
+     */
+    private final int compareSchemes(String schemeId1,
+             String schemeId2) {
+        if (!schemeId2.equals(schemeId1)) {
+            for (int i = 0; i < activeSchemeIds.length; i++) {
+                String schemePointer = activeSchemeIds[i];
+                if (schemeId2.equals(schemePointer)) {
+                    return 1;
+
+                } else if (schemeId1.equals(schemePointer)) {
+                    return -1;
+
+                }
+
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * <p>
+     * Computes the bindings given the context tree, and inserts them into the
+     * <code>commandIdsByTrigger</code>. It is assumed that
+     * <code>locales</code>,<code>platforsm</code> and
+     * <code>schemeIds</code> correctly reflect the state of the application.
+     * This method does not deal with caching.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @param activeContextTree
+     *            The map representing the tree of active contexts. The map is
+     *            one of child to parent, each being a context id (
+     *            <code>String</code>). The keys are never <code>null</code>,
+     *            but the values may be (i.e., no parent). This map may be
+     *            empty. It may be <code>null</code> if we shouldn't consider
+     *            contexts.
+     * @param bindingsByTrigger
+     *            The empty of map that is intended to be filled with triggers (
+     *            <code>TriggerSequence</code>) to bindings (
+     *            <code>Binding</code>). This value must not be
+     *            <code>null</code> and must be empty.
+     * @param triggersByCommandId
+     *            The empty of map that is intended to be filled with command
+     *            identifiers (<code>String</code>) to triggers (
+     *            <code>TriggerSequence</code>). This value must either be
+     *            <code>null</code> (indicating that these values are not
+     *            needed), or empty (indicating that this map should be
+     *            computed).
+     */
+    private final void computeBindings(Map!(Object,Object) activeContextTree,
+            Map!(Object,Object) bindingsByTrigger, Map!(Object,Object) triggersByCommandId,
+            Map!(Object,Object) conflictsByTrigger) {
+        /*
+         * FIRST PASS: Remove all of the bindings that are marking deletions.
+         */
+        Binding[] trimmedBindings = removeDeletions(bindings);
+
+        /*
+         * SECOND PASS: Just throw in bindings that match the current state. If
+         * there is more than one match for a binding, then create a list.
+         */
+        auto possibleBindings = new HashMap!(Object,Object);
+        int length = trimmedBindings.length;
+        for (int i = 0; i < length; i++) {
+            Binding binding = trimmedBindings[i];
+            bool found;
+
+            // Check the context.
+            String contextId = binding.getContextId();
+            if ((activeContextTree !is null)
+                    && (!activeContextTree.containsKey( new ArrayWrapperString(contextId)))) {
+                continue;
+            }
+
+            // Check the locale.
+            if (!localeMatches(binding)) {
+                continue;
+            }
+
+            // Check the platform.
+            if (!platformMatches(binding)) {
+                continue;
+            }
+
+            // Check the scheme ids.
+            String schemeId = binding.getSchemeId();
+            found = false;
+            if (activeSchemeIds !is null) {
+                for (int j = 0; j < activeSchemeIds.length; j++) {
+                    if (Util.opEquals(schemeId, activeSchemeIds[j])) {
+                        found = true;
+                        break;
+                    }
+                }
+            }
+            if (!found) {
+                continue;
+            }
+
+            // Insert the match into the list of possible matches.
+            TriggerSequence trigger = binding.getTriggerSequence();
+            Object existingMatch = possibleBindings.get(trigger);
+            if (cast(Binding)existingMatch ) {
+                possibleBindings.remove(trigger);
+                auto matches = new ArraySeq!(Object);
+                matches.append(existingMatch);
+                matches.append(binding);
+                possibleBindings.add(trigger, matches);
+
+            } else if (cast(Seq!(Object))existingMatch ) {
+                auto matches = cast(Seq!(Object)) existingMatch;
+                matches.append(binding);
+
+            } else {
+                possibleBindings.add(trigger, binding);
+            }
+        }
+
+        MultiStatus conflicts = new MultiStatus("dwtx.jface", 0, //$NON-NLS-1$
+                "Keybinding conflicts occurred.  They may interfere with normal accelerator operation.", //$NON-NLS-1$
+                null);
+        /*
+         * THIRD PASS: In this pass, we move any non-conflicting bindings
+         * directly into the map. In the case of conflicts, we apply some
+         * further logic to try to resolve them. If the conflict can't be
+         * resolved, then we log the problem.
+         */
+        foreach( k,v; possibleBindings ){
+//         Iterator possibleBindingItr = possibleBindings.entrySet()
+//                 .iterator();
+//         while (possibleBindingItr.hasNext()) {
+//             Map.Entry entry = cast(Map.Entry) possibleBindingItr.next();
+            TriggerSequence trigger = cast(TriggerSequence) k;//entry.getKey();
+            Object match = v;//entry.getValue();
+            /*
+             * What we do depends slightly on whether we are trying to build a
+             * list of all possible bindings (disregarding context), or a flat
+             * map given the currently active contexts.
+             */
+            if (activeContextTree is null) {
+                // We are building the list of all possible bindings.
+                auto bindings = new ArraySeq!(Object);
+                if (cast(Binding)match ) {
+                    bindings.append(match);
+                    bindingsByTrigger.add(trigger, bindings);
+                    addReverseLookup(triggersByCommandId, (cast(Binding) match)
+                            .getParameterizedCommand(), trigger);
+
+                } else if (cast(View!(Object))match ) {
+                    bindings.append( (cast(View!(Object)) match).elements );
+                    bindingsByTrigger.add(trigger, bindings);
+
+                    foreach( e; bindings ){
+//                     Iterator matchItr = bindings.iterator();
+//                     while (matchItr.hasNext()) {
+                        addReverseLookup(triggersByCommandId,
+                                (cast(Binding) e)
+                                        .getParameterizedCommand(), trigger);
+                    }
+                }
+
+            } else {
+                // We are building the flat map of trigger to commands.
+                if (cast(Binding)match ) {
+                    Binding binding = cast(Binding) match;
+                    bindingsByTrigger.add(trigger, binding);
+                    addReverseLookup(triggersByCommandId, binding
+                            .getParameterizedCommand(), trigger);
+
+                } else if (cast(Seq!(Object))match ) {
+                    Binding winner = resolveConflicts(cast(Seq!(Object)) match,
+                            activeContextTree);
+                    if (winner is null) {
+                        // warn once ... so as not to flood the logs
+                        conflictsByTrigger.add(trigger, match);
+                        if (!triggerConflicts.contains(trigger)) {
+                            triggerConflicts.add(trigger);
+//                             StringWriter sw = new StringWriter();
+//                             BufferedWriter buffer = new BufferedWriter(sw);
+                            StringBuffer sb = new StringBuffer();
+                            try {
+                                sb.append("A conflict occurred for "); //$NON-NLS-1$
+                                sb.append(trigger.toString());
+                                sb.append(':');
+                                foreach( e; cast(Seq!(Object)) match){
+//                                 Iterator i = (cast(Seq!(Object)) match).iterator();
+//                                 while (i.hasNext()) {
+                                    sb.append('\n');
+                                    sb.append( e.toString() );
+                                }
+                            } catch (IOException e) {
+                                // we should not get this
+                            }
+                            conflicts.add(new Status(IStatus.WARNING,
+                                    "dwtx.jface", //$NON-NLS-1$
+                                    sb.toString()));
+                        }
+                        if (DEBUG) {
+                            Tracing.printTrace("BINDINGS", //$NON-NLS-1$
+                                    "A conflict occurred for " ~ trigger.toString); //$NON-NLS-1$
+                            Tracing.printTrace("BINDINGS", "    " ~ match.toString); //$NON-NLS-1$ //$NON-NLS-2$
+                        }
+                    } else {
+                        bindingsByTrigger.add(trigger, winner);
+                        addReverseLookup(triggersByCommandId, winner
+                                .getParameterizedCommand(), trigger);
+                    }
+                }
+            }
+        }
+        if (conflicts.getSeverity() !is IStatus.OK) {
+            Policy.getLog().log(conflicts);
+        }
+    }
+
+    /**
+     * <p>
+     * Notifies this manager that the context manager has changed. This method
+     * is intended for internal use only.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     */
+    public final void contextManagerChanged(
+            ContextManagerEvent contextManagerEvent) {
+        if (contextManagerEvent.isActiveContextsChanged()) {
+// clearSolution();
+            recomputeBindings();
+        }
+    }
+
+    /**
+     * Returns the number of strokes in an array of triggers. It is assumed that
+     * there is one natural key per trigger. The strokes are counted based on
+     * the type of key. Natural keys are worth one; ctrl is worth two; shift is
+     * worth four; and alt is worth eight.
+     *
+     * @param triggers
+     *            The triggers on which to count strokes; must not be
+     *            <code>null</code>.
+     * @return The value of the strokes in the triggers.
+     * @since 3.2
+     */
+    private final int countStrokes(Trigger[] triggers) {
+        int strokeCount = triggers.length;
+        for (int i = 0; i < triggers.length; i++) {
+            Trigger trigger = triggers[i];
+            if (cast(KeyStroke)trigger ) {
+                KeyStroke keyStroke = cast(KeyStroke) trigger;
+                int modifierKeys = keyStroke.getModifierKeys();
+                IKeyLookup lookup = KeyLookupFactory.getDefault();
+                if ((modifierKeys & lookup.getAlt()) !is 0) {
+                    strokeCount += 8;
+                }
+                if ((modifierKeys & lookup.getCtrl()) !is 0) {
+                    strokeCount += 2;
+                }
+                if ((modifierKeys & lookup.getShift()) !is 0) {
+                    strokeCount += 4;
+                }
+                if ((modifierKeys & lookup.getCommand()) !is 0) {
+                    strokeCount += 2;
+                }
+            } else {
+                strokeCount += 99;
+            }
+        }
+
+        return strokeCount;
+    }
+
+    /**
+     * <p>
+     * Creates a tree of context identifiers, representing the hierarchical
+     * structure of the given contexts. The tree is structured as a mapping from
+     * child to parent.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the height of the context tree.
+     * </p>
+     *
+     * @param contextIds
+     *            The set of context identifiers to be converted into a tree;
+     *            must not be <code>null</code>.
+     * @return The tree of contexts to use; may be empty, but never
+     *         <code>null</code>. The keys and values are both strings.
+     */
+    private final Map!(Object,Object) createContextTreeFor(Set!(Object) contextIds) {
+        auto contextTree = new HashMap!(Object,Object);
+
+        foreach( e; contextIds ){
+            auto childContextId = (cast(ArrayWrapperString)e).array;
+            while (childContextId !is null) {
+                // Check if we've already got the part of the tree from here up.
+                if (contextTree.containsKey(/+childContextId+/e)) {
+                    break;
+                }
+
+                // Retrieve the context.
+                Context childContext = contextManager
+                        .getContext(childContextId);
+
+                // Add the child-parent pair to the tree.
+                try {
+                    String parentContextId = childContext.getParentId();
+                    contextTree.add(new ArrayWrapperString(childContextId), new ArrayWrapperString(parentContextId));
+                    childContextId = parentContextId;
+                } catch (NotDefinedException e) {
+                    break; // stop ascending
+                }
+            }
+        }
+
+        return contextTree;
+    }
+
+    /**
+     * <p>
+     * Creates a tree of context identifiers, representing the hierarchical
+     * structure of the given contexts. The tree is structured as a mapping from
+     * child to parent. In this tree, the key binding specific filtering of
+     * contexts will have taken place.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n^2)</code>, where <code>n</code>
+     * is the height of the context tree.
+     * </p>
+     *
+     * @param contextIds
+     *            The set of context identifiers to be converted into a tree;
+     *            must not be <code>null</code>.
+     * @return The tree of contexts to use; may be empty, but never
+     *         <code>null</code>. The keys and values are both strings.
+     */
+    private final Map!(Object,Object) createFilteredContextTreeFor(Set!(Object) contextIds) {
+        // Check to see whether a dialog or window is active.
+        bool dialog = false;
+        bool window = false;
+        foreach( e; contextIds ){
+            String contextId = (cast(ArrayWrapperString) e).array;
+            if (IContextIds.CONTEXT_ID_DIALOG.equals(contextId)) {
+                dialog = true;
+                continue;
+            }
+            if (IContextIds.CONTEXT_ID_WINDOW.equals(contextId)) {
+                window = true;
+                continue;
+            }
+        }
+
+        /*
+         * Remove all context identifiers for contexts whose parents are dialog
+         * or window, and the corresponding dialog or window context is not
+         * active.
+         */
+        foreach( e; contextIds.dup ){
+            String contextId = (cast(ArrayWrapperString) e).array;
+            Context context = contextManager.getContext(contextId);
+            try {
+                String parentId = context.getParentId();
+                while (parentId !is null) {
+                    if (IContextIds.CONTEXT_ID_DIALOG.equals(parentId)) {
+                        if (!dialog) {
+                            contextIds.remove(e);
+//                             contextIdItr.remove();
+                        }
+                        break;
+                    }
+                    if (IContextIds.CONTEXT_ID_WINDOW.equals(parentId)) {
+                        if (!window) {
+                            contextIds.remove(e);
+//                             contextIdItr.remove();
+                        }
+                        break;
+                    }
+                    if (IContextIds.CONTEXT_ID_DIALOG_AND_WINDOW
+                            .equals(parentId)) {
+                        if ((!window) && (!dialog)) {
+                            contextIds.remove(e);
+//                          contextIdItr.remove();
+                        }
+                        break;
+                    }
+
+                    context = contextManager.getContext(parentId);
+                    parentId = context.getParentId();
+                }
+            } catch (NotDefinedException e) {
+                // since this context was part of an undefined hierarchy,
+                // I'm going to yank it out as a bad bet
+                contextIds.remove(e);
+//                 contextIdItr.remove();
+
+                // This is a logging optimization, only log the error once.
+                if (context is null || !bindingErrors.contains(new ArrayWrapperString(context.getId()))) {
+                    if (context !is null) {
+                        bindingErrors.add(new ArrayWrapperString(context.getId()));
+                    }
+
+                    // now log like you've never logged before!
+                    Policy.getLog().log(new Status( IStatus.ERROR, Policy.JFACE, IStatus.OK,
+                        "Undefined context while filtering dialog/window contexts", //$NON-NLS-1$
+                        e));
+                }
+            }
+        }
+
+        return createContextTreeFor(contextIds);
+    }
+
+    /**
+     * <p>
+     * Notifies all of the listeners to this manager that the defined or active
+     * schemes of bindings have changed.
+     * </p>
+     * <p>
+     * The time this method takes to complete is dependent on external
+     * listeners.
+     * </p>
+     *
+     * @param event
+     *            The event to send to all of the listeners; must not be
+     *            <code>null</code>.
+     */
+    private final void fireBindingManagerChanged(BindingManagerEvent event) {
+        if (event is null) {
+            throw new NullPointerException();
+        }
+
+        Object[] listeners = getListeners();
+        for (int i = 0; i < listeners.length; i++) {
+            IBindingManagerListener listener = cast(IBindingManagerListener) listeners[i];
+            listener.bindingManagerChanged(event);
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the active bindings. The caller must not modify the returned map.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the active bindings are
+     * not yet computed, then this completes in <code>O(nn)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @return The map of triggers (<code>TriggerSequence</code>) to
+     *         bindings (<code>Binding</code>) which are currently active.
+     *         This value may be <code>null</code> if there are no active
+     *         bindings, and it may be empty.
+     */
+    private final Map!(Object,Object) getActiveBindings() {
+        if (activeBindings is null) {
+            recomputeBindings();
+        }
+
+        return activeBindings;
+    }
+
+    /**
+     * <p>
+     * Returns the active bindings indexed by command identifier. The caller
+     * must not modify the returned map.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the active bindings are
+     * not yet computed, then this completes in <code>O(nn)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @return The map of fully-parameterized commands (<code>ParameterizedCommand</code>)
+     *         to triggers (<code>TriggerSequence</code>) which are
+     *         currently active. This value may be <code>null</code> if there
+     *         are no active bindings, and it may be empty.
+     */
+    private final Map!(Object,Object) getActiveBindingsByParameterizedCommand() {
+        if (activeBindingsByParameterizedCommand is null) {
+            recomputeBindings();
+        }
+
+        return activeBindingsByParameterizedCommand;
+    }
+
+    /**
+     * <p>
+     * Computes the bindings for the current state of the application, but
+     * disregarding the current contexts. This can be useful when trying to
+     * display all the possible bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @return A map of trigger (<code>TriggerSequence</code>) to bindings (
+     *         <code>Seq!(Object)</code> containing <code>Binding</code>).
+     *         This map may be empty, but it is never <code>null</code>.
+     */
+    public final Map!(Object,Object) getActiveBindingsDisregardingContext() {
+        if (bindings is null) {
+            // Not yet initialized. This is happening too early. Do nothing.
+            if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object);
+            return EMPTY_MAP;
+        }
+
+        // Build a cached binding set for that state.
+        CachedBindingSet bindingCache = new CachedBindingSet(null,
+                locales, platforms, activeSchemeIds);
+
+        /*
+         * Check if the cached binding set already exists. If so, simply set the
+         * active bindings and return.
+         */
+        CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings
+                .get(bindingCache);
+        if (existingCache is null) {
+            existingCache = bindingCache;
+            cachedBindings.add(existingCache, existingCache);
+        }
+        auto commandIdsByTrigger = existingCache.getBindingsByTrigger();
+        if (commandIdsByTrigger !is null) {
+            if (DEBUG) {
+                Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+
+            return commandIdsByTrigger;
+        }
+
+        // There is no cached entry for this.
+        if (DEBUG) {
+            Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$
+        }
+
+        // Compute the active bindings.
+        commandIdsByTrigger = new HashMap!(Object,Object);
+        auto triggersByParameterizedCommand = new HashMap!(Object,Object);
+        auto conflictsByTrigger = new HashMap!(Object,Object);
+        computeBindings(null, commandIdsByTrigger,
+                triggersByParameterizedCommand, conflictsByTrigger);
+        existingCache.setBindingsByTrigger(commandIdsByTrigger);
+        existingCache.setTriggersByCommandId(triggersByParameterizedCommand);
+        existingCache.setConflictsByTrigger(conflictsByTrigger);
+        return /+Collections.unmodifiableMap(+/commandIdsByTrigger;
+    }
+
+    /**
+     * <p>
+     * Computes the bindings for the current state of the application, but
+     * disregarding the current contexts. This can be useful when trying to
+     * display all the possible bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @return A map of trigger (<code>TriggerSequence</code>) to bindings (
+     *         <code>Seq!(Object)</code> containing <code>Binding</code>).
+     *         This map may be empty, but it is never <code>null</code>.
+     * @since 3.2
+     */
+    private final Map!(Object,Object) getActiveBindingsDisregardingContextByParameterizedCommand() {
+        if (bindings is null) {
+            // Not yet initialized. This is happening too early. Do nothing.
+            if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object);
+            return EMPTY_MAP;
+        }
+
+        // Build a cached binding set for that state.
+        CachedBindingSet bindingCache = new CachedBindingSet(null,
+                locales, platforms, activeSchemeIds);
+
+        /*
+         * Check if the cached binding set already exists. If so, simply set the
+         * active bindings and return.
+         */
+        CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings
+                .get(bindingCache);
+        if (existingCache is null) {
+            existingCache = bindingCache;
+            cachedBindings.add(existingCache, existingCache);
+        }
+        auto triggersByParameterizedCommand = existingCache
+                .getTriggersByCommandId();
+        if (triggersByParameterizedCommand !is null) {
+            if (DEBUG) {
+                Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+
+            return /+Collections.unmodifiableMap(+/triggersByParameterizedCommand;
+        }
+
+        // There is no cached entry for this.
+        if (DEBUG) {
+            Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$
+        }
+
+        // Compute the active bindings.
+        auto commandIdsByTrigger = new HashMap!(Object,Object);
+        auto conflictsByTrigger = new HashMap!(Object,Object);
+        triggersByParameterizedCommand = new HashMap!(Object,Object);
+        computeBindings(null, commandIdsByTrigger,
+                triggersByParameterizedCommand, conflictsByTrigger);
+        existingCache.setBindingsByTrigger(commandIdsByTrigger);
+        existingCache.setTriggersByCommandId(triggersByParameterizedCommand);
+        existingCache.setConflictsByTrigger(conflictsByTrigger);
+
+        return /+Collections.unmodifiableMap(+/triggersByParameterizedCommand;
+    }
+
+    /**
+     * <p>
+     * Computes the bindings for the current state of the application, but
+     * disregarding the current contexts. This can be useful when trying to
+     * display all the possible bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @return All of the active bindings (<code>Binding</code>), not sorted
+     *         in any fashion. This collection may be empty, but it is never
+     *         <code>null</code>.
+     */
+    public final View!(Object) getActiveBindingsDisregardingContextFlat() {
+        auto mergedBindings = new ArraySeq!(Object);
+
+        foreach( k,v; getActiveBindingsDisregardingContext() ){
+            auto bindingCollection = cast(View!(Object))v;
+            if ((bindingCollection !is null) && (!bindingCollection.drained())) {
+                foreach( e; bindingCollection ){
+                    mergedBindings.append(e);
+                }
+            }
+        }
+
+        return mergedBindings;
+    }
+
+    /**
+     * <p>
+     * Returns the active bindings for a particular command identifier, but
+     * discounting the current contexts. This method operates in O(n) time over
+     * the number of bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the active bindings are
+     * not yet computed, then this completes in <code>O(nn)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @param parameterizedCommand
+     *            The fully-parameterized command whose bindings are requested.
+     *            This argument may be <code>null</code>.
+     * @return The array of active triggers (<code>TriggerSequence</code>)
+     *         for a particular command identifier. This value is guaranteed to
+     *         never be <code>null</code>, but it may be empty.
+     * @since 3.2
+     */
+    public final TriggerSequence[] getActiveBindingsDisregardingContextFor(
+            ParameterizedCommand parameterizedCommand) {
+        Object object = getActiveBindingsDisregardingContextByParameterizedCommand()
+                .get(parameterizedCommand);
+        if (auto collection = cast(Seq!(Object))object ) {
+            return arraycast!(TriggerSequence)(collection.toArray());
+        }
+
+        return EMPTY_TRIGGER_SEQUENCE;
+    }
+
+    /**
+     * <p>
+     * Returns the active bindings for a particular command identifier. This
+     * method operates in O(n) time over the number of bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the active bindings are
+     * not yet computed, then this completes in <code>O(nn)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @param parameterizedCommand
+     *            The fully-parameterized command whose bindings are requested.
+     *            This argument may be <code>null</code>.
+     * @return The array of active triggers (<code>TriggerSequence</code>)
+     *         for a particular command identifier. This value is guaranteed to
+     *         never be <code>null</code>, but it may be empty.
+     */
+    public final TriggerSequence[] getActiveBindingsFor(
+            ParameterizedCommand parameterizedCommand) {
+        Object object = getActiveBindingsByParameterizedCommand().get(
+                parameterizedCommand);
+        if ( auto collection = cast(Seq!(Object))object ) {
+            return arraycast!(TriggerSequence)(collection.toArray());
+        }
+
+        return EMPTY_TRIGGER_SEQUENCE;
+    }
+
+    /**
+     * <p>
+     * Returns the active bindings for a particular command identifier. This
+     * method operates in O(n) time over the number of bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the active bindings are
+     * not yet computed, then this completes in <code>O(nn)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @param commandId
+     *            The identifier of the command whose bindings are requested.
+     *            This argument may be <code>null</code>. It is assumed that
+     *            the command has no parameters.
+     * @return The array of active triggers (<code>TriggerSequence</code>)
+     *         for a particular command identifier. This value is guaranteed not
+     *         to be <code>null</code>, but it may be empty.
+     */
+    public final TriggerSequence[] getActiveBindingsFor(String commandId) {
+        ParameterizedCommand parameterizedCommand = new ParameterizedCommand(
+                commandManager.getCommand(commandId), null);
+        Object object = getActiveBindingsByParameterizedCommand().get(
+                parameterizedCommand);
+        if ( auto collection = cast(Seq!(Object))object ) {
+            return arraycast!(TriggerSequence)(collection.toArray());
+        }
+
+        return EMPTY_TRIGGER_SEQUENCE;
+    }
+
+    /**
+     * A variation on {@link BindingManager#getActiveBindingsFor(String)} that
+     * returns an array of bindings, rather than trigger sequences. This method
+     * is needed for doing "best" calculations on the active bindings.
+     *
+     * @param commandId
+     *            The identifier of the command for which the active bindings
+     *            should be retrieved; must not be <code>null</code>.
+     * @return The active bindings for the given command; this value may be
+     *         <code>null</code> if there are no active bindings.
+     * @since 3.2
+     */
+    private final Binding[] getActiveBindingsFor1(String commandId) {
+        TriggerSequence[] triggers = getActiveBindingsFor(commandId);
+        if (triggers.length is 0) {
+            return null;
+        }
+
+        auto activeBindings = getActiveBindings();
+        if (activeBindings !is null) {
+            Binding[] bindings = new Binding[triggers.length];
+            for (int i = 0; i < triggers.length; i++) {
+                TriggerSequence triggerSequence = triggers[i];
+                Object object = activeBindings.get(triggerSequence);
+                Binding binding = cast(Binding) object;
+                bindings[i] = binding;
+            }
+            return bindings;
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>
+     * Gets the currently active scheme.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     *
+     * @return The active scheme; may be <code>null</code> if there is no
+     *         active scheme. If a scheme is returned, it is guaranteed to be
+     *         defined.
+     */
+    public final Scheme getActiveScheme() {
+        return activeScheme;
+    }
+
+    /**
+     * Gets the best active binding for a command. The best binding is the one
+     * that would be most appropriate to show in a menu. Bindings which belong
+     * to a child scheme are given preference over those in a parent scheme.
+     * Bindings which belong to a particular locale or platform are given
+     * preference over those that do not. The rest of the calculaton is based
+     * most on various concepts of "length", as well as giving some modifier
+     * keys preference (e.g., <code>Alt</code> is less likely to appear than
+     * <code>Ctrl</code>).
+     *
+     * @param commandId
+     *            The identifier of the command for which the best active
+     *            binding should be retrieved; must not be <code>null</code>.
+     * @return The trigger sequence for the best binding; may be
+     *         <code>null</code> if no bindings are active for the given
+     *         command.
+     * @since 3.2
+     */
+    public final TriggerSequence getBestActiveBindingFor(String commandId) {
+        Binding[] bindings = getActiveBindingsFor1(commandId);
+        if ((bindings is null) || (bindings.length is 0)) {
+            return null;
+        }
+
+        Binding bestBinding = bindings[0];
+        int compareTo;
+        for (int i = 1; i < bindings.length; i++) {
+            Binding currentBinding = bindings[i];
+
+            // Bindings in a child scheme are always given preference.
+            String bestSchemeId = bestBinding.getSchemeId();
+            String currentSchemeId = currentBinding.getSchemeId();
+            compareTo = compareSchemes(bestSchemeId, currentSchemeId);
+            if (compareTo > 0) {
+                bestBinding = currentBinding;
+            }
+            if (compareTo !is 0) {
+                continue;
+            }
+
+            /*
+             * Bindings with a locale are given preference over those that do
+             * not.
+             */
+            String bestLocale = bestBinding.getLocale();
+            String currentLocale = currentBinding.getLocale();
+            if ((bestLocale is null) && (currentLocale !is null)) {
+                bestBinding = currentBinding;
+            }
+            if (!(Util.opEquals(bestLocale, currentLocale))) {
+                continue;
+            }
+
+            /*
+             * Bindings with a platform are given preference over those that do
+             * not.
+             */
+            String bestPlatform = bestBinding.getPlatform();
+            String currentPlatform = currentBinding.getPlatform();
+            if ((bestPlatform is null) && (currentPlatform !is null)) {
+                bestBinding = currentBinding;
+            }
+            if (!(Util.opEquals(bestPlatform, currentPlatform))) {
+                continue;
+            }
+
+            /*
+             * Check to see which has the least number of triggers in the
+             * trigger sequence.
+             */
+            TriggerSequence bestTriggerSequence = bestBinding
+                    .getTriggerSequence();
+            TriggerSequence currentTriggerSequence = currentBinding
+                    .getTriggerSequence();
+            Trigger[] bestTriggers = bestTriggerSequence.getTriggers();
+            Trigger[] currentTriggers = currentTriggerSequence
+                    .getTriggers();
+            compareTo = bestTriggers.length - currentTriggers.length;
+            if (compareTo > 0) {
+                bestBinding = currentBinding;
+            }
+            if (compareTo !is 0) {
+                continue;
+            }
+
+            /*
+             * Compare the number of keys pressed in each trigger sequence. Some
+             * types of keys count less than others (i.e., some types of
+             * modifiers keys are less likely to be chosen).
+             */
+            compareTo = countStrokes(bestTriggers)
+                    - countStrokes(currentTriggers);
+            if (compareTo > 0) {
+                bestBinding = currentBinding;
+            }
+            if (compareTo !is 0) {
+                continue;
+            }
+
+            // If this is still a tie, then just chose the shortest text.
+            compareTo = bestTriggerSequence.format().length
+                    - currentTriggerSequence.format().length;
+            if (compareTo > 0) {
+                bestBinding = currentBinding;
+            }
+        }
+
+        return bestBinding.getTriggerSequence();
+    }
+
+    /**
+     * Gets the formatted string representing the best active binding for a
+     * command. The best binding is the one that would be most appropriate to
+     * show in a menu. Bindings which belong to a child scheme are given
+     * preference over those in a parent scheme. The rest of the calculaton is
+     * based most on various concepts of "length", as well as giving some
+     * modifier keys preference (e.g., <code>Alt</code> is less likely to
+     * appear than <code>Ctrl</code>).
+     *
+     * @param commandId
+     *            The identifier of the command for which the best active
+     *            binding should be retrieved; must not be <code>null</code>.
+     * @return The formatted string for the best binding; may be
+     *         <code>null</code> if no bindings are active for the given
+     *         command.
+     * @since 3.2
+     */
+    public final String getBestActiveBindingFormattedFor(String commandId) {
+        TriggerSequence binding = getBestActiveBindingFor(commandId);
+        if (binding !is null) {
+            return binding.format();
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>
+     * Returns the set of all bindings managed by this class.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     *
+     * @return The array of all bindings. This value may be <code>null</code>
+     *         and it may be empty.
+     */
+    public final Binding[] getBindings() {
+        if (bindings is null) {
+            return null;
+        }
+
+        Binding[] returnValue = new Binding[bindingCount];
+        System.arraycopy(bindings, 0, returnValue, 0, bindingCount);
+        return returnValue;
+    }
+
+    /**
+     * <p>
+     * Returns the array of schemes that are defined.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     *
+     * @return The array of defined schemes; this value may be empty or
+     *         <code>null</code>.
+     */
+    public final Scheme[] getDefinedSchemes() {
+        return arraycast!(Scheme)(definedHandleObjects.toArray());
+    }
+
+    /**
+     * <p>
+     * Returns the active locale for this binding manager. The locale is in the
+     * same format as <code>Locale.getDefault().toString()</code>.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     *
+     * @return The active locale; never <code>null</code>.
+     */
+    public final String getLocale() {
+        return locale;
+    }
+
+    /**
+     * <p>
+     * Returns all of the possible bindings that start with the given trigger
+     * (but are not equal to the given trigger).
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the bindings aren't
+     * currently computed, then this completes in <code>O(n)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @param trigger
+     *            The prefix to look for; must not be <code>null</code>.
+     * @return A map of triggers (<code>TriggerSequence</code>) to bindings (<code>Binding</code>).
+     *         This map may be empty, but it is never <code>null</code>.
+     */
+    public final Map!(Object,Object) getPartialMatches(TriggerSequence trigger) {
+        auto partialMatches = cast(Map!(Object,Object)) getPrefixTable().get(trigger);
+        if (partialMatches is null) {
+            if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object);
+            return EMPTY_MAP;
+        }
+
+        return partialMatches;
+    }
+
+    /**
+     * <p>
+     * Returns the command identifier for the active binding matching this
+     * trigger, if any.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the bindings aren't
+     * currently computed, then this completes in <code>O(n)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @param trigger
+     *            The trigger to match; may be <code>null</code>.
+     * @return The binding that matches, if any; <code>null</code> otherwise.
+     */
+    public final Binding getPerfectMatch(TriggerSequence trigger) {
+        return cast(Binding) getActiveBindings().get(trigger);
+    }
+
+    /**
+     * <p>
+     * Returns the active platform for this binding manager. The platform is in
+     * the same format as <code>DWT.getPlatform()</code>.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     *
+     * @return The active platform; never <code>null</code>.
+     */
+    public final String getPlatform() {
+        return platform;
+    }
+
+    /**
+     * <p>
+     * Returns the prefix table. The caller must not modify the returned map.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the active bindings are
+     * not yet computed, then this completes in <code>O(n)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @return A map of prefixes (<code>TriggerSequence</code>) to a map of
+     *         available completions (possibly <code>null</code>, which means
+     *         there is an exact match). The available completions is a map of
+     *         trigger (<code>TriggerSequence</code>) to binding (<code>Binding</code>).
+     *         This value will never be <code>null</code> but may be empty.
+     */
+    private final Map!(Object,Object) getPrefixTable() {
+        if (prefixTable is null) {
+            recomputeBindings();
+        }
+
+        return prefixTable;
+    }
+
+    /**
+     * <p>
+     * Gets the scheme with the given identifier. If the scheme does not already
+     * exist, then a new (undefined) scheme is created with that identifier.
+     * This guarantees that schemes will remain unique.
+     * </p>
+     * <p>
+     * This method completes in amortized <code>O(1)</code>.
+     * </p>
+     *
+     * @param schemeId
+     *            The identifier for the scheme to retrieve; must not be
+     *            <code>null</code>.
+     * @return A scheme with the given identifier.
+     */
+    public final Scheme getScheme(String schemeId) {
+        checkId(schemeId);
+
+        Scheme scheme = cast(Scheme) handleObjectsById.get(schemeId);
+        if (scheme is null) {
+            scheme = new Scheme(schemeId);
+            handleObjectsById.add(schemeId, scheme);
+            scheme.addSchemeListener(this);
+        }
+
+        return scheme;
+    }
+
+    /**
+     * <p>
+     * Ascends all of the parents of the scheme until no more parents are found.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the height of the context tree.
+     * </p>
+     *
+     * @param schemeId
+     *            The id of the scheme for which the parents should be found;
+     *            may be <code>null</code>.
+     * @return The array of scheme ids (<code>String</code>) starting with
+     *         <code>schemeId</code> and then ascending through its ancestors.
+     */
+    private final String[] getSchemeIds(String schemeId) {
+        auto strings = new ArraySeq!(Object);
+        while (schemeId !is null) {
+            strings.append( stringcast(schemeId));
+            try {
+                schemeId = getScheme(schemeId).getParentId();
+            } catch (NotDefinedException e) {
+                Policy.getLog().log( new Status(
+                    IStatus.ERROR, Policy.JFACE, IStatus.OK,
+                    "Failed ascending scheme parents", //$NON-NLS-1$
+                    e));
+                return null;
+            }
+        }
+
+        return stringcast(strings.toArray());
+    }
+
+    /**
+     * <p>
+     * Returns whether the given trigger sequence is a partial match for the
+     * given sequence.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the bindings aren't
+     * currently computed, then this completes in <code>O(n)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @param trigger
+     *            The sequence which should be the prefix for some binding;
+     *            should not be <code>null</code>.
+     * @return <code>true</code> if the trigger can be found in the active
+     *         bindings; <code>false</code> otherwise.
+     */
+    public final bool isPartialMatch(TriggerSequence trigger) {
+        return (getPrefixTable().get(trigger) !is null);
+    }
+
+    /**
+     * <p>
+     * Returns whether the given trigger sequence is a perfect match for the
+     * given sequence.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>. If the bindings aren't
+     * currently computed, then this completes in <code>O(n)</code>, where
+     * <code>n</code> is the number of bindings.
+     * </p>
+     *
+     * @param trigger
+     *            The sequence which should match exactly; should not be
+     *            <code>null</code>.
+     * @return <code>true</code> if the trigger can be found in the active
+     *         bindings; <code>false</code> otherwise.
+     */
+    public final bool isPerfectMatch(TriggerSequence trigger) {
+        return getActiveBindings().containsKey(trigger);
+    }
+
+    /**
+     * <p>
+     * Tests whether the locale for the binding matches one of the active
+     * locales.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of active locales.
+     * </p>
+     *
+     * @param binding
+     *            The binding with which to test; must not be <code>null</code>.
+     * @return <code>true</code> if the binding's locale matches;
+     *         <code>false</code> otherwise.
+     */
+    private final bool localeMatches(Binding binding) {
+        bool matches = false;
+
+        String locale = binding.getLocale();
+        if (locale is null) {
+            return true; // shortcut a common case
+        }
+
+        for (int i = 0; i < locales.length; i++) {
+            if (Util.opEquals(locales[i], locale)) {
+                matches = true;
+                break;
+            }
+        }
+
+        return matches;
+    }
+
+    /**
+     * <p>
+     * Tests whether the platform for the binding matches one of the active
+     * platforms.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of active platforms.
+     * </p>
+     *
+     * @param binding
+     *            The binding with which to test; must not be <code>null</code>.
+     * @return <code>true</code> if the binding's platform matches;
+     *         <code>false</code> otherwise.
+     */
+    private final bool platformMatches(Binding binding) {
+        bool matches = false;
+
+        String platform = binding.getPlatform();
+        if (platform is null) {
+            return true; // shortcut a common case
+        }
+
+        for (int i = 0; i < platforms.length; i++) {
+            if (Util.opEquals(platforms[i], platform)) {
+                matches = true;
+                break;
+            }
+        }
+
+        return matches;
+    }
+
+    /**
+     * <p>
+     * This recomputes the bindings based on changes to the state of the world.
+     * This computation can be triggered by changes to contexts, the active
+     * scheme, the locale, or the platform. This method tries to use the cache
+     * of pre-computed bindings, if possible. When this method completes,
+     * <code>activeBindings</code> will be set to the current set of bindings
+     * and <code>cachedBindings</code> will contain an instance of
+     * <code>CachedBindingSet</code> representing these bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n+pn)</code>, where <code>n</code>
+     * is the number of bindings, and <code>p</code> is the average number of
+     * triggers in a trigger sequence.
+     * </p>
+     */
+    private final void recomputeBindings() {
+        if (bindings is null) {
+            // Not yet initialized. This is happening too early. Do nothing.
+            if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object);
+            setActiveBindings(EMPTY_MAP, EMPTY_MAP,
+                    EMPTY_MAP, EMPTY_MAP);
+            return;
+        }
+
+        // Figure out the current state.
+        auto activeContextIds = new HashSet!(Object);
+        foreach( e; contextManager.getActiveContextIds()){
+            activeContextIds.add(stringcast(e));
+        }
+        auto activeContextTree = createFilteredContextTreeFor(activeContextIds);
+
+        // Build a cached binding set for that state.
+        CachedBindingSet bindingCache = new CachedBindingSet(
+                activeContextTree, locales, platforms, activeSchemeIds);
+
+        /*
+         * Check if the cached binding set already exists. If so, simply set the
+         * active bindings and return.
+         */
+        CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings
+                .get(bindingCache);
+        if (existingCache is null) {
+            existingCache = bindingCache;
+            cachedBindings.add(existingCache, existingCache);
+        }
+        auto commandIdsByTrigger = existingCache.getBindingsByTrigger();
+        if (commandIdsByTrigger !is null) {
+            if (DEBUG) {
+                Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+            setActiveBindings(commandIdsByTrigger, existingCache
+                    .getTriggersByCommandId(), existingCache.getPrefixTable(),
+                    existingCache.getConflictsByTrigger());
+            return;
+        }
+
+        // There is no cached entry for this.
+        if (DEBUG) {
+            Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$
+        }
+
+        // Compute the active bindings.
+        commandIdsByTrigger = new HashMap!(Object,Object);
+        auto triggersByParameterizedCommand = new HashMap!(Object,Object);
+        auto conflictsByTrigger = new HashMap!(Object,Object);
+        computeBindings(activeContextTree, commandIdsByTrigger,
+                triggersByParameterizedCommand, conflictsByTrigger);
+        existingCache.setBindingsByTrigger(commandIdsByTrigger);
+        existingCache.setTriggersByCommandId(triggersByParameterizedCommand);
+        existingCache.setConflictsByTrigger(conflictsByTrigger);
+        setActiveBindings(commandIdsByTrigger, triggersByParameterizedCommand,
+                buildPrefixTable(commandIdsByTrigger),
+                conflictsByTrigger);
+        existingCache.setPrefixTable(prefixTable);
+    }
+
+    /**
+     * <p>
+     * Remove the specific binding by identity. Does nothing if the binding is
+     * not in the manager.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @param binding
+     *            The binding to be removed; must not be <code>null</code>.
+     * @since 3.2
+     */
+    public final void removeBinding(Binding binding) {
+        if (bindings is null || bindings.length < 1) {
+            return;
+        }
+
+        Binding[] newBindings = new Binding[bindings.length];
+        bool bindingsChanged = false;
+        int index = 0;
+        for (int i = 0; i < bindingCount; i++) {
+            Binding b = bindings[i];
+            if (b is binding) {
+                bindingsChanged = true;
+            } else {
+                newBindings[index++] = b;
+            }
+        }
+
+        if (bindingsChanged) {
+            this.bindings = newBindings;
+            bindingCount = index;
+            clearCache();
+        }
+    }
+
+    /**
+     * <p>
+     * Removes a listener from this binding manager.
+     * </p>
+     * <p>
+     * This method completes in amortized <code>O(1)</code>.
+     * </p>
+     *
+     * @param listener
+     *            The listener to be removed; must not be <code>null</code>.
+     */
+    public final void removeBindingManagerListener(
+            IBindingManagerListener listener) {
+        removeListenerObject(cast(Object)listener);
+    }
+
+    /**
+     * <p>
+     * Removes any binding that matches the given values -- regardless of
+     * command identifier.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @param sequence
+     *            The sequence to match; may be <code>null</code>.
+     * @param schemeId
+     *            The scheme id to match; may be <code>null</code>.
+     * @param contextId
+     *            The context id to match; may be <code>null</code>.
+     * @param locale
+     *            The locale to match; may be <code>null</code>.
+     * @param platform
+     *            The platform to match; may be <code>null</code>.
+     * @param windowManager
+     *            The window manager to match; may be <code>null</code>. TODO
+     *            Currently ignored.
+     * @param type
+     *            The type to look for.
+     *
+     */
+    public final void removeBindings(TriggerSequence sequence,
+            String schemeId, String contextId, String locale,
+            String platform, String windowManager, int type) {
+        if ((bindings is null) || (bindingCount < 1)) {
+            return;
+        }
+
+        Binding[] newBindings = new Binding[bindings.length];
+        bool bindingsChanged = false;
+        int index = 0;
+        for (int i = 0; i < bindingCount; i++) {
+            Binding binding = bindings[i];
+            bool equals = true;
+            equals &= Util.opEquals(sequence, binding.getTriggerSequence());
+            equals &= Util.opEquals(schemeId, binding.getSchemeId());
+            equals &= Util.opEquals(contextId, binding.getContextId());
+            equals &= Util.opEquals(locale, binding.getLocale());
+            equals &= Util.opEquals(platform, binding.getPlatform());
+            equals &= (type is binding.getType());
+            if (equals) {
+                bindingsChanged = true;
+            } else {
+                newBindings[index++] = binding;
+            }
+        }
+
+        if (bindingsChanged) {
+            this.bindings = newBindings;
+            bindingCount = index;
+            clearCache();
+        }
+    }
+
+    /**
+     * <p>
+     * Attempts to remove deletion markers from the collection of bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @param bindings
+     *            The bindings from which the deleted items should be removed.
+     *            This array should not be <code>null</code>, but may be
+     *            empty.
+     * @return The array of bindings with the deletions removed; never
+     *         <code>null</code>, but may be empty. Contains only instances
+     *         of <code>Binding</code>.
+     */
+    private final Binding[] removeDeletions(Binding[] bindings) {
+        auto deletions = new HashMap!(Object,Object);
+        Binding[] bindingsCopy = new Binding[bindingCount];
+        System.arraycopy(bindings, 0, bindingsCopy, 0, bindingCount);
+        int deletedCount = 0;
+
+        // Extract the deletions.
+        for (int i = 0; i < bindingCount; i++) {
+            Binding binding = bindingsCopy[i];
+            if ((binding.getParameterizedCommand() is null)
+                    && (localeMatches(binding)) && (platformMatches(binding))) {
+                TriggerSequence sequence = binding.getTriggerSequence();
+                Object currentValue = deletions.get(sequence);
+                if (cast(Binding)currentValue ) {
+                    auto collection = new ArraySeq!(Object);
+                    collection.append(currentValue);
+                    collection.append(binding);
+                    deletions.add(sequence, collection);
+                } else if ( auto collection = cast(Seq!(Object))currentValue ) {
+                    collection.append(binding);
+                } else {
+                    deletions.add(sequence, binding);
+                }
+                bindingsCopy[i] = null;
+                deletedCount++;
+            }
+        }
+
+        if (DEBUG) {
+            Tracing.printTrace("BINDINGS", Format("There are {} deletion markers", deletions.size()) //$NON-NLS-1$ //$NON-NLS-2$
+                    ); //$NON-NLS-1$
+        }
+
+        // Remove the deleted items.
+        for (int i = 0; i < bindingCount; i++) {
+            Binding binding = bindingsCopy[i];
+            if (binding !is null) {
+                Object deletion = deletions.get(binding
+                        .getTriggerSequence());
+                if (cast(Binding)deletion ) {
+                    if ((cast(Binding) deletion).deletes(binding)) {
+                        bindingsCopy[i] = null;
+                        deletedCount++;
+                    }
+
+                } else if (cast(Seq!(Object))deletion ) {
+                    Seq!(Object) collection = cast(Seq!(Object)) deletion;
+                    foreach( e; collection){
+//                     Iterator iterator = collection.iterator();
+//                     while (iterator.hasNext()) {
+                        Object deletionBinding = e;//iterator.next();
+                        if (cast(Binding)deletionBinding ) {
+                            if ((cast(Binding) deletionBinding).deletes(binding)) {
+                                bindingsCopy[i] = null;
+                                deletedCount++;
+                                break;
+                            }
+                        }
+                    }
+
+                }
+            }
+        }
+
+        // Compact the array.
+        Binding[] returnValue = new Binding[bindingCount - deletedCount];
+        int index = 0;
+        for (int i = 0; i < bindingCount; i++) {
+            Binding binding = bindingsCopy[i];
+            if (binding !is null) {
+                returnValue[index++] = binding;
+            }
+        }
+
+        return returnValue;
+    }
+
+    /**
+     * <p>
+     * Attempts to resolve the conflicts for the given bindings.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @param bindings
+     *            The bindings which all match the same trigger sequence; must
+     *            not be <code>null</code>, and should contain at least two
+     *            items. This collection should only contain instances of
+     *            <code>Binding</code> (i.e., no <code>null</code> values).
+     * @param activeContextTree
+     *            The tree of contexts to be used for all of the comparison. All
+     *            of the keys should be active context identifiers (i.e., never
+     *            <code>null</code>). The values will be their parents (i.e.,
+     *            possibly <code>null</code>). Both keys and values are
+     *            context identifiers (<code>String</code>). This map should
+     *            never be empty, and must never be <code>null</code>.
+     * @return The binding which best matches the current state. If there is a
+     *         tie, then return <code>null</code>.
+     */
+    private final Binding resolveConflicts(View!(Object) bindings,
+            Map!(Object,Object) activeContextTree) {
+        /*
+         * This flag is used to indicate when the bestMatch binding conflicts
+         * with another binding. We keep the best match binding so that we know
+         * if we find a better binding. However, if we don't find a better
+         * binding, then we known to return null.
+         */
+        bool conflict = false;
+
+//         Iterator bindingItr = bindings.iterator();
+        Binding bestMatch;
+        bool first = true;
+        foreach( b; bindings ){
+            if( first ){
+                first = false;
+                bestMatch = cast(Binding) b;//bindingItr.next();
+                continue;
+            }
+
+        /*
+         * Iterate over each binding and compare it with the best match. If a
+         * better match is found, then replace the best match and set the
+         * conflict flag to false. If a conflict is found, then leave the best
+         * match and set the conflict flag. Otherwise, just continue.
+         */
+//         while (bindingItr.hasNext()) {
+            Binding current = cast(Binding) b;//bindingItr.next();
+
+            /*
+             * SCHEME: Test whether the current is in a child scheme. Bindings
+             * defined in a child scheme will always take priority over bindings
+             * defined in a parent scheme.
+             */
+            String currentSchemeId = current.getSchemeId();
+            String bestSchemeId = bestMatch.getSchemeId();
+            int compareTo = compareSchemes(bestSchemeId, currentSchemeId);
+            if (compareTo > 0) {
+                bestMatch = current;
+                conflict = false;
+            }
+            if (compareTo !is 0) {
+                continue;
+            }
+
+            /*
+             * CONTEXTS: Check for context superiority. Bindings defined in a
+             * child context will take priority over bindings defined in a
+             * parent context -- assuming that the schemes lead to a conflict.
+             */
+            String currentContext = current.getContextId();
+            String bestContext = bestMatch.getContextId();
+            if (!currentContext.equals(bestContext)) {
+                bool goToNextBinding = false;
+
+                // Ascend the current's context tree.
+                String contextPointer = currentContext;
+                while (contextPointer !is null) {
+                    if (contextPointer.equals(bestContext)) {
+                        // the current wins
+                        bestMatch = current;
+                        conflict = false;
+                        goToNextBinding = true;
+                        break;
+                    }
+                    contextPointer = stringcast(activeContextTree
+                            .get(stringcast(contextPointer)));
+                }
+
+                // Ascend the best match's context tree.
+                contextPointer = bestContext;
+                while (contextPointer !is null) {
+                    if (contextPointer.equals(currentContext)) {
+                        // the best wins
+                        goToNextBinding = true;
+                        break;
+                    }
+                    contextPointer = stringcast( activeContextTree
+                            .get(stringcast(contextPointer)));
+                }
+
+                if (goToNextBinding) {
+                    continue;
+                }
+            }
+
+            /*
+             * TYPE: Test for type superiority.
+             */
+            if (current.getType() > bestMatch.getType()) {
+                bestMatch = current;
+                conflict = false;
+                continue;
+            } else if (bestMatch.getType() > current.getType()) {
+                continue;
+            }
+
+            // We could not resolve the conflict between these two.
+            conflict = true;
+        }
+
+        // If the best match represents a conflict, then return null.
+        if (conflict) {
+            return null;
+        }
+
+        // Otherwise, we have a winner....
+        return bestMatch;
+    }
+
+    /**
+     * <p>
+     * Notifies this manager that a scheme has changed. This method is intended
+     * for internal use only.
+     * </p>
+     * <p>
+     * This method calls out to listeners, and so the time it takes to complete
+     * is dependent on third-party code.
+     * </p>
+     *
+     * @param schemeEvent
+     *            An event describing the change in the scheme.
+     */
+    public final void schemeChanged(SchemeEvent schemeEvent) {
+        if (schemeEvent.isDefinedChanged()) {
+            Scheme scheme = schemeEvent.getScheme();
+            bool schemeIdAdded = scheme.isDefined();
+            bool activeSchemeChanged = false;
+            if (schemeIdAdded) {
+                definedHandleObjects.add(scheme);
+            } else {
+                definedHandleObjects.remove(scheme);
+
+                if (activeScheme is scheme) {
+                    activeScheme = null;
+                    activeSchemeIds = null;
+                    activeSchemeChanged = true;
+
+                    // Clear the binding solution.
+                    clearSolution();
+                }
+            }
+
+            if (isListenerAttached()) {
+                fireBindingManagerChanged(new BindingManagerEvent(this, false,
+                        null, activeSchemeChanged, scheme, schemeIdAdded,
+                        false, false));
+            }
+        }
+    }
+
+    /**
+     * Sets the active bindings and the prefix table. This ensures that the two
+     * values change at the same time, and that any listeners are notified
+     * appropriately.
+     *
+     * @param activeBindings
+     *            This is a map of triggers ( <code>TriggerSequence</code>)
+     *            to bindings (<code>Binding</code>). This value will only
+     *            be <code>null</code> if the active bindings have not yet
+     *            been computed. Otherwise, this value may be empty.
+     * @param activeBindingsByCommandId
+     *            This is a map of fully-parameterized commands (<code>ParameterizedCommand</code>)
+     *            to triggers ( <code>TriggerSequence</code>). This value
+     *            will only be <code>null</code> if the active bindings have
+     *            not yet been computed. Otherwise, this value may be empty.
+     * @param prefixTable
+     *            A map of prefixes (<code>TriggerSequence</code>) to a map
+     *            of available completions (possibly <code>null</code>, which
+     *            means there is an exact match). The available completions is a
+     *            map of trigger (<code>TriggerSequence</code>) to binding (<code>Binding</code>).
+     *            This value may be <code>null</code> if there is no existing
+     *            solution.
+     */
+    private final void setActiveBindings(Map!(Object,Object) activeBindings,
+            Map!(Object,Object) activeBindingsByCommandId, Map!(Object,Object) prefixTable,
+            Map!(Object,Object) conflicts) {
+        this.activeBindings = activeBindings;
+        Map!(Object,Object) previousBindingsByParameterizedCommand = this.activeBindingsByParameterizedCommand;
+        this.activeBindingsByParameterizedCommand = activeBindingsByCommandId;
+        this.prefixTable = prefixTable;
+        InternalPolicy.currentConflicts = conflicts;
+
+        fireBindingManagerChanged(new BindingManagerEvent(this, true,
+                previousBindingsByParameterizedCommand, false, null, false,
+                false, false));
+    }
+
+    /**
+     * <p>
+     * Selects one of the schemes as the active scheme. This scheme must be
+     * defined.
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the height of the context tree.
+     * </p>
+     *
+     * @param scheme
+     *            The scheme to become active; must not be <code>null</code>.
+     * @throws NotDefinedException
+     *             If the given scheme is currently undefined.
+     */
+    public final void setActiveScheme(Scheme scheme) {
+        if (scheme is null) {
+            throw new NullPointerException("Cannot activate a null scheme"); //$NON-NLS-1$
+        }
+
+        if ((scheme is null) || (!scheme.isDefined())) {
+            throw new NotDefinedException(
+                    "Cannot activate an undefined scheme. " //$NON-NLS-1$
+                            ~ scheme.getId());
+        }
+
+        if (Util.opEquals(activeScheme, scheme)) {
+            return;
+        }
+
+        activeScheme = scheme;
+        activeSchemeIds = getSchemeIds(activeScheme.getId());
+        clearSolution();
+        fireBindingManagerChanged(new BindingManagerEvent(this, false, null,
+                true, null, false, false, false));
+    }
+
+    /**
+     * <p>
+     * Changes the set of bindings for this binding manager. Changing the set of
+     * bindings all at once ensures that: (1) duplicates are removed; and (2)
+     * avoids unnecessary intermediate computations. This method clears the
+     * existing bindings, but does not trigger a recomputation (other method
+     * calls are required to do that).
+     * </p>
+     * <p>
+     * This method completes in <code>O(n)</code>, where <code>n</code> is
+     * the number of bindings.
+     * </p>
+     *
+     * @param bindings
+     *            The new array of bindings; may be <code>null</code>. This
+     *            set is copied into a local data structure.
+     */
+    public final void setBindings(Binding[] bindings) {
+        if (Arrays.equals(this.bindings, bindings)) {
+            return; // nothing has changed
+        }
+
+        if ((bindings is null) || (bindings.length is 0)) {
+            this.bindings = null;
+            bindingCount = 0;
+        } else {
+            int bindingsLength = bindings.length;
+            this.bindings = new Binding[bindingsLength];
+            System.arraycopy(bindings, 0, this.bindings, 0, bindingsLength);
+            bindingCount = bindingsLength;
+        }
+        clearCache();
+    }
+
+    /**
+     * <p>
+     * Changes the locale for this binding manager. The locale can be used to
+     * provide locale-specific bindings. If the locale is different than the
+     * current locale, this will force a recomputation of the bindings. The
+     * locale is in the same format as
+     * <code>Locale.getDefault().toString()</code>.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     *
+     * @param locale
+     *            The new locale; must not be <code>null</code>.
+     * @see Locale#getDefault()
+     */
+    public final void setLocale(String locale) {
+        if (locale is null) {
+            throw new NullPointerException("The locale cannot be null"); //$NON-NLS-1$
+        }
+
+        if (!Util.opEquals(this.locale, locale)) {
+            this.locale = locale;
+            this.locales = expand(locale, LOCALE_SEPARATOR);
+            clearSolution();
+            fireBindingManagerChanged(new BindingManagerEvent(this, false,
+                    null, false, null, false, true, false));
+        }
+    }
+
+    /**
+     * <p>
+     * Changes the platform for this binding manager. The platform can be used
+     * to provide platform-specific bindings. If the platform is different than
+     * the current platform, then this will force a recomputation of the
+     * bindings. The locale is in the same format as
+     * <code>DWT.getPlatform()</code>.
+     * </p>
+     * <p>
+     * This method completes in <code>O(1)</code>.
+     * </p>
+     *
+     * @param platform
+     *            The new platform; must not be <code>null</code>.
+     * @see dwt.DWT#getPlatform()
+     */
+    public final void setPlatform(String platform) {
+        if (platform is null) {
+            throw new NullPointerException("The platform cannot be null"); //$NON-NLS-1$
+        }
+
+        if (!Util.opEquals(this.platform, platform)) {
+            this.platform = platform;
+            this.platforms = expand(platform, Util.ZERO_LENGTH_STRING);
+            clearSolution();
+            fireBindingManagerChanged(new BindingManagerEvent(this, false,
+                    null, false, null, false, false, true));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/BindingManagerEvent.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.BindingManagerEvent;
+
+import dwtx.jface.bindings.BindingManager;
+import dwtx.jface.bindings.Scheme;
+import dwtx.jface.bindings.TriggerSequence;
+
+// import tango.util.collection.Seq!(Object);
+import tango.util.collection.model.Map;
+import tango.util.collection.model.Seq;
+
+import dwtx.core.commands.ParameterizedCommand;
+import dwtx.core.commands.common.AbstractBitSetEvent;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+
+/**
+ * An instance of this class describes changes to an instance of
+ * <code>BindingManager</code>.
+ * <p>
+ * This class is not intended to be extended by clients.
+ * </p>
+ *
+ * @since 3.1
+ * @see IBindingManagerListener#bindingManagerChanged(BindingManagerEvent)
+ */
+public final class BindingManagerEvent : AbstractBitSetEvent {
+
+    /**
+     * The bit used to represent whether the map of active bindings has changed.
+     */
+    private static const int CHANGED_ACTIVE_BINDINGS = 1;
+
+    /**
+     * The bit used to represent whether the active scheme has changed.
+     */
+    private static const int CHANGED_ACTIVE_SCHEME = 1 << 1;
+
+    /**
+     * The bit used to represent whether the active locale has changed.
+     */
+    private static const int CHANGED_LOCALE = 1 << 2;
+
+    /**
+     * The bit used to represent whether the active platform has changed.
+     */
+    private static const int CHANGED_PLATFORM = 1 << 3;
+
+    /**
+     * The bit used to represent whether the scheme's defined state has changed.
+     */
+    private static const int CHANGED_SCHEME_DEFINED = 1 << 4;
+
+    /**
+     * The binding manager that has changed; this value is never
+     * <code>null</code>.
+     */
+    private const BindingManager manager;
+
+    /**
+     * The map of triggers (<code>Seq!(Object)</code> of
+     * <code>TriggerSequence</code>) by parameterized command (<code>ParameterizedCommand</code>)
+     * before the change occurred. This map may be empty and it may be
+     * <code>null</code>.
+     */
+    private const Map!(Object,Object) previousTriggersByParameterizedCommand;
+
+    /**
+     * The scheme that became defined or undefined. This value may be
+     * <code>null</code> if no scheme changed its defined state.
+     */
+    private const Scheme scheme;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param manager
+     *            the instance of the binding manager that changed; must not be
+     *            <code>null</code>.
+     * @param activeBindingsChanged
+     *            Whether the active bindings have changed.
+     * @param previousTriggersByParameterizedCommand
+     *            The map of triggers (<code>TriggerSequence</code>) by
+     *            fully-parameterized command (<code>ParameterizedCommand</code>)
+     *            before the change occured. This map may be <code>null</code>
+     *            or empty.
+     * @param activeSchemeChanged
+     *            true, iff the active scheme changed.
+     * @param scheme
+     *            The scheme that became defined or undefined; <code>null</code>
+     *            if no scheme changed state.
+     * @param schemeDefined
+     *            <code>true</code> if the given scheme became defined;
+     *            <code>false</code> otherwise.
+     * @param localeChanged
+     *            <code>true</code> iff the active locale changed
+     * @param platformChanged
+     *            <code>true</code> iff the active platform changed
+     */
+    public this(BindingManager manager,
+            bool activeBindingsChanged,
+            Map!(Object,Object) previousTriggersByParameterizedCommand,
+            bool activeSchemeChanged, Scheme scheme,
+            bool schemeDefined, bool localeChanged,
+            bool platformChanged) {
+        if (manager is null) {
+            throw new NullPointerException(
+                    "A binding manager event needs a binding manager"); //$NON-NLS-1$
+        }
+        this.manager = manager;
+
+        if (schemeDefined && (scheme is null)) {
+            throw new NullPointerException(
+                    "If a scheme changed defined state, then there should be a scheme identifier"); //$NON-NLS-1$
+        }
+        this.scheme = scheme;
+
+        this.previousTriggersByParameterizedCommand = previousTriggersByParameterizedCommand;
+
+        if (activeBindingsChanged) {
+            changedValues |= CHANGED_ACTIVE_BINDINGS;
+        }
+        if (activeSchemeChanged) {
+            changedValues |= CHANGED_ACTIVE_SCHEME;
+        }
+        if (localeChanged) {
+            changedValues |= CHANGED_LOCALE;
+        }
+        if (platformChanged) {
+            changedValues |= CHANGED_PLATFORM;
+        }
+        if (schemeDefined) {
+            changedValues |= CHANGED_SCHEME_DEFINED;
+        }
+    }
+
+    /**
+     * Returns the instance of the manager that changed.
+     *
+     * @return the instance of the manager that changed. Guaranteed not to be
+     *         <code>null</code>.
+     */
+    public final BindingManager getManager() {
+        return manager;
+    }
+
+    /**
+     * Returns the scheme that changed.
+     *
+     * @return The changed scheme
+     */
+    public final Scheme getScheme() {
+        return scheme;
+    }
+
+    /**
+     * Returns whether the active bindings have changed.
+     *
+     * @return <code>true</code> if the active bindings have changed;
+     *         <code>false</code> otherwise.
+     */
+    public final bool isActiveBindingsChanged() {
+        return ((changedValues & CHANGED_ACTIVE_BINDINGS) !is 0);
+    }
+
+    /**
+     * Computes whether the active bindings have changed for a given command
+     * identifier.
+     *
+     * @param parameterizedCommand
+     *            The fully-parameterized command whose bindings might have
+     *            changed; must not be <code>null</code>.
+     * @return <code>true</code> if the active bindings have changed for the
+     *         given command identifier; <code>false</code> otherwise.
+     */
+    public final bool isActiveBindingsChangedFor(
+            ParameterizedCommand parameterizedCommand) {
+        TriggerSequence[] currentBindings = manager
+                .getActiveBindingsFor(parameterizedCommand);
+        TriggerSequence[] previousBindings;
+        if (previousTriggersByParameterizedCommand !is null) {
+            Seq!(Object) previousBindingCollection = cast(Seq!(Object)) previousTriggersByParameterizedCommand
+                    .get(parameterizedCommand);
+            if (previousBindingCollection is null) {
+                previousBindings = null;
+            } else {
+                previousBindings = cast(TriggerSequence[])previousBindingCollection.toArray();
+            }
+        } else {
+            previousBindings = null;
+        }
+
+        return !Util.opEquals(currentBindings, previousBindings);
+    }
+
+    /**
+     * Returns whether or not the active scheme changed.
+     *
+     * @return true, iff the active scheme property changed.
+     */
+    public bool isActiveSchemeChanged() {
+        return ((changedValues & CHANGED_ACTIVE_SCHEME) !is 0);
+    }
+
+    /**
+     * Returns whether the locale has changed
+     *
+     * @return <code>true</code> if the locale changed; <code>false</code>
+     *         otherwise.
+     */
+    public bool isLocaleChanged() {
+        return ((changedValues & CHANGED_LOCALE) !is 0);
+    }
+
+    /**
+     * Returns whether the platform has changed
+     *
+     * @return <code>true</code> if the platform changed; <code>false</code>
+     *         otherwise.
+     */
+    public bool isPlatformChanged() {
+        return ((changedValues & CHANGED_PLATFORM) !is 0);
+    }
+
+    /**
+     * Returns whether the list of defined scheme identifiers has changed.
+     *
+     * @return <code>true</code> if the list of scheme identifiers has
+     *         changed; <code>false</code> otherwise.
+     */
+    public final bool isSchemeChanged() {
+        return (scheme !is null);
+    }
+
+    /**
+     * Returns whether or not the scheme became defined
+     *
+     * @return <code>true</code> if the scheme became defined.
+     */
+    public final bool isSchemeDefined() {
+        return (((changedValues & CHANGED_SCHEME_DEFINED) !is 0) && (scheme !is null));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/CachedBindingSet.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,372 @@
+/*******************************************************************************
+ * 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.jface.bindings.CachedBindingSet;
+
+import dwtx.jface.bindings.TriggerSequence;
+import dwtx.jface.bindings.Binding;
+
+import tango.util.collection.model.Map;
+
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A resolution of bindings for a given state. To see if we already have a
+ * cached binding set, just create one of these binding sets and then look it up
+ * in a map. If it is not already there, then add it and set the cached binding
+ * resolution.
+ * </p>
+ *
+ * @since 3.1
+ */
+final class CachedBindingSet {
+
+    /**
+     * A factor for computing the hash code for all cached binding sets.
+     */
+    private const static int HASH_FACTOR = 89;
+
+    /**
+     * <p>
+     * A representation of the tree of active contexts at the time this cached
+     * binding set was computed. It is a map of context id (<code>String</code>)
+     * to context id (<code>String</code>). Each key represents one of the
+     * active contexts or one of its ancestors, while each value represents its
+     * parent. This is a way of perserving information about what the hierarchy
+     * looked like.
+     * </p>
+     * <p>
+     * This value will be <code>null</code> if the contexts were disregarded
+     * in the computation. It may also be empty. All of the keys are guaranteed
+     * to be non- <code>null</code>, but the values can be <code>null</code>
+     * (i.e., no parent).
+     * </p>
+     */
+    private const Map!(Object,Object) activeContextTree;
+
+    /**
+     * The map representing the resolved state of the bindings. This is a map of
+     * a trigger (<code>TriggerSequence</code>) to binding (<code>Binding</code>).
+     * This value may be <code>null</code> if it has not yet been initialized.
+     */
+    private Map!(Object,Object) bindingsByTrigger = null;
+
+    /**
+     * A map of triggers to collections of bindings. If this binding set
+     * contains conflicts, they are logged here.
+     *
+     * @since 3.3
+     */
+    private Map!(Object,Object) conflictsByTrigger = null;
+
+    /**
+     * The hash code for this object. This value is computed lazily, and marked
+     * as invalid when one of the values on which it is based changes.
+     */
+    private /+transient+/ int hashCode;
+
+    /**
+     * Whether <code>hashCode</code> still contains a valid value.
+     */
+    private /+transient+/ bool hashCodeComputed = false;
+
+    /**
+     * <p>
+     * The list of locales that were active at the time this binding set was
+     * computed. This list starts with the most specific representation of the
+     * locale, and moves to more general representations. For example, this
+     * array might look like ["en_US", "en", "", null].
+     * </p>
+     * <p>
+     * This value will never be <code>null</code>, and it will never be
+     * empty. It must contain at least one element, but its elements can be
+     * <code>null</code>.
+     * </p>
+     */
+    private const String[] locales;
+
+    /**
+     * <p>
+     * The list of platforms that were active at the time this binding set was
+     * computed. This list starts with the most specific representation of the
+     * platform, and moves to more general representations. For example, this
+     * array might look like ["gtk", "", null].
+     * </p>
+     * <p>
+     * This value will never be <code>null</code>, and it will never be
+     * empty. It must contain at least one element, but its elements can be
+     * <code>null</code>.
+     * </p>
+     */
+    private const String[] platforms;
+
+    /**
+     * A map of prefixes (<code>TriggerSequence</code>) to a map of
+     * available completions (possibly <code>null</code>, which means there
+     * is an exact match). The available completions is a map of trigger (<code>TriggerSequence</code>)
+     * to command identifier (<code>String</code>). This value is
+     * <code>null</code> if it has not yet been initialized.
+     */
+    private Map!(Object,Object) prefixTable = null;
+
+    /**
+     * <p>
+     * The list of schemes that were active at the time this binding set was
+     * computed. This list starts with the active scheme, and then continues
+     * with all of its ancestors -- in order. For example, this might look like
+     * ["emacs", "default"].
+     * </p>
+     * <p>
+     * This value will never be <code>null</code>, and it will never be
+     * empty. It must contain at least one element. Its elements cannot be
+     * <code>null</code>.
+     * </p>
+     */
+    private const String[] schemeIds;
+
+    /**
+     * The map representing the resolved state of the bindings. This is a map of
+     * a command id (<code>String</code>) to triggers (<code>Collection</code>
+     * of <code>TriggerSequence</code>). This value may be <code>null</code>
+     * if it has not yet been initialized.
+     */
+    private Map!(Object,Object) triggersByCommandId = null;
+
+    /**
+     * Constructs a new instance of <code>CachedBindingSet</code>.
+     *
+     * @param activeContextTree
+     *            The set of context identifiers that were active when this
+     *            binding set was calculated; may be empty. If it is
+     *            <code>null</code>, then the contexts were disregarded in
+     *            the computation. This is a map of context id (
+     *            <code>String</code>) to parent context id (
+     *            <code>String</code>). This is a way of caching the look of
+     *            the context tree at the time the binding set was computed.
+     * @param locales
+     *            The locales that were active when this binding set was
+     *            calculated. The first element is the currently active locale,
+     *            and it is followed by increasingly more general locales. This
+     *            must not be <code>null</code> and must contain at least one
+     *            element. The elements can be <code>null</code>, though.
+     * @param platforms
+     *            The platform that were active when this binding set was
+     *            calculated. The first element is the currently active
+     *            platform, and it is followed by increasingly more general
+     *            platforms. This must not be <code>null</code> and must
+     *            contain at least one element. The elements can be
+     *            <code>null</code>, though.
+     * @param schemeIds
+     *            The scheme that was active when this binding set was
+     *            calculated, followed by its ancestors. This may be
+     *            <code>null</code or empty. The
+     *            elements cannot be <code>null</code>.
+     */
+    this(Map!(Object,Object) activeContextTree, String[] locales,
+            String[] platforms, String[] schemeIds) {
+        if (locales is null) {
+            throw new NullPointerException("The locales cannot be null."); //$NON-NLS-1$
+        }
+
+        if (locales.length is 0) {
+            throw new NullPointerException("The locales cannot be empty."); //$NON-NLS-1$
+        }
+
+        if (platforms is null) {
+            throw new NullPointerException("The platforms cannot be null."); //$NON-NLS-1$
+        }
+
+        if (platforms.length is 0) {
+            throw new NullPointerException("The platforms cannot be empty."); //$NON-NLS-1$
+        }
+
+        this.activeContextTree = activeContextTree;
+        this.locales = locales;
+        this.platforms = platforms;
+        this.schemeIds = schemeIds;
+    }
+
+    /**
+     * Compares this binding set with another object. The objects will be equal
+     * if they are both instance of <code>CachedBindingSet</code> and have
+     * equivalent values for all of their properties.
+     *
+     * @param object
+     *            The object with which to compare; may be <code>null</code>.
+     * @return <code>true</code> if they are both instances of
+     *         <code>CachedBindingSet</code> and have the same values for all
+     *         of their properties; <code>false</code> otherwise.
+     */
+    public final override int opEquals(Object object) {
+        if (!(cast(CachedBindingSet)object )) {
+            return false;
+        }
+
+        CachedBindingSet other = cast(CachedBindingSet) object;
+
+        if (!Util.opEquals(cast(Object)activeContextTree, cast(Object)other.activeContextTree)) {
+            return false;
+        }
+        if (!Util.opEquals(locales, other.locales)) {
+            return false;
+        }
+        if (!Util.opEquals(platforms, other.platforms)) {
+            return false;
+        }
+        return Util.opEquals(schemeIds, other.schemeIds);
+    }
+
+    /**
+     * Returns the map of command identifiers indexed by trigger sequence.
+     *
+     * @return A map of triggers (<code>TriggerSequence</code>) to bindings (<code>Binding</code>).
+     *         This value may be <code>null</code> if this was not yet
+     *         initialized.
+     */
+    final Map!(Object,Object) getBindingsByTrigger() {
+        return bindingsByTrigger;
+    }
+
+    /**
+     * Returns a map of conflicts for this set of contexts.
+     *
+     * @return A map of trigger to a collection of Bindings. May be
+     *         <code>null</code>.
+     * @since 3.3
+     */
+    final Map!(Object,Object) getConflictsByTrigger() {
+        return conflictsByTrigger;
+    }
+
+    /**
+     * Returns the map of prefixes to a map of trigger sequence to command
+     * identifiers.
+     *
+     * @return A map of prefixes (<code>TriggerSequence</code>) to a map of
+     *         available completions (possibly <code>null</code>, which means
+     *         there is an exact match). The available completions is a map of
+     *         trigger (<code>TriggerSequence</code>) to command identifier (<code>String</code>).
+     *         This value may be <code>null</code> if it has not yet been
+     *         initialized.
+     */
+    final Map!(Object,Object) getPrefixTable() {
+        return prefixTable;
+    }
+
+    /**
+     * Returns the map of triggers indexed by command identifiers.
+     *
+     * @return A map of command identifiers (<code>String</code>) to
+     *         triggers (<code>Collection</code> of
+     *         <code>TriggerSequence</code>). This value may be
+     *         <code>null</code> if this was not yet initialized.
+     */
+    final Map!(Object,Object) getTriggersByCommandId() {
+        return triggersByCommandId;
+    }
+
+    /**
+     * Computes the hash code for this cached binding set. The hash code is
+     * based only on the immutable values. This allows the set to be created and
+     * checked for in a hashed collection <em>before</em> doing any
+     * computation.
+     *
+     * @return The hash code for this cached binding set.
+     */
+    public final override hash_t toHash() {
+        if (!hashCodeComputed) {
+
+            auto HASH_INITIAL = dwt.dwthelper.utils.toHash(CachedBindingSet.classinfo.name );
+            hashCode = HASH_INITIAL;
+            hashCode = hashCode * HASH_FACTOR
+                    + Util.toHash(cast(Object)activeContextTree);
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(locales);
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(platforms);
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(schemeIds);
+            hashCodeComputed = true;
+        }
+
+        return hashCode;
+    }
+
+    /**
+     * Sets the map of command identifiers indexed by trigger.
+     *
+     * @param commandIdsByTrigger
+     *            The map to set; must not be <code>null</code>. This is a
+     *            map of triggers (<code>TriggerSequence</code>) to binding (<code>Binding</code>).
+     */
+    final void setBindingsByTrigger(Map!(Object,Object) commandIdsByTrigger) {
+        if (commandIdsByTrigger is null) {
+            throw new NullPointerException(
+                    "Cannot set a null binding resolution"); //$NON-NLS-1$
+        }
+
+        this.bindingsByTrigger = commandIdsByTrigger;
+    }
+
+    /**
+     * Sets the map of conflicting bindings by trigger.
+     *
+     * @param conflicts
+     *            The map to set; must not be <code>null</code>.
+     * @since 3.3
+     */
+    final void setConflictsByTrigger(Map!(Object,Object) conflicts) {
+        if (conflicts is null) {
+            throw new NullPointerException(
+                    "Cannot set a null binding conflicts"); //$NON-NLS-1$
+        }
+        conflictsByTrigger = conflicts;
+    }
+
+    /**
+     * Sets the map of prefixes to a map of trigger sequence to command
+     * identifiers.
+     *
+     * @param prefixTable
+     *            A map of prefixes (<code>TriggerSequence</code>) to a map
+     *            of available completions (possibly <code>null</code>, which
+     *            means there is an exact match). The available completions is a
+     *            map of trigger (<code>TriggerSequence</code>) to command
+     *            identifier (<code>String</code>). Must not be
+     *            <code>null</code>.
+     */
+    final void setPrefixTable(Map!(Object,Object) prefixTable) {
+        if (prefixTable is null) {
+            throw new NullPointerException("Cannot set a null prefix table"); //$NON-NLS-1$
+        }
+
+        this.prefixTable = prefixTable;
+    }
+
+    /**
+     * Sets the map of triggers indexed by command identifiers.
+     *
+     * @param triggersByCommandId
+     *            The map to set; must not be <code>null</code>. This is a
+     *            map of command identifiers (<code>String</code>) to
+     *            triggers (<code>Collection</code> of
+     *            <code>TriggerSequence</code>).
+     */
+    final void setTriggersByCommandId(Map!(Object,Object) triggersByCommandId) {
+        if (triggersByCommandId is null) {
+            throw new NullPointerException(
+                    "Cannot set a null binding resolution"); //$NON-NLS-1$
+        }
+
+        this.triggersByCommandId = triggersByCommandId;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/IBindingManagerListener.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.jface.bindings.IBindingManagerListener;
+
+import dwtx.jface.bindings.BindingManagerEvent;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * An instance of <code>BindingManagerListener</code> can be used by clients to
+ * receive notification of changes to an instance of
+ * <code>BindingManager</code>.
+ * </p>
+ * <p>
+ * This interface may be implemented by clients.
+ * </p>
+ *
+ * @since 3.1
+ * @see BindingManager#addBindingManagerListener(IBindingManagerListener)
+ * @see dwtx.jface.bindings.BindingManager#addBindingManagerListener(IBindingManagerListener)
+ * @see BindingManagerEvent
+ */
+public interface IBindingManagerListener {
+
+    /**
+     * Notifies that attributes inside an instance of <code>BindingManager</code> have changed.
+     * Specific details are described in the <code>BindingManagerEvent</code>.  Changes in the
+     * binding manager can cause the set of defined or active schemes or bindings to change.
+     *
+     * @param event
+     *            the binding manager event. Guaranteed not to be <code>null</code>.
+     */
+    void bindingManagerChanged(BindingManagerEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/ISchemeListener.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.jface.bindings.ISchemeListener;
+
+import dwtx.jface.bindings.SchemeEvent;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * An instance of <code>ISchemeListener</code> can be used by clients to
+ * receive notification of changes to one or more instances of
+ * <code>IScheme</code>.
+ * </p>
+ * <p>
+ * This interface may be implemented by clients.
+ * </p>
+ *
+ * @since 3.1
+ * @see Scheme#addSchemeListener(ISchemeListener)
+ * @see Scheme#removeSchemeListener(ISchemeListener)
+ * @see SchemeEvent
+ */
+public interface ISchemeListener {
+
+    /**
+     * Notifies that one or more attributes of an instance of
+     * <code>IScheme</code> have changed. Specific details are described in
+     * the <code>SchemeEvent</code>.
+     *
+     * @param schemeEvent
+     *            the scheme event. Guaranteed not to be <code>null</code>.
+     */
+    void schemeChanged(SchemeEvent schemeEvent);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/Scheme.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.Scheme;
+
+import dwtx.jface.bindings.ISchemeListener;
+import dwtx.jface.bindings.SchemeEvent;
+
+import tango.util.collection.HashSet;
+import tango.util.collection.model.Set;
+
+import dwtx.core.commands.common.NamedHandleObject;
+import dwtx.core.commands.common.NotDefinedException;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+import tango.text.convert.Format;
+
+/**
+ * <p>
+ * An instance of <code>IScheme</code> is a handle representing a binding
+ * scheme as defined by the extension point <code>dwtx.ui.bindings</code>.
+ * The identifier of the handle is the identifier of the scheme being represented.
+ * </p>
+ * <p>
+ * An instance of <code>IScheme</code> can be obtained from an instance of
+ * <code>ICommandManager</code> for any identifier, whether or not a scheme
+ * with that identifier is defined in the plugin registry.
+ * </p>
+ * <p>
+ * The handle-based nature of this API allows it to work well with runtime
+ * plugin activation and deactivation. If a scheme is defined, that means that
+ * its corresponding plug-in is active. If the plug-in is then deactivated, the
+ * scheme will still exist but it will be undefined. An attempt to use an
+ * undefined scheme will result in a <code>NotDefinedException</code>
+ * being thrown.
+ * </p>
+ * <p>
+ * This class is not intended to be extended by clients.
+ * </p>
+ *
+ * @since 3.1
+ * @see ISchemeListener
+ * @see dwtx.core.commands.CommandManager
+ */
+public final class Scheme : NamedHandleObject, Comparable {
+
+    /**
+     * The collection of all objects listening to changes on this scheme. This
+     * value is <code>null</code> if there are no listeners.
+     */
+    private Set!(ISchemeListener) listeners = null;
+
+    /**
+     * The parent identifier for this scheme. This is the identifier of the
+     * scheme from which this scheme inherits some of its bindings. This value
+     * can be <code>null</code> if the scheme has no parent.
+     */
+    private String parentId = null;
+
+    /**
+     * Constructs a new instance of <code>Scheme</code> with an identifier.
+     *
+     * @param id
+     *            The identifier to create; must not be <code>null</code>.
+     */
+    this(String id) {
+        super(id);
+    }
+
+    /**
+     * Registers an instance of <code>ISchemeListener</code> to listen for
+     * changes to attributes of this instance.
+     *
+     * @param schemeListener
+     *            the instance of <code>ISchemeListener</code> to register.
+     *            Must not be <code>null</code>. If an attempt is made to
+     *            register an instance of <code>ISchemeListener</code> which
+     *            is already registered with this instance, no operation is
+     *            performed.
+     */
+    public final void addSchemeListener(ISchemeListener schemeListener) {
+        if (schemeListener is null) {
+            throw new NullPointerException("Can't add a null scheme listener."); //$NON-NLS-1$
+        }
+
+        if (listeners is null) {
+            listeners = new HashSet!(ISchemeListener);
+        }
+
+        listeners.add(schemeListener);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public final int compareTo(Object object) {
+        Scheme scheme = cast(Scheme) object;
+        int compareTo = Util.compare(this.id, scheme.id);
+        if (compareTo is 0) {
+            compareTo = Util.compare(this.name, scheme.name);
+            if (compareTo is 0) {
+                compareTo = Util.compare(this.parentId, scheme.parentId);
+                if (compareTo is 0) {
+                    compareTo = Util.compare(this.description,
+                            scheme.description);
+                    if (compareTo is 0) {
+                        compareTo = Util.compare(this.defined, scheme.defined);
+                    }
+                }
+            }
+        }
+
+        return compareTo;
+    }
+
+    /**
+     * <p>
+     * Defines this scheme by giving it a name, and possibly a description and a
+     * parent identifier as well. The defined property for the scheme automatically
+     * becomes <code>true</code>.
+     * </p>
+     * <p>
+     * Notification is sent to all listeners that something has changed.
+     * </p>
+     *
+     * @param name
+     *            The name of this scheme; must not be <code>null</code>.
+     * @param description
+     *            The description for this scheme; may be <code>null</code>.
+     * @param parentId
+     *            The parent identifier for this scheme; may be
+     *            <code>null</code>.
+     */
+    public final void define(String name, String description,
+            String parentId) {
+        if (name is null) {
+            throw new NullPointerException(
+                    "The name of a scheme cannot be null"); //$NON-NLS-1$
+        }
+
+        bool definedChanged = !this.defined;
+        this.defined = true;
+
+        bool nameChanged = !Util.opEquals(this.name, name);
+        this.name = name;
+
+        bool descriptionChanged = !Util.opEquals(this.description,
+                description);
+        this.description = description;
+
+        bool parentIdChanged = !Util.opEquals(this.parentId, parentId);
+        this.parentId = parentId;
+
+        fireSchemeChanged(new SchemeEvent(this, definedChanged, nameChanged,
+                descriptionChanged, parentIdChanged));
+    }
+
+    /**
+     * Notifies all listeners that this scheme has changed. This sends the given
+     * event to all of the listeners, if any.
+     *
+     * @param event
+     *            The event to send to the listeners; must not be
+     *            <code>null</code>.
+     */
+    private final void fireSchemeChanged(SchemeEvent event) {
+        if (event is null) {
+            throw new NullPointerException(
+                    "Cannot send a null event to listeners."); //$NON-NLS-1$
+        }
+
+        if (listeners is null) {
+            return;
+        }
+        foreach( e; listeners ){
+            ISchemeListener listener = cast(ISchemeListener)e;
+            listener.schemeChanged(event);
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the identifier of the parent of the scheme represented by this
+     * handle.
+     * </p>
+     * <p>
+     * Notification is sent to all registered listeners if this attribute
+     * changes.
+     * </p>
+     *
+     * @return the identifier of the parent of the scheme represented by this
+     *         handle. May be <code>null</code>.
+     * @throws NotDefinedException
+     *             if the scheme represented by this handle is not defined.
+     */
+    public final String getParentId() {
+        if (!defined) {
+            throw new NotDefinedException(
+                    "Cannot get the parent identifier from an undefined scheme. "  //$NON-NLS-1$
+                    ~ id);
+        }
+
+        return parentId;
+    }
+
+    /**
+     * Unregisters an instance of <code>ISchemeListener</code> listening for
+     * changes to attributes of this instance.
+     *
+     * @param schemeListener
+     *            the instance of <code>ISchemeListener</code> to unregister.
+     *            Must not be <code>null</code>. If an attempt is made to
+     *            unregister an instance of <code>ISchemeListener</code> which
+     *            is not already registered with this instance, no operation is
+     *            performed.
+     */
+    public final void removeSchemeListener(ISchemeListener schemeListener) {
+        if (schemeListener is null) {
+            throw new NullPointerException("Cannot remove a null listener."); //$NON-NLS-1$
+        }
+
+        if (listeners is null) {
+            return;
+        }
+
+        listeners.remove(schemeListener);
+
+        if (listeners.drained()) {
+            listeners = null;
+        }
+    }
+
+    /**
+     * The string representation of this command -- for debugging purposes only.
+     * This string should not be shown to an end user.
+     *
+     * @return The string representation; never <code>null</code>.
+     */
+    public final String toString() {
+        if (string is null) {
+            string = Format("Scheme({},{},{},{},{})",id,name,description,parentId,defined);
+        }
+        return string;
+    }
+
+    /**
+     * Makes this scheme become undefined. This has the side effect of changing
+     * the name, description and parent identifier to <code>null</code>.
+     * Notification is sent to all listeners.
+     */
+    public final void undefine() {
+        string = null;
+
+        bool definedChanged = defined;
+        defined = false;
+
+        bool nameChanged = name !is null;
+        name = null;
+
+        bool descriptionChanged = description !is null;
+        description = null;
+
+        bool parentIdChanged = parentId !is null;
+        parentId = null;
+
+        fireSchemeChanged(new SchemeEvent(this, definedChanged, nameChanged,
+                descriptionChanged, parentIdChanged));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/SchemeEvent.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.SchemeEvent;
+
+import dwtx.jface.bindings.Scheme;
+
+import dwtx.core.commands.common.AbstractNamedHandleEvent;
+
+import dwt.dwthelper.utils;
+
+/**
+ * An instance of this class describes changes to an instance of
+ * <code>IScheme</code>.
+ * <p>
+ * This class is not intended to be extended by clients.
+ * </p>
+ *
+ * @since 3.1
+ * @see ISchemeListener#schemeChanged(SchemeEvent)
+ */
+public final class SchemeEvent : AbstractNamedHandleEvent {
+
+    /**
+     * The bit used to represent whether the scheme has changed its parent.
+     */
+    private static const int CHANGED_PARENT_ID = LAST_USED_BIT << 1;
+
+    /**
+     * The scheme that has changed; this value is never <code>null</code>.
+     */
+    private const Scheme scheme;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param scheme
+     *            the instance of the interface that changed; must not be
+     *            <code>null</code>.
+     * @param definedChanged
+     *            true, iff the defined property changed.
+     * @param nameChanged
+     *            true, iff the name property changed.
+     * @param descriptionChanged
+     *            <code>true</code> if the description property changed;
+     *            <code>false</code> otherwise.
+     * @param parentIdChanged
+     *            true, iff the parentId property changed.
+     */
+    public this(Scheme scheme, bool definedChanged,
+            bool nameChanged, bool descriptionChanged,
+            bool parentIdChanged) {
+        super(definedChanged, descriptionChanged, nameChanged);
+
+        if (scheme is null) {
+            throw new NullPointerException();
+        }
+        this.scheme = scheme;
+
+        if (parentIdChanged) {
+            changedValues |= CHANGED_PARENT_ID;
+        }
+    }
+
+    /**
+     * Returns the instance of the scheme that changed.
+     *
+     * @return the instance of the scheme that changed. Guaranteed not to be
+     *         <code>null</code>.
+     */
+    public final Scheme getScheme() {
+        return scheme;
+    }
+
+    /**
+     * Returns whether or not the parentId property changed.
+     *
+     * @return true, iff the parentId property changed.
+     */
+    public final bool isParentIdChanged() {
+        return ((changedValues & CHANGED_PARENT_ID) !is 0);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/Trigger.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * 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.jface.bindings.Trigger;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * The abstract class for any object that can be used as a trigger for a binding.
+ * This ensures that trigger conform to certain minimum requirements. Namely, triggers
+ * need to be hashable.
+ * </p>
+ *
+ * @since 3.1
+ */
+public abstract class Trigger : Comparable {
+
+    /**
+     * Tests whether this object is equal to another object. A handle object is
+     * only equal to another trigger with the same properties.
+     *
+     * @param object
+     *            The object with which to compare; may be <code>null</code>.
+     * @return <code>true</code> if the objects are equal; <code>false</code>
+     *         otherwise.
+     */
+    public override abstract int opEquals(Object object);
+
+    /**
+     * Computes the hash code for this object.
+     *
+     * @return The hash code for this object.
+     */
+    public override abstract hash_t toHash();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/TriggerSequence.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.TriggerSequence;
+
+import dwtx.jface.bindings.Trigger;
+
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A sequence of one or more triggers. None of these triggers may be
+ * <code>null</code>.
+ * </p>
+ *
+ * @since 3.1
+ */
+public abstract class TriggerSequence {
+
+    /**
+     * The value to see that hash code to if the hash code is not yet computed.
+     */
+    private static const int HASH_CODE_NOT_COMPUTED = -1;
+
+    /**
+     * A factor for computing the hash code for all trigger sequences.
+     */
+    private static const int HASH_FACTOR = 89;
+
+    /**
+     * The hash code for this object. This value is computed lazily, and marked
+     * as invalid when one of the values on which it is based changes.  This
+     * values is <code>HASH_CODE_NOT_COMPUTED</code> iff the hash code has not
+     * yet been computed.
+     */
+    protected /+transient+/ int hashCode = HASH_CODE_NOT_COMPUTED;
+
+    /**
+     * The list of trigger in this sequence. This value is never
+     * <code>null</code>, and never contains <code>null</code> elements.
+     */
+    protected const Trigger[] triggers;
+
+    /**
+     * Constructs a new instance of <code>TriggerSequence</code>.
+     *
+     * @param triggers
+     *            The triggers contained within this sequence; must not be
+     *            <code>null</code> or contain <code>null</code> elements.
+     *            May be empty.
+     */
+    public this(Trigger[] triggers) {
+        if (triggers is null) {
+            throw new NullPointerException("The triggers cannot be null"); //$NON-NLS-1$
+        }
+
+        for (int i = 0; i < triggers.length; i++) {
+            if (triggers[i] is null) {
+                throw new IllegalArgumentException(
+                        "All triggers in a trigger sequence must be an instance of Trigger"); //$NON-NLS-1$
+            }
+        }
+
+        int triggerLength = triggers.length;
+        this.triggers = new Trigger[triggerLength];
+        System.arraycopy(triggers, 0, this.triggers, 0, triggerLength);
+    }
+
+    /**
+     * Returns whether or not this key sequence ends with the given key
+     * sequence.
+     *
+     * @param triggerSequence
+     *            a trigger sequence. Must not be <code>null</code>.
+     * @param equals
+     *            whether or not an identical trigger sequence should be
+     *            considered as a possible match.
+     * @return <code>true</code>, iff the given trigger sequence ends with
+     *         this trigger sequence.
+     */
+    public final bool endsWith(TriggerSequence triggerSequence,
+            bool equals) {
+        if (triggerSequence is null) {
+            throw new NullPointerException(
+                    "Cannot end with a null trigger sequence"); //$NON-NLS-1$
+        }
+
+        return Util.endsWith(triggers, triggerSequence.triggers, equals);
+    }
+
+    public final override int opEquals(Object object) {
+        // Check if they're the same.
+        if (object is this) {
+            return true;
+        }
+
+        // Check if they're the same type.
+        if (!(cast(TriggerSequence)object )) {
+            return false;
+        }
+
+        TriggerSequence triggerSequence = cast(TriggerSequence) object;
+        return Util.opEquals(triggers, triggerSequence.triggers);
+    }
+
+    /**
+     * Formats this trigger sequence into the current default look.
+     *
+     * @return A string representation for this trigger sequence using the
+     *         default look; never <code>null</code>.
+     */
+    public abstract String format();
+
+    /**
+     * <p>
+     * Returns a list of prefixes for the current sequence. A prefix is any
+     * leading subsequence in a <code>TriggerSequence</code>. A prefix is
+     * also an instance of <code>TriggerSequence</code>.
+     * </p>
+     * <p>
+     * For example, consider a trigger sequence that consists of four triggers:
+     * A, B, C and D. The prefixes would be "", "A", "A B", and "A B C". The
+     * list of prefixes must always be the same as the size of the trigger list.
+     * </p>
+     *
+     * @return The array of possible prefixes for this sequence. This array must
+     *         not be <code>null</code>, but may be empty. It must only
+     *         contains instances of <code>TriggerSequence</code>.
+     */
+    public abstract TriggerSequence[] getPrefixes();
+
+    /**
+     * Returns the list of triggers.
+     *
+     * @return The triggers; never <code>null</code> and guaranteed to only
+     *         contain instances of <code>Trigger</code>.
+     */
+    public final Trigger[] getTriggers() {
+        int triggerLength = triggers.length;
+        Trigger[] triggerCopy = new Trigger[triggerLength];
+        System.arraycopy(triggers, 0, triggerCopy, 0, triggerLength);
+        return triggerCopy;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    public final override hash_t toHash() {
+        if (hashCode is HASH_CODE_NOT_COMPUTED) {
+            auto HASH_INITIAL = dwt.dwthelper.utils.toHash( TriggerSequence.classinfo.name );
+            hashCode = HASH_INITIAL;
+            hashCode = hashCode * HASH_FACTOR + Util.toHash(triggers);
+            if (hashCode is HASH_CODE_NOT_COMPUTED) {
+                hashCode++;
+            }
+        }
+
+        return hashCode;
+    }
+
+    /**
+     * Returns whether or not this trigger sequence is empty.
+     *
+     * @return <code>true</code>, iff the trigger sequence is empty.
+     */
+    public final bool isEmpty() {
+        return (triggers.length is 0);
+    }
+
+    /**
+     * Returns whether or not this trigger sequence starts with the given
+     * trigger sequence.
+     *
+     * @param triggerSequence
+     *            a trigger sequence. Must not be <code>null</code>.
+     * @param equals
+     *            whether or not an identical trigger sequence should be
+     *            considered as a possible match.
+     * @return <code>true</code>, iff the given trigger sequence starts with
+     *         this key sequence.
+     */
+    public final bool startsWith(TriggerSequence triggerSequence,
+            bool equals) {
+        if (triggerSequence is null) {
+            throw new NullPointerException(
+                    "A trigger sequence cannot start with null"); //$NON-NLS-1$
+        }
+
+        return Util.startsWith(triggers, triggerSequence.triggers, equals);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/IKeyLookup.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,462 @@
+/*******************************************************************************
+ * Copyright (c) 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.bindings.keys.IKeyLookup;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Integer;
+
+/**
+ * <p>
+ * A facilitiy for converting the formal representation for key strokes
+ * (i.e., used in persistence) into real key stroke instances.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IKeyLookup {
+    /**
+     * The formal name of the 'Alt' key.
+     */
+    public static const String ALT_NAME = "ALT"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Arrow Down' key.
+     */
+    public static const String ARROW_DOWN_NAME = "ARROW_DOWN"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Arrow Left' key.
+     */
+    public static const String ARROW_LEFT_NAME = "ARROW_LEFT"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Arrow Right' key.
+     */
+    public static const String ARROW_RIGHT_NAME = "ARROW_RIGHT"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Arrow Up' key.
+     */
+    public static const String ARROW_UP_NAME = "ARROW_UP"; //$NON-NLS-1$
+
+    /**
+     * An alternate name for the backspace key.
+     */
+    public static const String BACKSPACE_NAME = "BACKSPACE"; //$NON-NLS-1$
+
+    /**
+     * The formal name for the 'Break' key.
+     */
+    public static const String BREAK_NAME = "BREAK"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the backspace key.
+     */
+    public static const String BS_NAME = "BS"; //$NON-NLS-1$
+
+    /**
+     * The formal name for the 'Caps Lock' key.
+     */
+    public static const String CAPS_LOCK_NAME = "CAPS_LOCK"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Command' key.
+     */
+    public static const String COMMAND_NAME = "COMMAND"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the carriage return (U+000D)
+     */
+    public static const String CR_NAME = "CR"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Ctrl' key.
+     */
+    public static const String CTRL_NAME = "CTRL"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the delete (U+007F) key
+     */
+    public static const String DEL_NAME = "DEL"; //$NON-NLS-1$
+
+    /**
+     * An alternative name for the delete key.
+     */
+    public static const String DELETE_NAME = "DELETE"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'End' key.
+     */
+    public static const String END_NAME = "END"; //$NON-NLS-1$
+
+    /**
+     * An alternative name for the enter key.
+     */
+    public static const String ENTER_NAME = "ENTER"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the escape (U+001B) key.
+     */
+    public static const String ESC_NAME = "ESC"; //$NON-NLS-1$
+
+    /**
+     * An alternative name for the escape key.
+     */
+    public static const String ESCAPE_NAME = "ESCAPE"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F1' key.
+     */
+    public static const String F1_NAME = "F1"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F10' key.
+     */
+    public static const String F10_NAME = "F10"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F11' key.
+     */
+    public static const String F11_NAME = "F11"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F12' key.
+     */
+    public static const String F12_NAME = "F12"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F13' key.
+     */
+    public static const String F13_NAME = "F13"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F14' key.
+     */
+    public static const String F14_NAME = "F14"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F15' key.
+     */
+    public static const String F15_NAME = "F15"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F2' key.
+     */
+    public static const String F2_NAME = "F2"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F3' key.
+     */
+    public static const String F3_NAME = "F3"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F4' key.
+     */
+    public static const String F4_NAME = "F4"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F5' key.
+     */
+    public static const String F5_NAME = "F5"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F6' key.
+     */
+    public static const String F6_NAME = "F6"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F7' key.
+     */
+    public static const String F7_NAME = "F7"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F8' key.
+     */
+    public static const String F8_NAME = "F8"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'F9' key.
+     */
+    public static const String F9_NAME = "F9"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the form feed (U+000C) key.
+     */
+    public static const String FF_NAME = "FF"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Home' key.
+     */
+    public static const String HOME_NAME = "HOME"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Insert' key.
+     */
+    public static const String INSERT_NAME = "INSERT"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the line feed (U+000A) key.
+     */
+    public static const String LF_NAME = "LF"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'M1' key.
+     */
+    public static const String M1_NAME = "M1"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'M2' key.
+     */
+    public static const String M2_NAME = "M2"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'M3' key.
+     */
+    public static const String M3_NAME = "M3"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'M4' key.
+     */
+    public static const String M4_NAME = "M4"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the null (U+0000) key.
+     */
+    public static const String NUL_NAME = "NUL"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'NumLock' key.
+     */
+    public static const String NUM_LOCK_NAME = "NUM_LOCK"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '0' key on the numpad.
+     */
+    public static const String NUMPAD_0_NAME = "NUMPAD_0"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '1' key on the numpad.
+     */
+    public static const String NUMPAD_1_NAME = "NUMPAD_1"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '2' key on the numpad.
+     */
+    public static const String NUMPAD_2_NAME = "NUMPAD_2"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '3' key on the numpad.
+     */
+    public static const String NUMPAD_3_NAME = "NUMPAD_3"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '4' key on the numpad.
+     */
+    public static const String NUMPAD_4_NAME = "NUMPAD_4"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '5' key on the numpad.
+     */
+    public static const String NUMPAD_5_NAME = "NUMPAD_5"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '6' key on the numpad.
+     */
+    public static const String NUMPAD_6_NAME = "NUMPAD_6"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '7' key on the numpad.
+     */
+    public static const String NUMPAD_7_NAME = "NUMPAD_7"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '8' key on the numpad.
+     */
+    public static const String NUMPAD_8_NAME = "NUMPAD_8"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '9' key on the numpad.
+     */
+    public static const String NUMPAD_9_NAME = "NUMPAD_9"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Add' key on the numpad.
+     */
+    public static const String NUMPAD_ADD_NAME = "NUMPAD_ADD"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Decimal' key on the numpad.
+     */
+    public static const String NUMPAD_DECIMAL_NAME = "NUMPAD_DECIMAL"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Divide' key on the numpad.
+     */
+    public static const String NUMPAD_DIVIDE_NAME = "NUMPAD_DIVIDE"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Enter' key on the numpad.
+     */
+    public static const String NUMPAD_ENTER_NAME = "NUMPAD_ENTER"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the '=' key on the numpad.
+     */
+    public static const String NUMPAD_EQUAL_NAME = "NUMPAD_EQUAL"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Multiply' key on the numpad.
+     */
+    public static const String NUMPAD_MULTIPLY_NAME = "NUMPAD_MULTIPLY"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Subtract' key on the numpad.
+     */
+    public static const String NUMPAD_SUBTRACT_NAME = "NUMPAD_SUBTRACT"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Page Down' key.
+     */
+    public static const String PAGE_DOWN_NAME = "PAGE_DOWN"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Page Up' key.
+     */
+    public static const String PAGE_UP_NAME = "PAGE_UP"; //$NON-NLS-1$
+
+    /**
+     * The formal name for the 'Pause' key.
+     */
+    public static const String PAUSE_NAME = "PAUSE"; //$NON-NLS-1$
+
+    /**
+     * The formal name for the 'Print Screen' key.
+     */
+    public static const String PRINT_SCREEN_NAME = "PRINT_SCREEN"; //$NON-NLS-1$
+
+    /**
+     * An alternative name for the enter key.
+     */
+    public static const String RETURN_NAME = "RETURN"; //$NON-NLS-1$
+
+    /**
+     * The formal name for the 'Scroll Lock' key.
+     */
+    public static const String SCROLL_LOCK_NAME = "SCROLL_LOCK"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the 'Shift' key.
+     */
+    public static const String SHIFT_NAME = "SHIFT"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the space (U+0020) key.
+     */
+    public static const String SPACE_NAME = "SPACE"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the tab (U+0009) key.
+     */
+    public static const String TAB_NAME = "TAB"; //$NON-NLS-1$
+
+    /**
+     * The formal name of the vertical tab (U+000B) key.
+     */
+    public static const String VT_NAME = "VT"; //$NON-NLS-1$
+
+    /**
+     * Looks up a single natural key by its formal name, and returns the integer
+     * representation for this natural key
+     *
+     * @param name
+     *            The formal name of the natural key to look-up; must not be
+     *            <code>null</code>.
+     * @return The integer representation of this key. If the natural key cannot
+     *         be found, then this method returns <code>0</code>.
+     */
+    public int formalKeyLookup(String name);
+
+    /**
+     * Looks up a single natural key by its formal name, and returns the integer
+     * representation for this natural key
+     *
+     * @param name
+     *            The formal name of the natural key to look-up; must not be
+     *            <code>null</code>.
+     * @return The integer representation of this key. If the natural key cannot
+     *         be found, then this method returns <code>0</code>.
+     */
+    public Integer formalKeyLookupInteger(String name);
+
+    /**
+     * Looks up a single modifier key by its formal name, and returns the integer
+     * representation for this modifier key
+     *
+     * @param name
+     *            The formal name of the modifier key to look-up; must not be
+     *            <code>null</code>.
+     * @return The integer representation of this key. If the modifier key
+     *         cannot be found, then this method returns <code>0</code>.
+     */
+    public int formalModifierLookup(String name);
+
+    /**
+     * Looks up a key value, and returns the formal string representation for
+     * that key
+     *
+     * @param key
+     *            The key to look-up.
+     * @return The formal string representation of this key. If this key cannot
+     *         be found, then it is simply the character corresponding to that
+     *         integer value.
+     */
+    public String formalNameLookup(int key);
+
+    /**
+     * Returns the integer representation of the ALT key.
+     *
+     * @return The ALT key
+     */
+    public int getAlt();
+
+    /**
+     * Returns the integer representation of the COMMAND key.
+     *
+     * @return The COMMAND key
+     */
+    public int getCommand();
+
+    /**
+     * Returns the integer representation of the CTRL key.
+     *
+     * @return The CTRL key
+     */
+    public int getCtrl();
+
+    /**
+     * Returns the integer representation of the SHIFT key.
+     *
+     * @return The SHIFT key
+     */
+    public int getShift();
+
+    /**
+     * Returns whether the given key is a modifier key.
+     *
+     * @param key
+     *            The integer value of the key to check.
+     * @return <code>true</code> if the key is one of the modifier keys;
+     *         <code>false</code> otherwise.
+     */
+    public bool isModifierKey(int key);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/KeyBinding.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.KeyBinding;
+
+import dwtx.jface.bindings.keys.KeySequence;
+
+import dwtx.core.commands.ParameterizedCommand;
+import dwtx.jface.bindings.Binding;
+import dwtx.jface.bindings.TriggerSequence;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A keyboard shortcut. This is a binding between some keyboard input and the
+ * triggering of a command. This object is immutable.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class KeyBinding : Binding {
+
+    /**
+     * The key sequence which triggers this binding. This sequence is never
+     * <code>null</code>.
+     */
+    private const KeySequence keySequence;
+
+    /**
+     * Constructs a new instance of <code>KeyBinding</code>.
+     *
+     * @param keySequence
+     *            The key sequence which should trigger this binding. This value
+     *            must not be <code>null</code>. It also must be a complete,
+     *            non-empty key sequence.
+     * @param command
+     *            The parameterized command to which this binding applies; this
+     *            value may be <code>null</code> if the binding is meant to
+     *            "unbind" a previously defined binding.
+     * @param schemeId
+     *            The scheme to which this binding belongs; this value must not
+     *            be <code>null</code>.
+     * @param contextId
+     *            The context to which this binding applies; this value must not
+     *            be <code>null</code>.
+     * @param locale
+     *            The locale to which this binding applies; this value may be
+     *            <code>null</code> if it applies to all locales.
+     * @param platform
+     *            The platform to which this binding applies; this value may be
+     *            <code>null</code> if it applies to all platforms.
+     * @param windowManager
+     *            The window manager to which this binding applies; this value
+     *            may be <code>null</code> if it applies to all window
+     *            managers. This value is currently ignored.
+     * @param type
+     *            The type of binding. This should be either <code>SYSTEM</code>
+     *            or <code>USER</code>.
+     */
+    public this(KeySequence keySequence,
+            ParameterizedCommand command, String schemeId,
+            String contextId, String locale, String platform,
+            String windowManager, int type) {
+        super(command, schemeId, contextId, locale, platform, windowManager,
+                type);
+
+        if (keySequence is null) {
+            throw new NullPointerException("The key sequence cannot be null"); //$NON-NLS-1$
+        }
+
+        if (!keySequence.isComplete()) {
+            throw new IllegalArgumentException(
+                    "Cannot bind to an incomplete key sequence"); //$NON-NLS-1$
+        }
+
+        if (keySequence.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Cannot bind to an empty key sequence"); //$NON-NLS-1$
+        }
+
+        this.keySequence = keySequence;
+    }
+
+    /**
+     * Returns the key sequence which triggers this binding. The key sequence
+     * will not be <code>null</code>, empty or incomplete.
+     *
+     * @return The key sequence; never <code>null</code>.
+     */
+    public final KeySequence getKeySequence() {
+        return keySequence;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.Binding#getTriggerSequence()
+     */
+    public TriggerSequence getTriggerSequence() {
+        return getKeySequence();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/KeyLookupFactory.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.jface.bindings.keys.KeyLookupFactory;
+
+import dwtx.jface.bindings.keys.SWTKeyLookup;
+import dwtx.jface.bindings.keys.IKeyLookup;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A factory class for <code>ILookup</code> instances. This factory can be
+ * used to retrieve instances of look-ups defined by this package. It also
+ * allows you to define your own look-up for use in the classes.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class KeyLookupFactory {
+
+    /**
+     * The DWT key look-up defined by this package.
+     */
+    private static SWTKeyLookup SWT_KEY_LOOKUP;
+
+    /**
+     * The instance that should be used by <code>KeyStroke</code> in
+     * converting string representations to instances.
+     */
+    private static IKeyLookup defaultLookup;
+
+    private static void check_staticthis(){
+        if( SWT_KEY_LOOKUP is null ){
+            synchronized{
+                if( SWT_KEY_LOOKUP is null ){
+                    SWT_KEY_LOOKUP = new SWTKeyLookup();
+                    defaultLookup = SWT_KEY_LOOKUP;
+                }
+            }
+        }
+    }
+
+    /**
+     * Provides an instance of <code>SWTKeyLookup</code>.
+     *
+     * @return The DWT look-up table for key stroke format information; never
+     *         <code>null</code>.
+     */
+    public static final IKeyLookup getSWTKeyLookup() {
+        check_staticthis();
+        return SWT_KEY_LOOKUP;
+    }
+
+    /**
+     * An accessor for the current default look-up.
+     *
+     * @return The default look-up; never <code>null</code>.
+     */
+    public static final IKeyLookup getDefault() {
+        check_staticthis();
+        return defaultLookup;
+    }
+
+    /**
+     * Sets the default look-up.
+     *
+     * @param defaultLookup
+     *            the default look-up. Must not be <code>null</code>.
+     */
+    public static final void setDefault(IKeyLookup defaultLookup) {
+        check_staticthis();
+        if (defaultLookup is null) {
+            throw new NullPointerException("The look-up must not be null"); //$NON-NLS-1$
+        }
+
+        KeyLookupFactory.defaultLookup = defaultLookup;
+    }
+
+    /**
+     * This class should not be instantiated.
+     */
+    private this() {
+        // Not to be constructred.
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/KeySequence.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,294 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.keys.KeySequence;
+
+import dwtx.jface.bindings.keys.KeyStroke;
+
+import tango.util.collection.ArraySeq;
+import tango.util.collection.model.Seq;
+import tango.util.collection.model.SeqView;
+
+import dwtx.jface.bindings.TriggerSequence;
+import dwtx.jface.bindings.keys.formatting.KeyFormatterFactory;
+
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+static import tango.text.Util;
+/**
+ * <p>
+ * A <code>KeySequence</code> is defined as a list of zero or more
+ * <code>KeyStrokes</code>, with the stipulation that all
+ * <code>KeyStroke</code> objects must be complete, save for the last one,
+ * whose completeness is optional. A <code>KeySequence</code> is said to be
+ * complete if all of its <code>KeyStroke</code> objects are complete.
+ * </p>
+ * <p>
+ * All <code>KeySequence</code> objects have a formal string representation
+ * available via the <code>toString()</code> method. There are a number of
+ * methods to get instances of <code>KeySequence</code> objects, including one
+ * which can parse this formal string representation.
+ * </p>
+ * <p>
+ * All <code>KeySequence</code> objects, via the <code>format()</code>
+ * method, provide a version of their formal string representation translated by
+ * platform and locale, suitable for display to a user.
+ * </p>
+ * <p>
+ * <code>KeySequence</code> objects are immutable. Clients are not permitted
+ * to extend this class.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class KeySequence : TriggerSequence, Comparable {
+
+    /**
+     * An empty key sequence instance for use by everyone.
+     */
+    private const static KeySequence EMPTY_KEY_SEQUENCE;
+
+    static this(){
+        EMPTY_KEY_SEQUENCE = new KeySequence(null);
+    }
+
+    /**
+     * The delimiter between multiple key strokes in a single key sequence --
+     * expressed in the formal key stroke grammar. This is not to be displayed
+     * to the user. It is only intended as an internal representation.
+     */
+    public const static String KEY_STROKE_DELIMITER = "\u0020"; //$NON-NLS-1$
+
+    /**
+     * The set of delimiters for <code>KeyStroke</code> objects allowed during
+     * parsing of the formal string representation.
+     */
+    public const static String KEY_STROKE_DELIMITERS = KEY_STROKE_DELIMITER
+            ~ "\b\r\u007F\u001B\f\n\0\t\u000B"; //$NON-NLS-1$
+
+    /**
+     * Gets an instance of <code>KeySequence</code>.
+     *
+     * @return a key sequence. This key sequence will have no key strokes.
+     *         Guaranteed not to be <code>null</code>.
+     */
+    public static final KeySequence getInstance() {
+        return EMPTY_KEY_SEQUENCE;
+    }
+
+    /**
+     * Creates an instance of <code>KeySequence</code> given a key sequence
+     * and a key stroke.
+     *
+     * @param keySequence
+     *            a key sequence. Must not be <code>null</code>.
+     * @param keyStroke
+     *            a key stroke. Must not be <code>null</code>.
+     * @return a key sequence that is equal to the given key sequence with the
+     *         given key stroke appended to the end. Guaranteed not to be
+     *         <code>null</code>.
+     */
+    public static final KeySequence getInstance(KeySequence keySequence,
+            KeyStroke keyStroke) {
+        if (keySequence is null || keyStroke is null) {
+            throw new NullPointerException();
+        }
+
+        KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes();
+        int oldKeyStrokeLength = oldKeyStrokes.length;
+        KeyStroke[] newKeyStrokes = new KeyStroke[oldKeyStrokeLength + 1];
+        System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
+                        oldKeyStrokeLength);
+        newKeyStrokes[oldKeyStrokeLength] = keyStroke;
+        return new KeySequence(newKeyStrokes);
+    }
+
+    /**
+     * Creates an instance of <code>KeySequence</code> given a single key
+     * stroke.
+     *
+     * @param keyStroke
+     *            a single key stroke. Must not be <code>null</code>.
+     * @return a key sequence. Guaranteed not to be <code>null</code>.
+     */
+    public static final KeySequence getInstance(KeyStroke keyStroke) {
+        return new KeySequence([ keyStroke ]);
+    }
+
+    /**
+     * Creates an instance of <code>KeySequence</code> given an array of key
+     * strokes.
+     *
+     * @param keyStrokes
+     *            the array of key strokes. This array may be empty, but it must
+     *            not be <code>null</code>. This array must not contain
+     *            <code>null</code> elements.
+     * @return a key sequence. Guaranteed not to be <code>null</code>.
+     */
+    public static final KeySequence getInstance(KeyStroke[] keyStrokes) {
+        return new KeySequence(keyStrokes);
+    }
+
+    /**
+     * Creates an instance of <code>KeySequence</code> given a list of key
+     * strokes.
+     *
+     * @param keyStrokes
+     *            the list of key strokes. This list may be empty, but it must
+     *            not be <code>null</code>. If this list is not empty, it
+     *            must only contain instances of <code>KeyStroke</code>.
+     * @return a key sequence. Guaranteed not to be <code>null</code>.
+     */
+    public static final KeySequence getInstance(SeqView!(KeyStroke) keyStrokes) {
+        return new KeySequence(keyStrokes.toArray());
+    }
+
+    /**
+     * Creates an instance of <code>KeySequence</code> by parsing a given
+     * formal string representation.
+     *
+     * @param string
+     *            the formal string representation to parse.
+     * @return a key sequence. Guaranteed not to be <code>null</code>.
+     * @throws ParseException
+     *             if the given formal string representation could not be parsed
+     *             to a valid key sequence.
+     */
+    public static final KeySequence getInstance(String string) {
+        if (string is null) {
+            throw new NullPointerException();
+        }
+
+        auto keyStrokes = new ArraySeq!(KeyStroke);
+
+        try {
+            auto tokens = tango.text.Util.delimit( string, KEY_STROKE_DELIMITERS );
+            KeyStroke[] keyStrokeArray = new KeyStroke[ tokens.length ];
+            foreach( idx, tok; tokens ){
+                keyStrokeArray[idx] = KeyStroke.getInstance(tok);
+            }
+            return new KeySequence(keyStrokeArray);
+        } catch (IllegalArgumentException e) {
+            throw new ParseException(
+                    "Could not construct key sequence with these key strokes: " //$NON-NLS-1$
+                            /+~ keyStrokes+/);
+        } catch (NullPointerException e) {
+            throw new ParseException(
+                    "Could not construct key sequence with these key strokes: " //$NON-NLS-1$
+                            /+~ keyStrokes+/);
+        }
+    }
+
+    /**
+     * Constructs an instance of <code>KeySequence</code> given a list of key
+     * strokes.
+     *
+     * @param keyStrokes
+     *            the list of key strokes. This list may be empty, but it must
+     *            not be <code>null</code>. If this list is not empty, it
+     *            must only contain instances of <code>KeyStroke</code>.
+     */
+    protected this(KeyStroke[] keyStrokes) {
+        super(keyStrokes);
+
+        for (int i = 0; i < triggers.length - 1; i++) {
+            KeyStroke keyStroke = cast(KeyStroke) triggers[i];
+
+            if (!keyStroke.isComplete()) {
+                throw new IllegalArgumentException(null);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#compareTo(java.lang.Object)
+     */
+    public final int compareTo(Object object) {
+        KeySequence castedObject = cast(KeySequence) object;
+        return Util.compare( arraycast!(Comparable)(triggers), arraycast!(Comparable)(castedObject.triggers));
+    }
+
+    /**
+     * Formats this key sequence into the current default look.
+     *
+     * @return A string representation for this key sequence using the default
+     *         look; never <code>null</code>.
+     */
+    public final String format() {
+        return KeyFormatterFactory.getDefault().format(this);
+    }
+
+    /**
+     * Returns the list of key strokes for this key sequence.
+     *
+     * @return the list of key strokes keys. This list may be empty, but is
+     *         guaranteed not to be <code>null</code>. If this list is not
+     *         empty, it is guaranteed to only contain instances of
+     *         <code>KeyStroke</code>.
+     */
+    public final KeyStroke[] getKeyStrokes() {
+        int triggerLength = triggers.length;
+        KeyStroke[] keyStrokes = new KeyStroke[triggerLength];
+        System.arraycopy(triggers, 0, keyStrokes, 0, triggerLength);
+        return keyStrokes;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.TriggerSequence#getPrefixes()
+     */
+    public final TriggerSequence[] getPrefixes() {
+        int numberOfPrefixes = triggers.length;
+        TriggerSequence[] prefixes = new TriggerSequence[numberOfPrefixes];
+        prefixes[0] = KeySequence.getInstance();
+        for (int i = 0; i < numberOfPrefixes - 1; i++) {
+            final KeyStroke[] prefixKeyStrokes = new KeyStroke[i + 1];
+            System.arraycopy(triggers, 0, prefixKeyStrokes, 0, i + 1);
+            prefixes[i + 1] = KeySequence.getInstance(prefixKeyStrokes);
+        }
+
+        return prefixes;
+    }
+
+    /**
+     * Returns whether or not this key sequence is complete. Key sequences are
+     * complete iff all of their key strokes are complete.
+     *
+     * @return <code>true</code>, iff the key sequence is complete.
+     */
+    public final bool isComplete() {
+        int triggersLength = triggers.length;
+        for (int i = 0; i < triggersLength; i++) {
+            if (!(cast(KeyStroke) triggers[i]).isComplete()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the formal string representation for this key sequence.
+     *
+     * @return The formal string representation for this key sequence.
+     *         Guaranteed not to be <code>null</code>.
+     * @see java.lang.Object#toString()
+     */
+    public final String toString() {
+        return KeyFormatterFactory.getFormalKeyFormatter().format(this);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/KeySequenceText.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,964 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.KeySequenceText;
+
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.bindings.keys.SWTKeySupport;
+import dwtx.jface.bindings.keys.KeySequence;
+
+import tango.util.collection.ArraySeq;
+import tango.util.collection.HashSet;
+import tango.util.collection.model.Seq;
+import tango.util.collection.model.View;
+import tango.util.collection.TreeMap;
+
+import dwt.DWT;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.FocusEvent;
+import dwt.events.FocusListener;
+import dwt.events.ModifyEvent;
+import dwt.events.ModifyListener;
+import dwt.graphics.Font;
+import dwt.graphics.Device;
+import dwt.graphics.Point;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.Text;
+import dwtx.jface.util.IPropertyChangeListener;
+import dwtx.jface.util.PropertyChangeEvent;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A wrapper around the DWT text widget that traps literal key presses and
+ * converts them into key sequences for display. There are two types of key
+ * strokes that are displayed: complete and incomplete. A complete key stroke is
+ * one with a natural key, while an incomplete one has no natural key.
+ * Incomplete key strokes are only displayed until they are made complete or
+ * their component key presses are released.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class KeySequenceText {
+
+    /**
+     * A key listener that traps incoming events and displays them in the
+     * wrapped text field. It has no effect on traversal operations.
+     */
+    private class KeyTrapListener : Listener {
+        /**
+         * The index at which insertion should occur. This is used if there is a
+         * replacement occurring in the middle of the stroke, and the first key
+         * stroke was incomplete.
+         */
+        private int insertionIndex = -1;
+
+        /**
+         * Resets the insertion index to point nowhere. In other words, it is
+         * set to <code>-1</code>.
+         */
+        void clearInsertionIndex() {
+            insertionIndex = -1;
+        }
+
+        /**
+         * Deletes the current selection. If there is no selection, then it
+         * deletes the last key stroke.
+         *
+         * @param keyStrokes
+         *            The key strokes from which to delete. This list must not
+         *            be <code>null</code>, and must represent a valid key
+         *            sequence.
+         */
+        private final KeyStroke[] deleteKeyStroke(KeyStroke[] keyStrokes) {
+            clearInsertionIndex();
+
+            if (hasSelection()) {
+                /*
+                 * Delete the current selection -- disallowing incomplete
+                 * strokes in the middle of the sequence.
+                 */
+                KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
+                deleteSelection(keyStrokes, false, deletedKeyStrokes);
+                return deletedKeyStrokes[0];
+            }
+
+            // Remove the last key stroke.
+            if (keyStrokes.length > 0) {
+                int newKeyStrokesLength = keyStrokes.length - 1;
+                KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength];
+                System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
+                        newKeyStrokesLength);
+                return newKeyStrokes;
+            }
+
+            return keyStrokes;
+        }
+
+        /**
+         * Handles the key pressed and released events on the wrapped text
+         * widget. This makes sure to either add the pressed key to the
+         * temporary key stroke, or complete the current temporary key stroke
+         * and prompt for the next. In the case of a key release, this makes
+         * sure that the temporary stroke is correctly displayed --
+         * corresponding with modifier keys that may have been released.
+         *
+         * @param event
+         *            The triggering event; must not be <code>null</code>.
+         */
+        public void handleEvent(Event event) {
+            KeyStroke[] keyStrokes = getKeySequence().getKeyStrokes();
+
+            // Dispatch the event to the correct handler.
+            if (event.type is DWT.KeyDown) {
+                keyStrokes = handleKeyDown(event, keyStrokes);
+            } else if (event.type is DWT.KeyUp) {
+                keyStrokes = handleKeyUp(event, keyStrokes);
+            }
+
+            // Update the underlying widget.
+            setKeySequence(KeySequence.getInstance(keyStrokes));
+
+            // Prevent the event from reaching the widget.
+            event.doit = false;
+        }
+
+        /**
+         * Handles the case where the key event is an <code>DWT.KeyDown</code>
+         * event. This either causes a deletion (if it is an unmodified
+         * backspace key stroke), or an insertion (if it is any other key).
+         *
+         * @param event
+         *            The trigger key down event; must not be <code>null</code>.
+         * @param keyStrokes
+         *            The current list of key strokes. This valud must not be
+         *            <code>null</code>, and it must represent a valid key
+         *            sequence.
+         */
+        private KeyStroke[] handleKeyDown(Event event, KeyStroke[] keyStrokes) {
+            // Is it an unmodified backspace character?
+            if ((event.character is DWT.BS) && (event.stateMask is 0)) {
+                return deleteKeyStroke(keyStrokes);
+            }
+
+            return insertKeyStroke(event, keyStrokes);
+        }
+
+        /**
+         * Handles the case where the key event is an <code>DWT.KeyUp</code>
+         * event. This resets the insertion index. If there is an incomplete
+         * stroke, then that incomplete stroke is modified to match the keys
+         * that are still held. If no keys are held, then the incomplete stroke
+         * is removed.
+         *
+         * @param event
+         *            The triggering event; must not be <code>null</code>
+         * @param keyStrokes
+         *            The key strokes that are part of the current key sequence;
+         *            these key strokes are guaranteed to represent a valid key
+         *            sequence. This value must not be <code>null</code>.
+         */
+        private final KeyStroke[] handleKeyUp(Event event,
+                KeyStroke[] keyStrokes) {
+            if (hasIncompleteStroke()) {
+                /*
+                 * Figure out the DWT integer representation of the remaining
+                 * values.
+                 */
+                Event mockEvent = new Event();
+                if ((event.keyCode & DWT.MODIFIER_MASK) !is 0) {
+                    // This key up is a modifier key being released.
+                    mockEvent.stateMask = event.stateMask - event.keyCode;
+                } else {
+                    /*
+                     * This key up is the other end of a key down that was
+                     * trapped by the operating system or window manager.
+                     */
+                    mockEvent.stateMask = event.stateMask;
+                }
+
+                /*
+                 * Get a reasonable facsimile of the stroke that is still
+                 * pressed.
+                 */
+                int key = SWTKeySupport
+                        .convertEventToUnmodifiedAccelerator(mockEvent);
+                KeyStroke remainingStroke = SWTKeySupport
+                        .convertAcceleratorToKeyStroke(key);
+                int keyStrokesLength = keyStrokes.length;
+                KeyStroke[] newKeyStrokes;
+                if ((keyStrokesLength > 0)
+                        && (remainingStroke.getModifierKeys() !is 0)) {
+                    newKeyStrokes = new KeyStroke[keyStrokesLength];
+                    System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
+                            keyStrokesLength - 1);
+                    newKeyStrokes[keyStrokesLength - 1] = remainingStroke;
+
+                } else if (keyStrokesLength > 0) {
+                    newKeyStrokes = new KeyStroke[keyStrokesLength - 1];
+                    System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
+                            keyStrokesLength - 1);
+
+                } else if (remainingStroke.getModifierKeys() !is 0) {
+                    newKeyStrokes = new KeyStroke[keyStrokesLength + 1];
+                    System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
+                            keyStrokesLength);
+                    newKeyStrokes[keyStrokesLength] = remainingStroke;
+
+                } else {
+                    newKeyStrokes = keyStrokes;
+
+                }
+
+                return newKeyStrokes;
+            }
+
+            return keyStrokes;
+        }
+
+        /**
+         * <p>
+         * Handles the case where a key down event is leading to a key stroke
+         * being inserted. The current selection is deleted, and an invalid
+         * remanents of the stroke are also removed. The insertion is carried
+         * out at the cursor position.
+         * </p>
+         * <p>
+         * If only a natural key is selected (as part of a larger key stroke),
+         * then it is possible for the user to press a natural key to replace
+         * the old natural key. In this situation, pressing any modifier keys
+         * will replace the whole thing.
+         * </p>
+         * <p>
+         * If the insertion point is not at the end of the sequence, then
+         * incomplete strokes will not be immediately inserted. Only when the
+         * sequence is completed is the stroke inserted. This is a requirement
+         * as the widget must always represent a valid key sequence. The
+         * insertion point is tracked using <code>insertionIndex</code>,
+         * which is an index into the key stroke array.
+         * </p>
+         *
+         * @param event
+         *            The triggering key down event; must not be
+         *            <code>null</code>.
+         * @param keyStrokes
+         *            The key strokes into which the current stroke should be
+         *            inserted. This value must not be <code>null</code>, and
+         *            must represent a valid key sequence.
+         */
+        private final KeyStroke[] insertKeyStroke(Event event,
+                KeyStroke[] keyStrokes) {
+            // Compute the key stroke to insert.
+            int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(event);
+            KeyStroke stroke = SWTKeySupport.convertAcceleratorToKeyStroke(key);
+
+            /*
+             * Only insert the stroke if it is *not ScrollLock. Let's not get
+             * silly
+             */
+            if ((DWT.NUM_LOCK is stroke.getNaturalKey())
+                    || (DWT.CAPS_LOCK is stroke.getNaturalKey())
+                    || (DWT.SCROLL_LOCK is stroke.getNaturalKey())) {
+                return keyStrokes;
+            }
+
+            if (insertionIndex !is -1) {
+                // There is a previous replacement still going on.
+                if (stroke.isComplete()) {
+                    keyStrokes = insertStrokeAt(keyStrokes, stroke,
+                            insertionIndex);
+                    clearInsertionIndex();
+                }
+
+            } else if (hasSelection()) {
+                // There is a selection that needs to be replaced.
+                KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
+                insertionIndex = deleteSelection(keyStrokes, stroke
+                        .isComplete(), deletedKeyStrokes);
+                keyStrokes = deletedKeyStrokes[0];
+                if ((stroke.isComplete())
+                        || (insertionIndex >= keyStrokes.length)) {
+                    keyStrokes = insertStrokeAt(keyStrokes, stroke,
+                            insertionIndex);
+                    clearInsertionIndex();
+                }
+
+            } else {
+                // No selection, so remove the incomplete stroke, if any
+                if ((hasIncompleteStroke()) && (keyStrokes.length > 0)) {
+                    KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokes.length - 1];
+                    System.arraycopy(keyStrokes, 0, newKeyStrokes, 0,
+                            keyStrokes.length - 1);
+                    keyStrokes = newKeyStrokes;
+                }
+
+                // And then add the new stroke.
+                if ((keyStrokes.length is 0)
+                        || (insertionIndex >= keyStrokes.length)
+                        || (isCursorInLastPosition())) {
+                    keyStrokes = insertStrokeAt(keyStrokes, stroke,
+                            keyStrokes.length);
+                    clearInsertionIndex();
+                } else {
+                    /*
+                     * I'm just getting the insertionIndex here. No actual
+                     * deletion should occur.
+                     */
+                    KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
+                    insertionIndex = deleteSelection(keyStrokes, stroke
+                            .isComplete(), deletedKeyStrokes);
+                    keyStrokes = deletedKeyStrokes[0];
+                    if (stroke.isComplete()) {
+                        keyStrokes = insertStrokeAt(keyStrokes, stroke,
+                                insertionIndex);
+                        clearInsertionIndex();
+                    }
+                }
+
+            }
+
+            return keyStrokes;
+        }
+    }
+
+    /**
+     * A traversal listener that blocks all traversal except for tabs and arrow
+     * keys.
+     */
+    private class TraversalFilter : Listener {
+        /**
+         * Handles the traverse event on the text field wrapped by this class.
+         * It swallows all traverse events example for tab and arrow key
+         * navigation. The other forms of navigation can be reached by tabbing
+         * off of the control.
+         *
+         * @param event
+         *            The trigger event; must not be <code>null</code>.
+         */
+        public void handleEvent(Event event) {
+            switch (event.detail) {
+            case DWT.TRAVERSE_ESCAPE:
+            case DWT.TRAVERSE_MNEMONIC:
+            case DWT.TRAVERSE_NONE:
+            case DWT.TRAVERSE_PAGE_NEXT:
+            case DWT.TRAVERSE_PAGE_PREVIOUS:
+            case DWT.TRAVERSE_RETURN:
+                event.type = DWT.None;
+                event.doit = false;
+                break;
+
+            case DWT.TRAVERSE_TAB_NEXT:
+            case DWT.TRAVERSE_TAB_PREVIOUS:
+                // Check if modifiers other than just 'Shift' were
+                // down.
+                if ((event.stateMask & (DWT.MODIFIER_MASK ^ DWT.SHIFT)) !is 0) {
+                    // Modifiers other than shift were down.
+                    event.type = DWT.None;
+                    event.doit = false;
+                    break;
+                }
+
+                // fall through -- either no modifiers, or just shift.
+
+            case DWT.TRAVERSE_ARROW_NEXT:
+            case DWT.TRAVERSE_ARROW_PREVIOUS:
+            default:
+                // Let the traversal happen, but clear the incomplete
+                // stroke
+                if (hasIncompleteStroke()) {
+                    KeyStroke[] oldKeyStrokes = getKeySequence()
+                            .getKeyStrokes();
+                    int newKeyStrokesLength = oldKeyStrokes.length - 1;
+                    if (newKeyStrokesLength >= 1) {
+                        KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength];
+                        System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
+                                newKeyStrokesLength);
+                        setKeySequence(KeySequence.getInstance(newKeyStrokes));
+                    } else {
+                        setKeySequence(KeySequence.getInstance());
+                    }
+                }
+            }
+
+        }
+    }
+
+    /**
+     * The manager resposible for installing and removing the traversal filter
+     * when the key sequence entry widget gains and loses focus.
+     */
+    private class TraversalFilterManager : FocusListener {
+        /** The managed filter. We only need one instance. */
+        private TraversalFilter filter;
+
+        private bool filtering = false;
+
+        this(){
+            filter = new TraversalFilter();
+        }
+
+        /**
+         * Attaches the global traversal filter.
+         *
+         * @param event
+         *            Ignored.
+         */
+        public void focusGained(FocusEvent event) {
+            Display.getCurrent().addFilter(DWT.Traverse, filter);
+            filtering = true;
+        }
+
+        /**
+         * Detaches the global traversal filter.
+         *
+         * @param event
+         *            Ignored.
+         */
+        public void focusLost(FocusEvent event) {
+            Display.getCurrent().removeFilter(DWT.Traverse, filter);
+            filtering = false;
+        }
+
+        /**
+         * Remove the traverse filter if we close without focusOut.
+         */
+        public void dispose() {
+            if (filtering) {
+                Display.getCurrent().removeFilter(DWT.Traverse, filter);
+            }
+        }
+    }
+
+    /**
+     * A modification listener that makes sure that external events to this
+     * class (i.e., direct modification of the underlying text) do not break
+     * this class' view of the world.
+     */
+    private class UpdateSequenceListener : ModifyListener {
+        /**
+         * Handles the modify event on the underlying text widget.
+         *
+         * @param event
+         *            The triggering event; ignored.
+         */
+        public void modifyText(ModifyEvent event) {
+            try {
+                // The original sequence.
+                KeySequence originalSequence = getKeySequence();
+
+                // The new sequence drawn from the text.
+                String contents = getText();
+                KeySequence newSequence = KeySequence.getInstance(contents);
+
+                // Check to see if they're the same.
+                if (!originalSequence.opEquals(newSequence)) {
+                    setKeySequence(newSequence);
+                }
+
+            } catch (ParseException e) {
+                // Abort any cut/paste-driven modifications
+                setKeySequence(getKeySequence());
+            }
+        }
+    }
+
+    static this(){
+        HashSet!(KeyStroke) trappedKeys = new HashSet!(KeyStroke);
+        trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(DWT.TAB));
+        trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(DWT.TAB
+                | DWT.SHIFT));
+        trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(DWT.BS));
+        //List trappedKeyList = new ArrayList(trappedKeys);
+        TRAPPED_KEYS = trappedKeys;// Collections.unmodifiableList(trappedKeyList);
+    }
+
+    /** An empty string instance for use in clearing text values. */
+    private static const String EMPTY_STRING = ""; //$NON-NLS-1$
+
+    /**
+     * The special integer value for the maximum number of strokes indicating
+     * that an infinite number should be allowed.
+     */
+    public static const int INFINITE = -1;
+
+    /**
+     * The name of the property representing the current key sequence in this
+     * key sequence widget.
+     *
+     * @since 3.2
+     */
+    public static const String P_KEY_SEQUENCE = "dwtx.jface.bindings.keys.KeySequenceText.KeySequence"; //$NON-NLS-1$
+
+    /**
+     * The keys trapped by this widget. This list is guaranteed to be roughly
+     * accurate. Perfection is not possible, as DWT does not export traversal
+     * keys as constants.
+     */
+    public static const View!(KeyStroke) TRAPPED_KEYS;
+
+    /**
+     * The key filter attached to the underlying widget that traps key events.
+     */
+    private const KeyTrapListener keyFilter;
+
+    /**
+     * The text of the key sequence -- containing only the complete key strokes.
+     */
+    private KeySequence keySequence;
+
+    /**
+     * Those listening to changes to the key sequence in this widget. This value
+     * may be <code>null</code> if there are no listeners.
+     */
+    private ArraySeq!(IPropertyChangeListener) listeners = null;
+
+    /** The maximum number of key strokes permitted in the sequence. */
+    private int maxStrokes = INFINITE;
+
+    /** The text widget that is wrapped for this class. */
+    private const Text text;
+
+    /**
+     * The listener that makes sure that the text widget remains up-to-date with
+     * regards to external modification of the text (e.g., cut & pasting).
+     */
+    private const UpdateSequenceListener updateSequenceListener;
+
+    /**
+     * Constructs an instance of <code>KeySequenceTextField</code> with the
+     * text field to use. If the platform is carbon (MacOS X), then the font is
+     * set to be the same font used to display accelerators in the menus.
+     *
+     * @param wrappedText
+     *            The text widget to wrap; must not be <code>null</code>.
+     */
+    public this(Text wrappedText) {
+        keyFilter = new KeyTrapListener();
+        keySequence = KeySequence.getInstance();
+        updateSequenceListener = new UpdateSequenceListener();
+
+        text = wrappedText;
+
+        // Set the font if the platform is carbon.
+        if ("carbon".equals(DWT.getPlatform())) { //$NON-NLS-1$
+            // Don't worry about this font name here; it is the official menu
+            // font and point size on the Mac.
+            Font font = new Font(cast(Device)text.getDisplay(),
+                    "Lucida Grande", 13, DWT.NORMAL); //$NON-NLS-1$
+            text.setFont(font);
+            text.addDisposeListener(new class DisposeListener {
+                Font font_;
+                this(){
+                    font_=font;
+                }
+                public void widgetDisposed(DisposeEvent e) {
+                    font_.dispose();
+                }
+            });
+        }
+
+        // Add the key listener.
+        text.addListener(DWT.KeyUp, keyFilter);
+        text.addListener(DWT.KeyDown, keyFilter);
+
+        TraversalFilterManager traversalFilterManager = new TraversalFilterManager();
+        text.addFocusListener(traversalFilterManager);
+        text.addDisposeListener(new class DisposeListener {
+            TraversalFilterManager traversalFilterManager_;
+            this(){
+                traversalFilterManager_=traversalFilterManager;
+            }
+            public void widgetDisposed(DisposeEvent e) {
+                traversalFilterManager_.dispose();
+            }
+        });
+
+        // Add an internal modify listener.
+        text.addModifyListener(updateSequenceListener);
+    }
+
+    /**
+     * Adds a property change listener to this key sequence widget. It will be
+     * notified when the key sequence changes.
+     *
+     * @param listener
+     *            The listener to be notified when changes occur; must not be
+     *            <code>null</code>.
+     * @since 3.2
+     */
+    public final void addPropertyChangeListener(
+            IPropertyChangeListener listener) {
+        if (listener is null) {
+            return;
+        }
+
+        if (listeners is null) {
+            listeners = new ArraySeq!(IPropertyChangeListener);
+        }
+
+        listeners.append(listener);
+    }
+
+    /**
+     * Clears the text field and resets all the internal values.
+     */
+    public void clear() {
+        KeySequence oldKeySequence = keySequence;
+        keySequence = KeySequence.getInstance();
+        text.setText(EMPTY_STRING);
+        firePropertyChangeEvent(oldKeySequence);
+    }
+
+    /**
+     * Removes the key strokes from the list corresponding the selection. If
+     * <code>allowIncomplete</code>, then invalid key sequences will be
+     * allowed (i.e., those with incomplete strokes in the non-terminal
+     * position). Otherwise, incomplete strokes will be removed. This modifies
+     * <code>keyStrokes</code> in place, and has no effect on the text widget
+     * this class wraps.
+     *
+     * @param keyStrokes
+     *            The list of key strokes from which the selection should be
+     *            removed; must not be <code>null</code>.
+     * @param allowIncomplete
+     *            Whether incomplete strokes should be allowed to exist in the
+     *            list after the deletion.
+     * @return The index at which a subsequent insert should occur. This index
+     *         only has meaning to the <code>insertStrokeAt</code> method.
+     */
+    private final int deleteSelection(KeyStroke[] keyStrokes,
+            bool allowIncomplete, KeyStroke[][] deletedKeyStrokes) {
+        // Get the current selection.
+        Point selection = text.getSelection();
+        int start = selection.x;
+        int end = selection.y;
+
+        /*
+         * Using the key sequence format method, discover the point at which
+         * adding key strokes passes or equals the start of the selection. In
+         * other words, find the first stroke that is part of the selection.
+         * Keep track of the text range under which the stroke appears (i.e.,
+         * startTextIndex->string.length() is the first selected stroke).
+         */
+        String string;
+        auto currentStrokes = new ArraySeq!(KeyStroke);
+        int startTextIndex = 0; // keeps track of the start of the stroke
+        int keyStrokesLength = keyStrokes.length;
+        int i;
+        for (i = 0; (i < keyStrokesLength) && (string.length < start); i++) {
+            startTextIndex = string.length;
+            currentStrokes.append(keyStrokes[i]);
+            string = KeySequence.getInstance(currentStrokes).format();
+        }
+
+        /*
+         * If string.length() is start, then the cursor is positioned between
+         * strokes (i.e., selection is outside of a stroke).
+         */
+        int startStrokeIndex;
+        if (string.length is start) {
+            startStrokeIndex = currentStrokes.size();
+        } else {
+            startStrokeIndex = currentStrokes.size() - 1;
+        }
+
+        /*
+         * Check to see if the cursor is only positioned, rather than actually
+         * selecting something. We only need to compute the end if there is a
+         * selection.
+         */
+        int endStrokeIndex;
+        if (start is end) {
+            return startStrokeIndex;
+        }
+
+        for (; (i < keyStrokesLength) && (string.length < end); i++) {
+            currentStrokes.append(keyStrokes[i]);
+            string = KeySequence.getInstance(currentStrokes).format();
+        }
+        endStrokeIndex = currentStrokes.size() - 1;
+        if (endStrokeIndex < 0) {
+            endStrokeIndex = 0;
+        }
+
+        /*
+         * Remove the strokes that are touched by the selection. Keep track of
+         * the first stroke removed.
+         */
+        int newLength = endStrokeIndex - startStrokeIndex + 1;
+        deletedKeyStrokes[0] = new KeyStroke[newLength];
+        KeyStroke startStroke = keyStrokes[startStrokeIndex];
+        System.arraycopy(keyStrokes, 0, keyStrokes, 0, newLength);
+
+        /*
+         * Allow the first stroke removed to be replaced by an incomplete
+         * stroke.
+         */
+        if (allowIncomplete) {
+            int modifierKeys = startStroke.getModifierKeys();
+            KeyStroke incompleteStroke = KeyStroke.getInstance(modifierKeys,
+                    KeyStroke.NO_KEY);
+            int incompleteStrokeLength = incompleteStroke.format().length;
+            if ((startTextIndex + incompleteStrokeLength) <= start) {
+                KeyStroke[] added = new KeyStroke[newLength + 1];
+                System.arraycopy(deletedKeyStrokes[0], 0, added, 0,
+                        startStrokeIndex);
+                added[startStrokeIndex] = incompleteStroke;
+                System.arraycopy(deletedKeyStrokes[0], startStrokeIndex, added,
+                        startStrokeIndex + 1, newLength);
+                deletedKeyStrokes[0] = added;
+            }
+        }
+
+        return startStrokeIndex;
+    }
+
+    /**
+     * Fires a property change event to all of the listeners.
+     *
+     * @param oldKeySequence
+     *            The old key sequence; must not be <code>null</code>.
+     * @since 3.2
+     */
+    protected final void firePropertyChangeEvent(
+            KeySequence oldKeySequence) {
+        if (listeners !is null) {
+            PropertyChangeEvent event = new PropertyChangeEvent(this,
+                    P_KEY_SEQUENCE, oldKeySequence, getKeySequence());
+            foreach( listener; listeners ){
+                listener.propertyChange(event);
+            }
+        }
+    }
+
+    /**
+     * An accessor for the <code>KeySequence</code> that corresponds to the
+     * current state of the text field. This includes incomplete strokes.
+     *
+     * @return The key sequence representation; never <code>null</code>.
+     */
+    public KeySequence getKeySequence() {
+        return keySequence;
+    }
+
+    /**
+     * An accessor for the underlying text widget's contents.
+     *
+     * @return The text contents of this entry; never <code>null</code>.
+     */
+    private String getText() {
+        return text.getText();
+    }
+
+    /**
+     * Tests whether the current key sequence has a stroke with no natural key.
+     *
+     * @return <code>true</code> is there is an incomplete stroke;
+     *         <code>false</code> otherwise.
+     */
+    private bool hasIncompleteStroke() {
+        return !keySequence.isComplete();
+    }
+
+    /**
+     * Tests whether the current text widget has some text selection.
+     *
+     * @return <code>true</code> if the number of selected characters it
+     *         greater than zero; <code>false</code> otherwise.
+     */
+    private bool hasSelection() {
+        return (text.getSelectionCount() > 0);
+    }
+
+    /**
+     * Inserts the key stroke at the current insertion point. This does a
+     * regular delete and insert, as if the key had been pressed.
+     *
+     * @param stroke
+     *            The key stroke to insert; must not be <code>null</code>.
+     */
+    public void insert(KeyStroke stroke) {
+        if (!stroke.isComplete()) {
+            return;
+        }
+
+        // Copy the key strokes in the current key sequence.
+        KeySequence keySequence = getKeySequence();
+        KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes();
+        KeyStroke[] newKeyStrokes;
+        if ((hasIncompleteStroke()) && (!keySequence.isEmpty())) {
+            int newKeyStrokesLength = oldKeyStrokes.length - 1;
+            newKeyStrokes = new KeyStroke[newKeyStrokesLength];
+            System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
+                    newKeyStrokesLength);
+        } else {
+            newKeyStrokes = oldKeyStrokes;
+        }
+
+        KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1);
+        int index = deleteSelection(newKeyStrokes, false, deletedKeyStrokes);
+        if (index is -1) {
+            index = 0;
+        }
+
+        KeyStroke[] keyStrokes = insertStrokeAt(newKeyStrokes, stroke, index);
+        keyFilter.clearInsertionIndex();
+        setKeySequence(KeySequence.getInstance(keyStrokes));
+    }
+
+    /**
+     * Inserts the stroke at the given index in the list of strokes. If the
+     * stroke currently at that index is incomplete, then it tries to merge the
+     * two strokes. If merging is a complete failure (unlikely), then it will
+     * simply overwrite the incomplete stroke. If the stroke at the index is
+     * complete, then it simply inserts the stroke independently.
+     *
+     * @param keyStrokes
+     *            The list of key strokes in which the key stroke should be
+     *            appended; must not be <code>null</code>.
+     * @param stroke
+     *            The stroke to insert; should not be <code>null</code>.
+     * @param index
+     *            The index at which to insert; must be a valid index into the
+     *            list of key strokes.
+     */
+    private final KeyStroke[] insertStrokeAt(KeyStroke[] keyStrokes,
+            KeyStroke stroke, int index) {
+        int keyStrokesLength = keyStrokes.length;
+        KeyStroke currentStroke = (index >= keyStrokesLength) ? null
+                : keyStrokes[index];
+        if ((currentStroke !is null) && (!currentStroke.isComplete())) {
+            int modifierKeys = currentStroke.getModifierKeys();
+            int naturalKey = stroke.getNaturalKey();
+            modifierKeys |= stroke.getModifierKeys();
+            keyStrokes[index] = KeyStroke.getInstance(modifierKeys, naturalKey);
+            return keyStrokes;
+        }
+
+        KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokesLength + 1];
+        System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, index);
+        newKeyStrokes[index] = stroke;
+        if (index < keyStrokesLength) {
+            System.arraycopy(keyStrokes, index, newKeyStrokes, index + 1,
+                    keyStrokesLength-index);
+        }
+        return newKeyStrokes;
+    }
+
+    /**
+     * Tests whether the cursor is in the last position. This means that the
+     * selection extends to the last position.
+     *
+     * @return <code>true</code> if the selection extends to the last
+     *         position; <code>false</code> otherwise.
+     */
+    private bool isCursorInLastPosition() {
+        return (text.getSelection().y >= getText().length);
+    }
+
+    /**
+     * Removes the given listener from this key sequence widget.
+     *
+     * @param listener
+     *            The listener to be removed; must not be <code>null</code>.
+     * @since 3.2
+     */
+    public final void removePropertyChangeListener(
+            IPropertyChangeListener listener) {
+        if ((listener is null) || (listeners is null)) {
+            return;
+        }
+
+        listeners.remove(listener);
+    }
+
+    /**
+     * <p>
+     * A mutator for the key sequence stored within this widget. The text and
+     * caret position are updated.
+     * </p>
+     * <p>
+     * All sequences are limited to maxStrokes number of strokes in length. If
+     * there are already that number of strokes, then it does not show
+     * incomplete strokes, and does not keep track of them.
+     * </p>
+     *
+     * @param newKeySequence
+     *            The new key sequence for this widget; may be <code>null</code>
+     *            if none.
+     */
+    public void setKeySequence(KeySequence newKeySequence) {
+        KeySequence oldKeySequence = keySequence;
+        keySequence = newKeySequence;
+
+        // Trim any extra strokes.
+        if (maxStrokes !is INFINITE) {
+            KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes();
+            if (maxStrokes < oldKeyStrokes.length) {
+                KeyStroke[] newKeyStrokes = new KeyStroke[maxStrokes];
+                System
+                        .arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0,
+                                maxStrokes);
+                keySequence = KeySequence.getInstance(newKeyStrokes);
+            }
+        }
+
+        // Check to see if the text has changed.
+        String currentString = getText();
+        String newString = keySequence.format();
+        if (!currentString.equals(newString)) {
+            // We need to update the text
+            text.removeModifyListener(updateSequenceListener);
+            text.setText(keySequence.format());
+            text.addModifyListener(updateSequenceListener);
+            text.setSelection(getText().length);
+        }
+
+        firePropertyChangeEvent(oldKeySequence);
+    }
+
+    /**
+     * Returns the maximum number of strokes that are permitted in this widget
+     * at one time.
+     *
+     * @return The maximum number of strokes; will be a positive integer or
+     *         <code>INFINITE</code>.
+     */
+    public int getKeyStrokeLimit() {
+        return maxStrokes;
+    }
+
+    /**
+     * A mutator for the maximum number of strokes that are permitted in this
+     * widget at one time.
+     *
+     * @param keyStrokeLimit
+     *            The maximum number of strokes; must be a positive integer or
+     *            <code>INFINITE</code>.
+     */
+    public void setKeyStrokeLimit(int keyStrokeLimit) {
+        if (keyStrokeLimit > 0 || keyStrokeLimit is INFINITE) {
+            this.maxStrokes = keyStrokeLimit;
+        } else {
+            throw new IllegalArgumentException(null);
+        }
+
+        // Make sure we are obeying the new limit.
+        setKeySequence(getKeySequence());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/KeyStroke.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,283 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.keys.KeyStroke;
+
+import dwtx.jface.bindings.keys.IKeyLookup;
+import dwtx.jface.bindings.keys.KeyLookupFactory;
+
+import dwtx.jface.bindings.Trigger;
+import dwtx.jface.bindings.keys.formatting.KeyFormatterFactory;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+static import tango.text.Util;
+/**
+ * <p>
+ * A <code>KeyStroke</code> is defined as an optional set of modifier keys
+ * followed optionally by a natural key. A <code>KeyStroke</code> is said to
+ * be complete if it contains a natural key. A natural key is any Unicode
+ * character (e.g., "backspace", etc.), any character belonging to a natural
+ * language (e.g., "A", "1", "[", etc.), or any special control character
+ * specific to computers (e.g., "F10", "PageUp", etc.).
+ * </p>
+ * <p>
+ * All <code>KeyStroke</code> objects have a formal string representation
+ * available via the <code>toString()</code> method. There are a number of
+ * methods to get instances of <code>KeyStroke</code> objects, including one
+ * which can parse this formal string representation.
+ * </p>
+ * <p>
+ * All <code>KeyStroke</code> objects, via the <code>format()</code> method,
+ * provide a version of their formal string representation translated by
+ * platform and locale, suitable for display to a user.
+ * </p>
+ * <p>
+ * <code>KeyStroke</code> objects are immutable. Clients are not permitted to
+ * extend this class.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class KeyStroke : Trigger, Comparable {
+
+    /**
+     * The delimiter between multiple keys in a single key strokes -- expressed
+     * in the formal key stroke grammar. This is not to be displayed to the
+     * user. It is only intended as an internal representation.
+     */
+    public static const String KEY_DELIMITER = "\u002B"; //$NON-NLS-1$
+
+    /**
+     * The set of delimiters for <code>Key</code> objects allowed during
+     * parsing of the formal string representation.
+     */
+    public static const String KEY_DELIMITERS = KEY_DELIMITER;
+
+    /**
+     * The representation for no key.
+     */
+    public static const int NO_KEY = 0;
+
+    /**
+     * Creates an instance of <code>KeyStroke</code> given a natural key.
+     *
+     * @param naturalKey
+     *            the natural key. The format of this integer is defined by
+     *            whichever widget toolkit you are using; <code>NO_KEY</code>
+     *            always means no natural key.
+     * @return a key stroke. This key stroke will have no modifier keys.
+     *         Guaranteed not to be <code>null</code>.
+     * @see SWTKeySupport
+     */
+    public static final KeyStroke getInstance(int naturalKey) {
+        return new KeyStroke(NO_KEY, naturalKey);
+    }
+
+    /**
+     * Creates an instance of <code>KeyStroke</code> given a set of modifier keys
+     * and a natural key.
+     *
+     * @param modifierKeys
+     *            the modifier keys. The format of this integer is defined by
+     *            whichever widget toolkit you are using; <code>NO_KEY</code>
+     *            always means no modifier keys.
+     * @param naturalKey
+     *            the natural key. The format of this integer is defined by
+     *            whichever widget toolkit you are using; <code>NO_KEY</code>
+     *            always means no natural key.
+     * @return a key stroke. Guaranteed not to be <code>null</code>.
+     * @see SWTKeySupport
+     */
+    public static final KeyStroke getInstance(int modifierKeys,
+            int naturalKey) {
+        return new KeyStroke(modifierKeys, naturalKey);
+    }
+
+    /**
+     * Creates an instance of <code>KeyStroke</code> by parsing a given a formal
+     * string representation.
+     *
+     * @param string
+     *            the formal string representation to parse.
+     * @return a key stroke. Guaranteed not to be <code>null</code>.
+     * @throws ParseException
+     *             if the given formal string representation could not be parsed
+     *             to a valid key stroke.
+     */
+    public static final KeyStroke getInstance(String string) {
+        if (string is null) {
+            throw new NullPointerException("Cannot parse a null string"); //$NON-NLS-1$
+        }
+
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        int modifierKeys = NO_KEY;
+        int naturalKey = NO_KEY;
+        int i = 0;
+
+        auto tokens = tango.text.Util.delimit( string, KEY_DELIMITERS );
+        foreach( idx, token; tokens ){
+
+            if (i % 2 is 0) {
+                if ( idx+1 < tokens.length ) {
+                    token = token.toUpperCase();
+                    int modifierKey = lookup.formalModifierLookup(token);
+                    if (modifierKey is NO_KEY) {
+                        throw new ParseException(
+                                "Cannot create key stroke with duplicate or non-existent modifier key: " //$NON-NLS-1$
+                                        ~ token);
+                    }
+
+                    modifierKeys |= modifierKey;
+
+                } else if (token.length is 1) {
+                    naturalKey = token.charAt(0);
+
+                } else {
+                    token = token.toUpperCase();
+                    naturalKey = lookup.formalKeyLookup(token);
+                }
+            }
+
+            i++;
+        }
+
+        return new KeyStroke(modifierKeys, naturalKey);
+    }
+
+    /**
+     * An integer representation of the modifier keys; <code>NO_KEY</code>
+     * means that there is no modifier key.
+     */
+    private const int modifierKeys;
+
+    /**
+     * The natural key for this key stroke. This value is <code>NO_KEY</code>
+     * if the key stroke is incomplete (i.e., has no natural key).
+     */
+    private const int naturalKey;
+
+    /**
+     * Constructs an instance of <code>KeyStroke</code> given a set of
+     * modifier keys and a natural key.
+     *
+     * @param modifierKeys
+     *            the modifier keys. The format of this integer is defined by
+     *            whichever widget toolkit you are using; <code>NO_KEY</code>
+     *            always means no modifier keys.
+     * @param naturalKey
+     *            the natural key. The format of this integer is defined by
+     *            whichever widget toolkit you are using; <code>NO_KEY</code>
+     *            always means no natural key.
+     * @see SWTKeySupport
+     */
+    private this(int modifierKeys, int naturalKey) {
+        this.modifierKeys = modifierKeys;
+        this.naturalKey = naturalKey;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public final int compareTo(Object object) {
+        KeyStroke keyStroke = cast(KeyStroke) object;
+        int compareTo = Util.compare(modifierKeys, keyStroke.modifierKeys);
+
+        if (compareTo is 0) {
+            compareTo = Util.compare(naturalKey, keyStroke.naturalKey);
+        }
+
+        return compareTo;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public final override int opEquals(Object object) {
+        if (!(cast(KeyStroke)object )) {
+            return false;
+        }
+
+        KeyStroke keyStroke = cast(KeyStroke) object;
+        if (modifierKeys !is keyStroke.modifierKeys) {
+            return false;
+        }
+
+        return (naturalKey is keyStroke.naturalKey);
+    }
+
+    /**
+     * Formats this key stroke into the current default look.
+     *
+     * @return A string representation for this key stroke using the default
+     *         look; never <code>null</code>.
+     */
+    public final String format() {
+        return KeyFormatterFactory.getDefault().format(this);
+    }
+
+    /**
+     * Returns the modifier keys for this key stroke.
+     *
+     * @return the bit mask of modifier keys; <code>NO_KEY</code> means that
+     *         there is no modifier key.
+     */
+    public final int getModifierKeys() {
+        return modifierKeys;
+    }
+
+    /**
+     * Returns the natural key for this key stroke.
+     *
+     * @return The natural key for this key stroke. This value is
+     *         <code>NO_KEY</code> if the key stroke is incomplete (i.e., has
+     *         no natural key).
+     */
+    public final int getNaturalKey() {
+        return naturalKey;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    public final override hash_t toHash() {
+        return modifierKeys << 4 + naturalKey;
+    }
+
+    /**
+     * Returns whether or not this key stroke is complete. Key strokes are
+     * complete iff they have a natural key which is not <code>NO_KEY</code>.
+     *
+     * @return <code>true</code>, iff the key stroke is complete.
+     */
+    public final bool isComplete() {
+        return (naturalKey !is NO_KEY);
+    }
+
+    /**
+     * Returns the formal string representation for this key stroke.
+     *
+     * @return The formal string representation for this key stroke. Guaranteed
+     *         not to be <code>null</code>.
+     * @see java.lang.Object#toString()
+     */
+    public final override String toString() {
+        return KeyFormatterFactory.getFormalKeyFormatter().format(this);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/ParseException.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.ParseException;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * An exception indicating problems while parsing formal string representations
+ * of either <code>KeyStroke</code> or <code>KeySequence</code> objects.
+ * </p>
+ * <p>
+ * <code>ParseException</code> objects are immutable. Clients are not
+ * permitted to extend this class.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class ParseException : Exception {
+
+    /**
+     * Generated serial version UID for this class.
+     *
+     * @since 3.1
+     */
+    private static const long serialVersionUID = 3257009864814376241L;
+
+    /**
+     * Constructs a <code>ParseException</code> with the specified detail
+     * message.
+     *
+     * @param s
+     *            the detail message.
+     */
+    public this(String s) {
+        super(s);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/SWTKeyLookup.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,379 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 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.jface.bindings.keys.SWTKeyLookup;
+
+import dwtx.jface.bindings.keys.IKeyLookup;
+
+import tango.util.collection.HashMap;
+import tango.util.collection.model.Map;
+
+import dwt.DWT;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Integer;
+
+/**
+ * <p>
+ * A look-up table for the formal grammar for keys, and the integer values they
+ * represent. This look-up table is hard-coded to use DWT representations. By
+ * replacing this class (and
+ * {@link dwtx.jface.bindings.keys.SWTKeySupport}), you can remove the
+ * dependency on DWT.
+ * </p>
+ *
+ * @since 3.1
+ * @see dwtx.jface.bindings.keys.KeyLookupFactory
+ */
+public final class SWTKeyLookup : IKeyLookup {
+
+    /**
+     * The look-up table for modifier keys. This is a map of formal name (<code>String</code>)
+     * to integer value (<code>Integer</code>).
+     */
+    private const Map!(String,Integer) modifierKeyTable;
+
+    /**
+     * The look-up table for formal names. This is a map of integer value (<code>Integer</code>)
+     * to formal name (<code>String</code>).
+     */
+    private const Map!(Integer,String) nameTable;
+
+    /**
+     * The look-up table for natural keys. This is a map of formal name (<code>String</code>)
+     * to integer value (<code>Integer</code>).
+     */
+    private const Map!(String,Integer) naturalKeyTable;
+
+    /**
+     * Constructs a new look-up class. This should only be done by the look-up
+     * factory.
+     *
+     * @see KeyLookupFactory
+     */
+    this() {
+        modifierKeyTable = new HashMap!(String,Integer);
+        nameTable = new HashMap!(Integer,String);
+        naturalKeyTable = new HashMap!(String,Integer);
+        Integer alt = new Integer(DWT.ALT);
+        Integer command = new Integer(DWT.COMMAND);
+        Integer ctrl = new Integer(DWT.CTRL);
+        Integer shift = new Integer(DWT.SHIFT);
+        modifierKeyTable.add(ALT_NAME, alt);
+        nameTable.add(alt, ALT_NAME);
+        modifierKeyTable.add(COMMAND_NAME, command);
+        nameTable.add(command, COMMAND_NAME);
+        modifierKeyTable.add(CTRL_NAME, ctrl);
+        nameTable.add(ctrl, CTRL_NAME);
+        modifierKeyTable.add(SHIFT_NAME, shift);
+        nameTable.add(shift, SHIFT_NAME);
+        modifierKeyTable.add(M1_NAME,
+                "carbon".equals(DWT.getPlatform()) ? command : ctrl); //$NON-NLS-1$
+        modifierKeyTable.add(M2_NAME, shift);
+        modifierKeyTable.add(M3_NAME, alt);
+        modifierKeyTable.add(M4_NAME, "carbon".equals(DWT.getPlatform()) ? ctrl //$NON-NLS-1$
+                : command);
+
+        Integer arrowDown = new Integer(DWT.ARROW_DOWN);
+        naturalKeyTable.add(ARROW_DOWN_NAME, arrowDown);
+        nameTable.add(arrowDown, ARROW_DOWN_NAME);
+        Integer arrowLeft = new Integer(DWT.ARROW_LEFT);
+        naturalKeyTable.add(ARROW_LEFT_NAME, arrowLeft);
+        nameTable.add(arrowLeft, ARROW_LEFT_NAME);
+        Integer arrowRight = new Integer(DWT.ARROW_RIGHT);
+        naturalKeyTable.add(ARROW_RIGHT_NAME, arrowRight);
+        nameTable.add(arrowRight, ARROW_RIGHT_NAME);
+        Integer arrowUp = new Integer(DWT.ARROW_UP);
+        naturalKeyTable.add(ARROW_UP_NAME, arrowUp);
+        nameTable.add(arrowUp, ARROW_UP_NAME);
+        Integer breakKey = new Integer(DWT.BREAK);
+        naturalKeyTable.add(BREAK_NAME, breakKey);
+        nameTable.add(breakKey, BREAK_NAME);
+        Integer bs = new Integer(DWT.BS);
+        naturalKeyTable.add(BS_NAME, bs);
+        nameTable.add(bs, BS_NAME);
+        naturalKeyTable.add(BACKSPACE_NAME, bs);
+        Integer capsLock = new Integer(DWT.CAPS_LOCK);
+        naturalKeyTable.add(CAPS_LOCK_NAME, capsLock);
+        nameTable.add(capsLock, CAPS_LOCK_NAME);
+        Integer cr = new Integer(DWT.CR);
+        naturalKeyTable.add(CR_NAME, cr);
+        nameTable.add(cr, CR_NAME);
+        naturalKeyTable.add(ENTER_NAME, cr);
+        naturalKeyTable.add(RETURN_NAME, cr);
+        Integer del = new Integer(DWT.DEL);
+        naturalKeyTable.add(DEL_NAME, del);
+        nameTable.add(del, DEL_NAME);
+        naturalKeyTable.add(DELETE_NAME, del);
+        Integer end = new Integer(DWT.END);
+        naturalKeyTable.add(END_NAME, end);
+        nameTable.add(end, END_NAME);
+        Integer esc = new Integer(DWT.ESC);
+        naturalKeyTable.add(ESC_NAME, esc);
+        nameTable.add(esc, ESC_NAME);
+        naturalKeyTable.add(ESCAPE_NAME, esc);
+        Integer f1 = new Integer(DWT.F1);
+        naturalKeyTable.add(F1_NAME, f1);
+        nameTable.add(f1, F1_NAME);
+        Integer f2 = new Integer(DWT.F2);
+        naturalKeyTable.add(F2_NAME, new Integer(DWT.F2));
+        nameTable.add(f2, F2_NAME);
+        Integer f3 = new Integer(DWT.F3);
+        naturalKeyTable.add(F3_NAME, new Integer(DWT.F3));
+        nameTable.add(f3, F3_NAME);
+        Integer f4 = new Integer(DWT.F4);
+        naturalKeyTable.add(F4_NAME, new Integer(DWT.F4));
+        nameTable.add(f4, F4_NAME);
+        Integer f5 = new Integer(DWT.F5);
+        naturalKeyTable.add(F5_NAME, new Integer(DWT.F5));
+        nameTable.add(f5, F5_NAME);
+        Integer f6 = new Integer(DWT.F6);
+        naturalKeyTable.add(F6_NAME, new Integer(DWT.F6));
+        nameTable.add(f6, F6_NAME);
+        Integer f7 = new Integer(DWT.F7);
+        naturalKeyTable.add(F7_NAME, new Integer(DWT.F7));
+        nameTable.add(f7, F7_NAME);
+        Integer f8 = new Integer(DWT.F8);
+        naturalKeyTable.add(F8_NAME, new Integer(DWT.F8));
+        nameTable.add(f8, F8_NAME);
+        Integer f9 = new Integer(DWT.F9);
+        naturalKeyTable.add(F9_NAME, new Integer(DWT.F9));
+        nameTable.add(f9, F9_NAME);
+        Integer f10 = new Integer(DWT.F10);
+        naturalKeyTable.add(F10_NAME, new Integer(DWT.F10));
+        nameTable.add(f10, F10_NAME);
+        Integer f11 = new Integer(DWT.F11);
+        naturalKeyTable.add(F11_NAME, new Integer(DWT.F11));
+        nameTable.add(f11, F11_NAME);
+        Integer f12 = new Integer(DWT.F12);
+        naturalKeyTable.add(F12_NAME, new Integer(DWT.F12));
+        nameTable.add(f12, F12_NAME);
+        Integer f13 = new Integer(DWT.F13);
+        naturalKeyTable.add(F13_NAME, new Integer(DWT.F13));
+        nameTable.add(f13, F13_NAME);
+        Integer f14 = new Integer(DWT.F14);
+        naturalKeyTable.add(F14_NAME, new Integer(DWT.F14));
+        nameTable.add(f14, F14_NAME);
+        Integer f15 = new Integer(DWT.F15);
+        naturalKeyTable.add(F15_NAME, new Integer(DWT.F15));
+        nameTable.add(f15, F15_NAME);
+        Integer ff = new Integer(12); // ASCII 0x0C
+        naturalKeyTable.add(FF_NAME, ff);
+        nameTable.add(ff, FF_NAME);
+        Integer home = new Integer(DWT.HOME);
+        naturalKeyTable.add(HOME_NAME, home);
+        nameTable.add(home, HOME_NAME);
+        Integer insert = new Integer(DWT.INSERT);
+        naturalKeyTable.add(INSERT_NAME, insert);
+        nameTable.add(insert, INSERT_NAME);
+        Integer lf = new Integer(DWT.LF);
+        naturalKeyTable.add(LF_NAME, lf);
+        nameTable.add(lf, LF_NAME);
+        Integer nul = new Integer(DWT.NULL);
+        naturalKeyTable.add(NUL_NAME, nul);
+        nameTable.add(nul, NUL_NAME);
+        Integer numLock = new Integer(DWT.NUM_LOCK);
+        naturalKeyTable.add(NUM_LOCK_NAME, numLock);
+        nameTable.add(numLock, NUM_LOCK_NAME);
+        Integer keypad0 = new Integer(DWT.KEYPAD_0);
+        naturalKeyTable.add(NUMPAD_0_NAME, keypad0);
+        nameTable.add(keypad0, NUMPAD_0_NAME);
+        Integer keypad1 = new Integer(DWT.KEYPAD_1);
+        naturalKeyTable.add(NUMPAD_1_NAME, keypad1);
+        nameTable.add(keypad1, NUMPAD_1_NAME);
+        Integer keypad2 = new Integer(DWT.KEYPAD_2);
+        naturalKeyTable.add(NUMPAD_2_NAME, keypad2);
+        nameTable.add(keypad2, NUMPAD_2_NAME);
+        Integer keypad3 = new Integer(DWT.KEYPAD_3);
+        naturalKeyTable.add(NUMPAD_3_NAME, keypad3);
+        nameTable.add(keypad3, NUMPAD_3_NAME);
+        Integer keypad4 = new Integer(DWT.KEYPAD_4);
+        naturalKeyTable.add(NUMPAD_4_NAME, keypad4);
+        nameTable.add(keypad4, NUMPAD_4_NAME);
+        Integer keypad5 = new Integer(DWT.KEYPAD_5);
+        naturalKeyTable.add(NUMPAD_5_NAME, keypad5);
+        nameTable.add(keypad5, NUMPAD_5_NAME);
+        Integer keypad6 = new Integer(DWT.KEYPAD_6);
+        naturalKeyTable.add(NUMPAD_6_NAME, keypad6);
+        nameTable.add(keypad6, NUMPAD_6_NAME);
+        Integer keypad7 = new Integer(DWT.KEYPAD_7);
+        naturalKeyTable.add(NUMPAD_7_NAME, keypad7);
+        nameTable.add(keypad7, NUMPAD_7_NAME);
+        Integer keypad8 = new Integer(DWT.KEYPAD_8);
+        naturalKeyTable.add(NUMPAD_8_NAME, keypad8);
+        nameTable.add(keypad8, NUMPAD_8_NAME);
+        Integer keypad9 = new Integer(DWT.KEYPAD_9);
+        naturalKeyTable.add(NUMPAD_9_NAME, keypad9);
+        nameTable.add(keypad9, NUMPAD_9_NAME);
+        Integer keypadAdd = new Integer(DWT.KEYPAD_ADD);
+        naturalKeyTable.add(NUMPAD_ADD_NAME, keypadAdd);
+        nameTable.add(keypadAdd, NUMPAD_ADD_NAME);
+        Integer keypadDecimal = new Integer(DWT.KEYPAD_DECIMAL);
+        naturalKeyTable.add(NUMPAD_DECIMAL_NAME, keypadDecimal);
+        nameTable.add(keypadDecimal, NUMPAD_DECIMAL_NAME);
+        Integer keypadDivide = new Integer(DWT.KEYPAD_DIVIDE);
+        naturalKeyTable.add(NUMPAD_DIVIDE_NAME, keypadDivide);
+        nameTable.add(keypadDivide, NUMPAD_DIVIDE_NAME);
+        Integer keypadCr = new Integer(DWT.KEYPAD_CR);
+        naturalKeyTable.add(NUMPAD_ENTER_NAME, keypadCr);
+        nameTable.add(keypadCr, NUMPAD_ENTER_NAME);
+        Integer keypadEqual = new Integer(DWT.KEYPAD_EQUAL);
+        naturalKeyTable.add(NUMPAD_EQUAL_NAME, keypadEqual);
+        nameTable.add(keypadEqual, NUMPAD_EQUAL_NAME);
+        Integer keypadMultiply = new Integer(DWT.KEYPAD_MULTIPLY);
+        naturalKeyTable.add(NUMPAD_MULTIPLY_NAME, keypadMultiply);
+        nameTable.add(keypadMultiply, NUMPAD_MULTIPLY_NAME);
+        Integer keypadSubtract = new Integer(DWT.KEYPAD_SUBTRACT);
+        naturalKeyTable.add(NUMPAD_SUBTRACT_NAME, keypadSubtract);
+        nameTable.add(keypadSubtract, NUMPAD_SUBTRACT_NAME);
+        Integer pageDown = new Integer(DWT.PAGE_DOWN);
+        naturalKeyTable.add(PAGE_DOWN_NAME, pageDown);
+        nameTable.add(pageDown, PAGE_DOWN_NAME);
+        Integer pageUp = new Integer(DWT.PAGE_UP);
+        naturalKeyTable.add(PAGE_UP_NAME, pageUp);
+        nameTable.add(pageUp, PAGE_UP_NAME);
+        Integer pause = new Integer(DWT.PAUSE);
+        naturalKeyTable.add(PAUSE_NAME, pause);
+        nameTable.add(pause, PAUSE_NAME);
+        Integer printScreen = new Integer(DWT.PRINT_SCREEN);
+        naturalKeyTable.add(PRINT_SCREEN_NAME, printScreen);
+        nameTable.add(printScreen, PRINT_SCREEN_NAME);
+        Integer scrollLock = new Integer(DWT.SCROLL_LOCK);
+        naturalKeyTable.add(SCROLL_LOCK_NAME, scrollLock);
+        nameTable.add(scrollLock, SCROLL_LOCK_NAME);
+        Integer space = new Integer(' ');
+        naturalKeyTable.add(SPACE_NAME, space);
+        nameTable.add(space, SPACE_NAME);
+        Integer tab = new Integer(DWT.TAB);
+        naturalKeyTable.add(TAB_NAME, tab);
+        nameTable.add(tab, TAB_NAME);
+        Integer vt = new Integer(11); // ASCII 0x0B
+        naturalKeyTable.add(VT_NAME, vt);
+        nameTable.add(vt, VT_NAME);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#formalKeyLookup(java.lang.String)
+     *
+     */
+    public final int formalKeyLookup(String name) {
+        Object value = naturalKeyTable.get(name);
+        if (cast(Integer)value ) {
+            return (cast(Integer) value).intValue();
+        }
+
+        if (name.length > 0) {
+            throw new IllegalArgumentException("Unrecognized formal key name: " //$NON-NLS-1$
+                    ~ name);
+        }
+
+        return name.charAt(0);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#formalKeyLookupInteger(java.lang.String)
+     *
+     */
+    public final Integer formalKeyLookupInteger(String name) {
+        Object value = naturalKeyTable.get(name);
+        if (cast(Integer)value ) {
+            return cast(Integer) value;
+        }
+
+        return new Integer(name.charAt(0));
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#formalModifierLookup(java.lang.String)
+     *
+     */
+    public final int formalModifierLookup(String name) {
+        Object value = modifierKeyTable.get(name);
+        if (cast(Integer)value ) {
+            return (cast(Integer) value).intValue();
+        }
+
+        return 0;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#formalNameLookup(int)
+     *
+     */
+    public final String formalNameLookup(int key) {
+        Integer keyObject = new Integer(key);
+        if (nameTable.containsKey(keyObject) ) {
+            return nameTable.get(keyObject);
+        }
+
+        return dcharToString( cast(dchar) key );
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#getAlt()
+     *
+     */
+    public final int getAlt() {
+        return DWT.ALT;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#getCommand()
+     *
+     */
+    public final int getCommand() {
+        return DWT.COMMAND;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#getCtrl()
+     *
+     */
+    public final int getCtrl() {
+        return DWT.CTRL;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#getShift()
+     *
+     */
+    public final int getShift() {
+        return DWT.SHIFT;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.IKeyLookup#isModifierKey(int)
+     *
+     */
+    public final bool isModifierKey(int key) {
+        return ((key & DWT.MODIFIER_MASK) !is 0);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/SWTKeySupport.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,255 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.SWTKeySupport;
+
+import dwtx.jface.bindings.keys.KeyStroke;
+
+import dwt.DWT;
+import dwt.events.KeyEvent;
+import dwt.widgets.Event;
+import dwtx.jface.bindings.keys.formatting.IKeyFormatter;
+import dwtx.jface.bindings.keys.formatting.NativeKeyFormatter;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A utility class for converting DWT events into key strokes.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class SWTKeySupport {
+
+    /**
+     * A formatter that displays key sequences in a style native to the
+     * platform.
+     */
+    private static const IKeyFormatter NATIVE_FORMATTER;
+
+    static this(){
+        NATIVE_FORMATTER = new NativeKeyFormatter();
+    }
+
+    /**
+     * Given an DWT accelerator value, provide the corresponding key stroke.
+     *
+     * @param accelerator
+     *            The accelerator to convert; should be a valid DWT accelerator
+     *            value.
+     * @return The equivalent key stroke; never <code>null</code>.
+     */
+    public static final KeyStroke convertAcceleratorToKeyStroke(int accelerator) {
+        int modifierKeys = accelerator & DWT.MODIFIER_MASK;
+        int naturalKey;
+        if (accelerator is modifierKeys) {
+            naturalKey = KeyStroke.NO_KEY;
+        } else {
+            naturalKey = accelerator - modifierKeys;
+        }
+
+        return KeyStroke.getInstance(modifierKeys, naturalKey);
+    }
+
+    /**
+     * <p>
+     * Converts the given event into an DWT accelerator value -- considering the
+     * modified character with the shift modifier. This is the third accelerator
+     * value that should be checked when processing incoming key events.
+     * </p>
+     * <p>
+     * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as
+     * "Ctrl+Shift+%".
+     * </p>
+     *
+     * @param event
+     *            The event to be converted; must not be <code>null</code>.
+     * @return The combination of the state mask and the unmodified character.
+     */
+    public static final int convertEventToModifiedAccelerator(Event event) {
+        int modifiers = event.stateMask & DWT.MODIFIER_MASK;
+        char character = topKey(event);
+        return modifiers + toUpperCase(character);
+    }
+
+    /**
+     * <p>
+     * Converts the given event into an DWT accelerator value -- considering the
+     * unmodified character with all modifier keys. This is the first
+     * accelerator value that should be checked when processing incoming key
+     * events. However, all alphabetic characters are considered as their
+     * uppercase equivalents.
+     * </p>
+     * <p>
+     * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as
+     * "Ctrl+Shift+5".
+     * </p>
+     *
+     * @param event
+     *            The event to be converted; must not be <code>null</code>.
+     * @return The combination of the state mask and the unmodified character.
+     */
+    public static final int convertEventToUnmodifiedAccelerator(
+            Event event) {
+        return convertEventToUnmodifiedAccelerator(event.stateMask,
+                event.keyCode);
+    }
+
+    /**
+     * <p>
+     * Converts the given state mask and key code into an DWT accelerator value --
+     * considering the unmodified character with all modifier keys. All
+     * alphabetic characters are considered as their uppercase equivalents.
+     * </p>
+     * <p>
+     * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as
+     * "Ctrl+Shift+5".
+     * </p>
+     *
+     * @param stateMask
+     *            The integer mask of modifiers keys depressed when this was
+     *            pressed.
+     * @param keyCode
+     *            The key that was pressed, before being modified.
+     * @return The combination of the state mask and the unmodified character.
+     */
+    private static final int convertEventToUnmodifiedAccelerator(
+            int stateMask, int keyCode) {
+        int modifiers = stateMask & DWT.MODIFIER_MASK;
+        int character = keyCode;
+        return modifiers + toUpperCase(character);
+    }
+
+    /**
+     * <p>
+     * Converts the given event into an DWT accelerator value -- considering the
+     * unmodified character with all modifier keys. This is the first
+     * accelerator value that should be checked. However, all alphabetic
+     * characters are considered as their uppercase equivalents.
+     * </p>
+     * <p>
+     * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as
+     * "Ctrl+%".
+     * </p>
+     *
+     * @param event
+     *            The event to be converted; must not be <code>null</code>.
+     * @return The combination of the state mask and the unmodified character.
+     */
+    public static final int convertEventToUnmodifiedAccelerator(
+            KeyEvent event) {
+        return convertEventToUnmodifiedAccelerator(event.stateMask,
+                event.keyCode);
+    }
+
+    /**
+     * Converts the given event into an DWT accelerator value -- considering the
+     * modified character without the shift modifier. This is the second
+     * accelerator value that should be checked when processing incoming key
+     * events. Key strokes with alphabetic natural keys are run through
+     * <code>convertEventToUnmodifiedAccelerator</code>.
+     *
+     * @param event
+     *            The event to be converted; must not be <code>null</code>.
+     * @return The combination of the state mask without shift, and the modified
+     *         character.
+     */
+    public static final int convertEventToUnshiftedModifiedAccelerator(
+            Event event) {
+        // Disregard alphabetic key strokes.
+        if (CharacterIsLetter(cast(dchar) event.keyCode)) {
+            return convertEventToUnmodifiedAccelerator(event);
+        }
+
+        int modifiers = event.stateMask & (DWT.MODIFIER_MASK ^ DWT.SHIFT);
+        char character = topKey(event);
+        return modifiers + toUpperCase(character);
+    }
+
+    /**
+     * Given a key stroke, this method provides the equivalent DWT accelerator
+     * value. The functional inverse of
+     * <code>convertAcceleratorToKeyStroke</code>.
+     *
+     * @param keyStroke
+     *            The key stroke to convert; must not be <code>null</code>.
+     * @return The DWT accelerator value
+     */
+    public static final int convertKeyStrokeToAccelerator(
+            KeyStroke keyStroke) {
+        return keyStroke.getModifierKeys() + keyStroke.getNaturalKey();
+    }
+
+    /**
+     * Provides an instance of <code>IKeyFormatter</code> appropriate for the
+     * current instance.
+     *
+     * @return an instance of <code>IKeyFormatter</code> appropriate for the
+     *         current instance; never <code>null</code>.
+     */
+    public static IKeyFormatter getKeyFormatterForPlatform() {
+        return NATIVE_FORMATTER;
+    }
+
+    /**
+     * Makes sure that a fully-modified character is converted to the normal
+     * form. This means that "Ctrl+" key strokes must reverse the modification
+     * caused by control-escaping. Also, all lower case letters are converted to
+     * uppercase.
+     *
+     * @param event
+     *            The event from which the fully-modified character should be
+     *            pulled.
+     * @return The modified character, uppercase and without control-escaping.
+     */
+    private static final char topKey(Event event) {
+        char character = event.character;
+        bool ctrlDown = (event.stateMask & DWT.CTRL) !is 0;
+
+        if (ctrlDown && event.character !is event.keyCode
+                && event.character < 0x20
+                && (event.keyCode & DWT.KEYCODE_BIT) is 0) {
+            character += 0x40;
+        }
+
+        return character;
+    }
+
+    /**
+     * Makes the given character uppercase if it is a letter.
+     *
+     * @param keyCode
+     *            The character to convert.
+     * @return The uppercase equivalent, if any; otherwise, the character
+     *         itself.
+     */
+    private static final int toUpperCase(int keyCode) {
+        // Will this key code be truncated?
+        if (keyCode > 0xFFFF) {
+            return keyCode;
+        }
+
+        // Downcast in safety. Only make characters uppercase.
+        char character = cast(char) keyCode;
+        return CharacterIsLetter(character) ? CharacterToUpper(character)
+                : keyCode;
+    }
+
+    /**
+     * This class should never be instantiated.
+     */
+    protected this() {
+        // This class should never be instantiated.
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/formatting/AbstractKeyFormatter.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,182 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.keys.formatting.AbstractKeyFormatter;
+
+import dwtx.jface.bindings.keys.formatting.IKeyFormatter;
+
+import tango.util.collection.HashSet;
+import tango.util.collection.model.Set;
+
+import dwtx.jface.bindings.keys.IKeyLookup;
+import dwtx.jface.bindings.keys.KeyLookupFactory;
+import dwtx.jface.bindings.keys.KeySequence;
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.ResourceBundle;
+
+/**
+ * <p>
+ * An abstract implementation of a key formatter that provides a lot of common
+ * key formatting functionality. It is recommended that implementations of
+ * <code>IKeyFormatter</code> subclass from here, rather than implementing
+ * <code>IKeyFormatter</code> directly.
+ * </p>
+ *
+ * @since 3.1
+ */
+public abstract class AbstractKeyFormatter : IKeyFormatter {
+
+    /**
+     * The key for the delimiter between keys. This is used in the
+     * internationalization bundles.
+     */
+    protected static const String KEY_DELIMITER_KEY = "KEY_DELIMITER"; //$NON-NLS-1$
+
+    /**
+     * The key for the delimiter between key strokes. This is used in the
+     * internationalization bundles.
+     */
+    protected static const String KEY_STROKE_DELIMITER_KEY = "KEY_STROKE_DELIMITER"; //$NON-NLS-1$
+
+    /**
+     * An empty integer array that can be used in
+     * <code>sortModifierKeys(int)</code>.
+     */
+    protected static const int[] NO_MODIFIER_KEYS = null;
+
+    /**
+     * The bundle in which to look up the internationalized text for all of the
+     * individual keys in the system. This is the platform-agnostic version of
+     * the internationalized strings. Some platforms (namely Carbon) provide
+     * special Unicode characters and glyphs for some keys.
+     */
+    private static const ResourceBundle RESOURCE_BUNDLE;
+
+    /**
+     * The keys in the resource bundle. This is used to avoid missing resource
+     * exceptions when they aren't necessary.
+     */
+    private static const Set!(String) resourceBundleKeys;
+
+    static this() {
+        RESOURCE_BUNDLE = ResourceBundle.getBundle(AbstractKeyFormatter.classinfo.name);
+        resourceBundleKeys = new HashSet!(String);
+        foreach( key; RESOURCE_BUNDLE.getKeys()){
+            resourceBundleKeys.add(key);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keysKeyFormatter#format(dwtx.jface.bindings.keys.KeySequence)
+     */
+    public String format(int key) {
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        String name = lookup.formalNameLookup(key);
+
+        if (resourceBundleKeys.contains(name)) {
+            return Util.translateString(RESOURCE_BUNDLE, name, name);
+        }
+
+        return name;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.KeyFormatter#format(dwtx.jface.bindings.keys.KeySequence)
+     */
+    public String format(KeySequence keySequence) {
+        StringBuffer stringBuffer = new StringBuffer();
+
+        KeyStroke[] keyStrokes = keySequence.getKeyStrokes();
+        int keyStrokesLength = keyStrokes.length;
+        for (int i = 0; i < keyStrokesLength; i++) {
+            stringBuffer.append(format(keyStrokes[i]));
+
+            if (i + 1 < keyStrokesLength) {
+                stringBuffer.append(getKeyStrokeDelimiter());
+            }
+        }
+
+        return stringBuffer.toString();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.KeyFormatter#formatKeyStroke(dwtx.jface.bindings.keys.KeyStroke)
+     */
+    public String format(KeyStroke keyStroke) {
+        String keyDelimiter = getKeyDelimiter();
+
+        // Format the modifier keys, in sorted order.
+        int modifierKeys = keyStroke.getModifierKeys();
+        int[] sortedModifierKeys = sortModifierKeys(modifierKeys);
+        StringBuffer stringBuffer = new StringBuffer();
+        if (sortedModifierKeys !is null) {
+            for (int i = 0; i < sortedModifierKeys.length; i++) {
+                int modifierKey = sortedModifierKeys[i];
+                if (modifierKey !is KeyStroke.NO_KEY) {
+                    stringBuffer.append(format(modifierKey));
+                    stringBuffer.append(keyDelimiter);
+                }
+            }
+        }
+
+        // Format the natural key, if any.
+        int naturalKey = keyStroke.getNaturalKey();
+        if (naturalKey !is 0) {
+            stringBuffer.append(format(naturalKey));
+        }
+
+        return stringBuffer.toString();
+
+    }
+
+    /**
+     * An accessor for the delimiter you wish to use between keys. This is used
+     * by the default format implementations to determine the key delimiter.
+     *
+     * @return The delimiter to use between keys; should not be
+     *         <code>null</code>.
+     */
+    protected abstract String getKeyDelimiter();
+
+    /**
+     * An accessor for the delimiter you wish to use between key strokes. This
+     * used by the default format implementations to determine the key stroke
+     * delimiter.
+     *
+     * @return The delimiter to use between key strokes; should not be
+     *         <code>null</code>.
+     */
+    protected abstract String getKeyStrokeDelimiter();
+
+    /**
+     * Separates the modifier keys from each other, and then places them in an
+     * array in some sorted order. The sort order is dependent on the type of
+     * formatter.
+     *
+     * @param modifierKeys
+     *            The modifier keys from the key stroke.
+     * @return An array of modifier key values -- separated and sorted in some
+     *         order. Any values in this array that are
+     *         <code>KeyStroke.NO_KEY</code> should be ignored.
+     */
+    protected abstract int[] sortModifierKeys(int modifierKeys);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/formatting/EmacsKeyFormatter.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.formatting.EmacsKeyFormatter;
+
+import dwtx.jface.bindings.keys.formatting.AbstractKeyFormatter;
+
+import dwtx.jface.bindings.keys.IKeyLookup;
+import dwtx.jface.bindings.keys.KeyLookupFactory;
+import dwtx.jface.bindings.keys.KeySequence;
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.ResourceBundle;
+
+/**
+ * <p>
+ * A key formatter providing the Emacs-style accelerators using single letters
+ * to represent the modifier keys.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class EmacsKeyFormatter : AbstractKeyFormatter {
+
+    /**
+     * The resource bundle used by <code>format()</code> to translate formal
+     * string representations by locale.
+     */
+    private const static ResourceBundle RESOURCE_BUNDLE;
+
+    static this(){
+        RESOURCE_BUNDLE = ResourceBundle.getBundle(EmacsKeyFormatter.classinfo.name);
+    }
+    /**
+     * Formats an individual key into a human readable format. This converts the
+     * key into a format similar to Xemacs.
+     *
+     * @param key
+     *            The key to format; must not be <code>null</code>.
+     * @return The key formatted as a string; should not be <code>null</code>.
+     */
+    public String format(int key) {
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        if (lookup.isModifierKey(key)) {
+            String formattedName = Util.translateString(RESOURCE_BUNDLE, lookup
+                    .formalNameLookup(key), null);
+            if (formattedName !is null) {
+                return formattedName;
+            }
+        }
+
+        return super.format(key).toLowerCase();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyDelimiter()
+     */
+    protected String getKeyDelimiter() {
+        return Util.translateString(RESOURCE_BUNDLE, KEY_DELIMITER_KEY,
+                KeyStroke.KEY_DELIMITER);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyStrokeDelimiter()
+     */
+    protected String getKeyStrokeDelimiter() {
+        return Util.translateString(RESOURCE_BUNDLE, KEY_STROKE_DELIMITER_KEY,
+                KeySequence.KEY_STROKE_DELIMITER);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#sortModifierKeys(int)
+     */
+    protected int[] sortModifierKeys(int modifierKeys) {
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        int[] sortedKeys = new int[4];
+        int index = 0;
+
+        if ((modifierKeys & lookup.getAlt()) !is 0) {
+            sortedKeys[index++] = lookup.getAlt();
+        }
+        if ((modifierKeys & lookup.getCommand()) !is 0) {
+            sortedKeys[index++] = lookup.getCommand();
+        }
+        if ((modifierKeys & lookup.getCtrl()) !is 0) {
+            sortedKeys[index++] = lookup.getCtrl();
+        }
+        if ((modifierKeys & lookup.getShift()) !is 0) {
+            sortedKeys[index++] = lookup.getShift();
+        }
+
+        return sortedKeys;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/formatting/FormalKeyFormatter.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.formatting.FormalKeyFormatter;
+
+import dwtx.jface.bindings.keys.formatting.AbstractKeyFormatter;
+
+import dwtx.jface.bindings.keys.IKeyLookup;
+import dwtx.jface.bindings.keys.KeyLookupFactory;
+import dwtx.jface.bindings.keys.KeySequence;
+import dwtx.jface.bindings.keys.KeyStroke;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * Formats the keys in the internal key sequence grammar. This is used for
+ * persistence, and is not really intended for display to the user.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class FormalKeyFormatter : AbstractKeyFormatter {
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.KeyFormatter#format(dwtx.ui.keys.KeySequence)
+     */
+    public String format(int key) {
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        return lookup.formalNameLookup(key);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyDelimiter()
+     */
+    protected String getKeyDelimiter() {
+        return KeyStroke.KEY_DELIMITER;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyStrokeDelimiter()
+     */
+    protected String getKeyStrokeDelimiter() {
+        return KeySequence.KEY_STROKE_DELIMITER;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#sortModifierKeys(int)
+     */
+    protected int[] sortModifierKeys(int modifierKeys) {
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        int[] sortedKeys = new int[4];
+        int index = 0;
+
+        if ((modifierKeys & lookup.getAlt()) !is 0) {
+            sortedKeys[index++] = lookup.getAlt();
+        }
+        if ((modifierKeys & lookup.getCommand()) !is 0) {
+            sortedKeys[index++] = lookup.getCommand();
+        }
+        if ((modifierKeys & lookup.getCtrl()) !is 0) {
+            sortedKeys[index++] = lookup.getCtrl();
+        }
+        if ((modifierKeys & lookup.getShift()) !is 0) {
+            sortedKeys[index++] = lookup.getShift();
+        }
+
+        return sortedKeys;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/formatting/IKeyFormatter.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.formatting.IKeyFormatter;
+
+import dwtx.jface.bindings.keys.KeySequence;
+import dwtx.jface.bindings.keys.KeyStroke;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * Any formatter capable of taking a key sequence or a key stroke and converting
+ * it into a string. These formatters are used to produce the strings that the
+ * user sees in the keys preference page and the menus, as well as the strings
+ * that are used for persistent storage.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IKeyFormatter {
+
+    /**
+     * Formats an individual key into a human readable format. This uses an
+     * internationalization resource bundle to look up the key. This does not do
+     * any platform-specific formatting (e.g., Carbon's command character).
+     *
+     * @param key
+     *            The key to format.
+     * @return The key formatted as a string; should not be <code>null</code>.
+     */
+    String format(int key);
+
+    /**
+     * Format the given key sequence into a string. The manner of the conversion
+     * is dependent on the formatter. It is required that unequal key sequences
+     * return unequal strings.
+     *
+     * @param keySequence
+     *            The key sequence to convert; must not be <code>null</code>.
+     * @return A string representation of the key sequence; must not be
+     *         <code>null</code>.
+     */
+    String format(KeySequence keySequence);
+
+    /**
+     * Format the given key strokes into a string. The manner of the conversion
+     * is dependent on the formatter. It is required that unequal key strokes
+     * return unequal strings.
+     *
+     * @param keyStroke
+     *            The key stroke to convert; must not be <Code>null</code>.
+     * @return A string representation of the key stroke; must not be <code>
+     *         null</code>
+     */
+    String format(KeyStroke keyStroke);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/formatting/KeyFormatterFactory.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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.jface.bindings.keys.formatting.KeyFormatterFactory;
+
+import dwtx.jface.bindings.keys.formatting.FormalKeyFormatter;
+import dwtx.jface.bindings.keys.formatting.EmacsKeyFormatter;
+import dwtx.jface.bindings.keys.formatting.IKeyFormatter;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A cache for formatters. It keeps a few instances of pre-defined instances of
+ * <code>IKeyFormatter</code> available for use. It also allows the default
+ * formatter to be changed.
+ * </p>
+ *
+ * @since 3.1
+ * @see dwtx.jface.bindings.keys.formatting.IKeyFormatter
+ */
+public final class KeyFormatterFactory {
+
+    /**
+     * The formatter that renders key bindings in a platform-dependent manner.
+     */
+    private static const IKeyFormatter FORMAL_KEY_FORMATTER;
+
+    /**
+     * The formatter that renders key bindings in a form similar to XEmacs
+     */
+    private static const IKeyFormatter EMACS_KEY_FORMATTER;
+
+    /**
+     * The default formatter. This is normally the formal key formatter, but can
+     * be changed by users of this API.
+     */
+    private static IKeyFormatter defaultKeyFormatter;
+
+    static this(){
+        FORMAL_KEY_FORMATTER = new FormalKeyFormatter();
+        EMACS_KEY_FORMATTER = new EmacsKeyFormatter();
+        defaultKeyFormatter = FORMAL_KEY_FORMATTER;
+    }
+
+    /**
+     * An accessor for the current default key formatter.
+     *
+     * @return The default formatter; never <code>null</code>.
+     */
+    public static final IKeyFormatter getDefault() {
+        return defaultKeyFormatter;
+    }
+
+    /**
+     * Provides an instance of <code>EmacsKeyFormatter</code>.
+     *
+     * @return The Xemacs formatter; never <code>null</code>.
+     */
+    public static final IKeyFormatter getEmacsKeyFormatter() {
+        return EMACS_KEY_FORMATTER;
+    }
+
+    /**
+     * Provides an instance of <code>FormalKeyFormatter</code>.
+     *
+     * @return The formal formatter; never <code>null</code>.
+     */
+    public static final IKeyFormatter getFormalKeyFormatter() {
+        return FORMAL_KEY_FORMATTER;
+    }
+
+    /**
+     * Sets the default key formatter.
+     *
+     * @param defaultKeyFormatter
+     *            the default key formatter. Must not be <code>null</code>.
+     */
+    public static final void setDefault(IKeyFormatter defaultKeyFormatter) {
+        if (defaultKeyFormatter is null) {
+            throw new NullPointerException();
+        }
+
+        KeyFormatterFactory.defaultKeyFormatter = defaultKeyFormatter;
+    }
+
+    /**
+     * This class should not be instantiated.
+     */
+    private this() {
+        // Not to be constructred.
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/bindings/keys/formatting/NativeKeyFormatter.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * 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.jface.bindings.keys.formatting.NativeKeyFormatter;
+
+import dwtx.jface.bindings.keys.formatting.AbstractKeyFormatter;
+
+import tango.util.collection.HashMap;
+
+import dwt.DWT;
+import dwtx.jface.bindings.keys.IKeyLookup;
+import dwtx.jface.bindings.keys.KeyLookupFactory;
+import dwtx.jface.bindings.keys.KeySequence;
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.ResourceBundle;
+
+/**
+ * <p>
+ * Formats the key sequences and key strokes into the native human-readable
+ * format. This is typically what you would see on the menus for the given
+ * platform and locale.
+ * </p>
+ *
+ * @since 3.1
+ */
+public final class NativeKeyFormatter : AbstractKeyFormatter {
+
+    /**
+     * The key into the internationalization resource bundle for the delimiter
+     * to use between keys (on the Carbon platform).
+     */
+    private const static String CARBON_KEY_DELIMITER_KEY = "CARBON_KEY_DELIMITER"; //$NON-NLS-1$
+
+    /**
+     * A look-up table for the string representations of various carbon keys.
+     */
+    private const static HashMap!(String,String) CARBON_KEY_LOOK_UP;
+
+    /**
+     * The resource bundle used by <code>format()</code> to translate formal
+     * string representations by locale.
+     */
+    private const static ResourceBundle RESOURCE_BUNDLE;
+
+    /**
+     * The key into the internationalization resource bundle for the delimiter
+     * to use between key strokes (on the Win32 platform).
+     */
+    private const static String WIN32_KEY_STROKE_DELIMITER_KEY = "WIN32_KEY_STROKE_DELIMITER"; //$NON-NLS-1$
+
+    static this() {
+        CARBON_KEY_LOOK_UP = new HashMap!(String,String);
+        RESOURCE_BUNDLE = ResourceBundle.getBundle(NativeKeyFormatter.classinfo.name);
+
+        String carbonBackspace = "\u232B"; //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.BS_NAME, carbonBackspace);
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.BACKSPACE_NAME, carbonBackspace);
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.CR_NAME, "\u21A9"); //$NON-NLS-1$
+        String carbonDelete = "\u2326"; //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.DEL_NAME, carbonDelete);
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.DELETE_NAME, carbonDelete);
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.SPACE_NAME, "\u2423"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.ALT_NAME, "\u2325"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.COMMAND_NAME, "\u2318"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.CTRL_NAME, "\u2303"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.SHIFT_NAME, "\u21E7"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_DOWN_NAME, "\u2193"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_LEFT_NAME, "\u2190"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_RIGHT_NAME, "\u2192"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_UP_NAME, "\u2191"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.END_NAME, "\u2198"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.NUMPAD_ENTER_NAME, "\u2324"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.HOME_NAME, "\u2196"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.PAGE_DOWN_NAME, "\u21DF"); //$NON-NLS-1$
+        CARBON_KEY_LOOK_UP.add(IKeyLookup.PAGE_UP_NAME, "\u21DE"); //$NON-NLS-1$
+    }
+
+    /**
+     * Formats an individual key into a human readable format. This uses an
+     * internationalization resource bundle to look up the key. This does the
+     * platform-specific formatting for Carbon.
+     *
+     * @param key
+     *            The key to format.
+     * @return The key formatted as a string; should not be <code>null</code>.
+     */
+    public final String format(int key) {
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        String name = lookup.formalNameLookup(key);
+
+        // TODO consider platform-specific resource bundles
+        if ("carbon".equals(DWT.getPlatform())) { //$NON-NLS-1$
+            String formattedName = cast(String) CARBON_KEY_LOOK_UP.get(name);
+            if (formattedName !is null) {
+                return formattedName;
+            }
+        }
+
+        return super.format(key);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyDelimiter()
+     */
+    protected String getKeyDelimiter() {
+        // We must do the look up every time, as our locale might change.
+        if ("carbon".equals(DWT.getPlatform())) { //$NON-NLS-1$
+            return Util.translateString(RESOURCE_BUNDLE,
+                    CARBON_KEY_DELIMITER_KEY, Util.ZERO_LENGTH_STRING);
+        }
+
+        return Util.translateString(RESOURCE_BUNDLE, KEY_DELIMITER_KEY,
+                KeyStroke.KEY_DELIMITER);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyStrokeDelimiter()
+     */
+    protected String getKeyStrokeDelimiter() {
+        // We must do the look up every time, as our locale might change.
+        if ("win32".equals(DWT.getPlatform())) { //$NON-NLS-1$
+            return Util.translateString(RESOURCE_BUNDLE,
+                    WIN32_KEY_STROKE_DELIMITER_KEY,
+                    KeySequence.KEY_STROKE_DELIMITER);
+        }
+
+        return Util.translateString(RESOURCE_BUNDLE, KEY_STROKE_DELIMITER_KEY,
+                KeySequence.KEY_STROKE_DELIMITER);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#sortModifierKeys(int)
+     */
+    protected int[] sortModifierKeys(int modifierKeys) {
+        IKeyLookup lookup = KeyLookupFactory.getDefault();
+        String platform = DWT.getPlatform();
+        int[] sortedKeys = new int[4];
+        int index = 0;
+
+        if ("win32".equals(platform) || "wpf".equals(platform)) { //$NON-NLS-1$ //$NON-NLS-2$
+            if ((modifierKeys & lookup.getCtrl()) !is 0) {
+                sortedKeys[index++] = lookup.getCtrl();
+            }
+            if ((modifierKeys & lookup.getAlt()) !is 0) {
+                sortedKeys[index++] = lookup.getAlt();
+            }
+            if ((modifierKeys & lookup.getShift()) !is 0) {
+                sortedKeys[index++] = lookup.getShift();
+            }
+
+        } else if ("gtk".equals(platform) || "motif".equals(platform)) { //$NON-NLS-1$ //$NON-NLS-2$
+            if ((modifierKeys & lookup.getShift()) !is 0) {
+                sortedKeys[index++] = lookup.getShift();
+            }
+            if ((modifierKeys & lookup.getCtrl()) !is 0) {
+                sortedKeys[index++] = lookup.getCtrl();
+            }
+            if ((modifierKeys & lookup.getAlt()) !is 0) {
+                sortedKeys[index++] = lookup.getAlt();
+            }
+
+        } else if ("carbon".equals(platform)) { //$NON-NLS-1$
+            if ((modifierKeys & lookup.getShift()) !is 0) {
+                sortedKeys[index++] = lookup.getShift();
+            }
+            if ((modifierKeys & lookup.getCtrl()) !is 0) {
+                sortedKeys[index++] = lookup.getCtrl();
+            }
+            if ((modifierKeys & lookup.getAlt()) !is 0) {
+                sortedKeys[index++] = lookup.getAlt();
+            }
+            if ((modifierKeys & lookup.getCommand()) !is 0) {
+                sortedKeys[index++] = lookup.getCommand();
+            }
+
+        }
+
+        return sortedKeys;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/dialogs/PopupDialog.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,1244 @@
+/*******************************************************************************
+ * 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
+ *     Stefan Xenos, IBM - bug 156790: Adopt GridLayoutFactory within JFace
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.dialogs.PopupDialog;
+
+import dwtx.jface.dialogs.IDialogConstants;
+import dwtx.jface.dialogs.IDialogSettings;
+import dwtx.jface.dialogs.Dialog;
+
+import tango.util.collection.ArraySeq;
+import tango.util.collection.model.Seq;
+import tango.util.collection.model.SeqView;
+
+import dwt.DWT;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.MouseAdapter;
+import dwt.events.MouseEvent;
+import dwt.events.SelectionAdapter;
+import dwt.events.SelectionEvent;
+import dwt.graphics.Color;
+import dwt.graphics.Font;
+import dwt.graphics.FontData;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+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.Menu;
+import dwt.widgets.Shell;
+import dwt.widgets.ToolBar;
+import dwt.widgets.ToolItem;
+import dwt.widgets.Tracker;
+import dwtx.jface.action.Action;
+import dwtx.jface.action.GroupMarker;
+import dwtx.jface.action.IAction;
+import dwtx.jface.action.IMenuManager;
+import dwtx.jface.action.MenuManager;
+import dwtx.jface.action.Separator;
+import dwtx.jface.layout.GridDataFactory;
+import dwtx.jface.layout.GridLayoutFactory;
+import dwtx.jface.resource.ImageDescriptor;
+import dwtx.jface.resource.JFaceResources;
+import dwtx.jface.window.Window;
+
+import dwt.dwthelper.utils;
+
+/**
+ * A lightweight, transient dialog that is popped up to show contextual or
+ * temporal information and is easily dismissed. Clients control whether the
+ * dialog should be able to receive input focus. An optional title area at the
+ * top and an optional info area at the bottom can be used to provide additional
+ * information.
+ * <p>
+ * Because the dialog is short-lived, most of the configuration of the dialog is
+ * done in the constructor. Set methods are only provided for those values that
+ * are expected to be dynamically computed based on a particular instance's
+ * internal state.
+ * <p>
+ * Clients are expected to override the creation of the main dialog area, and
+ * may optionally override the creation of the title area and info area in order
+ * to add content. In general, however, the creation of stylistic features, such
+ * as the dialog menu, separator styles, and fonts, is kept private so that all
+ * popup dialogs will have a similar appearance.
+ *
+ * @since 3.2
+ */
+public class PopupDialog : Window {
+
+    /**
+     *
+     */
+    private static const GridDataFactory LAYOUTDATA_GRAB_BOTH;
+
+    /**
+     * The dialog settings key name for stored dialog x location.
+     */
+    private static const String DIALOG_ORIGIN_X = "DIALOG_X_ORIGIN"; //$NON-NLS-1$
+
+    /**
+     * The dialog settings key name for stored dialog y location.
+     */
+    private static const String DIALOG_ORIGIN_Y = "DIALOG_Y_ORIGIN"; //$NON-NLS-1$
+
+    /**
+     * The dialog settings key name for stored dialog width.
+     */
+    private static const String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
+
+    /**
+     * The dialog settings key name for stored dialog height.
+     */
+    private static const String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
+
+    /**
+     * The dialog settings key name for remembering if the persisted bounds
+     * should be accessed.
+     */
+    private static const String DIALOG_USE_PERSISTED_BOUNDS = "DIALOG_USE_PERSISTED_BOUNDS"; //$NON-NLS-1$
+
+    /**
+     * Move action for the dialog.
+     */
+    private class MoveAction : Action {
+
+        this() {
+            super(JFaceResources.getString("PopupDialog.move"), //$NON-NLS-1$
+                    IAction.AS_PUSH_BUTTON);
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.jface.action.IAction#run()
+         */
+        public void run() {
+            performTrackerAction(DWT.NONE);
+        }
+
+    }
+
+    /**
+     * Resize action for the dialog.
+     */
+    private class ResizeAction : Action {
+
+        this() {
+            super(JFaceResources.getString("PopupDialog.resize"), //$NON-NLS-1$
+                    IAction.AS_PUSH_BUTTON);
+        }
+
+        /*
+         * @see dwtx.jface.action.Action#run()
+         */
+        public void run() {
+            performTrackerAction(DWT.RESIZE);
+        }
+    }
+
+    /**
+     *
+     * Remember bounds action for the dialog.
+     */
+    private class PersistBoundsAction : Action {
+
+        this() {
+            super(JFaceResources.getString("PopupDialog.persistBounds"), //$NON-NLS-1$
+                    IAction.AS_CHECK_BOX);
+            setChecked(persistBounds);
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.jface.action.IAction#run()
+         */
+        public void run() {
+            persistBounds = isChecked();
+        }
+    }
+
+    /**
+     * Shell style appropriate for a simple hover popup that cannot get focus.
+     */
+    public const static int HOVER_SHELLSTYLE = DWT.NO_FOCUS | DWT.ON_TOP
+            | DWT.NO_TRIM;
+
+    /**
+     * Shell style appropriate for an info popup that can get focus.
+     */
+    public const static int INFOPOPUP_SHELLSTYLE = DWT.NO_TRIM;
+
+    /**
+     * Shell style appropriate for a resizable info popup that can get focus.
+     */
+    public const static int INFOPOPUPRESIZE_SHELLSTYLE = DWT.RESIZE;
+
+    /**
+     * Margin width (in pixels) to be used in layouts inside popup dialogs
+     * (value is 0).
+     */
+    public const static int POPUP_MARGINWIDTH = 0;
+
+    /**
+     * Margin height (in pixels) to be used in layouts inside popup dialogs
+     * (value is 0).
+     */
+    public const static int POPUP_MARGINHEIGHT = 0;
+
+    /**
+     * Vertical spacing (in pixels) between cells in the layouts inside popup
+     * dialogs (value is 1).
+     */
+    public const static int POPUP_VERTICALSPACING = 1;
+
+    /**
+     * Vertical spacing (in pixels) between cells in the layouts inside popup
+     * dialogs (value is 1).
+     */
+    public const static int POPUP_HORIZONTALSPACING = 1;
+
+    /**
+     *
+     */
+    private static const GridLayoutFactory POPUP_LAYOUT_FACTORY;
+
+    static this(){
+        LAYOUTDATA_GRAB_BOTH = GridDataFactory.fillDefaults().grab(true,true);
+        POPUP_LAYOUT_FACTORY = GridLayoutFactory
+            .fillDefaults().margins(POPUP_MARGINWIDTH, POPUP_MARGINHEIGHT)
+            .spacing(POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING);
+    }
+    /**
+     * Border thickness in pixels.
+     */
+    private static const int BORDER_THICKNESS = 1;
+
+    /**
+     * The dialog's toolbar for the move and resize capabilities.
+     */
+    private ToolBar toolBar = null;
+
+    /**
+     * The dialog's menu manager.
+     */
+    private MenuManager menuManager = null;
+
+    /**
+     * The control representing the main dialog area.
+     */
+    private Control dialogArea;
+
+    /**
+     * Labels that contain title and info text. Cached so they can be updated
+     * dynamically if possible.
+     */
+    private Label titleLabel, infoLabel;
+
+    /**
+     * Separator controls. Cached so they can be excluded from color changes.
+     */
+    private Control titleSeparator, infoSeparator;
+
+    /**
+     * The images for the dialog menu.
+     */
+    private Image menuImage, disabledMenuImage = null;
+
+    /**
+     * Font to be used for the info area text. Computed based on the dialog's
+     * font.
+     */
+    private Font infoFont;
+
+    /**
+     * Font to be used for the title area text. Computed based on the dialog's
+     * font.
+     */
+    private Font titleFont;
+
+    /**
+     * Flags indicating whether we are listening for shell deactivate events,
+     * either those or our parent's. Used to prevent closure when a menu command
+     * is chosen or a secondary popup is launched.
+     */
+    private bool listenToDeactivate;
+
+    private bool listenToParentDeactivate;
+
+    private Listener parentDeactivateListener;
+
+    /**
+     * Flag indicating whether focus should be taken when the dialog is opened.
+     */
+    private bool takeFocusOnOpen = false;
+
+    /**
+     * Flag specifying whether a menu should be shown that allows the user to
+     * move and resize.
+     */
+    private bool showDialogMenu_ = false;
+
+    /**
+     * Flag specifying whether a menu action allowing the user to choose whether
+     * the dialog bounds should be persisted is to be shown.
+     */
+    private bool showPersistAction = false;
+
+    /**
+     * Flag specifying whether the bounds of the popup should be persisted. This
+     * flag is updated by a menu if the menu is shown.
+     */
+    private bool persistBounds = false;
+
+    /**
+     * Text to be shown in an optional title area (on top).
+     */
+    private String titleText;
+
+    /**
+     * Text to be shown in an optional info area (at the bottom).
+     */
+    private String infoText;
+
+    /**
+     * Constructs a new instance of <code>PopupDialog</code>.
+     *
+     * @param parent
+     *            The parent shell.
+     * @param shellStyle
+     *            The shell style.
+     * @param takeFocusOnOpen
+     *            A bool indicating whether focus should be taken by this
+     *            popup when it opens.
+     * @param persistBounds
+     *            A bool indicating whether the bounds should be persisted
+     *            upon close of the dialog. The bounds can only be persisted if
+     *            the dialog settings for persisting the bounds are also
+     *            specified. If a menu action will be provided that allows the
+     *            user to control this feature, then the last known value of the
+     *            user's setting will be used instead of this flag.
+     * @param showDialogMenu
+     *            A bool indicating whether a menu for moving and resizing
+     *            the popup should be provided.
+     * @param showPersistAction
+     *            A bool indicating whether an action allowing the user to
+     *            control the persisting of the dialog bounds should be shown in
+     *            the dialog menu. This parameter has no effect if
+     *            <code>showDialogMenu</code> is <code>false</code>.
+     * @param titleText
+     *            Text to be shown in an upper title area, or <code>null</code>
+     *            if there is no title.
+     * @param infoText
+     *            Text to be shown in a lower info area, or <code>null</code>
+     *            if there is no info area.
+     *
+     * @see PopupDialog#getDialogSettings()
+     */
+    public this(Shell parent, int shellStyle, bool takeFocusOnOpen,
+            bool persistBounds, bool showDialogMenu_,
+            bool showPersistAction, String titleText, String infoText) {
+        super(parent);
+        setShellStyle(shellStyle);
+        this.takeFocusOnOpen = takeFocusOnOpen;
+        this.showDialogMenu_ = showDialogMenu_;
+        this.showPersistAction = showPersistAction;
+        this.titleText = titleText;
+        this.infoText = infoText;
+
+        setBlockOnOpen(false);
+
+        this.persistBounds = persistBounds;
+        initializeWidgetState();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.window.Window#configureShell(Shell)
+     */
+    protected void configureShell(Shell shell) {
+        Display display = shell.getDisplay();
+        shell.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
+
+        int border = ((getShellStyle() & DWT.NO_TRIM) is 0) ? 0
+                : BORDER_THICKNESS;
+        GridLayoutFactory.fillDefaults().margins(border, border).spacing(5,5).applyTo(shell);
+
+        shell.addListener(DWT.Deactivate, new class Listener {
+            public void handleEvent(Event event) {
+                /*
+                 * Close if we are deactivating and have no child shells. If we
+                 * have child shells, we are deactivating due to their opening.
+                 * On X, we receive this when a menu child (such as the system
+                 * menu) of the shell opens, but I have not found a way to
+                 * distinguish that case here. Hence bug #113577 still exists.
+                 */
+                if (listenToDeactivate && event.widget is getShell()
+                        && getShell().getShells().length is 0) {
+                    close();
+                } else {
+                    /* We typically ignore deactivates to work around platform-specific
+                     * event ordering.  Now that we've ignored whatever we were supposed to,
+                     * start listening to deactivates.  Example issues can be found in
+                     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123392
+                     */
+                    listenToDeactivate = true;
+                }
+            }
+        });
+        // Set this true whenever we activate. It may have been turned
+        // off by a menu or secondary popup showing.
+        shell.addListener(DWT.Activate, new class Listener {
+            public void handleEvent(Event event) {
+                // ignore this event if we have launched a child
+                if (event.widget is getShell()
+                        && getShell().getShells().length is 0) {
+                    listenToDeactivate = true;
+                    // Typically we start listening for parent deactivate after
+                    // we are activated, except on the Mac, where the deactivate
+                    // is received after activate.
+                    // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=100668
+                    listenToParentDeactivate = !"carbon".equals(DWT.getPlatform()); //$NON-NLS-1$
+                }
+            }
+        });
+
+        if ((getShellStyle() & DWT.ON_TOP) !is 0 && shell.getParent() !is null) {
+            parentDeactivateListener= new class Listener {
+                public void handleEvent(Event event) {
+                    if (listenToParentDeactivate) {
+                        close();
+                    } else {
+                        // Our first deactivate, now start listening on the Mac.
+                        listenToParentDeactivate = listenToDeactivate;
+                    }
+                }
+            };
+            shell.getParent().addListener(DWT.Deactivate, parentDeactivateListener);
+        }
+
+        shell.addDisposeListener(new class DisposeListener {
+            public void widgetDisposed(DisposeEvent event) {
+                handleDispose();
+            }
+        });
+    }
+
+    /**
+     * The <code>PopupDialog</code> implementation of this <code>Window</code>
+     * method creates and lays out the top level composite for the dialog. It
+     * then calls the <code>createTitleMenuArea</code>,
+     * <code>createDialogArea</code>, and <code>createInfoTextArea</code>
+     * methods to create an optional title and menu area on the top, a dialog
+     * area in the center, and an optional info text area at the bottom.
+     * Overriding <code>createDialogArea</code> and (optionally)
+     * <code>createTitleMenuArea</code> and <code>createTitleMenuArea</code>
+     * are recommended rather than overriding this method.
+     *
+     * @param parent
+     *            the composite used to parent the contents.
+     *
+     * @return the control representing the contents.
+     */
+    protected Control createContents(Composite parent) {
+        Composite composite = new Composite(parent, DWT.NONE);
+        POPUP_LAYOUT_FACTORY.applyTo(composite);
+        LAYOUTDATA_GRAB_BOTH.applyTo(composite);
+
+        // Title area
+        if (hasTitleArea()) {
+            createTitleMenuArea(composite);
+            titleSeparator = createHorizontalSeparator(composite);
+        }
+        // Content
+        dialogArea = createDialogArea(composite);
+        // Create a grid data layout data if one was not provided.
+        // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=118025
+        if (dialogArea.getLayoutData() is null) {
+            LAYOUTDATA_GRAB_BOTH.applyTo(dialogArea);
+        }
+
+        // Info field
+        if (hasInfoArea()) {
+            infoSeparator = createHorizontalSeparator(composite);
+            createInfoTextArea(composite);
+        }
+
+        applyColors(composite);
+        applyFonts(composite);
+        return composite;
+    }
+
+    /**
+     * Creates and returns the contents of the dialog (the area below the title
+     * area and above the info text area.
+     * <p>
+     * The <code>PopupDialog</code> implementation of this framework method
+     * creates and returns a new <code>Composite</code> with standard margins
+     * and spacing.
+     * <p>
+     * The returned control's layout data must be an instance of
+     * <code>GridData</code>. This method must not modify the parent's
+     * layout.
+     * <p>
+     * Subclasses must override this method but may call <code>super</code> as
+     * in the following example:
+     *
+     * <pre>
+     * Composite composite = (Composite) super.createDialogArea(parent);
+     * //add controls to composite as necessary
+     * return composite;
+     * </pre>
+     *
+     * @param parent
+     *            the parent composite to contain the dialog area
+     * @return the dialog area control
+     */
+    protected Control createDialogArea(Composite parent) {
+        Composite composite = new Composite(parent, DWT.NONE);
+        POPUP_LAYOUT_FACTORY.applyTo(composite);
+        LAYOUTDATA_GRAB_BOTH.applyTo(composite);
+        return composite;
+    }
+
+    /**
+     * Returns the control that should get initial focus. Subclasses may
+     * override this method.
+     *
+     * @return the Control that should receive focus when the popup opens.
+     */
+    protected Control getFocusControl() {
+        return dialogArea;
+    }
+
+    /**
+     * Sets the tab order for the popup. Clients should override to introduce
+     * specific tab ordering.
+     *
+     * @param composite
+     *            the composite in which all content, including the title area
+     *            and info area, was created. This composite's parent is the
+     *            shell.
+     */
+    protected void setTabOrder(Composite composite) {
+        // default is to do nothing
+    }
+
+    /**
+     * Returns a bool indicating whether the popup should have a title area
+     * at the top of the dialog. Subclasses may override. Default behavior is to
+     * have a title area if there is to be a menu or title text.
+     *
+     * @return <code>true</code> if a title area should be created,
+     *         <code>false</code> if it should not.
+     */
+    protected bool hasTitleArea() {
+        return titleText !is null || showDialogMenu_;
+    }
+
+    /**
+     * Returns a bool indicating whether the popup should have an info area
+     * at the bottom of the dialog. Subclasses may override. Default behavior is
+     * to have an info area if info text was provided at the time of creation.
+     *
+     * @return <code>true</code> if a title area should be created,
+     *         <code>false</code> if it should not.
+     */
+    protected bool hasInfoArea() {
+        return infoText !is null;
+    }
+
+    /**
+     * Creates the title and menu area. Subclasses typically need not override
+     * this method, but instead should use the constructor parameters
+     * <code>showDialogMenu</code> and <code>showPersistAction</code> to
+     * indicate whether a menu should be shown, and
+     * <code>createTitleControl</code> to to customize the presentation of the
+     * title.
+     *
+     * <p>
+     * If this method is overridden, the returned control's layout data must be
+     * an instance of <code>GridData</code>. This method must not modify the
+     * parent's layout.
+     *
+     * @param parent
+     *            The parent composite.
+     * @return The Control representing the title and menu area.
+     */
+    protected Control createTitleMenuArea(Composite parent) {
+
+        Composite titleAreaComposite = new Composite(parent, DWT.NONE);
+        POPUP_LAYOUT_FACTORY.copy().numColumns(2).applyTo(titleAreaComposite);
+        GridDataFactory.fillDefaults()
+            .align_(DWT.FILL, DWT.CENTER).grab(true, false)
+            .applyTo(titleAreaComposite);
+
+        createTitleControl(titleAreaComposite);
+
+        if (showDialogMenu_) {
+            createDialogMenu(titleAreaComposite);
+        }
+        return titleAreaComposite;
+    }
+
+    /**
+     * Creates the control to be used to represent the dialog's title text.
+     * Subclasses may override if a different control is desired for
+     * representing the title text, or if something different than the title
+     * should be displayed in location where the title text typically is shown.
+     *
+     * <p>
+     * If this method is overridden, the returned control's layout data must be
+     * an instance of <code>GridData</code>. This method must not modify the
+     * parent's layout.
+     *
+     * @param parent
+     *            The parent composite.
+     * @return The Control representing the title area.
+     */
+    protected Control createTitleControl(Composite parent) {
+        titleLabel = new Label(parent, DWT.NONE);
+
+        GridDataFactory.fillDefaults()
+            .align_(DWT.FILL, DWT.CENTER)
+            .grab(true, false)
+            .span(showDialogMenu_ ? 1 : 2, 1)
+            .applyTo(titleLabel);
+
+        Font font = titleLabel.getFont();
+        FontData[] fontDatas = font.getFontData();
+        for (int i = 0; i < fontDatas.length; i++) {
+            fontDatas[i].setStyle(DWT.BOLD);
+        }
+        titleFont = new Font(titleLabel.getDisplay(), fontDatas);
+        titleLabel.setFont(titleFont);
+
+        if (titleText !is null) {
+            titleLabel.setText(titleText);
+        }
+        return titleLabel;
+    }
+
+    /**
+     * Creates the optional info text area. This method is only called if the
+     * <code>hasInfoArea()</code> method returns true. Subclasses typically
+     * need not override this method, but may do so.
+     *
+     * <p>
+     * If this method is overridden, the returned control's layout data must be
+     * an instance of <code>GridData</code>. This method must not modify the
+     * parent's layout.
+     *
+     *
+     * @param parent
+     *            The parent composite.
+     * @return The control representing the info text area.
+     *
+     * @see PopupDialog#hasInfoArea()
+     * @see PopupDialog#createTitleControl(Composite)
+     */
+    protected Control createInfoTextArea(Composite parent) {
+        // Status label
+        infoLabel = new Label(parent, DWT.RIGHT);
+        infoLabel.setText(infoText);
+        Font font = infoLabel.getFont();
+        FontData[] fontDatas = font.getFontData();
+        for (int i = 0; i < fontDatas.length; i++) {
+            fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
+        }
+        infoFont = new Font(infoLabel.getDisplay(), fontDatas);
+        infoLabel.setFont(infoFont);
+        GridDataFactory.fillDefaults().grab(true, false).align_(DWT.FILL, DWT.BEGINNING)
+            .applyTo(infoLabel);
+        infoLabel.setForeground(parent.getDisplay().getSystemColor(
+                DWT.COLOR_WIDGET_DARK_SHADOW));
+        return infoLabel;
+    }
+
+    /**
+     * Create a horizontal separator for the given parent.
+     *
+     * @param parent
+     *            The parent composite.
+     * @return The Control representing the horizontal separator.
+     */
+    private Control createHorizontalSeparator(Composite parent) {
+        Label separator = new Label(parent, DWT.SEPARATOR | DWT.HORIZONTAL
+                | DWT.LINE_DOT);
+        GridDataFactory.fillDefaults().align_(DWT.FILL, DWT.CENTER).grab(true, false).applyTo(separator);
+        return separator;
+    }
+
+    /**
+     * Create the dialog's menu for the move and resize actions.
+     *
+     * @param parent
+     *            The parent composite.
+     */
+    private void createDialogMenu(Composite parent) {
+
+        toolBar = new ToolBar(parent, DWT.FLAT);
+        ToolItem viewMenuButton = new ToolItem(toolBar, DWT.PUSH, 0);
+
+        GridDataFactory.fillDefaults().align_(DWT.END, DWT.CENTER).applyTo(toolBar);
+
+        menuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo,
+                "images/popup_menu.gif").createImage();//$NON-NLS-1$
+        disabledMenuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo,
+                "images/popup_menu_disabled.gif").createImage();//$NON-NLS-1$
+        viewMenuButton.setImage(menuImage);
+        viewMenuButton.setDisabledImage(disabledMenuImage);
+        viewMenuButton.setToolTipText(JFaceResources
+                .getString("PopupDialog.menuTooltip")); //$NON-NLS-1$
+        viewMenuButton.addSelectionListener(new class SelectionAdapter {
+            public void widgetSelected(SelectionEvent e) {
+                showDialogMenu();
+            }
+        });
+        viewMenuButton.addDisposeListener(new class DisposeListener {
+            public void widgetDisposed(DisposeEvent e) {
+                menuImage.dispose();
+                menuImage = null;
+                disabledMenuImage.dispose();
+                disabledMenuImage = null;
+            }
+        });
+        // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=177183
+        toolBar.addMouseListener(new class MouseAdapter {
+            public void mouseDown(MouseEvent e) {
+                showDialogMenu();
+            }
+        });
+    }
+
+    /**
+     * Fill the dialog's menu. Subclasses may extend or override.
+     *
+     * @param dialogMenu
+     *            The dialog's menu.
+     */
+    protected void fillDialogMenu(IMenuManager dialogMenu) {
+        dialogMenu.add(new GroupMarker("SystemMenuStart")); //$NON-NLS-1$
+        dialogMenu.add(new MoveAction());
+        dialogMenu.add(new ResizeAction());
+        if (showPersistAction) {
+            dialogMenu.add(new PersistBoundsAction());
+        }
+        dialogMenu.add(new Separator("SystemMenuEnd")); //$NON-NLS-1$
+    }
+
+    /**
+     * Perform the requested tracker action (resize or move).
+     *
+     * @param style
+     *            The track style (resize or move).
+     */
+    private void performTrackerAction(int style) {
+        Shell shell = getShell();
+        if (shell is null || shell.isDisposed()) {
+            return;
+        }
+
+        Tracker tracker = new Tracker(shell.getDisplay(), style);
+        tracker.setStippled(true);
+        Rectangle[] r = [ shell.getBounds() ];
+        tracker.setRectangles(r);
+
+        // Ignore any deactivate events caused by opening the tracker.
+        // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=120656
+        bool oldListenToDeactivate = listenToDeactivate;
+        listenToDeactivate = false;
+        if (tracker.open()) {
+            if (shell !is null && !shell.isDisposed()) {
+                shell.setBounds(tracker.getRectangles()[0]);
+            }
+        }
+        listenToDeactivate = oldListenToDeactivate;
+    }
+
+    /**
+     * Show the dialog's menu. This message has no effect if the receiver was
+     * not configured to show a menu. Clients may call this method in order to
+     * trigger the menu via keystrokes or other gestures. Subclasses typically
+     * do not override method.
+     */
+    protected void showDialogMenu() {
+        if (!showDialogMenu_) {
+            return;
+        }
+
+        if (menuManager is null) {
+            menuManager = new MenuManager();
+            fillDialogMenu(menuManager);
+        }
+        // Setting this flag works around a problem that remains on X only,
+        // whereby activating the menu deactivates our shell.
+        listenToDeactivate = !"gtk".equals(DWT.getPlatform()); //$NON-NLS-1$
+
+        Menu menu = menuManager.createContextMenu(getShell());
+        Rectangle bounds = toolBar.getBounds();
+        Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
+        topLeft = getShell().toDisplay(topLeft);
+        menu.setLocation(topLeft.x, topLeft.y);
+        menu.setVisible(true);
+    }
+
+    /**
+     * Set the text to be shown in the popup's info area. This message has no
+     * effect if there was no info text supplied when the dialog first opened.
+     * Subclasses may override this method.
+     *
+     * @param text
+     *            the text to be shown when the info area is displayed.
+     *
+     */
+    protected void setInfoText(String text) {
+        infoText = text;
+        if (infoLabel !is null) {
+            infoLabel.setText(text);
+        }
+    }
+
+    /**
+     * Set the text to be shown in the popup's title area. This message has no
+     * effect if there was no title label specified when the dialog was
+     * originally opened. Subclasses may override this method.
+     *
+     * @param text
+     *            the text to be shown when the title area is displayed.
+     *
+     */
+    protected void setTitleText(String text) {
+        titleText = text;
+        if (titleLabel !is null) {
+            titleLabel.setText(text);
+        }
+    }
+
+    /**
+     * Return a bool indicating whether this dialog will persist its bounds.
+     * This value is initially set in the dialog's constructor, but can be
+     * modified if the persist bounds action is shown on the menu and the user
+     * has changed its value. Subclasses may override this method.
+     *
+     * @return <true> if the dialogs bounds will be persisted, false if it will
+     *         not.
+     */
+    protected bool getPersistBounds() {
+        return persistBounds;
+    }
+
+    /**
+     * Opens this window, creating it first if it has not yet been created.
+     * <p>
+     * This method is reimplemented for special configuration of PopupDialogs.
+     * It never blocks on open, immediately returning <code>OK</code> if the
+     * open is successful, or <code>CANCEL</code> if it is not. It provides
+     * framework hooks that allow subclasses to set the focus and tab order, and
+     * avoids the use of <code>shell.open()</code> in cases where the focus
+     * should not be given to the shell initially.
+     *
+     * @return the return code
+     *
+     * @see dwtx.jface.window.Window#open()
+     */
+    public int open() {
+
+        Shell shell = getShell();
+        if (shell is null || shell.isDisposed()) {
+            shell = null;
+            // create the window
+            create();
+            shell = getShell();
+        }
+
+        // provide a hook for adjusting the bounds. This is only
+        // necessary when there is content driven sizing that must be
+        // adjusted each time the dialog is opened.
+        adjustBounds();
+
+        // limit the shell size to the display size
+        constrainShellSize();
+
+        // set up the tab order for the dialog
+        setTabOrder(cast(Composite) getContents());
+
+        // initialize flags for listening to deactivate
+        listenToDeactivate = false;
+        listenToParentDeactivate = false;
+
+        // open the window
+        if (takeFocusOnOpen) {
+            shell.open();
+            getFocusControl().setFocus();
+        } else {
+            shell.setVisible(true);
+        }
+
+        return OK;
+
+    }
+
+    /**
+     * Closes this window, disposes its shell, and removes this window from its
+     * window manager (if it has one).
+     * <p>
+     * This method is extended to save the dialog bounds and initialize widget
+     * state so that the widgets can be recreated if the dialog is reopened.
+     * This method may be extended (<code>super.close</code> must be called).
+     * </p>
+     *
+     * @return <code>true</code> if the window is (or was already) closed, and
+     *         <code>false</code> if it is still open
+     */
+    public bool close() {
+        // If already closed, there is nothing to do.
+        // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127505
+        if (getShell() is null || getShell().isDisposed()) {
+            return true;
+        }
+
+        saveDialogBounds(getShell());
+        // Widgets are about to be disposed, so null out any state
+        // related to them that was not handled in dispose listeners.
+        // We do this before disposal so that any received activate or
+        // deactivate events are duly ignored.
+        initializeWidgetState();
+
+        if (parentDeactivateListener !is null) {
+            getShell().getParent().removeListener(DWT.Deactivate, parentDeactivateListener);
+            parentDeactivateListener = null;
+        }
+
+        return super.close();
+    }
+
+    /**
+     * Gets the dialog settings that should be used for remembering the bounds
+     * of the dialog. Subclasses should override this method when they wish to
+     * persist the bounds of the dialog.
+     *
+     * @return settings the dialog settings used to store the dialog's location
+     *         and/or size, or <code>null</code> if the dialog's bounds should
+     *         never be stored.
+     */
+    protected IDialogSettings getDialogSettings() {
+        return null;
+    }
+
+    /**
+     * Saves the bounds of the shell in the appropriate dialog settings. The
+     * bounds are recorded relative to the parent shell, if there is one, or
+     * display coordinates if there is no parent shell. Subclasses typically
+     * need not override this method, but may extend it (calling
+     * <code>super.saveDialogBounds</code> if additional bounds information
+     * should be stored. Clients may also call this method to persist the bounds
+     * at times other than closing the dialog.
+     *
+     * @param shell
+     *            The shell whose bounds are to be stored
+     */
+    protected void saveDialogBounds(Shell shell) {
+        IDialogSettings settings = getDialogSettings();
+        if (settings !is null) {
+            Point shellLocation = shell.getLocation();
+            Point shellSize = shell.getSize();
+            Shell parent = getParentShell();
+            if (parent !is null) {
+                Point parentLocation = parent.getLocation();
+                shellLocation.x -= parentLocation.x;
+                shellLocation.y -= parentLocation.y;
+            }
+            if (persistBounds) {
+                String prefix = this.classinfo.name;
+                settings.put(prefix ~ DIALOG_ORIGIN_X, shellLocation.x);
+                settings.put(prefix ~ DIALOG_ORIGIN_Y, shellLocation.y);
+                settings.put(prefix ~ DIALOG_WIDTH, shellSize.x);
+                settings.put(prefix ~ DIALOG_HEIGHT, shellSize.y);
+            }
+            if (showPersistAction && showDialogMenu_) {
+                settings.put(
+                        this.classinfo.name ~ DIALOG_USE_PERSISTED_BOUNDS,
+                        persistBounds);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.window.Window#getInitialSize()
+     */
+    protected Point getInitialSize() {
+        Point result = super.getInitialSize();
+        if (persistBounds) {
+            IDialogSettings settings = getDialogSettings();
+            if (settings !is null) {
+                try {
+                    int width = settings.getInt(this.classinfo.name
+                            ~ DIALOG_WIDTH);
+                    int height = settings.getInt(this.classinfo.name
+                            ~ DIALOG_HEIGHT);
+                    result = new Point(width, height);
+
+                } catch (NumberFormatException e) {
+                }
+            }
+        }
+        // No attempt is made to constrain the bounds. The default
+        // constraining behavior in Window will be used.
+        return result;
+    }
+
+    /**
+     * Adjust the bounds of the popup as necessary prior to opening the dialog.
+     * Default is to do nothing, which honors any bounds set directly by clients
+     * or those that have been saved in the dialog settings. Subclasses should
+     * override this method when there are bounds computations that must be
+     * checked each time the dialog is opened.
+     */
+    protected void adjustBounds() {
+    }
+
+    /**
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.window.Window#getInitialLocation(dwt.graphics.Point)
+     */
+    protected Point getInitialLocation(Point initialSize) {
+        Point result = super.getInitialLocation(initialSize);
+        if (persistBounds) {
+            IDialogSettings settings = getDialogSettings();
+            if (settings !is null) {
+                try {
+                    int x = settings.getInt(this.classinfo.name
+                            ~ DIALOG_ORIGIN_X);
+                    int y = settings.getInt(this.classinfo.name
+                            ~ DIALOG_ORIGIN_Y);
+                    result = new Point(x, y);
+                    // The coordinates were stored relative to the parent shell.
+                    // Convert to display coordinates.
+                    Shell parent = getParentShell();
+                    if (parent !is null) {
+                        Point parentLocation = parent.getLocation();
+                        result.x += parentLocation.x;
+                        result.y += parentLocation.y;
+                    }
+                } catch (NumberFormatException e) {
+                }
+            }
+        }
+        // No attempt is made to constrain the bounds. The default
+        // constraining behavior in Window will be used.
+        return result;
+    }
+
+    /**
+     * Apply any desired color to the specified composite and its children.
+     *
+     * @param composite
+     *            the contents composite
+     */
+    private void applyColors(Composite composite) {
+        applyForegroundColor(getShell().getDisplay().getSystemColor(
+                DWT.COLOR_INFO_FOREGROUND), composite,
+                getForegroundColorExclusions());
+        applyBackgroundColor(getShell().getDisplay().getSystemColor(
+                DWT.COLOR_INFO_BACKGROUND), composite,
+                getBackgroundColorExclusions());
+    }
+
+    /**
+     * Apply any desired fonts to the specified composite and its children.
+     *
+     * @param composite
+     *            the contents composite
+     */
+    private void applyFonts(Composite composite) {
+        Dialog.applyDialogFont(composite);
+
+    }
+
+    /**
+     * Set the specified foreground color for the specified control and all of
+     * its children, except for those specified in the list of exclusions.
+     *
+     * @param color
+     *            the color to use as the foreground color
+     * @param control
+     *            the control whose color is to be changed
+     * @param exclusions
+     *            a list of controls who are to be excluded from getting their
+     *            color assigned
+     */
+    private void applyForegroundColor(Color color, Control control,
+            SeqView!(Control) exclusions) {
+        if (!exclusions.contains(control)) {
+            control.setForeground(color);
+        }
+        if ( auto comp = cast(Composite)control ) {
+            Control[] children = comp.getChildren();
+            for (int i = 0; i < children.length; i++) {
+                applyForegroundColor(color, children[i], exclusions);
+            }
+        }
+    }
+
+    /**
+     * Set the specified background color for the specified control and all of
+     * its children.
+     *
+     * @param color
+     *            the color to use as the background color
+     * @param control
+     *            the control whose color is to be changed
+     * @param exclusions
+     *            a list of controls who are to be excluded from getting their
+     *            color assigned
+     */
+    private void applyBackgroundColor(Color color, Control control,
+            SeqView!(Control) exclusions) {
+        if (!exclusions.contains(control)) {
+            control.setBackground(color);
+        }
+        if (auto comp = cast(Composite)control ) {
+            Control[] children = comp.getChildren();
+            for (int i = 0; i < children.length; i++) {
+                applyBackgroundColor(color, children[i], exclusions);
+            }
+        }
+    }
+
+    /**
+     * Set the specified foreground color for the specified control and all of
+     * its children. Subclasses may override this method, but typically do not.
+     * If a subclass wishes to exclude a particular control in its contents from
+     * getting the specified foreground color, it may instead override
+     * <code>PopupDialog.getForegroundColorExclusions</code>.
+     *
+     * @param color
+     *            the color to use as the background color
+     * @param control
+     *            the control whose color is to be changed
+     * @see PopupDialog#getBackgroundColorExclusions()
+     */
+    protected void applyForegroundColor(Color color, Control control) {
+        applyForegroundColor(color, control, getForegroundColorExclusions());
+    }
+
+    /**
+     * Set the specified background color for the specified control and all of
+     * its children. Subclasses may override this method, but typically do not.
+     * If a subclass wishes to exclude a particular control in its contents from
+     * getting the specified background color, it may instead override
+     * <code>PopupDialog.getBackgroundColorExclusions</code>.
+     *
+     * @param color
+     *            the color to use as the background color
+     * @param control
+     *            the control whose color is to be changed
+     * @see PopupDialog#getBackgroundColorExclusions()
+     */
+    protected void applyBackgroundColor(Color color, Control control) {
+        applyBackgroundColor(color, control, getBackgroundColorExclusions());
+    }
+
+    /**
+     * Return a list of controls which should never have their foreground color
+     * reset. Subclasses may extend this method (should always call
+     * <code>super.getForegroundColorExclusions</code> to aggregate the list.
+     *
+     *
+     * @return the List of controls
+     */
+    protected SeqView!(Control) getForegroundColorExclusions() {
+        auto list = new ArraySeq!(Control);
+        list.capacity(3);
+        if (infoLabel !is null) {
+            list.append(infoLabel);
+        }
+        if (titleSeparator !is null) {
+            list.append(titleSeparator);
+        }
+        if (infoSeparator !is null) {
+            list.append(infoSeparator);
+        }
+        return list;
+    }
+
+    /**
+     * Return a list of controls which should never have their background color
+     * reset. Subclasses may extend this method (should always call
+     * <code>super.getBackgroundColorExclusions</code> to aggregate the list.
+     *
+     * @return the List of controls
+     */
+    protected SeqView!(Control) getBackgroundColorExclusions() {
+        auto list = new ArraySeq!(Control);
+        list.capacity(2);
+        if (titleSeparator !is null) {
+            list.append(titleSeparator);
+        }
+        if (infoSeparator !is null) {
+            list.append(infoSeparator);
+        }
+        return list;
+    }
+
+    /**
+     * Initialize any state related to the widgetry that should be set up each
+     * time widgets are created.
+     */
+    private void initializeWidgetState() {
+        menuManager = null;
+        dialogArea = null;
+        titleLabel = null;
+        titleSeparator = null;
+        infoSeparator = null;
+        infoLabel = null;
+        toolBar = null;
+
+        // If the menu item for persisting bounds is displayed, use the stored
+        // value to determine whether any persisted bounds should be honored at
+        // all.
+        if (showDialogMenu_ && showPersistAction) {
+            IDialogSettings settings = getDialogSettings();
+            if (settings !is null) {
+                persistBounds = settings.getBoolean(this.classinfo.name
+                        ~ DIALOG_USE_PERSISTED_BOUNDS);
+            }
+        }
+
+    }
+
+    /**
+     * The dialog is being disposed.  Dispose of any resources allocated.
+     *
+     */
+    private void handleDispose() {
+        if (infoFont !is null && !infoFont.isDisposed()) {
+            infoFont.dispose();
+        }
+        infoFont = null;
+        if (titleFont !is null && !titleFont.isDisposed()) {
+            titleFont.dispose();
+        }
+        titleFont = null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/dialogs/StatusDialog.d	Tue Apr 01 08:00:31 2008 +0200
@@ -0,0 +1,306 @@
+/*******************************************************************************
+ * 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.jface.dialogs.StatusDialog;
+
+import dwtx.jface.dialogs.Dialog;
+import dwtx.jface.dialogs.TrayDialog;
+import dwtx.jface.dialogs.IDialogConstants;
+
+import dwt.DWT;
+import dwt.custom.CLabel;
+import dwt.graphics.Color;
+import dwt.graphics.Image;
+import dwt.layout.GridData;
+import dwt.layout.GridLayout;
+import dwt.widgets.Button;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Shell;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.Status;
+import dwtx.jface.resource.JFaceColors;
+import dwtx.jface.resource.JFaceResources;
+import dwtx.jface.util.Policy;
+import dwtx.jface.util.Util;
+
+import dwt.dwthelper.utils;
+
+/**
+ * An abstract base class for dialogs with a status bar and OK/CANCEL buttons.
+ * The status message is specified in an IStatus which can be of severity ERROR,
+ * WARNING, INFO or OK. The OK button is enabled or disabled depending on the
+ * status.
+ *
+ * @since 3.1
+ */
+public abstract class StatusDialog : TrayDialog {
+
+    private Button fOkButton;
+
+    private MessageLine fStatusLine;
+
+    private IStatus fLastStatus;
+
+    private String fTitle;
+
+    private Image fImage;
+
+    private bool fStatusLineAboveButtons = true;
+
+    /**
+     * A message line displaying a status.
+     */
+    private class MessageLine : CLabel {
+
+        private Color fNormalMsgAreaBackground;
+
+        /**
+         * Creates a new message line as a child of the given parent.
+         *
+         * @param parent
+         */
+        public this(Composite parent) {
+            this(parent, DWT.LEFT);
+        }
+
+        /**
+         * Creates a new message line as a child of the parent and with the
+         * given DWT stylebits.
+         *
+         * @param parent
+         * @param style
+         */
+        public this(Composite parent, int style) {
+            super(parent, style);
+            fNormalMsgAreaBackground = getBackground();
+        }
+
+        /**
+         * Find an image assocated with the status.
+         *
+         * @param status
+         * @return Image
+         */
+        private Image findImage(IStatus status) {
+            if (status.isOK()) {
+                return null;
+            } else if (status.matches(IStatus.ERROR)) {
+                return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_ERROR);
+            } else if (status.matches(IStatus.WARNING)) {
+                return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING);
+            } else if (status.matches(IStatus.INFO)) {
+                return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO);
+            }
+            return null;
+        }
+
+        /**
+         * Sets the message and image to the given status.
+         *
+         * @param status
+         *            IStatus or <code>null</code>. <code>null</code> will
+         *            set the empty text and no image.
+         */
+        public void setErrorStatus(IStatus status) {
+            if (status !is null && !status.isOK()) {
+                String message = status.getMessage();
+                if (message !is null && message.length > 0) {
+                    setText(message);
+                    // unqualified call of setImage is too ambiguous for
+                    // Foundation 1.0 compiler
+                    // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=140576
+                    this.outer.setImage(findImage(status));
+                    setBackground(JFaceColors.getErrorBackground(getDisplay()));
+                    return;
+                }
+            }
+            setText(""); //$NON-NLS-1$
+            // unqualified call of setImage is too ambiguous for Foundation 1.0
+            // compiler
+            // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=140576
+            this.outer.setImage(null);
+            setBackground(fNormalMsgAreaBackground);
+        }
+    }
+
+    /**
+     * Creates an instance of a status dialog.
+     *
+     * @param parent
+     *            the parent Shell of the dialog
+     */
+    public this(Shell parent) {
+        super(parent);
+        fLastStatus = new Status(IStatus.OK, Policy.JFACE, IStatus.OK,
+                Util.ZERO_LENGTH_STRING, null);
+    }
+
+    /**
+     * Specifies whether status line appears to the left of the buttons
+     * (default) or above them.
+     *
+     * @param aboveButtons
+     *            if <code>true</code> status line is placed above buttons; if
+     *            <code>false</code> to the right
+     */
+    public void setStatusLineAboveButtons(bool aboveButtons) {
+        fStatusLineAboveButtons = aboveButtons;
+    }
+
+    /**
+     * Update the dialog's status line to reflect the given status. It is safe
+     * to call this method before the dialog has been opened.
+     *
+     * @param status
+     *            the status to set
+     */
+    protected void updateStatus(IStatus status) {
+        fLastStatus = status;
+        if (fStatusLine !is null && !fStatusLine.isDisposed()) {
+            updateButtonsEnableState(status);
+            fStatusLine.setErrorStatus(status);
+        }
+    }
+
+    /**
+     * Returns the last status.
+     *
+     * @return IStatus
+     */
+    public IStatus getStatus() {
+        return fLastStatus;
+    }
+
+    /**
+     * Updates the status of the ok button to reflect the given status.
+     * Subclasses may override this method to update additional buttons.
+     *
+     * @param status
+     *            the status.
+     */
+    protected void updateButtonsEnableState(IStatus status) {
+        if (fOkButton !is null && !fOkButton.isDisposed()) {
+            fOkButton.setEnabled(!status.matches(IStatus.ERROR));
+        }
+    }
+
+    /*
+     * @see Window#create(Shell)
+     */
+    protected void configureShell(Shell shell) {
+        super.configureShell(shell);
+        if (fTitle !is null) {
+            shell.setText(fTitle);
+        }
+    }
+
+    /*
+     * @see Window#create()
+     */
+    public void create() {
+        super.create();
+        if (fLastStatus !is null) {
+            // policy: dialogs are not allowed to come up with an error message
+            if (fLastStatus.matches(IStatus.ERROR)) {
+                // remove the message
+                fLastStatus = new Status(IStatus.ERROR,
+                        fLastStatus.getPlugin(), fLastStatus.getCode(),
+                        "", fLastStatus.getException()); //$NON-NLS-1$
+            }
+            updateStatus(fLastStatus);
+        }
+    }
+
+    /*
+     * @see Dialog#createButtonsForButtonBar(Composite)
+     */
+    protected void createButtonsForButtonBar(Composite parent) {
+        fOkButton = createButton(parent, IDialogConstants.OK_ID,
+                IDialogConstants.OK_LABEL, true);
+        createButton(parent, IDialogConstants.CANCEL_ID,
+                IDialogConstants.CANCEL_LABEL, false);
+    }
+
+    /*
+     * @see Dialog#createButtonBar(Composite)
+     */
+    protected Control createButtonBar(Composite parent) {
+        Composite composite = new Composite(parent, DWT.NULL);
+        GridLayout layout = new GridLayout();
+
+        if (fStatusLineAboveButtons) {
+            layout.numColumns = 1;
+        } else {
+            layout.numColumns = 2;
+        }
+
+        layout.marginHeight = 0;
+        layout.marginLeft = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
+        layout.marginWidth = 0;
+        composite.setLayout(layout);
+        composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+        if (!fStatusLineAboveButtons && isHelpAvailable()) {
+            createHelpControl(composite);
+        }
+        fStatusLine = new MessageLine(composite);
+        fStatusLine.setAlignment(DWT.LEFT);
+        GridData statusData = new GridData(GridData.FILL_HORIZONTAL);
+        fStatusLine.setErrorStatus(null);
+        if (fStatusLineAboveButtons && isHelpAvailable()) {
+            statusData.horizontalSpan = 2;
+            createHelpControl(composite);
+        }
+        fStatusLine.setLayoutData(statusData);
+        applyDialogFont(composite);
+
+        /*
+         * Create the rest of the button bar, but tell it not to create a help
+         * button (we've already created it).
+         */
+        bool helpAvailable = isHelpAvailable();
+        setHelpAvailable(false);
+        super.createButtonBar(composite);
+        setHelpAvailable(helpAvailable);
+        return composite;
+    }
+
+    /**
+     * Sets the title for this dialog.
+     *
+     * @param title
+     *            the title.
+     */
+    public void setTitle(String title) {
+        fTitle = title !is null ? title : ""; //$NON-NLS-1$
+        Shell shell = getShell();
+        if ((shell !is null) && !shell.isDisposed()) {
+            shell.setText(fTitle);
+        }
+    }
+
+    /**
+     * Sets the image for this dialog.
+     *
+     * @param image
+     *            the image.
+     */
+    public void setImage(Image image) {
+        fImage = image;
+        Shell shell = getShell();
+        if ((shell !is null) && !shell.isDisposed()) {
+            shell.setImage(fImage);
+        }
+    }
+
+}
--- a/dwtx/jface/util/Util.d	Mon Mar 31 02:00:41 2008 +0200
+++ b/dwtx/jface/util/Util.d	Tue Apr 01 08:00:31 2008 +0200
@@ -179,6 +179,9 @@
             return 0;
         }
     }
+    public static final int compare(String left, String right) {
+        return left < right;
+    }
 
 //     /**
 //      * Compares two lists -- account for <code>null</code>. The lists must
@@ -272,6 +275,9 @@
         return left is null ? right is null : ((right !is null) && left
                 .opEquals(right));
     }
+    public static final bool opEquals(String left, String right) {
+        return left == right;
+    }
 
     /**
      * Tests whether two arrays of objects are equal to each other. The arrays
@@ -316,6 +322,22 @@
         return true;
     }
 
+    public static final bool opEquals(String[] leftArray, String[] rightArray) {
+        if (leftArray.length !is rightArray.length) {
+            return false;
+        }
+
+        for (int i = 0; i < leftArray.length; i++) {
+            String left = leftArray[i];
+            String right = rightArray[i];
+            if (left != right) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     /**
      * Provides a hash code based on the given integer value.
      *
@@ -365,6 +387,18 @@
 
         return hashCode;
     }
+    public static final hash_t toHash(String str) {
+        return dwt.dwthelper.utils.toHash(str);
+    }
+    public static final hash_t toHash(String[] objects) {
+        int hashCode = 89;
+        for (int i = 0; i < objects.length; i++) {
+            auto object = objects[i];
+            hashCode = hashCode * 31 + toHash(object);
+        }
+
+        return hashCode;
+    }
 
     /**
      * Checks whether the second array is a subsequence of the first array, and