diff dwtx/jface/internal/text/link/contentassist/ContextInformationPopup2.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/ContextInformationPopup2.d	Sat Aug 23 19:10:48 2008 +0200
@@ -0,0 +1,641 @@
+/*******************************************************************************
+ * 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+
+module dwtx.jface.internal.text.link.contentassist.ContextInformationPopup2;
+
+import dwt.dwthelper.utils;
+
+
+import java.util.Stack;
+
+import dwt.DWT;
+import dwt.custom.BusyIndicator;
+import dwt.custom.StyledText;
+import dwt.events.KeyEvent;
+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.Display;
+import dwt.widgets.Shell;
+import dwt.widgets.Table;
+import dwt.widgets.TableItem;
+import dwtx.jface.text.ITextViewer;
+import dwtx.jface.text.TextPresentation;
+import dwtx.jface.text.contentassist.IContextInformation;
+import dwtx.jface.text.contentassist.IContextInformationExtension;
+import dwtx.jface.text.contentassist.IContextInformationPresenter;
+import dwtx.jface.text.contentassist.IContextInformationValidator;
+
+
+/**
+ * This class is used to present context information to the user.
+ * If multiple contexts are valid at the current cursor location,
+ * a list is presented from which the user may choose one context.
+ * Once the user makes their choice, or if there was only a single
+ * possible context, the context information is shown in a tooltip like popup. <p>
+ * If the tooltip is visible and the user wants to see context information of
+ * a context embedded into the one for which context information is displayed,
+ * context information for the embedded context is shown. As soon as the
+ * cursor leaves the embedded context area, the context information for
+ * the embedding context is shown again.
+ *
+ * @see IContextInformation
+ * @see IContextInformationValidator
+ */
+class ContextInformationPopup2 : IContentAssistListener2 {
+
+
+
+    /**
+     * Represents the state necessary for embedding contexts.
+     * @since 2.0
+     */
+    static class ContextFrame {
+        public int fBeginOffset;
+        public int fOffset;
+        public int fVisibleOffset;
+        public IContextInformation fInformation;
+        public IContextInformationValidator fValidator;
+        public IContextInformationPresenter fPresenter;
+    }
+
+    private ITextViewer fViewer;
+    private ContentAssistant2 fContentAssistant;
+
+    private PopupCloser2 fPopupCloser= new PopupCloser2();
+    private Shell fContextSelectorShell;
+    private Table fContextSelectorTable;
+    private IContextInformation[] fContextSelectorInput;
+    private String fLineDelimiter= null;
+
+    private Shell fContextInfoPopup;
+    private StyledText fContextInfoText;
+    private TextPresentation fTextPresentation;
+
+    private Stack fContextFrameStack= new Stack();
+
+
+    /**
+     * Creates a new context information popup.
+     *
+     * @param contentAssistant the content assist for computing the context information
+     * @param viewer the viewer on top of which the context information is shown
+     */
+    public ContextInformationPopup2(ContentAssistant2 contentAssistant, ITextViewer viewer) {
+        fContentAssistant= contentAssistant;
+        fViewer= viewer;
+    }
+
+    /**
+     * Shows all possible contexts for the given cursor position of the viewer.
+     *
+     * @param autoActivated <code>true</code>  if auto activated
+     * @return  a potential error message or <code>null</code> in case of no error
+     */
+    public String showContextProposals(final bool autoActivated) {
+        final StyledText styledText= fViewer.getTextWidget();
+        BusyIndicator.showWhile(styledText.getDisplay(), new Runnable() {
+            public void run() {
+
+                int position= fViewer.getSelectedRange().x;
+
+                IContextInformation[] contexts= computeContextInformation(position);
+                int count = (contexts is null ? 0 : contexts.length);
+                if (count is 1) {
+
+                    // Show context information directly
+                    internalShowContextInfo(contexts[0], position);
+
+                } else if (count > 0) {
+                    // Precise context must be selected
+
+                    if (fLineDelimiter is null)
+                        fLineDelimiter= styledText.getLineDelimiter();
+
+                    createContextSelector();
+                    setContexts(contexts);
+                    displayContextSelector();
+                    hideContextInfoPopup();
+
+                } else if (!autoActivated) {
+                    styledText.getDisplay().beep();
+                }
+            }
+        });
+
+        return getErrorMessage();
+    }
+
+    /**
+     * Displays the given context information for the given offset.
+     *
+     * @param info the context information
+     * @param position the offset
+     * @since 2.0
+     */
+    public void showContextInformation(final IContextInformation info, final int position) {
+        Control control= fViewer.getTextWidget();
+        BusyIndicator.showWhile(control.getDisplay(), new Runnable() {
+            public void run() {
+                internalShowContextInfo(info, position);
+                hideContextSelector();
+            }
+        });
+    }
+
+    /**
+     * Displays the given context information for the given offset.
+     *
+     * @param information the context information
+     * @param offset the offset
+     * @since 2.0
+     */
+
+    private void internalShowContextInfo(IContextInformation information, int offset) {
+
+        IContextInformationValidator validator= fContentAssistant.getContextInformationValidator(fViewer, offset);
+
+        if (validator !is null) {
+            ContextFrame current= new ContextFrame();
+            current.fInformation= information;
+            current.fBeginOffset= (information instanceof IContextInformationExtension) ? ((IContextInformationExtension) information).getContextInformationPosition() : offset;
+            if (current.fBeginOffset is -1) current.fBeginOffset= offset;
+            current.fOffset= offset;
+            current.fVisibleOffset= fViewer.getTextWidget().getSelectionRange().x - (offset - current.fBeginOffset);
+            current.fValidator= validator;
+            current.fPresenter= fContentAssistant.getContextInformationPresenter(fViewer, offset);
+
+            fContextFrameStack.push(current);
+
+            internalShowContextFrame(current, fContextFrameStack.size() is 1);
+        }
+    }
+
+    /**
+     * Shows the given context frame.
+     *
+     * @param frame the frane to display
+     * @param initial <code>true</code> if this is the first frame to be displayed
+     * @since 2.0
+     */
+    private void internalShowContextFrame(ContextFrame frame, bool initial) {
+
+        frame.fValidator.install(frame.fInformation, fViewer, frame.fOffset);
+
+        if (frame.fPresenter !is null) {
+            if (fTextPresentation is null)
+                fTextPresentation= new TextPresentation();
+            frame.fPresenter.install(frame.fInformation, fViewer, frame.fBeginOffset);
+            frame.fPresenter.updatePresentation(frame.fOffset, fTextPresentation);
+        }
+
+        createContextInfoPopup();
+
+        fContextInfoText.setText(frame.fInformation.getInformationDisplayString());
+        if (fTextPresentation !is null)
+            TextPresentation.applyTextPresentation(fTextPresentation, fContextInfoText);
+        resize();
+
+        if (initial) {
+            if (fContentAssistant.addContentAssistListener(this, ContentAssistant2.CONTEXT_INFO_POPUP)) {
+                fContentAssistant.addToLayout(this, fContextInfoPopup, ContentAssistant2.LayoutManager.LAYOUT_CONTEXT_INFO_POPUP, frame.fVisibleOffset);
+                fContextInfoPopup.setVisible(true);
+            }
+        } else {
+            fContentAssistant.layout(ContentAssistant2.LayoutManager.LAYOUT_CONTEXT_INFO_POPUP, frame.fVisibleOffset);
+        }
+    }
+
+    /**
+     * Computes all possible context information for the given offset.
+     *
+     * @param position the offset
+     * @return all possible context information for the given offset
+     * @since 2.0
+     */
+    private IContextInformation[] computeContextInformation(int position) {
+        return fContentAssistant.computeContextInformation(fViewer, position);
+    }
+
+    /**
+     *Returns the error message generated while computing context information.
+     *
+     * @return the error message
+     */
+    private String getErrorMessage() {
+        return fContentAssistant.getErrorMessage();
+    }
+
+    /**
+     * Creates the context information popup. This is the tooltip like overlay window.
+     */
+    private void createContextInfoPopup() {
+        if (Helper2.okToUse(fContextInfoPopup))
+            return;
+
+        Control control= fViewer.getTextWidget();
+        Display display= control.getDisplay();
+
+        fContextInfoPopup= new Shell(control.getShell(), DWT.NO_TRIM | DWT.ON_TOP);
+        fContextInfoPopup.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
+
+        fContextInfoText= new StyledText(fContextInfoPopup, DWT.MULTI | DWT.READ_ONLY);
+
+        Color c= fContentAssistant.getContextInformationPopupBackground();
+        if (c is null)
+            c= display.getSystemColor(DWT.COLOR_INFO_BACKGROUND);
+        fContextInfoText.setBackground(c);
+
+        c= fContentAssistant.getContextInformationPopupForeground();
+        if (c is null)
+            c= display.getSystemColor(DWT.COLOR_INFO_FOREGROUND);
+        fContextInfoText.setForeground(c);
+    }
+
+    /**
+     * Resizes the context information popup.
+     * @since 2.0
+     */
+    private void resize() {
+        Point size= fContextInfoText.computeSize(DWT.DEFAULT, DWT.DEFAULT, true);
+        size.x += 3;
+        fContextInfoText.setSize(size);
+        fContextInfoText.setLocation(1,1);
+        size.x += 2;
+        size.y += 2;
+        fContextInfoPopup.setSize(size);
+    }
+
+    /**
+     *Hides the context information popup.
+     */
+    private void hideContextInfoPopup() {
+
+        if (Helper2.okToUse(fContextInfoPopup)) {
+
+            int size= fContextFrameStack.size();
+            if (size > 0) {
+                fContextFrameStack.pop();
+                -- size;
+            }
+
+            if (size > 0) {
+                ContextFrame current= (ContextFrame) fContextFrameStack.peek();
+                internalShowContextFrame(current, false);
+            } else {
+
+                fContentAssistant.removeContentAssistListener(this, ContentAssistant2.CONTEXT_INFO_POPUP);
+
+                fContextInfoPopup.setVisible(false);
+                fContextInfoPopup.dispose();
+                fContextInfoPopup= null;
+
+                if (fTextPresentation !is null) {
+                    fTextPresentation.clear();
+                    fTextPresentation= null;
+                }
+            }
+        }
+
+        if (fContextInfoPopup is null)
+            fContentAssistant.contextInformationClosed();
+    }
+
+    /**
+     * Creates the context selector in case the user has the choice between multiple valid contexts
+     * at a given offset.
+     */
+    private void createContextSelector() {
+        if (Helper2.okToUse(fContextSelectorShell))
+            return;
+
+        Control control= fViewer.getTextWidget();
+        fContextSelectorShell= new Shell(control.getShell(), DWT.NO_TRIM | DWT.ON_TOP);
+        GridLayout layout= new GridLayout();
+        layout.marginWidth= 0;
+        layout.marginHeight= 0;
+        fContextSelectorShell.setLayout(layout);
+        fContextSelectorShell.setBackground(control.getDisplay().getSystemColor(DWT.COLOR_BLACK));
+
+
+        fContextSelectorTable= new Table(fContextSelectorShell, DWT.H_SCROLL | DWT.V_SCROLL);
+        fContextSelectorTable.setLocation(1, 1);
+        GridData gd= new GridData(GridData.FILL_BOTH);
+        gd.heightHint= fContextSelectorTable.getItemHeight() * 10;
+        gd.widthHint= 300;
+        fContextSelectorTable.setLayoutData(gd);
+
+        fContextSelectorShell.pack(true);
+
+        Color c= fContentAssistant.getContextSelectorBackground();
+        if (c is null)
+            c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_BACKGROUND);
+        fContextSelectorTable.setBackground(c);
+
+        c= fContentAssistant.getContextSelectorForeground();
+        if (c is null)
+            c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_FOREGROUND);
+        fContextSelectorTable.setForeground(c);
+
+        fContextSelectorTable.addSelectionListener(new SelectionListener() {
+            public void widgetSelected(SelectionEvent e) {
+            }
+
+            public void widgetDefaultSelected(SelectionEvent e) {
+                insertSelectedContext();
+                hideContextSelector();
+            }
+        });
+
+        fPopupCloser.install(fContentAssistant, fContextSelectorTable);
+
+        fContextSelectorTable.setHeaderVisible(false);
+        fContentAssistant.addToLayout(this, fContextSelectorShell, ContentAssistant2.LayoutManager.LAYOUT_CONTEXT_SELECTOR, fContentAssistant.getSelectionOffset());
+    }
+
+    /**
+     * Causes the context information of the context selected in the context selector
+     * to be displayed in the context information popup.
+     */
+    private void insertSelectedContext() {
+        int i= fContextSelectorTable.getSelectionIndex();
+
+        if (i < 0 || i >= fContextSelectorInput.length)
+            return;
+
+        int position= fViewer.getSelectedRange().x;
+        internalShowContextInfo(fContextSelectorInput[i], position);
+    }
+
+    /**
+     * Sets the contexts in the context selector to the given set.
+     *
+     * @param contexts the possible contexts
+     */
+    private void setContexts(IContextInformation[] contexts) {
+        if (Helper2.okToUse(fContextSelectorTable)) {
+
+            fContextSelectorInput= contexts;
+
+            fContextSelectorTable.setRedraw(false);
+            fContextSelectorTable.removeAll();
+
+            TableItem item;
+            IContextInformation t;
+            for (int i= 0; i < contexts.length; i++) {
+                t= contexts[i];
+                item= new TableItem(fContextSelectorTable, DWT.NULL);
+                if (t.getImage() !is null)
+                    item.setImage(t.getImage());
+                item.setText(t.getContextDisplayString());
+            }
+
+            fContextSelectorTable.select(0);
+            fContextSelectorTable.setRedraw(true);
+        }
+    }
+
+    /**
+     * Displays the context selector.
+     */
+    private void displayContextSelector() {
+        if (fContentAssistant.addContentAssistListener(this, ContentAssistant2.CONTEXT_SELECTOR))
+            fContextSelectorShell.setVisible(true);
+    }
+
+    /**
+     * Hodes the context selector.
+     */
+    private void hideContextSelector() {
+        if (Helper2.okToUse(fContextSelectorShell)) {
+            fContentAssistant.removeContentAssistListener(this, ContentAssistant2.CONTEXT_SELECTOR);
+
+            fPopupCloser.uninstall();
+            fContextSelectorShell.setVisible(false);
+            fContextSelectorShell.dispose();
+            fContextSelectorShell= null;
+        }
+
+        if (!Helper2.okToUse(fContextInfoPopup))
+            fContentAssistant.contextInformationClosed();
+    }
+
+    /**
+     *Returns whether the context selector has the focus.
+     *
+     * @return <code>true</code> if teh context selector has the focus
+     */
+    public bool hasFocus() {
+        if (Helper2.okToUse(fContextSelectorShell))
+            return (fContextSelectorShell.isFocusControl() || fContextSelectorTable.isFocusControl());
+
+        return false;
+    }
+
+    /**
+     * Hides context selector and context information popup.
+     */
+    public void hide() {
+        hideContextSelector();
+        hideContextInfoPopup();
+    }
+
+    /**
+     * Returns whether this context information popup is active. I.e., either
+     * a context selector or context information is displayed.
+     *
+     * @return <code>true</code> if the context selector is active
+     */
+    public bool isActive() {
+        return (Helper2.okToUse(fContextInfoPopup) || Helper2.okToUse(fContextSelectorShell));
+    }
+
+    /*
+     * @see IContentAssistListener#verifyKey(VerifyEvent)
+     */
+    public bool verifyKey(VerifyEvent e) {
+        if (Helper2.okToUse(fContextSelectorShell))
+            return contextSelectorKeyPressed(e);
+        if (Helper2.okToUse(fContextInfoPopup))
+            return contextInfoPopupKeyPressed(e);
+        return true;
+    }
+
+    /**
+     * Processes a key stroke in the context selector.
+     *
+     * @param e the verify event describing the key stroke
+     * @return <code>true</code> if processing can be stopped
+     */
+    private bool contextSelectorKeyPressed(VerifyEvent e) {
+
+        char key= e.character;
+        if (key is 0) {
+
+            int change;
+            int visibleRows= (fContextSelectorTable.getSize().y / fContextSelectorTable.getItemHeight()) - 1;
+            int selection= fContextSelectorTable.getSelectionIndex();
+
+            switch (e.keyCode) {
+
+                case DWT.ARROW_UP:
+                    change= (fContextSelectorTable.getSelectionIndex() > 0 ? -1 : 0);
+                    break;
+
+                case DWT.ARROW_DOWN:
+                    change= (fContextSelectorTable.getSelectionIndex() < fContextSelectorTable.getItemCount() - 1 ? 1 : 0);
+                    break;
+
+                case DWT.PAGE_DOWN :
+                    change= visibleRows;
+                    if (selection + change >= fContextSelectorTable.getItemCount())
+                        change= fContextSelectorTable.getItemCount() - selection;
+                    break;
+
+                case DWT.PAGE_UP :
+                    change= -visibleRows;
+                    if (selection + change < 0)
+                        change= -selection;
+                    break;
+
+                case DWT.HOME :
+                    change= -selection;
+                    break;
+
+                case DWT.END :
+                    change= fContextSelectorTable.getItemCount() - selection;
+                    break;
+
+                default:
+                    if (e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4)
+                        hideContextSelector();
+                    return true;
+            }
+
+            fContextSelectorTable.setSelection(selection + change);
+            fContextSelectorTable.showSelection();
+            e.doit= false;
+            return false;
+
+        } else if ('\t' is key) {
+            // switch focus to selector shell
+            e.doit= false;
+            fContextSelectorShell.setFocus();
+            return false;
+        } else if (key is DWT.ESC) {
+            e.doit= false;
+            hideContextSelector();
+        }
+
+        return true;
+    }
+
+    /**
+     * Processes a key stroke while the info popup is up.
+     *
+     * @param e the verify event describing the key stroke
+     * @return <code>true</code> if processing can be stopped
+     */
+    private bool contextInfoPopupKeyPressed(KeyEvent e) {
+
+        char key= e.character;
+        if (key is 0) {
+
+            switch (e.keyCode) {
+                case DWT.ARROW_LEFT:
+                case DWT.ARROW_RIGHT:
+                    validateContextInformation();
+                    break;
+                default:
+                    if (e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4)
+                        hideContextInfoPopup();
+                    break;
+            }
+
+        } else if (key is DWT.ESC) {
+            e.doit= false;
+            hideContextInfoPopup();
+        } else {
+            validateContextInformation();
+        }
+        return true;
+    }
+
+    /*
+     * @see IEventConsumer#processEvent(VerifyEvent)
+     */
+    public void processEvent(VerifyEvent event) {
+        if (Helper2.okToUse(fContextSelectorShell))
+            contextSelectorProcessEvent(event);
+        if (Helper2.okToUse(fContextInfoPopup))
+            contextInfoPopupProcessEvent(event);
+    }
+
+    /**
+     * Processes a key stroke in the context selector.
+     *
+     * @param e the verify event describing the key stroke
+     */
+    private void contextSelectorProcessEvent(VerifyEvent e) {
+
+        if (e.start is e.end && e.text !is null && e.text.equals(fLineDelimiter)) {
+            e.doit= false;
+            insertSelectedContext();
+        }
+
+        hideContextSelector();
+    }
+
+    /**
+     * Processes a key stroke while the info popup is up.
+     *
+     * @param e the verify event describing the key stroke
+     */
+    private void contextInfoPopupProcessEvent(VerifyEvent e) {
+        if (e.start !is e.end && (e.text is null || e.text.length() is 0))
+            validateContextInformation();
+    }
+
+    /**
+     * Validates the context information for the viewer's actual cursor position.
+     */
+    private void validateContextInformation() {
+        /*
+         * Post the code in the event queue in order to ensure that the
+         * action described by this verify key event has already beed executed.
+         * Otherwise, we'd validate the context information based on the
+         * pre-key-stroke state.
+         */
+        fContextInfoPopup.getDisplay().asyncExec(new Runnable() {
+
+            private ContextFrame fFrame= (ContextFrame) fContextFrameStack.peek();
+
+            public void run() {
+                if (Helper2.okToUse(fContextInfoPopup) && fFrame is fContextFrameStack.peek()) {
+                    int offset= fViewer.getSelectedRange().x;
+                    if (fFrame.fValidator is null || !fFrame.fValidator.isContextInformationValid(offset)) {
+                        hideContextInfoPopup();
+                    } else if (fFrame.fPresenter !is null && fFrame.fPresenter.updatePresentation(offset, fTextPresentation)) {
+                        TextPresentation.applyTextPresentation(fTextPresentation, fContextInfoText);
+                        resize();
+                    }
+                }
+            }
+        });
+    }
+}