changeset 29:f12d40e7da8f

fieldassist
author Frank Benoit <benoit@tionex.de>
date Thu, 03 Apr 2008 18:56:20 +0200
parents 50b0163e18f8
children 913f0fd3b347
files dwtx/jface/fieldassist/AutoCompleteField.d dwtx/jface/fieldassist/ComboContentAdapter.d dwtx/jface/fieldassist/ContentProposalAdapter.d dwtx/jface/fieldassist/ControlDecoration.d dwtx/jface/fieldassist/FieldAssistColors.d dwtx/jface/fieldassist/IContentProposal.d dwtx/jface/fieldassist/IContentProposalListener.d dwtx/jface/fieldassist/IContentProposalListener2.d dwtx/jface/fieldassist/IContentProposalProvider.d dwtx/jface/fieldassist/IControlContentAdapter.d dwtx/jface/fieldassist/SimpleContentProposalProvider.d dwtx/jface/fieldassist/TextContentAdapter.d
diffstat 12 files changed, 4197 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/AutoCompleteField.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.fieldassist.AutoCompleteField;
+
+import dwtx.jface.fieldassist.IControlContentAdapter;
+import dwtx.jface.fieldassist.SimpleContentProposalProvider;
+import dwtx.jface.fieldassist.ContentProposalAdapter;
+
+import dwt.widgets.Control;
+
+import dwt.dwthelper.utils;
+
+/**
+ * AutoCompleteField is a class which attempts to auto-complete a user's
+ * keystrokes by activating a popup that filters a list of proposals according
+ * to the content typed by the user.
+ *
+ * @see ContentProposalAdapter
+ * @see SimpleContentProposalProvider
+ *
+ * @since 3.3
+ */
+public class AutoCompleteField {
+
+    private SimpleContentProposalProvider proposalProvider;
+    private ContentProposalAdapter adapter;
+
+    /**
+     * Construct an AutoComplete field on the specified control, whose
+     * completions are characterized by the specified array of Strings.
+     *
+     * @param control
+     *            the control for which autocomplete is desired. May not be
+     *            <code>null</code>.
+     * @param controlContentAdapter
+     *            the <code>IControlContentAdapter</code> used to obtain and
+     *            update the control's contents. May not be <code>null</code>.
+     * @param proposals
+     *            the array of Strings representing valid content proposals for
+     *            the field.
+     */
+    public this(Control control,
+            IControlContentAdapter controlContentAdapter, String[] proposals) {
+        proposalProvider = new SimpleContentProposalProvider(proposals);
+        proposalProvider.setFiltering(true);
+        adapter = new ContentProposalAdapter(control, controlContentAdapter,
+                proposalProvider, null, null);
+        adapter.setPropagateKeys(true);
+        adapter
+                .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
+    }
+
+    /**
+     * Set the Strings to be used as content proposals.
+     *
+     * @param proposals
+     *            the array of Strings to be used as proposals.
+     */
+    public void setProposals(String[] proposals) {
+        proposalProvider.setProposals(proposals);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/ComboContentAdapter.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * 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.fieldassist.ComboContentAdapter;
+
+import dwtx.jface.fieldassist.IControlContentAdapter;
+
+import dwt.graphics.GC;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Combo;
+import dwt.widgets.Control;
+
+import dwt.dwthelper.utils;
+import tango.text.Text;
+alias tango.text.Text.Text!(char) StringBuffer;
+/**
+ * An {@link IControlContentAdapter} for DWT Combo controls. This is a
+ * convenience class for easily creating a {@link ContentProposalAdapter} for
+ * combo fields.
+ *
+ * @since 3.2
+ */
+public class ComboContentAdapter : IControlContentAdapter {
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.taskassistance.IControlContentAdapter#getControlContents(dwt.widgets.Control)
+     */
+    public String getControlContents(Control control) {
+        return (cast(Combo) control).getText();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#setControlContents(dwt.widgets.Control,
+     *      java.lang.String, int)
+     */
+    public void setControlContents(Control control, String text,
+            int cursorPosition) {
+        (cast(Combo) control).setText(text);
+        (cast(Combo) control)
+                .setSelection(new Point(cursorPosition, cursorPosition));
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#insertControlContents(dwt.widgets.Control,
+     *      java.lang.String, int)
+     */
+    public void insertControlContents(Control control, String text,
+            int cursorPosition) {
+        Combo combo = cast(Combo) control;
+        String contents = combo.getText();
+        Point selection = combo.getSelection();
+        StringBuffer sb = new StringBuffer();
+        sb.append(contents.substring(0, selection.x));
+        sb.append(text);
+        if (selection.y < contents.length) {
+            sb.append(contents.substring(selection.y, contents.length));
+        }
+        combo.setText(sb.toString());
+        selection.x = selection.x + cursorPosition;
+        selection.y = selection.x;
+        combo.setSelection(selection);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#getCursorPosition(dwt.widgets.Control)
+     */
+    public int getCursorPosition(Control control) {
+        return (cast(Combo) control).getSelection().x;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#getInsertionBounds(dwt.widgets.Control)
+     */
+    public Rectangle getInsertionBounds(Control control) {
+        Combo combo = cast(Combo) control;
+        int position = combo.getSelection().y;
+        String contents = combo.getText();
+        GC gc = new GC(combo);
+        gc.setFont(combo.getFont());
+        Point extent = gc.textExtent(contents.substring(0, Math.min(position,
+                contents.length)));
+        gc.dispose();
+        return new Rectangle(combo.getClientArea().x + extent.x, combo
+                .getClientArea().y, 1, combo.getClientArea().height);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#setCursorPosition(dwt.widgets.Control,
+     *      int)
+     */
+    public void setCursorPosition(Control control, int index) {
+        (cast(Combo) control).setSelection(new Point(index, index));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/ContentProposalAdapter.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,2051 @@
+/*******************************************************************************
+ * 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.fieldassist.ContentProposalAdapter;
+
+import dwtx.jface.fieldassist.IContentProposal;
+import dwtx.jface.fieldassist.IContentProposalProvider;
+import dwtx.jface.fieldassist.IControlContentAdapter;
+import dwtx.jface.fieldassist.IContentProposalListener;
+import dwtx.jface.fieldassist.IContentProposalListener2;
+
+import tango.util.collection.ArraySeq;
+
+import dwt.DWT;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.FocusAdapter;
+import dwt.events.FocusEvent;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.layout.GridData;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.ScrollBar;
+import dwt.widgets.Shell;
+import dwt.widgets.Table;
+import dwt.widgets.TableItem;
+import dwt.widgets.Text;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.ListenerList;
+import dwtx.jface.bindings.keys.KeyStroke;
+import dwtx.jface.dialogs.PopupDialog;
+import dwtx.jface.viewers.ILabelProvider;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+import tango.core.Thread;
+static import tango.text.Text;
+import tango.io.Stdout;
+import tango.text.convert.Format;
+alias tango.text.Text.Text!(char) StringBuffer;
+/**
+ * ContentProposalAdapter can be used to attach content proposal behavior to a
+ * control. This behavior includes obtaining proposals, opening a popup dialog,
+ * managing the content of the control relative to the selections in the popup,
+ * and optionally opening up a secondary popup to further describe proposals.
+ * <p>
+ * A number of configurable options are provided to determine how the control
+ * content is altered when a proposal is chosen, how the content proposal popup
+ * is activated, and whether any filtering should be done on the proposals as
+ * the user types characters.
+ * <p>
+ * This class is not intended to be subclassed.
+ *
+ * @since 3.2
+ */
+public class ContentProposalAdapter {
+
+    /*
+     * The lightweight popup used to show content proposals for a text field. If
+     * additional information exists for a proposal, then selecting that
+     * proposal will result in the information being displayed in a secondary
+     * popup.
+     */
+    class ContentProposalPopup : PopupDialog {
+        /*
+         * The listener we install on the popup and related controls to
+         * determine when to close the popup. Some events (move, resize, close,
+         * deactivate) trigger closure as soon as they are received, simply
+         * because one of the registered listeners received them. Other events
+         * depend on additional circumstances.
+         */
+        private final class PopupCloserListener : Listener {
+            private bool scrollbarClicked = false;
+
+            public void handleEvent(Event e) {
+
+                // If focus is leaving an important widget or the field's
+                // shell is deactivating
+                if (e.type is DWT.FocusOut) {
+                    scrollbarClicked = false;
+                    /*
+                     * Ignore this event if it's only happening because focus is
+                     * moving between the popup shells, their controls, or a
+                     * scrollbar. Do this in an async since the focus is not
+                     * actually switched when this event is received.
+                     */
+                    e.display.asyncExec(new class Runnable {
+                        Event e_;
+                        this(){ e_=e; }
+                        public void run() {
+                            if (isValid()) {
+                                if (scrollbarClicked
+                                        || hasFocus()
+                                        || (infoPopup !is null && infoPopup
+                                                .hasFocus())) {
+                                    return;
+                                }
+                                // Workaround a problem on X and Mac, whereby at
+                                // this point, the focus control is not known.
+                                // This can happen, for example, when resizing
+                                // the popup shell on the Mac.
+                                // Check the active shell.
+                                Shell activeShell = e_.display.getActiveShell();
+                                if (activeShell is getShell()
+                                        || (infoPopup !is null && infoPopup
+                                                .getShell() is activeShell)) {
+                                    return;
+                                }
+                                /*
+                                 * System.out.println(e);
+                                 * System.out.println(e.display.getFocusControl());
+                                 * System.out.println(e.display.getActiveShell());
+                                 */
+                                close();
+                            }
+                        }
+                    });
+                    return;
+                }
+
+                // Scroll bar has been clicked. Remember this for focus event
+                // processing.
+                if (e.type is DWT.Selection) {
+                    scrollbarClicked = true;
+                    return;
+                }
+                // For all other events, merely getting them dictates closure.
+                close();
+            }
+
+            // Install the listeners for events that need to be monitored for
+            // popup closure.
+            void installListeners() {
+                // Listeners on this popup's table and scroll bar
+                proposalTable.addListener(DWT.FocusOut, this);
+                ScrollBar scrollbar = proposalTable.getVerticalBar();
+                if (scrollbar !is null) {
+                    scrollbar.addListener(DWT.Selection, this);
+                }
+
+                // Listeners on this popup's shell
+                getShell().addListener(DWT.Deactivate, this);
+                getShell().addListener(DWT.Close, this);
+
+                // Listeners on the target control
+                control.addListener(DWT.MouseDoubleClick, this);
+                control.addListener(DWT.MouseDown, this);
+                control.addListener(DWT.Dispose, this);
+                control.addListener(DWT.FocusOut, this);
+                // Listeners on the target control's shell
+                Shell controlShell = control.getShell();
+                controlShell.addListener(DWT.Move, this);
+                controlShell.addListener(DWT.Resize, this);
+
+            }
+
+            // Remove installed listeners
+            void removeListeners() {
+                if (isValid()) {
+                    proposalTable.removeListener(DWT.FocusOut, this);
+                    ScrollBar scrollbar = proposalTable.getVerticalBar();
+                    if (scrollbar !is null) {
+                        scrollbar.removeListener(DWT.Selection, this);
+                    }
+
+                    getShell().removeListener(DWT.Deactivate, this);
+                    getShell().removeListener(DWT.Close, this);
+                }
+
+                if (control !is null && !control.isDisposed()) {
+
+                    control.removeListener(DWT.MouseDoubleClick, this);
+                    control.removeListener(DWT.MouseDown, this);
+                    control.removeListener(DWT.Dispose, this);
+                    control.removeListener(DWT.FocusOut, this);
+
+                    Shell controlShell = control.getShell();
+                    controlShell.removeListener(DWT.Move, this);
+                    controlShell.removeListener(DWT.Resize, this);
+                }
+            }
+        }
+
+        /*
+         * The listener we will install on the target control.
+         */
+        private final class TargetControlListener : Listener {
+            // Key events from the control
+            public void handleEvent(Event e) {
+                if (!isValid()) {
+                    return;
+                }
+
+                char key = e.character;
+
+                // Traverse events are handled depending on whether the
+                // event has a character.
+                if (e.type is DWT.Traverse) {
+                    // If the traverse event contains a legitimate character,
+                    // then we must set doit false so that the widget will
+                    // receive the key event. We return immediately so that
+                    // the character is handled only in the key event.
+                    // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132101
+                    if (key !is 0) {
+                        e.doit = false;
+                        return;
+                    }
+                    // Traversal does not contain a character. Set doit true
+                    // to indicate TRAVERSE_NONE will occur and that no key
+                    // event will be triggered. We will check for navigation
+                    // keys below.
+                    e.detail = DWT.TRAVERSE_NONE;
+                    e.doit = true;
+                } else {
+                    // Default is to only propagate when configured that way.
+                    // Some keys will always set doit to false anyway.
+                    e.doit = propagateKeys;
+                }
+
+                // No character. Check for navigation keys.
+
+                if (key is 0) {
+                    int newSelection = proposalTable.getSelectionIndex();
+                    int visibleRows = (proposalTable.getSize().y / proposalTable
+                            .getItemHeight()) - 1;
+                    switch (e.keyCode) {
+                    case DWT.ARROW_UP:
+                        newSelection -= 1;
+                        if (newSelection < 0) {
+                            newSelection = proposalTable.getItemCount() - 1;
+                        }
+                        // Not typical - usually we get this as a Traverse and
+                        // therefore it never propagates. Added for consistency.
+                        if (e.type is DWT.KeyDown) {
+                            // don't propagate to control
+                            e.doit = false;
+                        }
+
+                        break;
+
+                    case DWT.ARROW_DOWN:
+                        newSelection += 1;
+                        if (newSelection > proposalTable.getItemCount() - 1) {
+                            newSelection = 0;
+                        }
+                        // Not typical - usually we get this as a Traverse and
+                        // therefore it never propagates. Added for consistency.
+                        if (e.type is DWT.KeyDown) {
+                            // don't propagate to control
+                            e.doit = false;
+                        }
+
+                        break;
+
+                    case DWT.PAGE_DOWN:
+                        newSelection += visibleRows;
+                        if (newSelection >= proposalTable.getItemCount()) {
+                            newSelection = proposalTable.getItemCount() - 1;
+                        }
+                        if (e.type is DWT.KeyDown) {
+                            // don't propagate to control
+                            e.doit = false;
+                        }
+                        break;
+
+                    case DWT.PAGE_UP:
+                        newSelection -= visibleRows;
+                        if (newSelection < 0) {
+                            newSelection = 0;
+                        }
+                        if (e.type is DWT.KeyDown) {
+                            // don't propagate to control
+                            e.doit = false;
+                        }
+                        break;
+
+                    case DWT.HOME:
+                        newSelection = 0;
+                        if (e.type is DWT.KeyDown) {
+                            // don't propagate to control
+                            e.doit = false;
+                        }
+                        break;
+
+                    case DWT.END:
+                        newSelection = proposalTable.getItemCount() - 1;
+                        if (e.type is DWT.KeyDown) {
+                            // don't propagate to control
+                            e.doit = false;
+                        }
+                        break;
+
+                    // If received as a Traverse, these should propagate
+                    // to the control as keydown. If received as a keydown,
+                    // proposals should be recomputed since the cursor
+                    // position has changed.
+                    case DWT.ARROW_LEFT:
+                    case DWT.ARROW_RIGHT:
+                        if (e.type is DWT.Traverse) {
+                            e.doit = false;
+                        } else {
+                            e.doit = true;
+                            String contents = getControlContentAdapter()
+                                    .getControlContents(getControl());
+                            // If there are no contents, changes in cursor
+                            // position
+                            // have no effect. Note also that we do not affect
+                            // the filter
+                            // text on ARROW_LEFT as we would with BS.
+                            if (contents.length > 0) {
+                                asyncRecomputeProposals(filterText);
+                            }
+                        }
+                        break;
+
+                    // Any unknown keycodes will cause the popup to close.
+                    // Modifier keys are explicitly checked and ignored because
+                    // they are not complete yet (no character).
+                    default:
+                        if (e.keyCode !is DWT.CAPS_LOCK && e.keyCode !is DWT.MOD1
+                                && e.keyCode !is DWT.MOD2
+                                && e.keyCode !is DWT.MOD3
+                                && e.keyCode !is DWT.MOD4) {
+                            close();
+                        }
+                        return;
+                    }
+
+                    // If any of these navigation events caused a new selection,
+                    // then handle that now and return.
+                    if (newSelection >= 0) {
+                        selectProposal(newSelection);
+                    }
+                    return;
+                }
+
+                // key !is 0
+                // Check for special keys involved in cancelling, accepting, or
+                // filtering the proposals.
+                switch (key) {
+                case DWT.ESC:
+                    e.doit = false;
+                    close();
+                    break;
+
+                case DWT.LF:
+                case DWT.CR:
+                    e.doit = false;
+                    Object p = cast(Object)getSelectedProposal();
+                    if (p !is null) {
+                        acceptCurrentProposal();
+                    } else {
+                        close();
+                    }
+                    break;
+
+                case DWT.TAB:
+                    e.doit = false;
+                    getShell().setFocus();
+                    return;
+
+                case DWT.BS:
+                    // Backspace should back out of any stored filter text
+                    if (filterStyle !is FILTER_NONE) {
+                        // We have no filter to back out of, so do nothing
+                        if (filterText.length is 0) {
+                            return;
+                        }
+                        // There is filter to back out of
+                        filterText = filterText.substring(0, filterText
+                                .length - 1);
+                        asyncRecomputeProposals(filterText);
+                        return;
+                    }
+                    // There is no filtering provided by us, but some
+                    // clients provide their own filtering based on content.
+                    // Recompute the proposals if the cursor position
+                    // will change (is not at 0).
+                    int pos = getControlContentAdapter().getCursorPosition(
+                            getControl());
+                    // We rely on the fact that the contents and pos do not yet
+                    // reflect the result of the BS. If the contents were
+                    // already empty, then BS should not cause
+                    // a recompute.
+                    if (pos > 0) {
+                        asyncRecomputeProposals(filterText);
+                    }
+                    break;
+
+                default:
+                    // If the key is a defined unicode character, and not one of
+                    // the special cases processed above, update the filter text
+                    // and filter the proposals.
+                    if (CharacterIsDefined(key)) {
+                        if (filterStyle is FILTER_CUMULATIVE) {
+                            filterText = filterText ~ dcharToString(key);
+                        } else if (filterStyle is FILTER_CHARACTER) {
+                            filterText = dcharToString(key);
+                        }
+                        // Recompute proposals after processing this event.
+                        asyncRecomputeProposals(filterText);
+                    }
+                    break;
+                }
+            }
+        }
+
+        /*
+         * Internal class used to implement the secondary popup.
+         */
+        private class InfoPopupDialog : PopupDialog {
+
+            /*
+             * The text control that displays the text.
+             */
+            private Text text;
+
+            /*
+             * The String shown in the popup.
+             */
+            private String contents = EMPTY;
+
+            /*
+             * Construct an info-popup with the specified parent.
+             */
+            this(Shell parent) {
+                super(parent, PopupDialog.HOVER_SHELLSTYLE, false, false,
+                        false, false, null, null);
+            }
+
+            /*
+             * Create a text control for showing the info about a proposal.
+             */
+            protected Control createDialogArea(Composite parent) {
+                text = new Text(parent, DWT.MULTI | DWT.READ_ONLY | DWT.WRAP
+                        | DWT.NO_FOCUS);
+
+                // Use the compact margins employed by PopupDialog.
+                GridData gd = new GridData(GridData.BEGINNING
+                        | GridData.FILL_BOTH);
+                gd.horizontalIndent = PopupDialog.POPUP_HORIZONTALSPACING;
+                gd.verticalIndent = PopupDialog.POPUP_VERTICALSPACING;
+                text.setLayoutData(gd);
+                text.setText(contents);
+
+                // since DWT.NO_FOCUS is only a hint...
+                text.addFocusListener(new class FocusAdapter {
+                    public void focusGained(FocusEvent event) {
+                        this.outer.close();
+                    }
+                });
+                return text;
+            }
+
+            /*
+             * Adjust the bounds so that we appear adjacent to our parent shell
+             */
+            protected void adjustBounds() {
+                Rectangle parentBounds = getParentShell().getBounds();
+                Rectangle proposedBounds;
+                // Try placing the info popup to the right
+                Rectangle rightProposedBounds = new Rectangle(parentBounds.x
+                        + parentBounds.width
+                        + PopupDialog.POPUP_HORIZONTALSPACING, parentBounds.y
+                        + PopupDialog.POPUP_VERTICALSPACING,
+                        parentBounds.width, parentBounds.height);
+                rightProposedBounds = getConstrainedShellBounds(rightProposedBounds);
+                // If it won't fit on the right, try the left
+                if (rightProposedBounds.intersects(parentBounds)) {
+                    Rectangle leftProposedBounds = new Rectangle(parentBounds.x
+                            - parentBounds.width - POPUP_HORIZONTALSPACING - 1,
+                            parentBounds.y, parentBounds.width,
+                            parentBounds.height);
+                    leftProposedBounds = getConstrainedShellBounds(leftProposedBounds);
+                    // If it won't fit on the left, choose the proposed bounds
+                    // that fits the best
+                    if (leftProposedBounds.intersects(parentBounds)) {
+                        if (rightProposedBounds.x - parentBounds.x >= parentBounds.x
+                                - leftProposedBounds.x) {
+                            rightProposedBounds.x = parentBounds.x
+                                    + parentBounds.width
+                                    + PopupDialog.POPUP_HORIZONTALSPACING;
+                            proposedBounds = rightProposedBounds;
+                        } else {
+                            leftProposedBounds.width = parentBounds.x
+                                    - POPUP_HORIZONTALSPACING
+                                    - leftProposedBounds.x;
+                            proposedBounds = leftProposedBounds;
+                        }
+                    } else {
+                        // use the proposed bounds on the left
+                        proposedBounds = leftProposedBounds;
+                    }
+                } else {
+                    // use the proposed bounds on the right
+                    proposedBounds = rightProposedBounds;
+                }
+                getShell().setBounds(proposedBounds);
+            }
+
+            /*
+             * Set the text contents of the popup.
+             */
+            void setContents(String newContents) {
+                if (newContents is null) {
+                    newContents = EMPTY;
+                }
+                this.contents = newContents;
+                if (text !is null && !text.isDisposed()) {
+                    text.setText(contents);
+                }
+            }
+
+            /*
+             * Return whether the popup has focus.
+             */
+            bool hasFocus() {
+                if (text is null || text.isDisposed()) {
+                    return false;
+                }
+                return text.getShell().isFocusControl()
+                        || text.isFocusControl();
+            }
+        }
+
+        /*
+         * The listener installed on the target control.
+         */
+        private Listener targetControlListener;
+
+        /*
+         * The listener installed in order to close the popup.
+         */
+        private PopupCloserListener popupCloser;
+
+        /*
+         * The table used to show the list of proposals.
+         */
+        private Table proposalTable;
+
+        /*
+         * The proposals to be shown (cached to avoid repeated requests).
+         */
+        private IContentProposal[] proposals;
+
+        /*
+         * Secondary popup used to show detailed information about the selected
+         * proposal..
+         */
+        private InfoPopupDialog infoPopup;
+
+        /*
+         * Flag indicating whether there is a pending secondary popup update.
+         */
+        private bool pendingDescriptionUpdate = false;
+
+        /*
+         * Filter text - tracked while popup is open, only if we are told to
+         * filter
+         */
+        private String filterText = EMPTY;
+
+        /**
+         * Constructs a new instance of this popup, specifying the control for
+         * which this popup is showing content, and how the proposals should be
+         * obtained and displayed.
+         *
+         * @param infoText
+         *            Text to be shown in a lower info area, or
+         *            <code>null</code> if there is no info area.
+         */
+        this(String infoText, IContentProposal[] proposals) {
+            // IMPORTANT: Use of DWT.ON_TOP is critical here for ensuring
+            // that the target control retains focus on Mac and Linux. Without
+            // it, the focus will disappear, keystrokes will not go to the
+            // popup, and the popup closer will wrongly close the popup.
+            // On platforms where DWT.ON_TOP overrides DWT.RESIZE, we will live
+            // with this.
+            // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=126138
+            super(control.getShell(), DWT.RESIZE | DWT.ON_TOP, false, false,
+                    false, false, null, infoText);
+            this.proposals = proposals;
+        }
+
+        /*
+         * Overridden to force change of colors. See
+         * https://bugs.eclipse.org/bugs/show_bug.cgi?id=136244 (non-Javadoc)
+         *
+         * @see dwtx.jface.dialogs.PopupDialog#createContents(dwt.widgets.Composite)
+         */
+        protected Control createContents(Composite parent) {
+            Control contents = super.createContents(parent);
+            changeDefaultColors(parent);
+            return contents;
+        }
+
+        /*
+         * Set the colors of the popup. The contents have already been created.
+         */
+        private void changeDefaultColors(Control control) {
+            applyForegroundColor(getShell().getDisplay().getSystemColor(
+                    DWT.COLOR_LIST_FOREGROUND), control);
+            applyBackgroundColor(getShell().getDisplay().getSystemColor(
+                    DWT.COLOR_LIST_BACKGROUND), control);
+        }
+
+        /*
+         * Creates the content area for the proposal popup. This creates a table
+         * and places it inside the composite. The table will contain a list of
+         * all the proposals.
+         *
+         * @param parent The parent composite to contain the dialog area; must
+         * not be <code>null</code>.
+         */
+        protected final Control createDialogArea(Composite parent) {
+            // Use virtual where appropriate (see flag definition).
+            if (USE_VIRTUAL) {
+                proposalTable = new Table(parent, DWT.H_SCROLL | DWT.V_SCROLL
+                        | DWT.VIRTUAL);
+
+                Listener listener = new class Listener {
+                    public void handleEvent(Event event) {
+                        handleSetData(event);
+                    }
+                };
+                proposalTable.addListener(DWT.SetData, listener);
+            } else {
+                proposalTable = new Table(parent, DWT.H_SCROLL | DWT.V_SCROLL);
+            }
+
+            // set the proposals to force population of the table.
+            setProposals(filterProposals(proposals, filterText));
+
+            proposalTable.setHeaderVisible(false);
+            proposalTable.addSelectionListener(new class SelectionListener {
+
+                public void widgetSelected(SelectionEvent e) {
+                    // If a proposal has been selected, show it in the secondary
+                    // popup. Otherwise close the popup.
+                    if (e.item is null) {
+                        if (infoPopup !is null) {
+                            infoPopup.close();
+                        }
+                    } else {
+                        showProposalDescription();
+                    }
+                }
+
+                // Default selection was made. Accept the current proposal.
+                public void widgetDefaultSelected(SelectionEvent e) {
+                    acceptCurrentProposal();
+                }
+            });
+            return proposalTable;
+        }
+
+        /*
+         * (non-Javadoc)
+         *
+         * @see dwtx.jface.dialogs.PopupDialog.adjustBounds()
+         */
+        protected void adjustBounds() {
+            // Get our control's location in display coordinates.
+            Point location = control.getDisplay().map(control.getParent(),
+                    null, control.getLocation());
+            int initialX = location.x + POPUP_OFFSET;
+            int initialY = location.y + control.getSize().y + POPUP_OFFSET;
+            // If we are inserting content, use the cursor position to
+            // position the control.
+            if (getProposalAcceptanceStyle() is PROPOSAL_INSERT) {
+                Rectangle insertionBounds = controlContentAdapter
+                        .getInsertionBounds(control);
+                initialX = initialX + insertionBounds.x;
+                initialY = location.y + insertionBounds.y
+                        + insertionBounds.height;
+            }
+
+            // If there is no specified size, force it by setting
+            // up a layout on the table.
+            if (popupSize is null) {
+                GridData data = new GridData(GridData.FILL_BOTH);
+                data.heightHint = proposalTable.getItemHeight()
+                        * POPUP_CHAR_HEIGHT;
+                data.widthHint = Math.max(control.getSize().x,
+                        POPUP_MINIMUM_WIDTH);
+                proposalTable.setLayoutData(data);
+                getShell().pack();
+                popupSize = getShell().getSize();
+            }
+            getShell().setBounds(initialX, initialY, popupSize.x, popupSize.y);
+
+            // Now set up a listener to monitor any changes in size.
+            getShell().addListener(DWT.Resize, new class Listener {
+                public void handleEvent(Event e) {
+                    popupSize = getShell().getSize();
+                    if (infoPopup !is null) {
+                        infoPopup.adjustBounds();
+                    }
+                }
+            });
+        }
+
+        /*
+         * Handle the set data event. Set the item data of the requested item to
+         * the corresponding proposal in the proposal cache.
+         */
+        private void handleSetData(Event event) {
+            TableItem item = cast(TableItem) event.item;
+            int index = proposalTable.indexOf(item);
+
+            if (0 <= index && index < proposals.length) {
+                IContentProposal current = proposals[index];
+                item.setText(getString(current));
+                item.setImage(getImage(current));
+                item.setData(cast(Object)current);
+            } else {
+                // this should not happen, but does on win32
+            }
+        }
+
+        /*
+         * Caches the specified proposals and repopulates the table if it has
+         * been created.
+         */
+        private void setProposals(IContentProposal[] newProposals) {
+            if (newProposals is null || newProposals.length is 0) {
+                newProposals = getEmptyProposalArray();
+            }
+            this.proposals = newProposals;
+
+            // If there is a table
+            if (isValid()) {
+                final int newSize = newProposals.length;
+                if (USE_VIRTUAL) {
+                    // Set and clear the virtual table. Data will be
+                    // provided in the DWT.SetData event handler.
+                    proposalTable.setItemCount(newSize);
+                    proposalTable.clearAll();
+                } else {
+                    // Populate the table manually
+                    proposalTable.setRedraw(false);
+                    proposalTable.setItemCount(newSize);
+                    TableItem[] items = proposalTable.getItems();
+                    for (int i = 0; i < items.length; i++) {
+                        TableItem item = items[i];
+                        IContentProposal proposal = newProposals[i];
+                        item.setText(getString(proposal));
+                        item.setImage(getImage(proposal));
+                        item.setData(cast(Object)proposal);
+                    }
+                    proposalTable.setRedraw(true);
+                }
+                // Default to the first selection if there is content.
+                if (newProposals.length > 0) {
+                    selectProposal(0);
+                } else {
+                    // No selection, close the secondary popup if it was open
+                    if (infoPopup !is null) {
+                        infoPopup.close();
+                    }
+
+                }
+            }
+        }
+
+        /*
+         * Get the string for the specified proposal. Always return a String of
+         * some kind.
+         */
+        private String getString(IContentProposal proposal) {
+            if (proposal is null) {
+                return EMPTY;
+            }
+            if (labelProvider is null) {
+                return proposal.getLabel() is null ? proposal.getContent()
+                        : proposal.getLabel();
+            }
+            return labelProvider.getText(cast(Object)proposal);
+        }
+
+        /*
+         * Get the image for the specified proposal. If there is no image
+         * available, return null.
+         */
+        private Image getImage(IContentProposal proposal) {
+            if (proposal is null || labelProvider is null) {
+                return null;
+            }
+            return labelProvider.getImage(cast(Object)proposal);
+        }
+
+        /*
+         * Return an empty array. Used so that something always shows in the
+         * proposal popup, even if no proposal provider was specified.
+         */
+        private IContentProposal[] getEmptyProposalArray() {
+            return new IContentProposal[0];
+        }
+
+        /*
+         * Answer true if the popup is valid, which means the table has been
+         * created and not disposed.
+         */
+        private bool isValid() {
+            return proposalTable !is null && !proposalTable.isDisposed();
+        }
+
+        /*
+         * Return whether the receiver has focus.
+         */
+        private bool hasFocus() {
+            if (!isValid()) {
+                return false;
+            }
+            return getShell().isFocusControl()
+                    || proposalTable.isFocusControl();
+        }
+
+        /*
+         * Return the current selected proposal.
+         */
+        private IContentProposal getSelectedProposal() {
+            if (isValid()) {
+                int i = proposalTable.getSelectionIndex();
+                if (proposals is null || i < 0 || i >= proposals.length) {
+                    return null;
+                }
+                return proposals[i];
+            }
+            return null;
+        }
+
+        /*
+         * Select the proposal at the given index.
+         */
+        private void selectProposal(int index) {
+            Assert
+                    .isTrue(index >= 0,
+                            "Proposal index should never be negative"); //$NON-NLS-1$
+            if (!isValid() || proposals is null || index >= proposals.length) {
+                return;
+            }
+            proposalTable.setSelection(index);
+            proposalTable.showSelection();
+
+            showProposalDescription();
+        }
+
+        /**
+         * Opens this ContentProposalPopup. This method is extended in order to
+         * add the control listener when the popup is opened and to invoke the
+         * secondary popup if applicable.
+         *
+         * @return the return code
+         *
+         * @see dwtx.jface.window.Window#open()
+         */
+        public int open() {
+            int value = super.open();
+            if (popupCloser is null) {
+                popupCloser = new PopupCloserListener();
+            }
+            popupCloser.installListeners();
+            IContentProposal p = getSelectedProposal();
+            if (p !is null) {
+                showProposalDescription();
+            }
+            return value;
+        }
+
+        /**
+         * Closes this popup. This method is extended to remove the control
+         * listener.
+         *
+         * @return <code>true</code> if the window is (or was already) closed,
+         *         and <code>false</code> if it is still open
+         */
+        public bool close() {
+            popupCloser.removeListeners();
+            if (infoPopup !is null) {
+                infoPopup.close();
+            }
+            bool ret = super.close();
+            notifyPopupClosed();
+            return ret;
+        }
+
+        /*
+         * Show the currently selected proposal's description in a secondary
+         * popup.
+         */
+        private void showProposalDescription() {
+            // If we do not already have a pending update, then
+            // create a thread now that will show the proposal description
+            if (!pendingDescriptionUpdate) {
+                // Create a thread that will sleep for the specified delay
+                // before creating the popup. We do not use Jobs since this
+                // code must be able to run independently of the Eclipse
+                // runtime.
+                auto r = new class() Runnable {
+                    public void run() {
+                        pendingDescriptionUpdate = true;
+
+                        try {
+                            Thread.sleep( POPUP_DELAY / 1000.0 );
+                        }
+                        catch (InterruptedException e) {
+                        }
+
+                        if (!isValid()) {
+                            return;
+                        }
+                        getShell().getDisplay().syncExec(new class() Runnable {
+                            public void run() {
+                                // Query the current selection since we have
+                                // been delayed
+                                IContentProposal p = getSelectedProposal();
+                                if (p !is null) {
+                                    String description = p.getDescription();
+                                    if (description !is null) {
+                                        if (infoPopup is null) {
+                                            infoPopup = new InfoPopupDialog(
+                                                    getShell());
+                                            infoPopup.open();
+                                            infoPopup
+                                                    .getShell()
+                                                    .addDisposeListener(
+                                                            new class DisposeListener {
+                                                                public void widgetDisposed(
+                                                                        DisposeEvent event) {
+                                                                    infoPopup = null;
+                                                                }
+                                                            });
+                                        }
+                                        infoPopup.setContents(p
+                                                .getDescription());
+                                    } else if (infoPopup !is null) {
+                                        infoPopup.close();
+                                    }
+                                    pendingDescriptionUpdate = false;
+
+                                }
+                            }
+                        });
+                    }
+                };
+                Thread t = new Thread(&r.run);
+                t.start();
+            }
+        }
+
+        /*
+         * Accept the current proposal.
+         */
+        private void acceptCurrentProposal() {
+            // Close before accepting the proposal.
+            // This is important so that the cursor position can be
+            // properly restored at acceptance, which does not work without
+            // focus on some controls.
+            // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108
+            IContentProposal proposal = getSelectedProposal();
+            close();
+            proposalAccepted(proposal);
+        }
+
+        /*
+         * Request the proposals from the proposal provider, and recompute any
+         * caches. Repopulate the popup if it is open.
+         */
+        private void recomputeProposals(String filterText) {
+            IContentProposal[] allProposals = getProposals();
+            // If the non-filtered proposal list is empty, we should
+            // close the popup.
+            // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377
+            if (allProposals.length is 0) {
+                proposals = allProposals;
+                close();
+            } else {
+                // Keep the popup open, but filter by any provided filter text
+                setProposals(filterProposals(allProposals, filterText));
+            }
+        }
+
+        /*
+         * In an async block, request the proposals. This is used when clients
+         * are in the middle of processing an event that affects the widget
+         * content. By using an async, we ensure that the widget content is up
+         * to date with the event.
+         */
+        private void asyncRecomputeProposals(String filterText) {
+            if (isValid()) {
+                control.getDisplay().asyncExec(new class Runnable {
+                    String filterText_;
+                    this(){filterText_=filterText;}
+                    public void run() {
+                        recordCursorPosition();
+                        recomputeProposals(filterText_);
+                    }
+                });
+            } else {
+                recomputeProposals(filterText);
+            }
+        }
+
+        /*
+         * Filter the provided list of content proposals according to the filter
+         * text.
+         */
+        private IContentProposal[] filterProposals(
+                IContentProposal[] proposals, String filterString) {
+            if (filterString.length is 0) {
+                return proposals;
+            }
+
+            // Check each string for a match. Use the string displayed to the
+            // user, not the proposal content.
+            auto list = new ArraySeq!(IContentProposal);
+            for (int i = 0; i < proposals.length; i++) {
+                String string = getString(proposals[i]);
+                if (string.length >= filterString.length
+                        && string.substring(0, filterString.length)
+                                .equalsIgnoreCase(filterString)) {
+                    list.append(proposals[i]);
+                }
+
+            }
+            return list.toArray();
+        }
+
+        Listener getTargetControlListener() {
+            if (targetControlListener is null) {
+                targetControlListener = new TargetControlListener();
+            }
+            return targetControlListener;
+        }
+    }
+
+    /**
+     * Flag that controls the printing of debug info.
+     */
+    public static const bool DEBUG = false;
+
+    /**
+     * Indicates that a chosen proposal should be inserted into the field.
+     */
+    public static const int PROPOSAL_INSERT = 1;
+
+    /**
+     * Indicates that a chosen proposal should replace the entire contents of
+     * the field.
+     */
+    public static const int PROPOSAL_REPLACE = 2;
+
+    /**
+     * Indicates that the contents of the control should not be modified when a
+     * proposal is chosen. This is typically used when a client needs more
+     * specialized behavior when a proposal is chosen. In this case, clients
+     * typically register an IContentProposalListener so that they are notified
+     * when a proposal is chosen.
+     */
+    public static const int PROPOSAL_IGNORE = 3;
+
+    /**
+     * Indicates that there should be no filter applied as keys are typed in the
+     * popup.
+     */
+    public static const int FILTER_NONE = 1;
+
+    /**
+     * Indicates that a single character filter applies as keys are typed in the
+     * popup.
+     */
+    public static const int FILTER_CHARACTER = 2;
+
+    /**
+     * Indicates that a cumulative filter applies as keys are typed in the
+     * popup. That is, each character typed will be added to the filter.
+     */
+    public static const int FILTER_CUMULATIVE = 3;
+
+    /*
+     * Set to <code>true</code> to use a Table with DWT.VIRTUAL. This is a
+     * workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c40
+     * The corresponding DWT bug is
+     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321
+     */
+    private static const bool USE_VIRTUAL = !"motif".equals(DWT.getPlatform()); //$NON-NLS-1$
+
+    /*
+     * The delay before showing a secondary popup.
+     */
+    private static const int POPUP_DELAY = 750;
+
+    /*
+     * The character height hint for the popup. May be overridden by using
+     * setInitialPopupSize.
+     */
+    private static const int POPUP_CHAR_HEIGHT = 10;
+
+    /*
+     * The minimum pixel width for the popup. May be overridden by using
+     * setInitialPopupSize.
+     */
+    private static const int POPUP_MINIMUM_WIDTH = 300;
+
+    /*
+     * The pixel offset of the popup from the bottom corner of the control.
+     */
+    private static const int POPUP_OFFSET = 3;
+
+    /*
+     * Empty string.
+     */
+    private static const String EMPTY = ""; //$NON-NLS-1$
+
+    /*
+     * The object that provides content proposals.
+     */
+    private IContentProposalProvider proposalProvider;
+
+    /*
+     * A label provider used to display proposals in the popup, and to extract
+     * Strings from non-String proposals.
+     */
+    private ILabelProvider labelProvider;
+
+    /*
+     * The control for which content proposals are provided.
+     */
+    private Control control;
+
+    /*
+     * The adapter used to extract the String contents from an arbitrary
+     * control.
+     */
+    private IControlContentAdapter controlContentAdapter;
+
+    /*
+     * The popup used to show proposals.
+     */
+    private ContentProposalPopup popup;
+
+    /*
+     * The keystroke that signifies content proposals should be shown.
+     */
+    private KeyStroke triggerKeyStroke;
+
+    /*
+     * The String containing characters that auto-activate the popup.
+     */
+    private String autoActivateString;
+
+    /*
+     * Integer that indicates how an accepted proposal should affect the
+     * control. One of PROPOSAL_IGNORE, PROPOSAL_INSERT, or PROPOSAL_REPLACE.
+     * Default value is PROPOSAL_INSERT.
+     */
+    private int proposalAcceptanceStyle = PROPOSAL_INSERT;
+
+    /*
+     * A bool that indicates whether key events received while the proposal
+     * popup is open should also be propagated to the control. Default value is
+     * true.
+     */
+    private bool propagateKeys = true;
+
+    /*
+     * Integer that indicates the filtering style. One of FILTER_CHARACTER,
+     * FILTER_CUMULATIVE, FILTER_NONE.
+     */
+    private int filterStyle = FILTER_NONE;
+
+    /*
+     * The listener we install on the control.
+     */
+    private Listener controlListener;
+
+    /*
+     * The list of IContentProposalListener listeners.
+     */
+    private ListenerList proposalListeners;
+
+    /*
+     * The list of IContentProposalListener2 listeners.
+     */
+    private ListenerList proposalListeners2;
+
+    /*
+     * Flag that indicates whether the adapter is enabled. In some cases,
+     * adapters may be installed but depend upon outside state.
+     */
+    private bool isEnabled_ = true;
+
+    /*
+     * The delay in milliseconds used when autoactivating the popup.
+     */
+    private int autoActivationDelay = 0;
+
+    /*
+     * A bool indicating whether a keystroke has been received. Used to see
+     * if an autoactivation delay was interrupted by a keystroke.
+     */
+    private bool receivedKeyDown;
+
+    /*
+     * The desired size in pixels of the proposal popup.
+     */
+    private Point popupSize;
+
+    /*
+     * The remembered position of the insertion position. Not all controls will
+     * restore the insertion position if the proposal popup gets focus, so we
+     * need to remember it.
+     */
+    private int insertionPos = -1;
+
+    /*
+     * A flag that indicates that a pending modify event was caused by
+     * the adapter rather than the user.
+     */
+    private bool modifyingControlContent = false;
+
+    /**
+     * Construct a content proposal adapter that can assist the user with
+     * choosing content for the field.
+     *
+     * @param control
+     *            the control for which the adapter is providing content assist.
+     *            May not be <code>null</code>.
+     * @param controlContentAdapter
+     *            the <code>IControlContentAdapter</code> used to obtain and
+     *            update the control's contents as proposals are accepted. May
+     *            not be <code>null</code>.
+     * @param proposalProvider
+     *            the <code>IContentProposalProvider</code> used to obtain
+     *            content proposals for this control, or <code>null</code> if
+     *            no content proposal is available.
+     * @param keyStroke
+     *            the keystroke that will invoke the content proposal popup. If
+     *            this value is <code>null</code>, then proposals will be
+     *            activated automatically when any of the auto activation
+     *            characters are typed.
+     * @param autoActivationCharacters
+     *            An array of characters that trigger auto-activation of content
+     *            proposal. If specified, these characters will trigger
+     *            auto-activation of the proposal popup, regardless of whether
+     *            an explicit invocation keyStroke was specified. If this
+     *            parameter is <code>null</code>, then only a specified
+     *            keyStroke will invoke content proposal. If this parameter is
+     *            <code>null</code> and the keyStroke parameter is
+     *            <code>null</code>, then all alphanumeric characters will
+     *            auto-activate content proposal.
+     */
+    public this(Control control,
+            IControlContentAdapter controlContentAdapter,
+            IContentProposalProvider proposalProvider, KeyStroke keyStroke,
+            char[] autoActivationCharacters) {
+        proposalListeners = new ListenerList();
+        proposalListeners2 = new ListenerList();
+        //super();
+        // We always assume the control and content adapter are valid.
+        Assert.isNotNull(cast(Object)control);
+        Assert.isNotNull(cast(Object)controlContentAdapter);
+        this.control = control;
+        this.controlContentAdapter = controlContentAdapter;
+
+        // The rest of these may be null
+        this.proposalProvider = proposalProvider;
+        this.triggerKeyStroke = keyStroke;
+        if (autoActivationCharacters.length !is 0 ) {
+            this.autoActivateString = autoActivationCharacters;
+        }
+        addControlListener(control);
+    }
+
+    /**
+     * Get the control on which the content proposal adapter is installed.
+     *
+     * @return the control on which the proposal adapter is installed.
+     */
+    public Control getControl() {
+        return control;
+    }
+
+    /**
+     * Get the label provider that is used to show proposals.
+     *
+     * @return the {@link ILabelProvider} used to show proposals, or
+     *         <code>null</code> if one has not been installed.
+     */
+    public ILabelProvider getLabelProvider() {
+        return labelProvider;
+    }
+
+    /**
+     * Return a bool indicating whether the receiver is enabled.
+     *
+     * @return <code>true</code> if the adapter is enabled, and
+     *         <code>false</code> if it is not.
+     */
+    public bool isEnabled() {
+        return isEnabled_;
+    }
+
+    /**
+     * Set the label provider that is used to show proposals. The lifecycle of
+     * the specified label provider is not managed by this adapter. Clients must
+     * dispose the label provider when it is no longer needed.
+     *
+     * @param labelProvider
+     *            the (@link ILabelProvider} used to show proposals.
+     */
+    public void setLabelProvider(ILabelProvider labelProvider) {
+        this.labelProvider = labelProvider;
+    }
+
+    /**
+     * Return the proposal provider that provides content proposals given the
+     * current content of the field. A value of <code>null</code> indicates
+     * that there are no content proposals available for the field.
+     *
+     * @return the {@link IContentProposalProvider} used to show proposals. May
+     *         be <code>null</code>.
+     */
+    public IContentProposalProvider getContentProposalProvider() {
+        return proposalProvider;
+    }
+
+    /**
+     * Set the content proposal provider that is used to show proposals.
+     *
+     * @param proposalProvider
+     *            the {@link IContentProposalProvider} used to show proposals
+     */
+    public void setContentProposalProvider(
+            IContentProposalProvider proposalProvider) {
+        this.proposalProvider = proposalProvider;
+    }
+
+    /**
+     * Return the array of characters on which the popup is autoactivated.
+     *
+     * @return An array of characters that trigger auto-activation of content
+     *         proposal. If specified, these characters will trigger
+     *         auto-activation of the proposal popup, regardless of whether an
+     *         explicit invocation keyStroke was specified. If this parameter is
+     *         <code>null</code>, then only a specified keyStroke will invoke
+     *         content proposal. If this value is <code>null</code> and the
+     *         keyStroke value is <code>null</code>, then all alphanumeric
+     *         characters will auto-activate content proposal.
+     */
+    public char[] getAutoActivationCharacters() {
+        if (autoActivateString is null) {
+            return null;
+        }
+        return autoActivateString/+.toCharArray()+/;
+    }
+
+    /**
+     * Set the array of characters that will trigger autoactivation of the
+     * popup.
+     *
+     * @param autoActivationCharacters
+     *            An array of characters that trigger auto-activation of content
+     *            proposal. If specified, these characters will trigger
+     *            auto-activation of the proposal popup, regardless of whether
+     *            an explicit invocation keyStroke was specified. If this
+     *            parameter is <code>null</code>, then only a specified
+     *            keyStroke will invoke content proposal. If this parameter is
+     *            <code>null</code> and the keyStroke value is
+     *            <code>null</code>, then all alphanumeric characters will
+     *            auto-activate content proposal.
+     *
+     */
+    public void setAutoActivationCharacters(char[] autoActivationCharacters) {
+        if (autoActivationCharacters.length is 0) {
+            this.autoActivateString = null;
+        } else {
+            this.autoActivateString = autoActivationCharacters;
+        }
+    }
+
+    /**
+     * Set the delay, in milliseconds, used before any autoactivation is
+     * triggered.
+     *
+     * @return the time in milliseconds that will pass before a popup is
+     *         automatically opened
+     */
+    public int getAutoActivationDelay() {
+        return autoActivationDelay;
+
+    }
+
+    /**
+     * Set the delay, in milliseconds, used before autoactivation is triggered.
+     *
+     * @param delay
+     *            the time in milliseconds that will pass before a popup is
+     *            automatically opened
+     */
+    public void setAutoActivationDelay(int delay) {
+        autoActivationDelay = delay;
+
+    }
+
+    /**
+     * Get the integer style that indicates how an accepted proposal affects the
+     * control's content.
+     *
+     * @return a constant indicating how an accepted proposal should affect the
+     *         control's content. Should be one of <code>PROPOSAL_INSERT</code>,
+     *         <code>PROPOSAL_REPLACE</code>, or <code>PROPOSAL_IGNORE</code>.
+     *         (Default is <code>PROPOSAL_INSERT</code>).
+     */
+    public int getProposalAcceptanceStyle() {
+        return proposalAcceptanceStyle;
+    }
+
+    /**
+     * Set the integer style that indicates how an accepted proposal affects the
+     * control's content.
+     *
+     * @param acceptance
+     *            a constant indicating how an accepted proposal should affect
+     *            the control's content. Should be one of
+     *            <code>PROPOSAL_INSERT</code>, <code>PROPOSAL_REPLACE</code>,
+     *            or <code>PROPOSAL_IGNORE</code>
+     */
+    public void setProposalAcceptanceStyle(int acceptance) {
+        proposalAcceptanceStyle = acceptance;
+    }
+
+    /**
+     * Return the integer style that indicates how keystrokes affect the content
+     * of the proposal popup while it is open.
+     *
+     * @return a constant indicating how keystrokes in the proposal popup affect
+     *         filtering of the proposals shown. <code>FILTER_NONE</code>
+     *         specifies that no filtering will occur in the content proposal
+     *         list as keys are typed. <code>FILTER_CUMULATIVE</code>
+     *         specifies that the content of the popup will be filtered by a
+     *         string containing all the characters typed since the popup has
+     *         been open. <code>FILTER_CHARACTER</code> specifies the content
+     *         of the popup will be filtered by the most recently typed
+     *         character. The default is <code>FILTER_NONE</code>.
+     */
+    public int getFilterStyle() {
+        return filterStyle;
+    }
+
+    /**
+     * Set the integer style that indicates how keystrokes affect the content of
+     * the proposal popup while it is open. Popup-based filtering is useful for
+     * narrowing and navigating the list of proposals provided once the popup is
+     * open. Filtering of the proposals will occur even when the control content
+     * is not affected by user typing. Note that automatic filtering is not used
+     * to achieve content-sensitive filtering such as auto-completion. Filtering
+     * that is sensitive to changes in the control content should be performed
+     * by the supplied {@link IContentProposalProvider}.
+     *
+     * @param filterStyle
+     *            a constant indicating how keystrokes received in the proposal
+     *            popup affect filtering of the proposals shown.
+     *            <code>FILTER_NONE</code> specifies that no automatic
+     *            filtering of the content proposal list will occur as keys are
+     *            typed in the popup. <code>FILTER_CUMULATIVE</code> specifies
+     *            that the content of the popup will be filtered by a string
+     *            containing all the characters typed since the popup has been
+     *            open. <code>FILTER_CHARACTER</code> specifies that the
+     *            content of the popup will be filtered by the most recently
+     *            typed character.
+     */
+    public void setFilterStyle(int filterStyle) {
+        this.filterStyle = filterStyle;
+    }
+
+    /**
+     * Return the size, in pixels, of the content proposal popup.
+     *
+     * @return a Point specifying the last width and height, in pixels, of the
+     *         content proposal popup.
+     */
+    public Point getPopupSize() {
+        return popupSize;
+    }
+
+    /**
+     * Set the size, in pixels, of the content proposal popup. This size will be
+     * used the next time the content proposal popup is opened.
+     *
+     * @param size
+     *            a Point specifying the desired width and height, in pixels, of
+     *            the content proposal popup.
+     */
+    public void setPopupSize(Point size) {
+        popupSize = size;
+    }
+
+    /**
+     * Get the bool that indicates whether key events (including
+     * auto-activation characters) received by the content proposal popup should
+     * also be propagated to the adapted control when the proposal popup is
+     * open.
+     *
+     * @return a bool that indicates whether key events (including
+     *         auto-activation characters) should be propagated to the adapted
+     *         control when the proposal popup is open. Default value is
+     *         <code>true</code>.
+     */
+    public bool getPropagateKeys() {
+        return propagateKeys;
+    }
+
+    /**
+     * Set the bool that indicates whether key events (including
+     * auto-activation characters) received by the content proposal popup should
+     * also be propagated to the adapted control when the proposal popup is
+     * open.
+     *
+     * @param propagateKeys
+     *            a bool that indicates whether key events (including
+     *            auto-activation characters) should be propagated to the
+     *            adapted control when the proposal popup is open.
+     */
+    public void setPropagateKeys(bool propagateKeys) {
+        this.propagateKeys = propagateKeys;
+    }
+
+    /**
+     * Return the content adapter that can get or retrieve the text contents
+     * from the adapter's control. This method is used when a client, such as a
+     * content proposal listener, needs to update the control's contents
+     * manually.
+     *
+     * @return the {@link IControlContentAdapter} which can update the control
+     *         text.
+     */
+    public IControlContentAdapter getControlContentAdapter() {
+        return controlContentAdapter;
+    }
+
+    /**
+     * Set the bool flag that determines whether the adapter is enabled.
+     *
+     * @param enabled
+     *            <code>true</code> if the adapter is enabled and responding
+     *            to user input, <code>false</code> if it is ignoring user
+     *            input.
+     *
+     */
+    public void setEnabled(bool enabled) {
+        // If we are disabling it while it's proposing content, close the
+        // content proposal popup.
+        if (isEnabled_ && !enabled) {
+            if (popup !is null) {
+                popup.close();
+            }
+        }
+        isEnabled_ = enabled;
+    }
+
+    /**
+     * Add the specified listener to the list of content proposal listeners that
+     * are notified when content proposals are chosen.
+     * </p>
+     *
+     * @param listener
+     *            the IContentProposalListener to be added as a listener. Must
+     *            not be <code>null</code>. If an attempt is made to register
+     *            an instance which is already registered with this instance,
+     *            this method has no effect.
+     *
+     * @see dwtx.jface.fieldassist.IContentProposalListener
+     */
+    public void addContentProposalListener(IContentProposalListener listener) {
+        proposalListeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Removes the specified listener from the list of content proposal
+     * listeners that are notified when content proposals are chosen.
+     * </p>
+     *
+     * @param listener
+     *            the IContentProposalListener to be removed as a listener. Must
+     *            not be <code>null</code>. If the listener has not already
+     *            been registered, this method has no effect.
+     *
+     * @since 3.3
+     * @see dwtx.jface.fieldassist.IContentProposalListener
+     */
+    public void removeContentProposalListener(IContentProposalListener listener) {
+        proposalListeners.remove(cast(Object)listener);
+    }
+
+    /**
+     * Add the specified listener to the list of content proposal listeners that
+     * are notified when a content proposal popup is opened or closed.
+     * </p>
+     *
+     * @param listener
+     *            the IContentProposalListener2 to be added as a listener. Must
+     *            not be <code>null</code>. If an attempt is made to register
+     *            an instance which is already registered with this instance,
+     *            this method has no effect.
+     *
+     * @since 3.3
+     * @see dwtx.jface.fieldassist.IContentProposalListener2
+     */
+    public void addContentProposalListener(IContentProposalListener2 listener) {
+        proposalListeners2.add(cast(Object)listener);
+    }
+
+    /**
+     * Remove the specified listener from the list of content proposal listeners
+     * that are notified when a content proposal popup is opened or closed.
+     * </p>
+     *
+     * @param listener
+     *            the IContentProposalListener2 to be removed as a listener.
+     *            Must not be <code>null</code>. If the listener has not
+     *            already been registered, this method has no effect.
+     *
+     * @since 3.3
+     * @see dwtx.jface.fieldassist.IContentProposalListener2
+     */
+    public void removeContentProposalListener(IContentProposalListener2 listener) {
+        proposalListeners2.remove(cast(Object)listener);
+    }
+
+    /*
+     * Add our listener to the control. Debug information to be left in until
+     * this support is stable on all platforms.
+     */
+    private void addControlListener(Control control) {
+        if (DEBUG) {
+            Stdout.formatln("ContentProposalListener#installControlListener()"); //$NON-NLS-1$
+        }
+
+        if (controlListener !is null) {
+            return;
+        }
+        controlListener = new class Listener {
+            public void handleEvent(Event e) {
+                if (!isEnabled_) {
+                    return;
+                }
+
+                switch (e.type) {
+                case DWT.Traverse:
+                case DWT.KeyDown:
+                    if (DEBUG) {
+                        StringBuffer sb;
+                        if (e.type is DWT.Traverse) {
+                            sb = new StringBuffer("Traverse"); //$NON-NLS-1$
+                        } else {
+                            sb = new StringBuffer("KeyDown"); //$NON-NLS-1$
+                        }
+                        sb.append(" received by adapter"); //$NON-NLS-1$
+                        dump(sb.toString(), e);
+                    }
+                    // If the popup is open, it gets first shot at the
+                    // keystroke and should set the doit flags appropriately.
+                    if (popup !is null) {
+                        popup.getTargetControlListener().handleEvent(e);
+                        if (DEBUG) {
+                            StringBuffer sb;
+                            if (e.type is DWT.Traverse) {
+                                sb = new StringBuffer("Traverse"); //$NON-NLS-1$
+                            } else {
+                                sb = new StringBuffer("KeyDown"); //$NON-NLS-1$
+                            }
+                            sb.append(" after being handled by popup"); //$NON-NLS-1$
+                            dump(sb.toString(), e);
+                        }
+
+                        return;
+                    }
+
+                    // We were only listening to traverse events for the popup
+                    if (e.type is DWT.Traverse) {
+                        return;
+                    }
+
+                    // The popup is not open. We are looking at keydown events
+                    // for a trigger to open the popup.
+                    if (triggerKeyStroke !is null) {
+                        // Either there are no modifiers for the trigger and we
+                        // check the character field...
+                        if ((triggerKeyStroke.getModifierKeys() is KeyStroke.NO_KEY && triggerKeyStroke
+                                .getNaturalKey() is e.character)
+                                ||
+                                // ...or there are modifiers, in which case the
+                                // keycode and state must match
+                                (triggerKeyStroke.getNaturalKey() is e.keyCode && ((triggerKeyStroke
+                                        .getModifierKeys() & e.stateMask) is triggerKeyStroke
+                                        .getModifierKeys()))) {
+                            // We never propagate the keystroke for an explicit
+                            // keystroke invocation of the popup
+                            e.doit = false;
+                            openProposalPopup(false);
+                            return;
+                        }
+                    }
+                    /*
+                     * The triggering keystroke was not invoked. Check for
+                     * autoactivation characters.
+                     */
+                    if (e.character !is 0) {
+                        // Auto-activation characters were specified. Check
+                        // them.
+                        if (autoActivateString !is null) {
+                            if (autoActivateString.indexOf(e.character) >= 0) {
+                                e.doit = propagateKeys;
+                                autoActivate();
+                            }
+                        } else {
+                            // No autoactivation occurred, so record the key
+                            // down
+                            // as a means to interrupt any autoactivation that
+                            // is
+                            // pending.
+                            receivedKeyDown = true;
+                        }
+                    }
+                    break;
+
+                // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377
+                // Given that we will close the popup when there are no valid
+                // proposals, we must reopen it when there are. Normally, the
+                // keydown event handling will catch all the cases where it
+                // should reopen. But when autoactivation should occur on all
+                // content changes, we check it here after keys have been
+                // processed.
+                // See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650
+                // We should not autoactivate if the content change was caused
+                // by the popup itself.
+                case DWT.Modify:
+                    if (triggerKeyStroke is null && autoActivateString is null
+                            && !modifyingControlContent) {
+                        if (DEBUG) {
+                            dump("Modify event triggers autoactivation", e); //$NON-NLS-1$
+                        }
+                        autoActivate();
+                    }
+                    break;
+                default:
+                    break;
+                }
+            }
+
+            /**
+             * Dump the given events to "standard" output.
+             *
+             * @param who
+             *            who is dumping the event
+             * @param e
+             *            the event
+             */
+            private void dump(String who, Event e) {
+                StringBuffer sb = new StringBuffer(
+                        "--- [ContentProposalAdapter]\n"); //$NON-NLS-1$
+                sb.append(who);
+                sb.append(Format(" - e: keyCode={}{}", e.keyCode, hex(e.keyCode))); //$NON-NLS-1$
+                sb.append(Format("; character={}{}", e.character, hex(e.character))); //$NON-NLS-1$
+                sb.append(Format("; stateMask={}{}", e.stateMask, hex(e.stateMask))); //$NON-NLS-1$
+                sb.append(Format("; doit={}", e.doit)); //$NON-NLS-1$
+                sb.append(Format("; detail={}", e.detail, hex(e.detail))); //$NON-NLS-1$
+                sb.append(Format("; widget={}", e.widget)); //$NON-NLS-1$
+                Stdout.formatln("{}",sb.toString);
+            }
+
+            private String hex(int i) {
+                return Format("[0x{:X}]", i); //$NON-NLS-1$
+            }
+        };
+        control.addListener(DWT.KeyDown, controlListener);
+        control.addListener(DWT.Traverse, controlListener);
+        control.addListener(DWT.Modify, controlListener);
+
+        if (DEBUG) {
+            Stdout.formatln("ContentProposalAdapter#installControlListener() - installed"); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * Open the proposal popup and display the proposals provided by the
+     * proposal provider. If there are no proposals to be shown, do not show the
+     * popup. This method returns immediately. That is, it does not wait for the
+     * popup to open or a proposal to be selected.
+     *
+     * @param autoActivated
+     *            a bool indicating whether the popup was autoactivated. If
+     *            false, a beep will sound when no proposals can be shown.
+     */
+    private void openProposalPopup(bool autoActivated) {
+        if (isValid()) {
+            if (popup is null) {
+                // Check whether there are any proposals to be shown.
+                recordCursorPosition(); // must be done before getting proposals
+                IContentProposal[] proposals = getProposals();
+                if (proposals.length > 0) {
+                    if (DEBUG) {
+                        Stdout.formatln("POPUP OPENED BY PRECEDING EVENT"); //$NON-NLS-1$
+                    }
+                    recordCursorPosition();
+                    popup = new ContentProposalPopup(null, proposals);
+                    popup.open();
+                    popup.getShell().addDisposeListener(new class DisposeListener {
+                        public void widgetDisposed(DisposeEvent event) {
+                            popup = null;
+                        }
+                    });
+                    notifyPopupOpened();
+                } else if (!autoActivated) {
+                    getControl().getDisplay().beep();
+                }
+            }
+        }
+    }
+
+    /**
+     * Open the proposal popup and display the proposals provided by the
+     * proposal provider. This method returns immediately. That is, it does not
+     * wait for a proposal to be selected. This method is used by subclasses to
+     * explicitly invoke the opening of the popup. If there are no proposals to
+     * show, the popup will not open and a beep will be sounded.
+     */
+    protected void openProposalPopup() {
+        openProposalPopup(false);
+    }
+
+    /**
+     * Close the proposal popup without accepting a proposal. This method
+     * returns immediately, and has no effect if the proposal popup was not
+     * open. This method is used by subclasses to explicitly close the popup
+     * based on additional logic.
+     *
+     * @since 3.3
+     */
+    protected void closeProposalPopup() {
+        if (popup !is null) {
+            popup.close();
+        }
+    }
+
+    /*
+     * A content proposal has been accepted. Update the control contents
+     * accordingly and notify any listeners.
+     *
+     * @param proposal the accepted proposal
+     */
+    private void proposalAccepted(IContentProposal proposal) {
+        switch (proposalAcceptanceStyle) {
+        case (PROPOSAL_REPLACE):
+            setControlContent(proposal.getContent(), proposal
+                    .getCursorPosition());
+            break;
+        case (PROPOSAL_INSERT):
+            insertControlContent(proposal.getContent(), proposal
+                    .getCursorPosition());
+            break;
+        default:
+            // do nothing. Typically a listener is installed to handle this in
+            // a custom way.
+            break;
+        }
+
+        // In all cases, notify listeners of an accepted proposal.
+        notifyProposalAccepted(proposal);
+    }
+
+    /*
+     * Set the text content of the control to the specified text, setting the
+     * cursorPosition at the desired location within the new contents.
+     */
+    private void setControlContent(String text, int cursorPosition) {
+        if (isValid()) {
+            // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650
+            modifyingControlContent = true;
+
+            controlContentAdapter.setControlContents(control, text,
+                    cursorPosition);
+
+            modifyingControlContent = false;
+        }
+    }
+
+    /*
+     * Insert the specified text into the control content, setting the
+     * cursorPosition at the desired location within the new contents.
+     */
+    private void insertControlContent(String text, int cursorPosition) {
+        if (isValid()) {
+            // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650
+            modifyingControlContent = true;
+            // Not all controls preserve their selection index when they lose
+            // focus, so we must set it explicitly here to what it was before
+            // the popup opened.
+            // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108
+            if (insertionPos !is -1) {
+                controlContentAdapter.setCursorPosition(control, insertionPos);
+            }
+            controlContentAdapter.insertControlContents(control, text,
+                    cursorPosition);
+            modifyingControlContent = false;
+        }
+    }
+
+    /*
+     * Check that the control and content adapter are valid.
+     */
+    private bool isValid() {
+        return control !is null && !control.isDisposed()
+                && controlContentAdapter !is null;
+    }
+
+    /*
+     * Record the control's cursor position.
+     */
+    private void recordCursorPosition() {
+        if (isValid()) {
+            insertionPos = getControlContentAdapter()
+                    .getCursorPosition(control);
+
+        }
+    }
+
+    /*
+     * Get the proposals from the proposal provider. Gets all of the proposals
+     * without doing any filtering.
+     */
+    private IContentProposal[] getProposals() {
+        if (proposalProvider is null || !isValid()) {
+            return null;
+        }
+        if (DEBUG) {
+            Stdout.formatln(">>> obtaining proposals from provider"); //$NON-NLS-1$
+        }
+        int position = insertionPos;
+        if (position is -1) {
+            position = getControlContentAdapter().getCursorPosition(
+                    getControl());
+        }
+        String contents = getControlContentAdapter().getControlContents(
+                getControl());
+        IContentProposal[] proposals = proposalProvider.getProposals(contents,
+                position);
+        return proposals;
+    }
+
+    /**
+     * Autoactivation has been triggered. Open the popup using any specified
+     * delay.
+     */
+    private void autoActivate() {
+        if (autoActivationDelay > 0) {
+            auto r = new class Runnable{
+                public void run(){
+                    receivedKeyDown = false;
+                    try {
+                        Thread.sleep(autoActivationDelay);
+                    } catch (InterruptedException e) {
+                    }
+                    if (!isValid() || receivedKeyDown) {
+                        return;
+                    }
+                    getControl().getDisplay().syncExec(new class Runnable {
+                        public void run() {
+                            openProposalPopup(true);
+                        }
+                    });
+                }
+            };
+            Thread t = new Thread(&r.run);
+            t.start();
+        } else {
+            // Since we do not sleep, we must open the popup
+            // in an async exec. This is necessary because
+            // this method may be called in the middle of handling
+            // some event that will cause the cursor position or
+            // other important info to change as a result of this
+            // event occurring.
+            getControl().getDisplay().asyncExec(new class Runnable {
+                public void run() {
+                    if (isValid()) {
+                        openProposalPopup(true);
+                    }
+                }
+            });
+        }
+    }
+
+    /*
+     * A proposal has been accepted. Notify interested listeners.
+     */
+    private void notifyProposalAccepted(IContentProposal proposal) {
+        if (DEBUG) {
+            Stdout.formatln("Notify listeners - proposal accepted."); //$NON-NLS-1$
+        }
+        Object[] listenerArray = proposalListeners.getListeners();
+        for (int i = 0; i < listenerArray.length; i++) {
+            (cast(IContentProposalListener) listenerArray[i])
+                    .proposalAccepted(proposal);
+        }
+    }
+
+    /*
+     * The proposal popup has opened. Notify interested listeners.
+     */
+    private void notifyPopupOpened() {
+        if (DEBUG) {
+            Stdout.formatln("Notify listeners - popup opened."); //$NON-NLS-1$
+        }
+        Object[] listenerArray = proposalListeners2.getListeners();
+        for (int i = 0; i < listenerArray.length; i++) {
+            (cast(IContentProposalListener2) listenerArray[i])
+                    .proposalPopupOpened(this);
+        }
+    }
+
+    /*
+     * The proposal popup has closed. Notify interested listeners.
+     */
+    private void notifyPopupClosed() {
+        if (DEBUG) {
+            Stdout.formatln("Notify listeners - popup closed."); //$NON-NLS-1$
+        }
+        Object[] listenerArray = proposalListeners2.getListeners();
+        for (int i = 0; i < listenerArray.length; i++) {
+            (cast(IContentProposalListener2) listenerArray[i])
+                    .proposalPopupClosed(this);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/ControlDecoration.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,1204 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.fieldassist.ControlDecoration;
+
+
+import dwt.DWT;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.FocusEvent;
+import dwt.events.FocusListener;
+import dwt.events.MenuDetectEvent;
+import dwt.events.MenuDetectListener;
+import dwt.events.MouseAdapter;
+import dwt.events.MouseEvent;
+import dwt.events.MouseMoveListener;
+import dwt.events.MouseTrackListener;
+import dwt.events.PaintEvent;
+import dwt.events.PaintListener;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.graphics.GC;
+import dwt.graphics.Image;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.graphics.Region;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.Shell;
+import dwt.widgets.Widget;
+import dwtx.core.runtime.ListenerList;
+
+import dwt.dwthelper.utils;
+import tango.io.Stdout;
+
+/**
+ * ControlDecoration renders an image decoration near a control. It allows
+ * clients to specify an image and a position for the image relative to the
+ * control. A ControlDecoration may be assigned description text, which can
+ * optionally be shown when the user hovers over the image. Clients can decorate
+ * any kind of control.
+ * <p>
+ * Decoration images always appear on the left or right side of the field, never
+ * above or below it. Decorations can be positioned at the top, center, or
+ * bottom of either side of the control. Future implementations may provide
+ * additional positioning options for decorations.
+ * <p>
+ * ControlDecoration renders the image adjacent to the specified (already
+ * created) control, with no guarantee that it won't be clipped or otherwise
+ * obscured or overlapped by adjacent controls, including another
+ * ControlDecoration placed in the same location. Clients should ensure that
+ * there is adequate space adjacent to the control to show the decoration
+ * properly.
+ * <p>
+ * Clients using ControlDecoration should typically ensure that enough margin
+ * space is reserved for a decoration by altering the layout data margins,
+ * although this is not assumed or required by the ControlDecoration
+ * implementation.
+ * <p>
+ * This class is intended to be instantiated and used by clients. It is not
+ * intended to be subclassed by clients.
+ *
+ * @since 3.3
+ *
+ * @see FieldDecoration
+ * @see FieldDecorationRegistry
+ */
+public class ControlDecoration {
+    /**
+     * Debug flag for tracing
+     */
+    private static bool DEBUG = false;
+
+    /**
+     * Cached platform flags for dealing with platform-specific issues.
+     */
+    private static bool CARBON = "carbon".equals(DWT.getPlatform()); //$NON-NLS-1$
+
+    /**
+     * The associated control
+     */
+    private Control control;
+
+    /**
+     * The composite on which to render the decoration and hook mouse events, or
+     * null if we are hooking all parent composites.
+     */
+    private Composite composite;
+
+    /**
+     * The associated image.
+     */
+    private Image image;
+
+    /**
+     * The associated description text.
+     */
+    private String descriptionText;
+    /**
+     * The position of the decoration.
+     */
+    private int position;
+
+    /**
+     * The decoration's visibility flag
+     */
+    private bool visible = true;
+
+    /**
+     * bool indicating whether the decoration should only be shown when the
+     * control has focus
+     */
+    private bool showOnlyOnFocus = false;
+
+    /**
+     * bool indicating whether the decoration should show its description
+     * text in a hover when the user hovers over the decoration.
+     */
+    private bool showHover = true;
+
+    /**
+     * Margin width used between the decorator and the control.
+     */
+    private int marginWidth = 0;
+
+    /**
+     * Registered selection listeners.
+     */
+    private ListenerList selectionListeners;
+
+    /**
+     * Registered menu detect listeners.
+     */
+    private ListenerList menuDetectListeners;
+
+    /**
+     * The focus listener
+     */
+    private FocusListener focusListener;
+
+    /**
+     * The dispose listener
+     */
+    private DisposeListener disposeListener;
+
+    /**
+     * The paint listener installed for drawing the decoration
+     */
+    private PaintListener paintListener;
+
+    /**
+     * The mouse listener installed for tracking the hover
+     */
+    private MouseTrackListener mouseTrackListener;
+
+    /**
+     * The mouse move listener installed for tracking the hover
+     */
+    private MouseMoveListener mouseMoveListener;
+
+    /**
+     * The untyped listener installed for notifying external listeners
+     */
+    private Listener compositeListener;
+
+    /**
+     * Control that we last installed a move listener on. We only want one at a
+     * time.
+     */
+    private Control moveListeningTarget = null;
+
+    /**
+     * Debug counter used to match add and remove listeners
+     */
+    private int listenerInstalls = 0;
+
+    /**
+     * The current rectangle used for tracking mouse moves
+     */
+    private Rectangle decorationRectangle;
+
+    /**
+     * An internal flag tracking whether we have focus. We use this rather than
+     * isFocusControl() so that we can set the flag as soon as we get the focus
+     * callback, rather than having to do an asyncExec in the middle of a focus
+     * callback to ensure that isFocusControl() represents the outcome of the
+     * event.
+     */
+    private bool hasFocus = false;
+
+    /**
+     * The hover used for showing description text
+     */
+    private Hover hover;
+
+    /**
+     * The hover used to show a decoration image's description.
+     */
+    class Hover {
+        private static const String EMPTY = ""; //$NON-NLS-1$
+
+        /**
+         * Offset of info hover arrow from the left or right side.
+         */
+        private int hao = 10;
+
+        /**
+         * Width of info hover arrow.
+         */
+        private int haw = 8;
+
+        /**
+         * Height of info hover arrow.
+         */
+        private int hah = 10;
+
+        /**
+         * Margin around info hover text.
+         */
+        private int hm = 2;
+
+        /**
+         * This info hover's shell.
+         */
+        Shell hoverShell;
+
+        /**
+         * The info hover text.
+         */
+        String text = EMPTY;
+
+        /**
+         * The region used to manage the shell shape
+         */
+        Region region;
+
+        /**
+         * bool indicating whether the last computed polygon location had an
+         * arrow on left. (true if left, false if right).
+         */
+        bool arrowOnLeft = true;
+
+        /*
+         * Create a hover parented by the specified shell.
+         */
+        this(Shell parent) {
+            Display display = parent.getDisplay();
+            hoverShell = new Shell(parent, DWT.NO_TRIM | DWT.ON_TOP
+                    | DWT.NO_FOCUS);
+            hoverShell.setBackground(display
+                    .getSystemColor(DWT.COLOR_INFO_BACKGROUND));
+            hoverShell.setForeground(display
+                    .getSystemColor(DWT.COLOR_INFO_FOREGROUND));
+            hoverShell.addPaintListener(new class PaintListener {
+                public void paintControl(PaintEvent pe) {
+                    pe.gc.drawText(text, hm, hm);
+                    if (!CARBON) {
+                        pe.gc.drawPolygon(getPolygon(true));
+                    }
+                }
+            });
+            hoverShell.addMouseListener(new class MouseAdapter {
+                public void mouseDown(MouseEvent e) {
+                    hideHover();
+                }
+            });
+        }
+
+        /*
+         * Compute a polygon that represents a hover with an arrow pointer. If
+         * border is true, compute the polygon inset by 1-pixel border. Consult
+         * the arrowOnLeft flag to determine which side the arrow is on.
+         */
+        int[] getPolygon(bool border) {
+            Point e = getExtent();
+            int b = border ? 1 : 0;
+            if (arrowOnLeft) {
+                return [ 0, 0, e.x - b, 0, e.x - b, e.y - b,
+                        hao + haw, e.y - b, hao + haw / 2, e.y + hah - b, hao,
+                        e.y - b, 0, e.y - b, 0, 0 ];
+            }
+            return [ 0, 0, e.x - b, 0, e.x - b, e.y - b,
+                    e.x - hao - b, e.y - b, e.x - hao - haw / 2, e.y + hah - b,
+                    e.x - hao - haw, e.y - b, 0, e.y - b, 0, 0 ];
+        }
+
+        /*
+         * Dispose the hover, it is no longer needed. Dispose any resources
+         * allocated by the hover.
+         */
+        void dispose() {
+            if (!hoverShell.isDisposed()) {
+                hoverShell.dispose();
+            }
+            if (region !is null) {
+                region.dispose();
+            }
+        }
+
+        /*
+         * Set the visibility of the hover.
+         */
+        void setVisible(bool visible) {
+            if (visible) {
+                if (!hoverShell.isVisible()) {
+                    hoverShell.setVisible(true);
+                }
+            } else {
+                if (hoverShell.isVisible()) {
+                    hoverShell.setVisible(false);
+                }
+            }
+        }
+
+        /*
+         * Set the text of the hover to the specified text. Recompute the size
+         * and location of the hover to hover near the decoration rectangle,
+         * pointing the arrow toward the target control.
+         */
+        void setText(String t, Rectangle decorationRectangle,
+                Control targetControl) {
+            if (t is null) {
+                t = EMPTY;
+            }
+            if (!t.equals(text)) {
+                Point oldSize = getExtent();
+                text = t;
+                hoverShell.redraw();
+                Point newSize = getExtent();
+                if (!oldSize.opEquals(newSize)) {
+                    // set a flag that indicates the direction of arrow
+                    arrowOnLeft = decorationRectangle.x <= targetControl
+                            .getLocation().x;
+                    setNewShape();
+                }
+            }
+
+            Point extent = getExtent();
+            int y = -extent.y - hah + 1;
+            int x = arrowOnLeft ? -hao + haw / 2 : -extent.x + hao + haw / 2;
+
+            hoverShell.setLocation(control.getParent().toDisplay(
+                    decorationRectangle.x + x, decorationRectangle.y + y));
+        }
+
+        /*
+         * Return whether or not the hover (shell) is visible.
+         */
+        bool isVisible() {
+            return hoverShell.isVisible();
+        }
+
+        /*
+         * Compute the extent of the hover for the current text.
+         */
+        Point getExtent() {
+            GC gc = new GC(hoverShell);
+            Point e = gc.textExtent(text);
+            gc.dispose();
+            e.x += hm * 2;
+            e.y += hm * 2;
+            return e;
+        }
+
+        /*
+         * Compute a new shape for the hover shell.
+         */
+        void setNewShape() {
+            Region oldRegion = region;
+            region = new Region();
+            region.add(getPolygon(false));
+            hoverShell.setRegion(region);
+            if (oldRegion !is null) {
+                oldRegion.dispose();
+            }
+
+        }
+    }
+
+    /**
+     * Construct a ControlDecoration for decorating the specified control at the
+     * specified position relative to the control. Render the decoration on top
+     * of any Control that happens to appear at the specified location.
+     * <p>
+     * DWT constants are used to specify the position of the decoration relative
+     * to the control. The position should include style bits describing both
+     * the vertical and horizontal orientation. <code>DWT.LEFT</code> and
+     * <code>DWT.RIGHT</code> describe the horizontal placement of the
+     * decoration relative to the control, and the constants
+     * <code>DWT.TOP</code>, <code>DWT.CENTER</code>, and
+     * <code>DWT.BOTTOM</code> describe the vertical alignment of the
+     * decoration relative to the control. Decorations always appear on either
+     * the left or right side of the control, never above or below it. For
+     * example, a decoration appearing on the left side of the field, at the
+     * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are
+     * specified, the control decoration will be positioned to the left and
+     * center of the control (<code>DWT.LEFT | DWT.CENTER</code>).
+     * </p>
+     *
+     * @param control
+     *            the control to be decorated
+     * @param position
+     *            bit-wise or of position constants (<code>DWT.TOP</code>,
+     *            <code>DWT.BOTTOM</code>, <code>DWT.LEFT</code>,
+     *            <code>DWT.RIGHT</code>, and <code>DWT.CENTER</code>).
+     */
+    public this(Control control, int position) {
+        this(control, position, null);
+
+    }
+
+    /**
+     * Construct a ControlDecoration for decorating the specified control at the
+     * specified position relative to the control. Render the decoration only on
+     * the specified Composite or its children. The decoration will be clipped
+     * if it does not appear within the visible bounds of the composite or its
+     * child composites.
+     * <p>
+     * DWT constants are used to specify the position of the decoration relative
+     * to the control. The position should include style bits describing both
+     * the vertical and horizontal orientation. <code>DWT.LEFT</code> and
+     * <code>DWT.RIGHT</code> describe the horizontal placement of the
+     * decoration relative to the control, and the constants
+     * <code>DWT.TOP</code>, <code>DWT.CENTER</code>, and
+     * <code>DWT.BOTTOM</code> describe the vertical alignment of the
+     * decoration relative to the control. Decorations always appear on either
+     * the left or right side of the control, never above or below it. For
+     * example, a decoration appearing on the left side of the field, at the
+     * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are
+     * specified, the control decoration will be positioned to the left and
+     * center of the control (<code>DWT.LEFT | DWT.CENTER</code>).
+     * </p>
+     *
+     * @param control
+     *            the control to be decorated
+     * @param position
+     *            bit-wise or of position constants (<code>DWT.TOP</code>,
+     *            <code>DWT.BOTTOM</code>, <code>DWT.LEFT</code>,
+     *            <code>DWT.RIGHT</code>, and <code>DWT.CENTER</code>).
+     * @param composite
+     *            The DWT composite within which the decoration should be
+     *            rendered. The decoration will be clipped to this composite,
+     *            but it may be rendered on a child of the composite. The
+     *            decoration will not be visible if the specified composite or
+     *            its child composites are not visible in the space relative to
+     *            the control, where the decoration is to be rendered. If this
+     *            value is <code>null</code>, then the decoration will be
+     *            rendered on whichever composite (or composites) are located in
+     *            the specified position.
+     */
+    public this(Control control, int position, Composite composite) {
+        selectionListeners = new ListenerList();
+        menuDetectListeners = new ListenerList();
+        this.position = position;
+        this.control = control;
+        this.composite = composite;
+
+        addControlListeners();
+
+    }
+
+    /**
+     * Adds the listener to the collection of listeners who will be notified
+     * when the platform-specific context menu trigger has occurred, by sending
+     * it one of the messages defined in the <code>MenuDetectListener</code>
+     * interface.
+     * <p>
+     * The <code>widget</code> field in the SelectionEvent will contain the
+     * Composite on which the decoration is rendered that received the click.
+     * The <code>x</code> and <code>y</code> fields will be in coordinates
+     * relative to the display. The <code>data</code> field will contain the
+     * decoration that received the event.
+     * </p>
+     *
+     * @param listener
+     *            the listener which should be notified
+     *
+     * @see dwt.events.MenuDetectListener
+     * @see dwt.events.MenuDetectEvent
+     * @see #removeMenuDetectListener
+     */
+    public void addMenuDetectListener(MenuDetectListener listener) {
+        menuDetectListeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Removes the listener from the collection of listeners who will be
+     * notified when the platform-specific context menu trigger has occurred.
+     *
+     * @param listener
+     *            the listener which should no longer be notified. This message
+     *            has no effect if the listener was not previously added to the
+     *            receiver.
+     *
+     * @see dwt.events.MenuDetectListener
+     * @see #addMenuDetectListener
+     */
+    public void removeMenuDetectListener(MenuDetectListener listener) {
+        menuDetectListeners.remove(cast(Object)listener);
+    }
+
+    /**
+     * Adds the listener to the collection of listeners who will be notified
+     * when the decoration is selected, by sending it one of the messages
+     * defined in the <code>SelectionListener</code> interface.
+     * <p>
+     * <code>widgetSelected</code> is called when the decoration is selected
+     * (by mouse click). <code>widgetDefaultSelected</code> is called when the
+     * decoration is double-clicked.
+     * </p>
+     * <p>
+     * The <code>widget</code> field in the SelectionEvent will contain the
+     * Composite on which the decoration is rendered that received the click.
+     * The <code>x</code> and <code>y</code> fields will be in coordinates
+     * relative to that widget. The <code>data</code> field will contain the
+     * decoration that received the event.
+     * </p>
+     *
+     * @param listener
+     *            the listener which should be notified
+     *
+     * @see dwt.events.SelectionListener
+     * @see dwt.events.SelectionEvent
+     * @see #removeSelectionListener
+     */
+    public void addSelectionListener(SelectionListener listener) {
+        selectionListeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Removes the listener from the collection of listeners who will be
+     * notified when the decoration is selected.
+     *
+     * @param listener
+     *            the listener which should no longer be notified. This message
+     *            has no effect if the listener was not previously added to the
+     *            receiver.
+     *
+     * @see dwt.events.SelectionListener
+     * @see #addSelectionListener
+     */
+    public void removeSelectionListener(SelectionListener listener) {
+        selectionListeners.remove(cast(Object)listener);
+    }
+
+    /**
+     * Dispose this ControlDecoration. Unhook any listeners that have been
+     * installed on the target control. This method has no effect if the
+     * receiver is already disposed.
+     */
+    public void dispose() {
+        if (control is null) {
+            return;
+        }
+        if (hover !is null) {
+            hover.dispose();
+            hover = null;
+        }
+        removeControlListeners();
+        control = null;
+    }
+
+    /**
+     * Get the control that is decorated by the receiver.
+     *
+     * @return the Control decorated by the receiver. May be <code>null</code>
+     *         if the control has been uninstalled.
+     */
+    public Control getControl() {
+        return control;
+    }
+
+    /**
+     * Add any listeners needed on the target control and on the composite where
+     * the decoration is to be rendered.
+     */
+    private void addControlListeners() {
+        disposeListener = new class DisposeListener {
+            public void widgetDisposed(DisposeEvent event) {
+                dispose();
+            }
+        };
+        printAddListener(control, "DISPOSE"); //$NON-NLS-1$
+        control.addDisposeListener(disposeListener);
+
+        focusListener = new class FocusListener {
+            public void focusGained(FocusEvent event) {
+                hasFocus = true;
+                if (showOnlyOnFocus) {
+                    update();
+                }
+            }
+
+            public void focusLost(FocusEvent event) {
+                hasFocus = false;
+                if (showOnlyOnFocus) {
+                    update();
+                }
+            }
+        };
+        printAddListener(control, "FOCUS"); //$NON-NLS-1$
+        control.addFocusListener(focusListener);
+
+        // Listener for painting the decoration
+        paintListener = new class PaintListener {
+            public void paintControl(PaintEvent event) {
+                Control control = cast(Control) event.widget;
+                Rectangle rect = getDecorationRectangle(control);
+                if (shouldShowDecoration()) {
+                    event.gc.drawImage(getImage(), rect.x, rect.y);
+                }
+            }
+        };
+
+        // Listener for tracking the end of a hover. Only installed
+        // after a hover begins.
+        mouseMoveListener = new class MouseMoveListener {
+            public void mouseMove(MouseEvent event) {
+                if (showHover) {
+                    if (!decorationRectangle.contains(event.x, event.y)) {
+                        hideHover();
+                        // No need to listen any longer
+                        printRemoveListener(event.widget, "MOUSEMOVE"); //$NON-NLS-1$
+                        (cast(Control) event.widget)
+                                .removeMouseMoveListener(mouseMoveListener);
+                        moveListeningTarget = null;
+                    }
+                }
+            }
+        };
+
+        // Listener for tracking the beginning of a hover. Always installed.
+        mouseTrackListener = new class MouseTrackListener {
+            public void mouseExit(MouseEvent event) {
+                // Just in case we didn't catch it before.
+                Control target = cast(Control) event.widget;
+                if (target is moveListeningTarget) {
+                    printRemoveListener(target, "MOUSEMOVE"); //$NON-NLS-1$
+                    target.removeMouseMoveListener(mouseMoveListener);
+                    moveListeningTarget = null;
+                }
+                hideHover();
+            }
+
+            public void mouseHover(MouseEvent event) {
+                if (showHover) {
+                    decorationRectangle = getDecorationRectangle(cast(Control) event.widget);
+                    if (decorationRectangle.contains(event.x, event.y)) {
+                        showHoverText(getDescriptionText());
+                        Control target = cast(Control) event.widget;
+                        if (moveListeningTarget is null) {
+                            printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$
+                            target.addMouseMoveListener(mouseMoveListener);
+                            moveListeningTarget = target;
+                        } else if (target !is moveListeningTarget) {
+                            printRemoveListener(moveListeningTarget,
+                                    "MOUSEMOVE"); //$NON-NLS-1$
+                            moveListeningTarget
+                                    .removeMouseMoveListener(mouseMoveListener);
+                            printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$
+                            target.addMouseMoveListener(mouseMoveListener);
+                            moveListeningTarget = target;
+                        } else {
+                            // It is already installed on this control.
+                        }
+                    }
+                }
+            }
+
+            public void mouseEnter(MouseEvent event) {
+                // Nothing to do until a hover occurs.
+            }
+        };
+
+        compositeListener = new class Listener {
+            public void handleEvent(Event event) {
+                // Don't forward events if decoration is not showing
+                if (!visible) {
+                    return;
+                }
+                // Notify listeners if any are registered.
+                switch (event.type) {
+                case DWT.MouseDown:
+                    if (!selectionListeners.isEmpty())
+                        notifySelectionListeners(event);
+                    break;
+                case DWT.MouseDoubleClick:
+                    if (!selectionListeners.isEmpty())
+                        notifySelectionListeners(event);
+                    break;
+                case DWT.MenuDetect:
+                    if (!menuDetectListeners.isEmpty())
+                        notifyMenuDetectListeners(event);
+                    break;
+                }
+            }
+        };
+
+        // We do not know which parent in the control hierarchy
+        // is providing the decoration space, so hook all the way up, until
+        // the shell or the specified parent composite is reached.
+        Composite c = control.getParent();
+        while (c !is null) {
+            installCompositeListeners(c);
+            if (composite !is null && composite is c) {
+                // We just installed on the specified composite, so stop.
+                c = null;
+            } else if (cast(Shell)c ) {
+                // We just installed on a shell, so don't go further
+                c = null;
+            } else {
+                c = c.getParent();
+            }
+        }
+        // force a redraw of the decoration area so our paint listener
+        // is notified.
+        update();
+    }
+
+    /*
+     * Install the listeners used to paint and track mouse events on the
+     * composite.
+     */
+    private void installCompositeListeners(Composite c) {
+        if (!c.isDisposed()) {
+            printAddListener(c, "PAINT"); //$NON-NLS-1$
+            c.addPaintListener(paintListener);
+            printAddListener(c, "MOUSETRACK"); //$NON-NLS-1$
+            c.addMouseTrackListener(mouseTrackListener);
+            printAddListener(c, "DWT.MenuDetect"); //$NON-NLS-1$
+            c.addListener(DWT.MenuDetect, compositeListener);
+            printAddListener(c, "DWT.MouseDown"); //$NON-NLS-1$
+            c.addListener(DWT.MouseDown, compositeListener);
+            printAddListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$
+            c.addListener(DWT.MouseDoubleClick, compositeListener);
+        }
+    }
+
+    /*
+     * Remove the listeners used to paint and track mouse events on the
+     * composite.
+     */
+    private void removeCompositeListeners(Composite c) {
+        if (!c.isDisposed()) {
+            printRemoveListener(c, "PAINT"); //$NON-NLS-1$
+            c.removePaintListener(paintListener);
+            printRemoveListener(c, "MOUSETRACK"); //$NON-NLS-1$
+            c.removeMouseTrackListener(mouseTrackListener);
+            printRemoveListener(c, "DWT.MenuDetect"); //$NON-NLS-1$
+            c.removeListener(DWT.MenuDetect, compositeListener);
+            printRemoveListener(c, "DWT.MouseDown"); //$NON-NLS-1$
+            c.removeListener(DWT.MouseDown, compositeListener);
+            printRemoveListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$
+            c.removeListener(DWT.MouseDoubleClick, compositeListener);
+        }
+    }
+
+    private void notifySelectionListeners(Event event) {
+        if (!(cast(Control)event.widget )) {
+            return;
+        }
+        if (getDecorationRectangle(cast(Control) event.widget).contains(event.x,
+                event.y)) {
+            SelectionEvent clientEvent = new SelectionEvent(event);
+            clientEvent.data = this;
+            if (getImage() !is null) {
+                clientEvent.height = getImage().getBounds().height;
+                clientEvent.width = getImage().getBounds().width;
+            }
+            Object[] listeners;
+            switch (event.type) {
+            case DWT.MouseDoubleClick:
+                if (event.button is 1) {
+                    listeners = selectionListeners.getListeners();
+                    for (int i = 0; i < listeners.length; i++) {
+                        (cast(SelectionListener) listeners[i])
+                                .widgetDefaultSelected(clientEvent);
+                    }
+                }
+                break;
+            case DWT.MouseDown:
+                if (event.button is 1) {
+                    listeners = selectionListeners.getListeners();
+                    for (int i = 0; i < listeners.length; i++) {
+                        (cast(SelectionListener) listeners[i])
+                                .widgetSelected(clientEvent);
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    private void notifyMenuDetectListeners(Event event) {
+        if (getDecorationRectangle(null).contains(event.x, event.y)) {
+            MenuDetectEvent clientEvent = new MenuDetectEvent(event);
+            clientEvent.data = this;
+            Object[] listeners = menuDetectListeners.getListeners();
+            for (int i = 0; i < listeners.length; i++) {
+                (cast(MenuDetectListener) listeners[i]).menuDetected(clientEvent);
+
+            }
+        }
+    }
+
+    /**
+     * Show the specified text using the same hover dialog as is used to show
+     * decorator descriptions. When {@link #setShowHover(bool)} has been set
+     * to <code>true</code>, a decoration's description text will be shown in
+     * an info hover over the field's control whenever the mouse hovers over the
+     * decoration. This method can be used to show a decoration's description
+     * text at other times (such as when the control receives focus), or to show
+     * other text associated with the field.
+     *
+     * @param text
+     *            the text to be shown in the info hover, or <code>null</code>
+     *            if no text should be shown.
+     */
+    public void showHoverText(String text) {
+        if (control is null) {
+            return;
+        }
+        showHoverText(text, control);
+    }
+
+    /**
+     * Hide any hover popups that are currently showing on the control. When
+     * {@link #setShowHover(bool)} has been set to <code>true</code>, a
+     * decoration's description text will be shown in an info hover over the
+     * field's control as long as the mouse hovers over the decoration, and will
+     * be hidden when the mouse exits the decoration. This method can be used to
+     * hide a hover, whether it was shown explicitly using
+     * {@link #showHoverText(String)}, or was showing because the user was
+     * hovering in the decoration.
+     * <p>
+     * This message has no effect if there is no current hover.
+     *
+     */
+    public void hideHover() {
+        if (hover !is null) {
+            hover.setVisible(false);
+        }
+    }
+
+    /**
+     * Show the control decoration. This message has no effect if the decoration
+     * is already showing. If {@link #setShowOnlyOnFocus(bool)} is set to
+     * <code>true</code>, the decoration will only be shown if the control
+     * has focus.
+     */
+    public void show() {
+        if (!visible) {
+            visible = true;
+            update();
+        }
+    }
+
+    /**
+     * Hide the control decoration. This message has no effect if the decoration
+     * is already hidden.
+     */
+    public void hide() {
+        if (visible) {
+            visible = false;
+            update();
+        }
+    }
+
+    /**
+     * Get the description text that may be shown in a hover for this
+     * decoration.
+     *
+     * @return the text to be shown as a description for the decoration, or
+     *         <code>null</code> if none has been set.
+     */
+    public String getDescriptionText() {
+        return descriptionText;
+    }
+
+    /**
+     * Set the image shown in this control decoration. Update the rendered
+     * decoration.
+     *
+     * @param text
+     *            the text to be shown as a description for the decoration, or
+     *            <code>null</code> if none has been set.
+     */
+    public void setDescriptionText(String text) {
+        this.descriptionText = text;
+        update();
+    }
+
+    /**
+     * Get the image shown in this control decoration.
+     *
+     * @return the image to be shown adjacent to the control, or
+     *         <code>null</code> if one has not been set.
+     */
+    public Image getImage() {
+        return image;
+    }
+
+    /**
+     * Set the image shown in this control decoration. Update the rendered
+     * decoration.
+     *
+     * @param image
+     *            the image to be shown adjacent to the control
+     */
+    public void setImage(Image image) {
+        this.image = image;
+        update();
+    }
+
+    /**
+     * Get the bool that controls whether the decoration is shown only when
+     * the control has focus. The default value of this setting is
+     * <code>false</code>.
+     *
+     * @return <code>true</code> if the decoration should only be shown when
+     *         the control has focus, and <code>false</code> if it should
+     *         always be shown. Note that if the control is not capable of
+     *         receiving focus (<code>DWT.NO_FOCUS</code>), then the
+     *         decoration will never show when this value is <code>true</code>.
+     */
+    public bool getShowOnlyOnFocus() {
+        return showOnlyOnFocus;
+    }
+
+    /**
+     * Set the bool that controls whether the decoration is shown only when
+     * the control has focus. The default value of this setting is
+     * <code>false</code>.
+     *
+     * @param showOnlyOnFocus
+     *            <code>true</code> if the decoration should only be shown
+     *            when the control has focus, and <code>false</code> if it
+     *            should always be shown. Note that if the control is not
+     *            capable of receiving focus (<code>DWT.NO_FOCUS</code>),
+     *            then the decoration will never show when this value is
+     *            <code>true</code>.
+     */
+    public void setShowOnlyOnFocus(bool showOnlyOnFocus) {
+        this.showOnlyOnFocus = showOnlyOnFocus;
+        update();
+    }
+
+    /**
+     * Get the bool that controls whether the decoration's description text
+     * should be shown in a hover when the user hovers over the decoration. The
+     * default value of this setting is <code>true</code>.
+     *
+     * @return <code>true</code> if a hover popup containing the decoration's
+     *         description text should be shown when the user hovers over the
+     *         decoration, and <code>false</code> if a hover should not be
+     *         shown.
+     */
+    public bool getShowHover() {
+        return showHover;
+    }
+
+    /**
+     * Set the bool that controls whether the decoration's description text
+     * should be shown in a hover when the user hovers over the decoration. The
+     * default value of this setting is <code>true</code>.
+     *
+     * @param showHover
+     *            <code>true</code> if a hover popup containing the
+     *            decoration's description text should be shown when the user
+     *            hovers over the decoration, and <code>false</code> if a
+     *            hover should not be shown.
+     */
+    public void setShowHover(bool showHover) {
+        this.showHover = showHover;
+        update();
+    }
+
+    /**
+     * Get the margin width in pixels that should be used between the decorator
+     * and the horizontal edge of the control. The default value of this setting
+     * is <code>0</code>.
+     *
+     * @return the number of pixels that should be reserved between the
+     *         horizontal edge of the control and the adjacent edge of the
+     *         decoration.
+     */
+    public int getMarginWidth() {
+        return marginWidth;
+    }
+
+    /**
+     * Set the margin width in pixels that should be used between the decorator
+     * and the horizontal edge of the control. The default value of this setting
+     * is <code>0</code>.
+     *
+     * @param marginWidth
+     *            the number of pixels that should be reserved between the
+     *            horizontal edge of the control and the adjacent edge of the
+     *            decoration.
+     */
+    public void setMarginWidth(int marginWidth) {
+        this.marginWidth = marginWidth;
+        update();
+    }
+
+    /**
+     * Something has changed, requiring redraw. Redraw the decoration and update
+     * the hover text if appropriate.
+     */
+    protected void update() {
+        if (control is null || control.isDisposed()) {
+            return;
+        }
+        Rectangle rect = getDecorationRectangle(control.getShell());
+        // Redraw this rectangle in all children
+        control.getShell()
+                .redraw(rect.x, rect.y, rect.width, rect.height, true);
+        control.getShell().update();
+        if (hover !is null && getDescriptionText() !is null) {
+            hover.setText(getDescriptionText(), getDecorationRectangle(control
+                    .getParent()), control);
+        }
+    }
+
+    /*
+     * Show the specified text in the hover, positioning the hover near the
+     * specified control.
+     */
+    private void showHoverText(String text, Control hoverNear) {
+        // If we aren't to show a hover, don't do anything.
+        if (!showHover) {
+            return;
+        }
+        // If there is no text, don't do anything.
+        if (text is null) {
+            hideHover();
+            return;
+        }
+
+        // If there is no control, nothing to do
+        if (control is null) {
+            return;
+        }
+        // Create the hover if it's not showing
+        if (hover is null) {
+            hover = new Hover(hoverNear.getShell());
+        }
+        hover.setText(text, getDecorationRectangle(control.getParent()),
+                control);
+        hover.setVisible(true);
+    }
+
+    /*
+     * Remove any listeners installed on the controls.
+     */
+    private void removeControlListeners() {
+        if (control is null) {
+            return;
+        }
+        printRemoveListener(control, "FOCUS"); //$NON-NLS-1$
+        control.removeFocusListener(focusListener);
+        focusListener = null;
+
+        printRemoveListener(control, "DISPOSE"); //$NON-NLS-1$
+        control.removeDisposeListener(disposeListener);
+        disposeListener = null;
+
+        Composite c = control.getParent();
+        while (c !is null) {
+            removeCompositeListeners(c);
+            if (composite !is null && composite is c) {
+                // We previously installed listeners only to the specified
+                // composite, so stop.
+                c = null;
+            } else if (cast(Shell)c ) {
+                // We previously installed listeners only up to the first Shell
+                // encountered, so stop.
+                c = null;
+            } else {
+                c = c.getParent();
+            }
+        }
+        paintListener = null;
+        mouseTrackListener = null;
+        compositeListener = null;
+
+        // We may have a remaining mouse move listener installed
+        if (moveListeningTarget !is null) {
+            printRemoveListener(moveListeningTarget, "MOUSEMOVE"); //$NON-NLS-1$
+            moveListeningTarget.removeMouseMoveListener(mouseMoveListener);
+            moveListeningTarget = null;
+            mouseMoveListener = null;
+        }
+        if (DEBUG) {
+            if (listenerInstalls > 0) {
+                Stdout.formatln("LISTENER LEAK>>>CHECK TRACE ABOVE"); //$NON-NLS-1$
+            } else if (listenerInstalls < 0) {
+                Stdout.formatln("REMOVED UNREGISTERED LISTENERS>>>CHECK TRACE ABOVE"); //$NON-NLS-1$
+            } else {
+                Stdout.formatln("ALL INSTALLED LISTENERS WERE REMOVED."); //$NON-NLS-1$
+            }
+        }
+    }
+
+    /**
+     * Return the rectangle in which the decoration should be rendered, in
+     * coordinates relative to the specified control. If the specified control
+     * is null, return the rectangle in display coordinates.
+     *
+     * @param targetControl
+     *            the control whose coordinates should be used
+     * @return the rectangle in which the decoration should be rendered
+     */
+    protected Rectangle getDecorationRectangle(Control targetControl) {
+        if (getImage() is null || control is null) {
+            return new Rectangle(0, 0, 0, 0);
+        }
+        // Compute the bounds first relative to the control's parent.
+        Rectangle imageBounds = getImage().getBounds();
+        Rectangle controlBounds = control.getBounds();
+        int x, y;
+        // Compute x
+        if ((position & DWT.RIGHT) is DWT.RIGHT) {
+            x = controlBounds.x + controlBounds.width + marginWidth;
+        } else {
+            // default is left
+            x = controlBounds.x - imageBounds.width - marginWidth;
+        }
+        // Compute y
+        if ((position & DWT.TOP) is DWT.TOP) {
+            y = controlBounds.y;
+        } else if ((position & DWT.BOTTOM) is DWT.BOTTOM) {
+            y = controlBounds.y + control.getBounds().height
+                    - imageBounds.height;
+        } else {
+            // default is center
+            y = controlBounds.y
+                    + (control.getBounds().height - imageBounds.height) / 2;
+        }
+
+        // Now convert to coordinates relative to the target control.
+        Point globalPoint = control.getParent().toDisplay(x, y);
+        Point targetPoint;
+        if (targetControl is null) {
+            targetPoint = globalPoint;
+        } else {
+            targetPoint = targetControl.toControl(globalPoint);
+        }
+        return new Rectangle(targetPoint.x, targetPoint.y, imageBounds.width,
+                imageBounds.height);
+    }
+
+    /*
+     * Return true if the decoration should be shown, false if it should not.
+     */
+    private bool shouldShowDecoration() {
+        if (!visible) {
+            return false;
+        }
+        if (control is null || control.isDisposed() || getImage() is null) {
+            return false;
+        }
+
+        if (!control.isVisible()) {
+            return false;
+        }
+        if (showOnlyOnFocus) {
+            return hasFocus;
+        }
+        return true;
+    }
+
+    /*
+     * If in debug mode, print info about adding the specified listener.
+     */
+    private void printAddListener(Widget widget, String listenerType) {
+        listenerInstalls++;
+        if (DEBUG) {
+            Stdout.formatln("Added listener>>>{} to>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$
+        }
+    }
+
+    /*
+     * If in debug mode, print info about adding the specified listener.
+     */
+    private void printRemoveListener(Widget widget, String listenerType) {
+        listenerInstalls--;
+        if (DEBUG) {
+            Stdout.formatln("Removed listener>>>{} from>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/FieldAssistColors.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,222 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.fieldassist.FieldAssistColors;
+
+import tango.util.collection.ArraySeq;
+import tango.util.collection.HashMap;
+import tango.util.collection.model.Map;
+import tango.util.collection.model.Seq;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.RGB;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwtx.jface.resource.JFaceColors;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+import tango.io.Stdout;
+
+/**
+ * FieldAssistColors defines protocol for retrieving colors that can be used to
+ * provide visual cues with fields. For consistency with JFace dialogs and
+ * wizards, it is recommended that FieldAssistColors is used when colors are
+ * used to annotate fields.
+ * <p>
+ * Color resources that are returned using methods in this class are maintained
+ * in the JFace color registries, or by DWT. Users of any color resources
+ * provided by this class are not responsible for the lifecycle of the color.
+ * Colors provided by this class should never be disposed by clients. In some
+ * cases, clients are provided information, such as RGB values, in order to
+ * create their own color resources. In these cases, the client should manage
+ * the lifecycle of any created resource.
+ *
+ * @since 3.2
+ * @deprecated As of 3.3, this class is no longer necessary.
+ */
+public class FieldAssistColors {
+
+    private static bool DEBUG = false;
+
+    /*
+     * Keys are background colors, values are the color with the alpha value
+     * applied
+     */
+    private static Map!(Object,Object) requiredFieldColorMap;
+
+    /*
+     * Keys are colors we have created, values are the displays on which they
+     * were created.
+     */
+    private static Map!(Object,Object) displays;
+
+    static this(){
+        requiredFieldColorMap = new HashMap!(Object,Object);
+        displays = new HashMap!(Object,Object);
+    }
+
+    /**
+     * Compute the RGB of the color that should be used for the background of a
+     * control to indicate that the control has an error. Because the color
+     * suitable for indicating an error depends on the colors set into the
+     * control, this color is always computed dynamically and provided as an RGB
+     * value. Clients who use this RGB to create a Color resource are
+     * responsible for managing the life cycle of the color.
+     * <p>
+     * This color is computed dynamically each time that it is queried. Clients
+     * should typically call this method once, create a color from the RGB
+     * provided, and dispose of the color when finished using it.
+     *
+     * @param control
+     *            the control for which the background color should be computed.
+     * @return the RGB value indicating a background color appropriate for
+     *         indicating an error in the control.
+     */
+    public static RGB computeErrorFieldBackgroundRGB(Control control) {
+        /*
+         * Use a 10% alpha of the error color applied on top of the widget
+         * background color.
+         */
+        Color dest = control.getBackground();
+        Color src = JFaceColors.getErrorText(control.getDisplay());
+        int destRed = dest.getRed();
+        int destGreen = dest.getGreen();
+        int destBlue = dest.getBlue();
+
+        // 10% alpha
+        int alpha = cast(int) (0xFF * 0.10f);
+        // Alpha blending math
+        destRed += (src.getRed() - destRed) * alpha / 0xFF;
+        destGreen += (src.getGreen() - destGreen) * alpha / 0xFF;
+        destBlue += (src.getBlue() - destBlue) * alpha / 0xFF;
+
+        return new RGB(destRed, destGreen, destBlue);
+    }
+
+    /**
+     * Return the color that should be used for the background of a control to
+     * indicate that the control is a required field and does not have content.
+     * <p>
+     * This color is managed by FieldAssistResources and should never be
+     * disposed by clients.
+     *
+     * @param control
+     *            the control on which the background color will be used.
+     * @return the color used to indicate that a field is required.
+     */
+    public static Color getRequiredFieldBackgroundColor(Control control) {
+        final Display display = control.getDisplay();
+
+        // If we are in high contrast mode, then don't apply an alpha
+        if (display.getHighContrast()) {
+            return control.getBackground();
+        }
+
+        // See if a color has already been computed
+        Object storedColor = requiredFieldColorMap.get(control.getBackground());
+        if (storedColor !is null) {
+            return cast(Color) storedColor;
+        }
+
+        // There is no color already created, so we must create one.
+        // Use a 15% alpha of yellow on top of the widget background.
+        Color dest = control.getBackground();
+        Color src = display.getSystemColor(DWT.COLOR_YELLOW);
+        int destRed = dest.getRed();
+        int destGreen = dest.getGreen();
+        int destBlue = dest.getBlue();
+
+        // 15% alpha
+        int alpha = cast(int) (0xFF * 0.15f);
+        // Alpha blending math
+        destRed += (src.getRed() - destRed) * alpha / 0xFF;
+        destGreen += (src.getGreen() - destGreen) * alpha / 0xFF;
+        destBlue += (src.getBlue() - destBlue) * alpha / 0xFF;
+
+        // create the color
+        Color color = new Color(display, destRed, destGreen, destBlue);
+        // record the color in a map using the original color as the key
+        requiredFieldColorMap.add(dest, color);
+        // If we have never created a color on this display before, install
+        // a dispose exec on the display.
+        if (!displays.contains(display)) {
+            display.disposeExec(new class Runnable {
+                public void run() {
+                    disposeColors(display);
+                }
+            });
+        }
+        // Record the color and its display in a map for later disposal.
+        displays.add(color, display);
+        return color;
+    }
+
+    /*
+     * Dispose any colors that were allocated for the given display.
+     */
+    private static void disposeColors(Display display) {
+        auto toBeRemoved = new ArraySeq!(Object);
+
+        if (DEBUG) {
+            Stdout.formatln("Display map is {}", (cast(Object)displays).toString()); //$NON-NLS-1$
+            Stdout.formatln("Color map is {}", (cast(Object)requiredFieldColorMap).toString()); //$NON-NLS-1$
+        }
+
+        // Look for any stored colors that were created on this display
+        foreach( k, v; displays ){
+            Color color = cast(Color) k;
+            if ((cast(Display) displays.get(color)).opEquals(display)) {
+                // The color is on this display. Mark it for removal.
+                toBeRemoved.append(color);
+
+                // Now look for any references to it in the required field color
+                // map
+                auto toBeRemovedFromRequiredMap = new ArraySeq!(Object);
+                foreach( k, v; requiredFieldColorMap ){
+                    Color bgColor = cast(Color) k;
+                    if ((cast(Color) requiredFieldColorMap.get(bgColor))
+                            .opEquals(color)) {
+                        // mark it for removal from the required field color map
+                        toBeRemovedFromRequiredMap.append(bgColor);
+                    }
+                }
+                // Remove references in the required field map now that
+                // we are done iterating.
+                for (int j = 0; j < toBeRemovedFromRequiredMap.size(); j++) {
+                    requiredFieldColorMap.remove(toBeRemovedFromRequiredMap
+                            .get(j));
+                }
+            }
+        }
+        // Remove references in the display map now that we are
+        // done iterating
+        for (int i = 0; i < toBeRemoved.size(); i++) {
+            Color color = cast(Color) toBeRemoved.get(i);
+            // Removing from the display map must be done before disposing the
+            // color or else the comparison between this color and the one
+            // in the map will fail.
+            displays.remove(color);
+            // Dispose it
+            if (DEBUG) {
+                Stdout.formatln("Disposing color {}", color.toString()); //$NON-NLS-1$
+            }
+            color.dispose();
+        }
+        if (DEBUG) {
+            Stdout.formatln("Display map is {}", (cast(Object)displays).toString()); //$NON-NLS-1$
+            Stdout.formatln("Color map is {}", (cast(Object)requiredFieldColorMap).toString()); //$NON-NLS-1$
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/IContentProposal.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.fieldassist.IContentProposal;
+
+import dwt.dwthelper.utils;
+
+/**
+ * IContentProposal describes a content proposal to be shown. It consists of the
+ * content that will be provided if the proposal is accepted, an optional label
+ * used to describe the content to the user, and an optional description that
+ * further elaborates the meaning of the proposal.
+ *
+ * @since 3.2
+ */
+public interface IContentProposal {
+    /**
+     * Return the content represented by this proposal.
+     *
+     * @return the String content represented by this proposal.
+     */
+    public String getContent();
+
+    /**
+     * Return the integer position within the contents that the cursor should be
+     * placed after the proposal is accepted.
+     *
+     * @return the zero-based index position within the contents where the
+     *         cursor should be placed after the proposal is accepted.
+     */
+    public int getCursorPosition();
+
+    /**
+     * Return the label used to describe this proposal.
+     *
+     * @return the String label used to display the proposal. If
+     *         <code>null</code>, then the content will be displayed as the
+     *         label.
+     */
+    public String getLabel();
+
+    /**
+     * Return a description that describes this proposal.
+     *
+     * @return the String label used to further the proposal. If
+     *         <code>null</code>, then no description will be displayed.
+     */
+    public String getDescription();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/IContentProposalListener.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * 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.fieldassist.IContentProposalListener;
+
+import dwtx.jface.fieldassist.IContentProposal;
+
+/**
+ * This interface is used to listen to notifications from a
+ * {@link ContentProposalAdapter}.
+ *
+ * @since 3.2
+ */
+public interface IContentProposalListener {
+    /**
+     * A content proposal has been accepted.
+     *
+     * @param proposal
+     *            the accepted content proposal
+     */
+    public void proposalAccepted(IContentProposal proposal);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/IContentProposalListener2.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.fieldassist.IContentProposalListener2;
+
+import dwtx.jface.fieldassist.ContentProposalAdapter;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This interface is used to listen to additional notifications from a
+ * {@link ContentProposalAdapter}.
+ *
+ * @since 3.3
+ */
+public interface IContentProposalListener2 {
+    /**
+     * A content proposal popup has been opened for content proposal assistance.
+     *
+     * @param adapter
+     *            the ContentProposalAdapter which is providing content proposal
+     *            behavior to a control
+     */
+    public void proposalPopupOpened(ContentProposalAdapter adapter);
+
+    /**
+     * A content proposal popup has been closed.
+     *
+     * @param adapter
+     *            the ContentProposalAdapter which is providing content proposal
+     *            behavior to a control
+     */
+    public void proposalPopupClosed(ContentProposalAdapter adapter);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/IContentProposalProvider.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * 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.fieldassist.IContentProposalProvider;
+
+import dwtx.jface.fieldassist.IContentProposal;
+
+import dwt.dwthelper.utils;
+
+/**
+ * IContentProposalProvider provides an array of IContentProposals that are
+ * appropriate for a textual dialog field, given the field's current content and
+ * the current cursor position.
+ *
+ * @since 3.2
+ */
+public interface IContentProposalProvider {
+
+    /**
+     * Return an array of Objects representing the valid content proposals for a
+     * field.
+     *
+     * @param contents
+     *            the current contents of the text field
+     * @param position
+     *            the current position of the cursor in the contents
+     *
+     * @return the array of {@link IContentProposal} that represent valid
+     *         proposals for the field.
+     */
+    IContentProposal[] getProposals(String contents, int position);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/IControlContentAdapter.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * 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.fieldassist.IControlContentAdapter;
+
+import dwt.graphics.Rectangle;
+import dwt.widgets.Control;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This interface is used to set and retrieve text content from an arbitrary
+ * control. Clients are expected to implement this interface when defining a
+ * {@link ContentProposalAdapter}, in order to specify how to retrieve and set
+ * the contents of the control being adapted.
+ *
+ * @since 3.2
+ */
+public interface IControlContentAdapter {
+    /**
+     * Set the contents of the specified control to the specified text. Must not
+     * be <code>null</code>.
+     *
+     * @param control
+     *            the control whose contents are to be set (replaced).
+     * @param contents
+     *            the String specifying the new control content.
+     * @param cursorPosition
+     *            the zero-based index representing the desired cursor position
+     *            in the control's contents after the contents are set.
+     */
+    public void setControlContents(Control control, String contents,
+            int cursorPosition);
+
+    /**
+     * Insert the specified contents into the control's current contents. Must
+     * not be <code>null</code>.
+     *
+     * @param control
+     *            the control whose contents are to be altered.
+     * @param contents
+     *            the String to be inserted into the control contents.
+     * @param cursorPosition
+     *            the zero-based index representing the desired cursor position
+     *            within the inserted contents after the insertion is made.
+     */
+    public void insertControlContents(Control control, String contents,
+            int cursorPosition);
+
+    /**
+     * Get the text contents of the control.
+     *
+     * @param control
+     *            the control whose contents are to be retrieved.
+     * @return the String contents of the control.
+     */
+    public String getControlContents(Control control);
+
+    /**
+     * Get the current cursor position in the control. The position is specified
+     * as a zero-based index into the string. Valid ranges are from 0 to N,
+     * where N is the size of the contents string. A value of N indicates that
+     * the cursor is at the end of the contents.
+     *
+     * @param control
+     *            the control whose position is to be retrieved.
+     * @return the zero-based index representing the cursor position in the
+     *         control's contents.
+     */
+    public int getCursorPosition(Control control);
+
+    /**
+     * Get the bounds (in pixels) of the insertion point for the control
+     * content. This is a rectangle, in coordinates relative to the control,
+     * where the insertion point is displayed. If the implementer does not have
+     * an insertion point, or cannot determine the location of the insertion
+     * point, it is appropriate to return the bounds of the entire control. This
+     * value may be used to position a content proposal popup.
+     *
+     * @param control
+     *            the control whose offset is to be retrieved.
+     * @return the pixel width representing the distance between the edge of the
+     *         control and the insertion point.
+     */
+    public Rectangle getInsertionBounds(Control control);
+
+    /**
+     * Set the current cursor position in the control. The position is specified
+     * as a zero-based index into the string. Valid ranges are from 0 to N,
+     * where N is the size of the contents string. A value of N indicates that
+     * the cursor is at the end of the contents.
+     *
+     * @param control
+     *            the control whose cursor position is to be set.
+     * @param index
+     *            the zero-based index representing the cursor position in the
+     *            control's contents.
+     */
+    public void setCursorPosition(Control control, int index);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/SimpleContentProposalProvider.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * 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.fieldassist.SimpleContentProposalProvider;
+
+import dwtx.jface.fieldassist.IContentProposalProvider;
+import dwtx.jface.fieldassist.IContentProposal;
+
+import tango.util.collection.ArraySeq;
+
+import dwt.dwthelper.utils;
+
+/**
+ * SimpleContentProposalProvider is a class designed to map a static list of
+ * Strings to content proposals.
+ *
+ * @see IContentProposalProvider
+ * @since 3.2
+ *
+ */
+public class SimpleContentProposalProvider : IContentProposalProvider {
+
+    /*
+     * The proposals provided.
+     */
+    private String[] proposals;
+
+    /*
+     * The proposals mapped to IContentProposal. Cached for speed in the case
+     * where filtering is not used.
+     */
+    private IContentProposal[] contentProposals;
+
+    /*
+     * bool that tracks whether filtering is used.
+     */
+    private bool filterProposals = false;
+
+    /**
+     * Construct a SimpleContentProposalProvider whose content proposals are
+     * always the specified array of Objects.
+     *
+     * @param proposals
+     *            the array of Strings to be returned whenever proposals are
+     *            requested.
+     */
+    public this(String[] proposals) {
+        //super();
+        this.proposals = proposals;
+    }
+
+    /**
+     * Return an array of Objects representing the valid content proposals for a
+     * field. Ignore the current contents of the field.
+     *
+     * @param contents
+     *            the current contents of the field (only consulted if filtering
+     *            is set to <code>true</code>)
+     * @param position
+     *            the current cursor position within the field (ignored)
+     * @return the array of Objects that represent valid proposals for the field
+     *         given its current content.
+     */
+    public IContentProposal[] getProposals(String contents, int position) {
+        if (filterProposals) {
+            auto list = new ArraySeq!(IContentProposal);
+            for (int i = 0; i < proposals.length; i++) {
+                if (proposals[i].length > contents.length
+                        && proposals[i].substring(0, contents.length)
+                                .equalsIgnoreCase(contents)) {
+                    list.append(makeContentProposal(proposals[i]));
+                }
+            }
+            return list.toArray();
+        }
+        if (contentProposals is null) {
+            contentProposals = new IContentProposal[proposals.length];
+            for (int i = 0; i < proposals.length; i++) {
+                contentProposals[i] = makeContentProposal(proposals[i]);
+            }
+        }
+        return contentProposals;
+    }
+
+    /**
+     * Set the Strings to be used as content proposals.
+     *
+     * @param items
+     *            the array of Strings to be used as proposals.
+     */
+    public void setProposals(String[] items) {
+        this.proposals = items;
+        contentProposals = null;
+    }
+
+    /**
+     * Set the bool that controls whether proposals are filtered according to
+     * the current field content.
+     *
+     * @param filterProposals
+     *            <code>true</code> if the proposals should be filtered to
+     *            show only those that match the current contents of the field,
+     *            and <code>false</code> if the proposals should remain the
+     *            same, ignoring the field content.
+     * @since 3.3
+     */
+    public void setFiltering(bool filterProposals) {
+        this.filterProposals = filterProposals;
+        // Clear any cached proposals.
+        contentProposals = null;
+    }
+
+    /*
+     * Make an IContentProposal for showing the specified String.
+     */
+    private IContentProposal makeContentProposal( String proposal) {
+        return new class IContentProposal {
+            String proposal_;
+            this(){proposal_=proposal;}
+            public String getContent() {
+                return proposal_;
+            }
+
+            public String getDescription() {
+                return null;
+            }
+
+            public String getLabel() {
+                return null;
+            }
+
+            public int getCursorPosition() {
+                return proposal_.length;
+            }
+        };
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/TextContentAdapter.d	Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * 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.fieldassist.TextContentAdapter;
+
+import dwtx.jface.fieldassist.IControlContentAdapter;
+
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Control;
+import dwt.widgets.Text;
+
+import dwt.dwthelper.utils;
+
+/**
+ * An {@link IControlContentAdapter} for DWT Text controls. This is a
+ * convenience class for easily creating a {@link ContentProposalAdapter} for
+ * text fields.
+ *
+ * @since 3.2
+ */
+public class TextContentAdapter : IControlContentAdapter {
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.dialogs.taskassistance.IControlContentAdapter#getControlContents(dwt.widgets.Control)
+     */
+    public String getControlContents(Control control) {
+        return (cast(Text) control).getText();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#setControlContents(dwt.widgets.Control,
+     *      java.lang.String, int)
+     */
+    public void setControlContents(Control control, String text,
+            int cursorPosition) {
+        (cast(Text) control).setText(text);
+        (cast(Text) control).setSelection(cursorPosition, cursorPosition);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#insertControlContents(dwt.widgets.Control,
+     *      java.lang.String, int)
+     */
+    public void insertControlContents(Control control, String text,
+            int cursorPosition) {
+        Point selection = (cast(Text) control).getSelection();
+        (cast(Text) control).insert(text);
+        // Insert will leave the cursor at the end of the inserted text. If this
+        // is not what we wanted, reset the selection.
+        if (cursorPosition < text.length) {
+            (cast(Text) control).setSelection(selection.x + cursorPosition,
+                    selection.x + cursorPosition);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#getCursorPosition(dwt.widgets.Control)
+     */
+    public int getCursorPosition(Control control) {
+        return (cast(Text) control).getCaretPosition();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#getInsertionBounds(dwt.widgets.Control)
+     */
+    public Rectangle getInsertionBounds(Control control) {
+        Text text = cast(Text) control;
+        Point caretOrigin = text.getCaretLocation();
+        return new Rectangle(caretOrigin.x, caretOrigin.y, 1, text
+                .getLineHeight());
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.fieldassist.IControlContentAdapter#setCursorPosition(dwt.widgets.Control,
+     *      int)
+     */
+    public void setCursorPosition(Control control, int position) {
+        (cast(Text) control).setSelection(new Point(position, position));
+    }
+}