diff dwtx/jface/text/contentassist/AdditionalInfoController.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/text/contentassist/AdditionalInfoController.d	Sat Aug 23 19:10:48 2008 +0200
@@ -0,0 +1,597 @@
+/*******************************************************************************
+ * 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.text.contentassist.AdditionalInfoController;
+
+import dwt.dwthelper.utils;
+
+
+
+
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwt.widgets.Shell;
+import dwt.widgets.Table;
+import dwt.widgets.TableItem;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.IProgressMonitor;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.Status;
+import dwtx.core.runtime.jobs.Job;
+import dwtx.jface.internal.text.InformationControlReplacer;
+import dwtx.jface.text.AbstractInformationControlManager;
+import dwtx.jface.text.AbstractReusableInformationControlCreator;
+import dwtx.jface.text.DefaultInformationControl;
+import dwtx.jface.text.IInformationControl;
+import dwtx.jface.text.IInformationControlCreator;
+import dwtx.jface.text.IInformationControlExtension3;
+
+
+/**
+ * Displays the additional information available for a completion proposal.
+ *
+ * @since 2.0
+ */
+class AdditionalInfoController : AbstractInformationControlManager {
+    
+    /**
+     * A timer thread.
+     * 
+     * @since 3.2
+     */
+    private static abstract class Timer {
+        private static final int DELAY_UNTIL_JOB_IS_SCHEDULED= 50;
+        
+        /**
+         * A <code>Task</code> is {@link Task#run() run} when {@link #delay()} milliseconds have
+         * elapsed after it was scheduled without a {@link #reset(ICompletionProposal) reset}
+         * to occur.
+         */
+        private abstract class Task : Runnable {
+            /**
+             * @return the delay in milliseconds before this task should be run
+             */
+            public abstract long delay();
+            /**
+             * Runs this task.
+             */
+            public abstract void run();
+            /**
+             * @return the task to be scheduled after this task has been run
+             */
+            public abstract Task nextTask();
+        }
+        
+        /**
+         * IDLE: the initial task, and active whenever the info has been shown. It cannot be run,
+         * but specifies an infinite delay.
+         */
+        private final Task IDLE= new Task() {
+            public void run() {
+                Assert.isTrue(false);
+            }
+            
+            public Task nextTask() {
+                Assert.isTrue(false);
+                return null;
+            }
+            
+            public long delay() {
+                return Long.MAX_VALUE;
+            }
+            
+            public String toString() {
+                return "IDLE"; //$NON-NLS-1$
+            }
+        };
+        /**
+         * FIRST_WAIT: Schedules a platform {@link Job} to fetch additional info from an {@link ICompletionProposalExtension5}.
+         */
+        private final Task FIRST_WAIT= new Task() {
+            public void run() {
+                final ICompletionProposalExtension5 proposal= getCurrentProposalEx();
+                Job job= new Job(JFaceTextMessages.getString("AdditionalInfoController.job_name")) { //$NON-NLS-1$
+                    protected IStatus run(IProgressMonitor monitor) {
+                        Object info;
+                        try {
+                            info= proposal.getAdditionalProposalInfo(monitor);
+                        } catch (RuntimeException x) {
+                            /*
+                             * XXX: This is the safest fix at this point so close to end of 3.2.
+                             *      Will be revisited when fixing https://bugs.eclipse.org/bugs/show_bug.cgi?id=101033
+                             */
+                            return new Status(IStatus.WARNING, "dwtx.jface.text", IStatus.OK, "", x); //$NON-NLS-1$ //$NON-NLS-2$
+                        }
+                        setInfo((ICompletionProposal) proposal, info);
+                        return new Status(IStatus.OK, "dwtx.jface.text", IStatus.OK, "", null); //$NON-NLS-1$ //$NON-NLS-2$
+                    }
+                };
+                job.schedule();
+            }
+            
+            public Task nextTask() {
+                return SECOND_WAIT;
+            }
+            
+            public long delay() {
+                return DELAY_UNTIL_JOB_IS_SCHEDULED;
+            }
+            
+            public String toString() {
+                return "FIRST_WAIT"; //$NON-NLS-1$
+            }
+        };
+        /**
+         * SECOND_WAIT: Allows display of additional info obtained from an
+         * {@link ICompletionProposalExtension5}.
+         */
+        private final Task SECOND_WAIT= new Task() {
+            public void run() {
+                // show the info
+                allowShowing();
+            }
+            
+            public Task nextTask() {
+                return IDLE;
+            }
+            
+            public long delay() {
+                return fDelay - DELAY_UNTIL_JOB_IS_SCHEDULED;
+            }
+            
+            public String toString() {
+                return "SECOND_WAIT"; //$NON-NLS-1$
+            }
+        };
+        /**
+         * LEGACY_WAIT: Posts a runnable into the display thread to fetch additional info from non-{@link ICompletionProposalExtension5}s.
+         */
+        private final Task LEGACY_WAIT= new Task() {
+            public void run() {
+                final ICompletionProposal proposal= getCurrentProposal();
+                if (!fDisplay.isDisposed()) {
+                    fDisplay.asyncExec(new Runnable() {
+                        public void run() {
+                            synchronized (Timer.this) {
+                                if (proposal is getCurrentProposal()) {
+                                    Object info= proposal.getAdditionalProposalInfo();
+                                    showInformation(proposal, info);
+                                }
+                            }
+                        }
+                    });
+                }
+            }
+            
+            public Task nextTask() {
+                return IDLE;
+            }
+            
+            public long delay() {
+                return fDelay;
+            }
+            
+            public String toString() {
+                return "LEGACY_WAIT"; //$NON-NLS-1$
+            }
+        };
+        /**
+         * EXIT: The task that triggers termination of the timer thread.
+         */
+        private final Task EXIT= new Task() {
+            public long delay() {
+                return 1;
+            }
+
+            public Task nextTask() {
+                Assert.isTrue(false);
+                return EXIT;
+            }
+
+            public void run() {
+                Assert.isTrue(false);
+            }
+            
+            public String toString() {
+                return "EXIT"; //$NON-NLS-1$
+            }
+        };
+        
+        /** The timer thread. */
+        private final Thread fThread;
+
+        /** The currently waiting / active task. */
+        private Task fTask;
+        /** The next wake up time. */
+        private long fNextWakeup;
+        
+        private ICompletionProposal fCurrentProposal= null;
+        private Object fCurrentInfo= null;
+        private bool fAllowShowing= false;
+
+        private final Display fDisplay;
+        private final int fDelay;
+        
+        /**
+         * Creates a new timer.
+         * 
+         * @param display the display to use for display thread posting.
+         * @param delay the delay until to show additional info
+         */
+        public Timer(Display display, int delay) {
+            fDisplay= display;
+            fDelay= delay;
+            long current= System.currentTimeMillis();
+            schedule(IDLE, current);
+
+            fThread= new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        loop();
+                    } catch (InterruptedException x) {
+                    }
+                }
+            }, JFaceTextMessages.getString("InfoPopup.info_delay_timer_name")); //$NON-NLS-1$
+            fThread.start();
+        }
+        
+        /**
+         * Terminates the timer thread.
+         */
+        public synchronized final void terminate() {
+            schedule(EXIT, System.currentTimeMillis());
+            notifyAll();
+        }
+        
+        /**
+         * Resets the timer thread as the selection has changed to a new proposal.
+         * 
+         * @param p the new proposal
+         */
+        public final synchronized void reset(ICompletionProposal p) {
+            if (fCurrentProposal !is p) {
+                fCurrentProposal= p;
+                fCurrentInfo= null;
+                fAllowShowing= false;
+
+                long oldWakeup= fNextWakeup;
+                Task task= taskOnReset(p);
+                schedule(task, System.currentTimeMillis());
+                if (fNextWakeup < oldWakeup)
+                    notifyAll();
+            }
+        }
+        
+        private Task taskOnReset(ICompletionProposal p) {
+            if (p is null)
+                return IDLE;
+            if (isExt5(p))
+                return FIRST_WAIT;
+            return LEGACY_WAIT;
+        }
+
+        private synchronized void loop() throws InterruptedException {
+            long current= System.currentTimeMillis();
+            Task task= currentTask();
+
+            while (task !is EXIT) {
+                long delay= fNextWakeup - current;
+                if (delay <= 0) {
+                    task.run();
+                    task= task.nextTask();
+                    schedule(task, current);
+                } else {
+                    wait(delay);
+                    current= System.currentTimeMillis();
+                    task= currentTask();
+                }
+            }
+        }
+        
+        private Task currentTask() {
+            return fTask;
+        }
+        
+        private void schedule(Task task, long current) {
+            fTask= task;
+            long nextWakeup= current + task.delay();
+            if (nextWakeup <= current)
+                fNextWakeup= Long.MAX_VALUE;
+            else
+                fNextWakeup= nextWakeup;
+        }
+
+        private bool isExt5(ICompletionProposal p) {
+            return p instanceof ICompletionProposalExtension5;
+        }
+
+        ICompletionProposal getCurrentProposal() {
+            return fCurrentProposal;
+        }
+        
+        ICompletionProposalExtension5 getCurrentProposalEx() {
+            Assert.isTrue(fCurrentProposal instanceof ICompletionProposalExtension5);
+            return (ICompletionProposalExtension5) fCurrentProposal;
+        }
+
+        synchronized void setInfo(ICompletionProposal proposal, Object info) {
+            if (proposal is fCurrentProposal) {
+                fCurrentInfo= info;
+                if (fAllowShowing) {
+                    triggerShowing();
+                }
+            }
+        }
+
+        private void triggerShowing() {
+            final Object info= fCurrentInfo;
+            if (!fDisplay.isDisposed()) {
+                fDisplay.asyncExec(new Runnable() {
+                    public void run() {
+                        synchronized (Timer.this) {
+                            if (info is fCurrentInfo) {
+                                showInformation(fCurrentProposal, info);
+                            }
+                        }
+                    }
+                });
+            }
+        }
+        
+        /**
+         * Called in the display thread to show additional info.
+         * 
+         * @param proposal the proposal to show information about
+         * @param info the information about <code>proposal</code>
+         */
+        protected abstract void showInformation(ICompletionProposal proposal, Object info);
+
+        void allowShowing() {
+            fAllowShowing= true;
+            triggerShowing();
+        }
+    }
+    /**
+     * Internal table selection listener.
+     */
+    private class TableSelectionListener : SelectionListener {
+
+        /*
+         * @see SelectionListener#widgetSelected(SelectionEvent)
+         */
+        public void widgetSelected(SelectionEvent e) {
+            handleTableSelectionChanged();
+        }
+
+        /*
+         * @see SelectionListener#widgetDefaultSelected(SelectionEvent)
+         */
+        public void widgetDefaultSelected(SelectionEvent e) {
+        }
+    }
+    
+    /**
+     * Default control creator for the information control replacer.
+     * @since 3.4
+     */
+    private static class DefaultPresenterControlCreator : AbstractReusableInformationControlCreator {
+        public IInformationControl doCreateInformationControl(Shell shell) {
+            return new DefaultInformationControl(shell, true);
+        }
+    }
+
+    /** The proposal table. */
+    private Table fProposalTable;
+    /** The table selection listener */
+    private SelectionListener fSelectionListener= new TableSelectionListener();
+    /** The delay after which additional information is displayed */
+    private final int fDelay;
+    /**
+     * The timer thread.
+     * @since 3.2
+     */
+    private Timer fTimer;
+    /**
+     * The proposal most recently set by {@link #showInformation(ICompletionProposal, Object)},
+     * possibly <code>null</code>.
+     * @since 3.2
+     */
+    private ICompletionProposal fProposal;
+    /**
+     * The information most recently set by {@link #showInformation(ICompletionProposal, Object)},
+     * possibly <code>null</code>.
+     * @since 3.2
+     */
+    private Object fInformation;
+
+    /**
+     * Creates a new additional information controller.
+     *
+     * @param creator the information control creator to be used by this controller
+     * @param delay time in milliseconds after which additional info should be displayed
+     */
+    AdditionalInfoController(IInformationControlCreator creator, int delay) {
+        super(creator);
+        fDelay= delay;
+        setAnchor(ANCHOR_RIGHT);
+        setFallbackAnchors(new Anchor[] { ANCHOR_RIGHT, ANCHOR_LEFT, ANCHOR_BOTTOM });
+        
+        /*
+         * Adjust the location by one pixel towards the proposal popup, so that the single pixel
+         * border of the additional info popup overlays with the border of the popup. This avoids
+         * having a double black line.
+         */
+        int spacing= -1;
+        setMargins(spacing, spacing); // see also adjustment in #computeLocation
+        
+        InformationControlReplacer replacer= new InformationControlReplacer(new DefaultPresenterControlCreator());
+        getInternalAccessor().setInformationControlReplacer(replacer);
+    }
+
+    /*
+     * @see AbstractInformationControlManager#install(Control)
+     */
+    public void install(Control control) {
+
+        if (fProposalTable is control) {
+            // already installed
+            return;
+        }
+
+        super.install(control.getShell());
+
+        Assert.isTrue(control instanceof Table);
+        fProposalTable= (Table) control;
+        fProposalTable.addSelectionListener(fSelectionListener);
+        getInternalAccessor().getInformationControlReplacer().install(fProposalTable);
+        
+        fTimer= new Timer(fProposalTable.getDisplay(), fDelay) {
+            protected void showInformation(ICompletionProposal proposal, Object info) {
+                InformationControlReplacer replacer= getInternalAccessor().getInformationControlReplacer();
+                if (replacer !is null)
+                    replacer.hideInformationControl();
+                AdditionalInfoController.this.showInformation(proposal, info);
+            }
+        };
+    }
+
+    /*
+     * @see AbstractInformationControlManager#disposeInformationControl()
+     */
+    public void disposeInformationControl() {
+
+        if (fTimer !is null) {
+            fTimer.terminate();
+            fTimer= null;
+        }
+
+        fProposal= null;
+        fInformation= null;
+
+        if (fProposalTable !is null && !fProposalTable.isDisposed()) {
+            fProposalTable.removeSelectionListener(fSelectionListener);
+            fProposalTable= null;
+        }
+
+        super.disposeInformationControl();
+    }
+
+    /**
+     *Handles a change of the line selected in the associated selector.
+     */
+    public void handleTableSelectionChanged() {
+
+        if (fProposalTable !is null && !fProposalTable.isDisposed() && fProposalTable.isVisible()) {
+            TableItem[] selection= fProposalTable.getSelection();
+            if (selection !is null && selection.length > 0) {
+
+                TableItem item= selection[0];
+
+                Object d= item.getData();
+                if (d instanceof ICompletionProposal) {
+                    ICompletionProposal p= (ICompletionProposal) d;
+                    fTimer.reset(p);
+                }
+            }
+        }
+    }
+
+    void showInformation(ICompletionProposal proposal, Object info) {
+        if (fProposalTable is null || fProposalTable.isDisposed())
+            return;
+        
+        if (fProposal is proposal && ((info is null && fInformation is null) || (info !is null && info.equals(fInformation))))
+            return;
+        
+        fInformation= info;
+        fProposal= proposal;
+        showInformation();
+    }
+
+    /*
+     * @see AbstractInformationControlManager#computeInformation()
+     */
+    protected void computeInformation() {
+        if (fProposal instanceof ICompletionProposalExtension3)
+            setCustomInformationControlCreator(((ICompletionProposalExtension3) fProposal).getInformationControlCreator());
+        else
+            setCustomInformationControlCreator(null);
+
+        // compute subject area
+        Point size= fProposalTable.getShell().getSize();
+
+        // set information & subject area
+        setInformation(fInformation, new Rectangle(0, 0, size.x, size.y));
+    }
+
+    /*
+     * @see dwtx.jface.text.AbstractInformationControlManager#computeLocation(dwt.graphics.Rectangle, dwt.graphics.Point, dwtx.jface.text.AbstractInformationControlManager.Anchor)
+     */
+    protected Point computeLocation(Rectangle subjectArea, Point controlSize, Anchor anchor) {
+        Point location= super.computeLocation(subjectArea, controlSize, anchor);
+        
+        /*
+         * The location is computed using subjectControl.toDisplay(), which does not include the
+         * trim of the subject control. As we want the additional info popup aligned with the outer
+         * coordinates of the proposal popup, adjust this here
+         */
+        Rectangle trim= fProposalTable.getShell().computeTrim(0, 0, 0, 0);
+        location.x += trim.x;
+        location.y += trim.y;
+
+        return location;
+    }
+
+    /*
+     * @see dwtx.jface.text.AbstractInformationControlManager#computeSizeConstraints(Control, IInformationControl)
+     */
+    protected Point computeSizeConstraints(Control subjectControl, IInformationControl informationControl) {
+        // at least as big as the proposal table
+        Point sizeConstraint= super.computeSizeConstraints(subjectControl, informationControl);
+        Point size= subjectControl.getShell().getSize();
+        
+        // AbstractInformationControlManager#internalShowInformationControl(Rectangle, Object) adds trims
+        // to the computed constraints. Need to remove them here, to make the outer bounds of the additional
+        // info shell fit the bounds of the proposal shell:
+        if (fInformationControl instanceof IInformationControlExtension3) {
+            Rectangle shellTrim= ((IInformationControlExtension3) fInformationControl).computeTrim();
+            size.x -= shellTrim.width;
+            size.y -= shellTrim.height;
+        }
+
+        if (sizeConstraint.x < size.x)
+            sizeConstraint.x= size.x;
+        if (sizeConstraint.y < size.y)
+            sizeConstraint.y= size.y;
+        return sizeConstraint;
+    }
+    
+    /*
+     * @see dwtx.jface.text.AbstractInformationControlManager#hideInformationControl()
+     */
+    protected void hideInformationControl() {
+        super.hideInformationControl();
+    }
+
+    /**
+     * @return the current information control, or <code>null</code> if none available
+     */
+    public IInformationControl getCurrentInformationControl2() {
+        return getInternalAccessor().getCurrentInformationControl();
+    }
+}
+
+