diff dwtx/jface/internal/text/link/contentassist/CompletionProposalPopup2.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/internal/text/link/contentassist/CompletionProposalPopup2.d	Sat Aug 23 19:10:48 2008 +0200
@@ -0,0 +1,950 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 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
+ *     Sean Montgomery, sean_montgomery@comcast.net - https://bugs.eclipse.org/bugs/show_bug.cgi?id=116454
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.internal.text.link.contentassist.CompletionProposalPopup2;
+
+import dwt.dwthelper.utils;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dwt.DWT;
+import dwt.custom.StyleRange;
+import dwt.custom.StyledText;
+import dwt.events.ControlEvent;
+import dwt.events.ControlListener;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.events.KeyEvent;
+import dwt.events.KeyListener;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.events.VerifyEvent;
+import dwt.graphics.Color;
+import dwt.graphics.Point;
+import dwt.layout.GridData;
+import dwt.layout.GridLayout;
+import dwt.widgets.Control;
+import dwt.widgets.Shell;
+import dwt.widgets.Table;
+import dwt.widgets.TableItem;
+import dwtx.jface.internal.text.TableOwnerDrawSupport;
+import dwtx.jface.resource.JFaceResources;
+import dwtx.jface.text.BadLocationException;
+import dwtx.jface.text.DocumentEvent;
+import dwtx.jface.text.IDocument;
+import dwtx.jface.text.IDocumentListener;
+import dwtx.jface.text.IEditingSupport;
+import dwtx.jface.text.IEditingSupportRegistry;
+import dwtx.jface.text.IRegion;
+import dwtx.jface.text.IRewriteTarget;
+import dwtx.jface.text.ITextViewer;
+import dwtx.jface.text.ITextViewerExtension;
+import dwtx.jface.text.TextUtilities;
+import dwtx.jface.text.contentassist.ICompletionProposal;
+import dwtx.jface.text.contentassist.ICompletionProposalExtension;
+import dwtx.jface.text.contentassist.ICompletionProposalExtension2;
+import dwtx.jface.text.contentassist.ICompletionProposalExtension6;
+import dwtx.jface.text.contentassist.IContextInformation;
+import dwtx.jface.viewers.StyledString;
+
+
+
+/**
+ * This class is used to present proposals to the user. If additional
+ * information exists for a proposal, then selecting that proposal
+ * will result in the information being displayed in a secondary
+ * window.
+ *
+ * @see dwtx.jface.text.contentassist.ICompletionProposal
+ * @see dwtx.jface.internal.text.link.contentassist.AdditionalInfoController2
+ */
+class CompletionProposalPopup2 : IContentAssistListener2 {
+
+    /** The associated text viewer */
+    private ITextViewer fViewer;
+    /** The associated content assistant */
+    private ContentAssistant2 fContentAssistant;
+    /** The used additional info controller */
+    private AdditionalInfoController2 fAdditionalInfoController;
+    /** The closing strategy for this completion proposal popup */
+    private PopupCloser2 fPopupCloser= new PopupCloser2();
+    /** The popup shell */
+    private Shell fProposalShell;
+    /** The proposal table */
+    private Table fProposalTable;
+    /** Indicates whether a completion proposal is being inserted */
+    private bool fInserting= false;
+    /** The key listener to control navigation */
+    private KeyListener fKeyListener;
+    /** List of document events used for filtering proposals */
+    private List fDocumentEvents= new ArrayList();
+    /** Listener filling the document event queue */
+    private IDocumentListener fDocumentListener;
+    /** Reentrance count for <code>filterProposals</code> */
+    private long fInvocationCounter= 0;
+    /** The filter list of proposals */
+    private ICompletionProposal[] fFilteredProposals;
+    /** The computed list of proposals */
+    private ICompletionProposal[] fComputedProposals;
+    /** The offset for which the proposals have been computed */
+    private int fInvocationOffset;
+    /** The offset for which the computed proposals have been filtered */
+    private int fFilterOffset;
+    /** The default line delimiter of the viewer's widget */
+    private String fLineDelimiter;
+    /** The most recently selected proposal. */
+    private ICompletionProposal fLastProposal;
+    /**
+     * Tells whether colored labels support is enabled.
+     * Only valid while the popup is active.
+     * 
+     * @since 3.4
+     */
+    private bool fIsColoredLabelsSupportEnabled= false;
+    
+    private final IEditingSupport fFocusEditingSupport= new IEditingSupport() {
+
+        public bool isOriginator(DocumentEvent event, IRegion focus) {
+            return false;
+        }
+
+        public bool ownsFocusShell() {
+            return Helper2.okToUse(fProposalShell) && fProposalShell.isFocusControl()
+                    || Helper2.okToUse(fProposalTable) && fProposalTable.isFocusControl();
+        }
+
+    };
+    private final IEditingSupport fModificationEditingSupport= new IEditingSupport() {
+
+        public bool isOriginator(DocumentEvent event, IRegion focus) {
+            if (fViewer !is null) {
+                Point selection= fViewer.getSelectedRange();
+                return selection.x <= focus.getOffset() + focus.getLength() && selection.x + selection.y >= focus.getOffset();
+            }
+            return false;
+        }
+
+        public bool ownsFocusShell() {
+            return false;
+        }
+
+    };
+
+
+    /**
+     * Creates a new completion proposal popup for the given elements.
+     *
+     * @param contentAssistant the content assistant feeding this popup
+     * @param viewer the viewer on top of which this popup appears
+     * @param infoController the info control collaborating with this popup
+     * @since 2.0
+     */
+    public CompletionProposalPopup2(ContentAssistant2 contentAssistant, ITextViewer viewer, AdditionalInfoController2 infoController) {
+        fContentAssistant= contentAssistant;
+        fViewer= viewer;
+        fAdditionalInfoController= infoController;
+    }
+
+    /**
+     * Computes and presents completion proposals. The flag indicates whether this call has
+     * be made out of an auto activation context.
+     *
+     * @param autoActivated <code>true</code> if auto activation context
+     * @return an error message or <code>null</code> in case of no error
+     */
+    public String showProposals(final bool autoActivated) {
+
+        if (fKeyListener is null) {
+            fKeyListener= new KeyListener() {
+                public void keyPressed(KeyEvent e) {
+                    if (!Helper2.okToUse(fProposalShell))
+                        return;
+
+                    if (e.character is 0 && e.keyCode is DWT.CTRL) {
+                        // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
+                        int index= fProposalTable.getSelectionIndex();
+                        if (index >= 0)
+                            selectProposal(index, true);
+                    }
+                }
+
+                public void keyReleased(KeyEvent e) {
+                    if (!Helper2.okToUse(fProposalShell))
+                        return;
+
+                    if (e.character is 0 && e.keyCode is DWT.CTRL) {
+                        // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
+                        int index= fProposalTable.getSelectionIndex();
+                        if (index >= 0)
+                            selectProposal(index, false);
+                    }
+                }
+            };
+        }
+
+        final StyledText styledText= fViewer.getTextWidget();
+        if (styledText !is null && !styledText.isDisposed())
+            styledText.addKeyListener(fKeyListener);
+
+//      BusyIndicator.showWhile(styledText.getDisplay(), new Runnable() {
+//          public void run() {
+
+                fInvocationOffset= fViewer.getSelectedRange().x;
+                // lazily compute proposals
+//              if (fComputedProposals is null) fComputedProposals= computeProposals(fContentAssistant.getCompletionPosition());
+                fComputedProposals= computeProposals(fInvocationOffset);
+
+                int count= (fComputedProposals is null ? 0 : fComputedProposals.length);
+                if (count is 0) {
+
+                    if (!autoActivated)
+                        styledText.getDisplay().beep();
+
+                } else {
+
+                    if (count is 1 && !autoActivated && fContentAssistant.isAutoInserting())
+
+                        insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
+
+                    else {
+
+                        if (fLineDelimiter is null)
+                            fLineDelimiter= styledText.getLineDelimiter();
+
+                        createProposalSelector();
+                        setProposals(fComputedProposals);
+                        resizeProposalSelector(true);
+                        displayProposals();
+                    }
+                }
+//          }
+//      });
+
+        return getErrorMessage();
+    }
+
+    /**
+     * Returns the completion proposal available at the given offset of the
+     * viewer's document. Delegates the work to the content assistant.
+     *
+     * @param offset the offset
+     * @return the completion proposals available at this offset
+     */
+    private ICompletionProposal[] computeProposals(int offset) {
+        return fContentAssistant.computeCompletionProposals(fViewer, offset);
+    }
+
+    /**
+     * Returns the error message.
+     *
+     * @return the error message
+     */
+    private String getErrorMessage() {
+        return fContentAssistant.getErrorMessage();
+    }
+
+    /**
+     * Creates the proposal selector.
+     */
+    private void createProposalSelector() {
+        if (Helper2.okToUse(fProposalShell))
+            return;
+
+        Control control= fViewer.getTextWidget();
+        fProposalShell= new Shell(control.getShell(), DWT.ON_TOP);
+//      fProposalShell= new Shell(control.getShell(), DWT.ON_TOP | DWT.RESIZE );
+        fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL);
+//      fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL);
+        
+
+        fIsColoredLabelsSupportEnabled= fContentAssistant.isColoredLabelsSupportEnabled();
+        if (fIsColoredLabelsSupportEnabled)
+            TableOwnerDrawSupport.install(fProposalTable);
+
+        fProposalTable.setLocation(0, 0);
+        if (fAdditionalInfoController !is null)
+            fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
+
+        GridLayout layout= new GridLayout();
+        layout.marginWidth= 0;
+        layout.marginHeight= 0;
+        fProposalShell.setLayout(layout);
+
+        GridData data= new GridData(GridData.FILL_BOTH);
+        fProposalTable.setLayoutData(data);
+
+        fProposalShell.pack();
+
+        // set location
+        Point currentLocation= fProposalShell.getLocation();
+        Point newLocation= getLocation();
+        if ((newLocation.x < currentLocation.x && newLocation.y is currentLocation.y) || newLocation.y < currentLocation.y)
+            fProposalShell.setLocation(newLocation);
+
+        if (fAdditionalInfoController !is null) {
+            fProposalShell.addControlListener(new ControlListener() {
+
+                public void controlMoved(ControlEvent e) {}
+
+                public void controlResized(ControlEvent e) {
+                    // resets the cached resize constraints
+                    fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
+                }
+            });
+        }
+
+        fProposalShell.setBackground(control.getDisplay().getSystemColor(DWT.COLOR_BLACK));
+
+        Color c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_BACKGROUND);
+        fProposalTable.setBackground(c);
+
+        c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_FOREGROUND);
+        fProposalTable.setForeground(c);
+
+        fProposalTable.addSelectionListener(new SelectionListener() {
+
+            public void widgetSelected(SelectionEvent e) {}
+
+            public void widgetDefaultSelected(SelectionEvent e) {
+                selectProposalWithMask(e.stateMask);
+            }
+        });
+
+        fPopupCloser.install(fContentAssistant, fProposalTable);
+
+        fProposalShell.addDisposeListener(new DisposeListener() {
+            public void widgetDisposed(DisposeEvent e) {
+                unregister(); // but don't dispose the shell, since we're being called from its disposal event!
+            }
+        });
+
+        fProposalTable.setHeaderVisible(false);
+        fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant2.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset());
+    }
+
+    /**
+     * Returns the proposal selected in the proposal selector.
+     *
+     * @return the selected proposal
+     * @since 2.0
+     */
+    private ICompletionProposal getSelectedProposal() {
+        int i= fProposalTable.getSelectionIndex();
+        if (i < 0 || i >= fFilteredProposals.length)
+            return null;
+        return fFilteredProposals[i];
+    }
+
+    /**
+     * Takes the selected proposal and applies it.
+     *
+     * @param stateMask the state mask
+     * @since 2.1
+     */
+    private void selectProposalWithMask(int stateMask) {
+        ICompletionProposal p= getSelectedProposal();
+        hide();
+        if (p !is null)
+            insertProposal(p, (char) 0, stateMask, fViewer.getSelectedRange().x);
+    }
+
+    /**
+     * Applies the given proposal at the given offset. The given character is the
+     * one that triggered the insertion of this proposal.
+     *
+     * @param p the completion proposal
+     * @param trigger the trigger character
+     * @param stateMask the state mask of the keyboard event triggering the insertion
+     * @param offset the offset
+     * @since 2.1
+     */
+    private void insertProposal(ICompletionProposal p, char trigger, int stateMask, final int offset) {
+
+        fInserting= true;
+        IRewriteTarget target= null;
+        IEditingSupportRegistry registry= null;
+
+        try {
+
+            IDocument document= fViewer.getDocument();
+
+            if (fViewer instanceof ITextViewerExtension) {
+                ITextViewerExtension extension= (ITextViewerExtension) fViewer;
+                target= extension.getRewriteTarget();
+            }
+
+            if (target !is null)
+                target.beginCompoundChange();
+
+            if (fViewer instanceof IEditingSupportRegistry) {
+                registry= (IEditingSupportRegistry) fViewer;
+                registry.register(fModificationEditingSupport);
+            }
+
+            if (p instanceof ICompletionProposalExtension2) {
+                ICompletionProposalExtension2 e= (ICompletionProposalExtension2) p;
+                e.apply(fViewer, trigger, stateMask, offset);
+            } else if (p instanceof ICompletionProposalExtension) {
+                ICompletionProposalExtension e= (ICompletionProposalExtension) p;
+                e.apply(document, trigger, offset);
+            } else {
+                p.apply(document);
+            }
+
+            Point selection= p.getSelection(document);
+            if (selection !is null) {
+                fViewer.setSelectedRange(selection.x, selection.y);
+                fViewer.revealRange(selection.x, selection.y);
+            }
+
+            IContextInformation info= p.getContextInformation();
+            if (info !is null) {
+
+                int position;
+                if (p instanceof ICompletionProposalExtension) {
+                    ICompletionProposalExtension e= (ICompletionProposalExtension) p;
+                    position= e.getContextInformationPosition();
+                } else {
+                    if (selection is null)
+                        selection= fViewer.getSelectedRange();
+                    position= selection.x + selection.y;
+                }
+
+                fContentAssistant.showContextInformation(info, position);
+            }
+
+            fContentAssistant.fireProposalChosen(p);
+
+        } finally {
+            if (target !is null)
+                target.endCompoundChange();
+
+            if (registry !is null)
+                registry.unregister(fModificationEditingSupport);
+
+            fInserting= false;
+        }
+    }
+
+    /**
+     * Returns whether this popup has the focus.
+     *
+     * @return <code>true</code> if the popup has the focus
+     */
+    public bool hasFocus() {
+        if (Helper2.okToUse(fProposalShell))
+            return (fProposalShell.isFocusControl() || fProposalTable.isFocusControl());
+
+        return false;
+    }
+
+    /**
+     * Hides this popup.
+     */
+    public void hide() {
+
+        unregister();
+
+        if (fViewer instanceof IEditingSupportRegistry) {
+            IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
+            registry.unregister(fFocusEditingSupport);
+        }
+
+        if (Helper2.okToUse(fProposalShell)) {
+            fContentAssistant.removeContentAssistListener(this, ContentAssistant2.PROPOSAL_SELECTOR);
+
+            fPopupCloser.uninstall();
+            // see bug 47511: setVisible may run the event loop on GTK
+            // and trigger a rentrant call - have to make sure we don't
+            // dispose another shell that was already brought up in a
+            // reentrant call when calling setVisible()
+            Shell tempShell= fProposalShell;
+            fProposalShell= null;
+            tempShell.setVisible(false);
+            tempShell.dispose();
+        }
+    }
+
+    private void unregister() {
+        if (fDocumentListener !is null) {
+            IDocument document= fViewer.getDocument();
+            if (document !is null)
+                document.removeDocumentListener(fDocumentListener);
+            fDocumentListener= null;
+        }
+        fDocumentEvents.clear();
+
+        StyledText styledText= fViewer.getTextWidget();
+        if (fKeyListener !is null && styledText !is null && !styledText.isDisposed())
+            styledText.removeKeyListener(fKeyListener);
+
+        if (fLastProposal !is null) {
+            if (fLastProposal instanceof ICompletionProposalExtension2) {
+                ICompletionProposalExtension2 extension= (ICompletionProposalExtension2) fLastProposal;
+                extension.unselected(fViewer);
+            }
+
+            fLastProposal= null;
+        }
+
+        fFilteredProposals= null;
+
+        fContentAssistant.possibleCompletionsClosed();
+    }
+
+    /**
+     *Returns whether this popup is active. It is active if the propsal selector is visible.
+     *
+     * @return <code>true</code> if this popup is active
+     */
+    public bool isActive() {
+        return fProposalShell !is null && !fProposalShell.isDisposed();
+    }
+
+    /**
+     * Initializes the proposal selector with these given proposals.
+     *
+     * @param proposals the proposals
+     */
+    private void setProposals(ICompletionProposal[] proposals) {
+        if (Helper2.okToUse(fProposalTable)) {
+
+            ICompletionProposal oldProposal= getSelectedProposal();
+            if (oldProposal instanceof ICompletionProposalExtension2)
+                ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
+
+            fFilteredProposals= proposals;
+
+            fProposalTable.setRedraw(false);
+            fProposalTable.removeAll();
+
+            Point selection= fViewer.getSelectedRange();
+            int endOffset;
+            endOffset= selection.x + selection.y;
+            IDocument document= fViewer.getDocument();
+            bool validate= false;
+            if (selection.y !is 0 && document !is null) validate= true;
+            int selectionIndex= 0;
+
+            TableItem item;
+            ICompletionProposal p;
+            for (int i= 0; i < proposals.length; i++) {
+                p= proposals[i];
+                item= new TableItem(fProposalTable, DWT.NULL);
+                if (p.getImage() !is null)
+                    item.setImage(p.getImage());
+                
+                String displayString;
+                StyleRange[] styleRanges= null;
+                if (fIsColoredLabelsSupportEnabled && p instanceof ICompletionProposalExtension6) {
+                    StyledString styledString= ((ICompletionProposalExtension6)p).getStyledDisplayString();
+                    displayString= styledString.getString();
+                    styleRanges= styledString.getStyleRanges();
+                } else
+                    displayString= p.getDisplayString();
+
+                item.setText(displayString);
+                if (fIsColoredLabelsSupportEnabled)
+                    TableOwnerDrawSupport.storeStyleRanges(item, 0, styleRanges);
+                
+                item.setData(p);
+
+                if (validate && validateProposal(document, p, endOffset, null)) {
+                    selectionIndex= i;
+                    validate= false;
+                }
+            }
+
+            resizeProposalSelector(false);
+
+            selectProposal(selectionIndex, false);
+            fProposalTable.setRedraw(true);
+        }
+    }
+
+    private void resizeProposalSelector(bool adjustWidth) {
+        // in order to fill in the table items so size computation works correctly
+        // will cause flicker, though
+        fProposalTable.setRedraw(true);
+
+        int width= adjustWidth ? DWT.DEFAULT : ((GridData)fProposalTable.getLayoutData()).widthHint;
+        Point size= fProposalTable.computeSize(width, DWT.DEFAULT, true);
+
+        GridData data= new GridData(GridData.FILL_BOTH);
+        data.widthHint= adjustWidth ? Math.min(size.x, 300) : width;
+        data.heightHint= Math.min(getTableHeightHint(fProposalTable, fProposalTable.getItemCount()), getTableHeightHint(fProposalTable, 10));
+        fProposalTable.setLayoutData(data);
+
+        fProposalShell.layout(true);
+        fProposalShell.pack();
+
+        if (adjustWidth) {
+            fProposalShell.setLocation(getLocation());
+        }
+    }
+
+    /**
+     * Computes the table hight hint for <code>table</code>.
+     *
+     * @param table the table to compute the height for
+     * @param rows the number of rows to compute the height for
+     * @return the height hint for <code>table</code>
+     */
+    private int getTableHeightHint(Table table, int rows) {
+        if (table.getFont().equals(JFaceResources.getDefaultFont()))
+            table.setFont(JFaceResources.getDialogFont());
+        int result= table.getItemHeight() * rows;
+        if (table.getLinesVisible())
+            result+= table.getGridLineWidth() * (rows - 1);
+
+        // TODO adjust to correct size. +4 works on windows, but not others
+//      return result + 4;
+        return result;
+    }
+
+    private bool validateProposal(IDocument document, ICompletionProposal p, int offset, DocumentEvent event) {
+        // detect selected
+        if (p instanceof ICompletionProposalExtension2) {
+            ICompletionProposalExtension2 e= (ICompletionProposalExtension2) p;
+            if (e.validate(document, offset, event))
+                return true;
+        } else if (p instanceof ICompletionProposalExtension) {
+            ICompletionProposalExtension e= (ICompletionProposalExtension) p;
+            if (e.isValidFor(document, offset))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the graphical location at which this popup should be made visible.
+     *
+     * @return the location of this popup
+     */
+    private Point getLocation() {
+        StyledText text= fViewer.getTextWidget();
+        Point selection= text.getSelection();
+        Point p= text.getLocationAtOffset(selection.x);
+        p.x -= fProposalShell.getBorderWidth();
+        if (p.x < 0) p.x= 0;
+        if (p.y < 0) p.y= 0;
+        p= new Point(p.x, p.y + text.getLineHeight(selection.x));
+        p= text.toDisplay(p);
+        return p;
+    }
+
+    /**
+     *Displays this popup and install the additional info controller, so that additional info
+     * is displayed when a proposal is selected and additional info is available.
+     */
+    private void displayProposals() {
+        if (fContentAssistant.addContentAssistListener(this, ContentAssistant2.PROPOSAL_SELECTOR)) {
+
+            if (fDocumentListener is null)
+                fDocumentListener=  new IDocumentListener()  {
+                    public void documentAboutToBeChanged(DocumentEvent event) {
+                        if (!fInserting)
+                            fDocumentEvents.add(event);
+                    }
+
+                    public void documentChanged(DocumentEvent event) {
+                        if (!fInserting)
+                            filterProposals();
+                    }
+                };
+            IDocument document= fViewer.getDocument();
+            if (document !is null)
+                document.addDocumentListener(fDocumentListener);
+
+
+            if (fViewer instanceof IEditingSupportRegistry) {
+                IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
+                registry.register(fFocusEditingSupport);
+            }
+
+            fProposalShell.setVisible(true);
+            // see bug 47511: setVisible may run the event loop on GTK
+            // and trigger a rentrant call - have to check whether we are still
+            // visible
+            if (!Helper2.okToUse(fProposalShell))
+                return;
+
+
+            if (fAdditionalInfoController !is null) {
+                fAdditionalInfoController.install(fProposalTable);
+                fAdditionalInfoController.handleTableSelectionChanged();
+            }
+        }
+    }
+
+        /*
+         * @see IContentAssistListener#verifyKey(VerifyEvent)
+         */
+        public bool verifyKey(VerifyEvent e) {
+            if (!Helper2.okToUse(fProposalShell))
+                return true;
+
+            char key= e.character;
+            if (key is 0) {
+                int newSelection= fProposalTable.getSelectionIndex();
+                int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1;
+                bool smartToggle= false;
+                switch (e.keyCode) {
+
+                    case DWT.ARROW_LEFT :
+                    case DWT.ARROW_RIGHT :
+                        filterProposals();
+                        return true;
+
+                    case DWT.ARROW_UP :
+                        newSelection -= 1;
+                        if (newSelection < 0)
+                            newSelection= fProposalTable.getItemCount() - 1;
+                        break;
+
+                    case DWT.ARROW_DOWN :
+                        newSelection += 1;
+                        if (newSelection > fProposalTable.getItemCount() - 1)
+                            newSelection= 0;
+                        break;
+
+                    case DWT.PAGE_DOWN :
+                        newSelection += visibleRows;
+                        if (newSelection >= fProposalTable.getItemCount())
+                            newSelection= fProposalTable.getItemCount() - 1;
+                        break;
+
+                    case DWT.PAGE_UP :
+                        newSelection -= visibleRows;
+                        if (newSelection < 0)
+                            newSelection= 0;
+                        break;
+
+                    case DWT.HOME :
+                        newSelection= 0;
+                        break;
+
+                    case DWT.END :
+                        newSelection= fProposalTable.getItemCount() - 1;
+                        break;
+
+                    default :
+                        if (e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4)
+                            hide();
+                        return true;
+                }
+
+                selectProposal(newSelection, smartToggle);
+
+                e.doit= false;
+                return false;
+
+            }
+
+            // key !is 0
+            switch (key) {
+                case 0x1B: // Esc
+                    e.doit= false;
+                    hide();
+                    break;
+
+                case '\n': // Ctrl-Enter on w2k
+                case '\r': // Enter
+                    if ((e.stateMask & DWT.CTRL) is 0) {
+                        e.doit= false;
+                        selectProposalWithMask(e.stateMask);
+                    }
+                    break;
+
+                    // in linked mode: hide popup
+                    // plus: don't invalidate the event in order to give LinkedUI a chance to handle it
+                case '\t':
+//                  hide();
+                    break;
+
+                default:
+                    ICompletionProposal p= getSelectedProposal();
+                if (p instanceof ICompletionProposalExtension) {
+                    ICompletionProposalExtension t= (ICompletionProposalExtension) p;
+                    char[] triggers= t.getTriggerCharacters();
+                    if (contains(triggers, key)) {
+                        hide();
+                        if (key is ';') {
+                            e.doit= true;
+                            insertProposal(p, (char) 0, e.stateMask, fViewer.getSelectedRange().x);
+                        } else {
+                            e.doit= false;
+                            insertProposal(p, key, e.stateMask, fViewer.getSelectedRange().x);
+                        }
+                    }
+                }
+            }
+
+            return true;
+        }
+
+    /**
+     * Selects the entry with the given index in the proposal selector and feeds
+     * the selection to the additional info controller.
+     *
+     * @param index the index in the list
+     * @param smartToggle <code>true</code> if the smart toogle key has been pressed
+     * @since 2.1
+     */
+    private void selectProposal(int index, bool smartToggle) {
+
+        ICompletionProposal oldProposal= getSelectedProposal();
+        if (oldProposal instanceof ICompletionProposalExtension2)
+            ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
+
+        ICompletionProposal proposal= fFilteredProposals[index];
+        if (proposal instanceof ICompletionProposalExtension2)
+            ((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle);
+
+        fLastProposal= proposal;
+
+        fProposalTable.setSelection(index);
+        fProposalTable.showSelection();
+        if (fAdditionalInfoController !is null)
+            fAdditionalInfoController.handleTableSelectionChanged();
+    }
+
+    /**
+     * Returns whether the given character is contained in the given array of
+     * characters.
+     *
+     * @param characters the list of characters
+     * @param c the character to look for in the list
+     * @return <code>true</code> if character belongs to the list
+     * @since 2.0
+     */
+    private bool contains(char[] characters, char c) {
+
+        if (characters is null)
+            return false;
+
+        for (int i= 0; i < characters.length; i++) {
+            if (c is characters[i])
+                return true;
+        }
+
+        return false;
+    }
+
+    /*
+     * @see IEventConsumer#processEvent(VerifyEvent)
+     */
+    public void processEvent(VerifyEvent e) {
+    }
+
+    /**
+     * Filters the displayed proposal based on the given cursor position and the
+     * offset of the original invocation of the content assistant.
+     */
+    private void filterProposals() {
+        ++ fInvocationCounter;
+        Control control= fViewer.getTextWidget();
+        control.getDisplay().asyncExec(new Runnable() {
+            long fCounter= fInvocationCounter;
+            public void run() {
+
+                if (fCounter !is fInvocationCounter) return;
+
+                int offset= fViewer.getSelectedRange().x;
+                ICompletionProposal[] proposals= null;
+                try  {
+                    if (offset > -1) {
+                        DocumentEvent event= TextUtilities.mergeProcessedDocumentEvents(fDocumentEvents);
+                        proposals= computeFilteredProposals(offset, event);
+                    }
+                } catch (BadLocationException x)  {
+                } finally  {
+                    fDocumentEvents.clear();
+                }
+                fFilterOffset= offset;
+
+                if (proposals !is null && proposals.length > 0)
+                    setProposals(proposals);
+                else
+                    hide();
+            }
+        });
+    }
+
+    /**
+     * Computes the subset of already computed propsals that are still valid for
+     * the given offset.
+     *
+     * @param offset the offset
+     * @param event the merged document event
+     * @return the set of filtered proposals
+     * @since 2.0
+     */
+    private ICompletionProposal[] computeFilteredProposals(int offset, DocumentEvent event) {
+
+        if (offset is fInvocationOffset && event is null)
+            return fComputedProposals;
+
+        if (offset < fInvocationOffset) {
+            return null;
+        }
+
+        ICompletionProposal[] proposals= fComputedProposals;
+        if (offset > fFilterOffset)
+            proposals= fFilteredProposals;
+
+        if (proposals is null)
+            return null;
+
+        IDocument document= fViewer.getDocument();
+        int length= proposals.length;
+        List filtered= new ArrayList(length);
+        for (int i= 0; i < length; i++) {
+
+            if (proposals[i] instanceof ICompletionProposalExtension2) {
+
+                ICompletionProposalExtension2 p= (ICompletionProposalExtension2) proposals[i];
+                if (p.validate(document, offset, event))
+                    filtered.add(p);
+
+            } else if (proposals[i] instanceof ICompletionProposalExtension) {
+
+                ICompletionProposalExtension p= (ICompletionProposalExtension) proposals[i];
+                if (p.isValidFor(document, offset))
+                    filtered.add(p);
+
+            } else {
+                // restore original behavior
+                fInvocationOffset= offset;
+                fComputedProposals= computeProposals(fInvocationOffset);
+                return fComputedProposals;
+            }
+        }
+
+        ICompletionProposal[] p= new ICompletionProposal[filtered.size()];
+        filtered.toArray(p);
+        return p;
+    }
+
+    /**
+     * Requests the proposal shell to take focus.
+     *
+     * @since 3.0
+     */
+    public void setFocus() {
+        if (Helper2.okToUse(fProposalShell))
+            fProposalShell.setFocus();
+    }
+}