diff dwtx/jface/text/TextViewerHoverManager.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/TextViewerHoverManager.d	Sat Aug 23 19:10:48 2008 +0200
@@ -0,0 +1,396 @@
+/*******************************************************************************
+ * 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.TextViewerHoverManager;
+
+import dwt.dwthelper.utils;
+
+
+
+
+import dwt.custom.StyledText;
+import dwt.events.MouseEvent;
+import dwt.events.MouseMoveListener;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.widgets.Display;
+import dwtx.core.runtime.ILog;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.Platform;
+import dwtx.core.runtime.Status;
+
+
+/**
+ * This manager controls the layout, content, and visibility of an information
+ * control in reaction to mouse hover events issued by the text widget of a
+ * text viewer. It overrides <code>computeInformation</code>, so that the
+ * computation is performed in a dedicated background thread. This implies
+ * that the used <code>ITextHover</code> objects must be capable of
+ * operating in a non-UI thread.
+ *
+ * @since 2.0
+ */
+class TextViewerHoverManager : AbstractHoverInformationControlManager , IWidgetTokenKeeper, IWidgetTokenKeeperExtension {
+
+
+    /**
+     * Priority of the hovers managed by this manager.
+     * Default value: <code>0</code>;
+     * @since 3.0
+     */
+    public final static int WIDGET_PRIORITY= 0;
+
+
+    /** The text viewer */
+    private TextViewer fTextViewer;
+    /** The hover information computation thread */
+    private Thread fThread;
+    /** The stopper of the computation thread */
+    private ITextListener fStopper;
+    /** Internal monitor */
+    private Object fMutex= new Object();
+    /** The currently shown text hover. */
+    private volatile ITextHover fTextHover;
+    /**
+     * Tells whether the next mouse hover event
+     * should be processed.
+     * @since 3.0
+     */
+    private bool fProcessMouseHoverEvent= true;
+    /**
+     * Internal mouse move listener.
+     * @since 3.0
+     */
+    private MouseMoveListener fMouseMoveListener;
+    /**
+     * Internal view port listener.
+     * @since 3.0
+     */
+    private IViewportListener fViewportListener;
+
+
+    /**
+     * Creates a new text viewer hover manager specific for the given text viewer.
+     * The manager uses the given information control creator.
+     *
+     * @param textViewer the viewer for which the controller is created
+     * @param creator the information control creator
+     */
+    public TextViewerHoverManager(TextViewer textViewer, IInformationControlCreator creator) {
+        super(creator);
+        fTextViewer= textViewer;
+        fStopper= new ITextListener() {
+            public void textChanged(TextEvent event) {
+                synchronized (fMutex) {
+                    if (fThread !is null) {
+                        fThread.interrupt();
+                        fThread= null;
+                    }
+                }
+            }
+        };
+        fViewportListener= new IViewportListener() {
+            /*
+             * @see dwtx.jface.text.IViewportListener#viewportChanged(int)
+             */
+            public void viewportChanged(int verticalOffset) {
+                fProcessMouseHoverEvent= false;
+            }
+        };
+        fTextViewer.addViewportListener(fViewportListener);
+        fMouseMoveListener= new MouseMoveListener() {
+            /*
+             * @see MouseMoveListener#mouseMove(MouseEvent)
+             */
+            public void mouseMove(MouseEvent event) {
+                fProcessMouseHoverEvent= true;
+            }
+        };
+        fTextViewer.getTextWidget().addMouseMoveListener(fMouseMoveListener);
+    }
+
+    /**
+     * Determines all necessary details and delegates the computation into
+     * a background thread.
+     */
+    protected void computeInformation() {
+
+        if (!fProcessMouseHoverEvent) {
+            setInformation(null, null);
+            return;
+        }
+
+        Point location= getHoverEventLocation();
+        int offset= computeOffsetAtLocation(location.x, location.y);
+        if (offset is -1) {
+            setInformation(null, null);
+            return;
+        }
+
+        final ITextHover hover= fTextViewer.getTextHover(offset, getHoverEventStateMask());
+        if (hover is null) {
+            setInformation(null, null);
+            return;
+        }
+
+        final IRegion region= hover.getHoverRegion(fTextViewer, offset);
+        if (region is null) {
+            setInformation(null, null);
+            return;
+        }
+
+        final Rectangle area= JFaceTextUtil.computeArea(region, fTextViewer);
+        if (area is null || area.isEmpty()) {
+            setInformation(null, null);
+            return;
+        }
+
+        if (fThread !is null) {
+            setInformation(null, null);
+            return;
+        }
+
+        fThread= new Thread("Text Viewer Hover Presenter") { //$NON-NLS-1$
+            public void run() {
+                // http://bugs.eclipse.org/bugs/show_bug.cgi?id=17693
+                bool hasFinished= false;
+                try {
+                    if (fThread !is null) {
+                        Object information;
+                        try {
+                            if (hover instanceof ITextHoverExtension2)
+                                information= ((ITextHoverExtension2)hover).getHoverInfo2(fTextViewer, region);
+                            else
+                                information= hover.getHoverInfo(fTextViewer, region);
+                        } catch (ArrayIndexOutOfBoundsException x) {
+                            /*
+                             * This code runs in a separate thread which can
+                             * lead to text offsets being out of bounds when
+                             * computing the hover info (see bug 32848).
+                             */
+                            information= null;
+                        }
+
+                        if (hover instanceof ITextHoverExtension)
+                            setCustomInformationControlCreator(((ITextHoverExtension) hover).getHoverControlCreator());
+                        else
+                            setCustomInformationControlCreator(null);
+
+                        setInformation(information, area);
+                        if (information !is null)
+                            fTextHover= hover;
+                    } else {
+                        setInformation(null, null);
+                    }
+                    hasFinished= true;
+                } catch (RuntimeException ex) {
+                    String PLUGIN_ID= "dwtx.jface.text"; //$NON-NLS-1$
+                    ILog log= Platform.getLog(Platform.getBundle(PLUGIN_ID));
+                    log.log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, "Unexpected runtime error while computing a text hover", ex)); //$NON-NLS-1$
+                } finally {
+                    synchronized (fMutex) {
+                        if (fTextViewer !is null)
+                            fTextViewer.removeTextListener(fStopper);
+                        fThread= null;
+                        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=44756
+                        if (!hasFinished)
+                            setInformation(null, null);
+                    }
+                }
+            }
+        };
+
+        fThread.setDaemon(true);
+        fThread.setPriority(Thread.MIN_PRIORITY);
+        synchronized (fMutex) {
+            fTextViewer.addTextListener(fStopper);
+            fThread.start();
+        }
+    }
+
+    /**
+     * As computation is done in the background, this method is
+     * also called in the background thread. Delegates the control
+     * flow back into the UI thread, in order to allow displaying the
+     * information in the information control.
+     */
+    protected void presentInformation() {
+        if (fTextViewer is null)
+            return;
+
+        StyledText textWidget= fTextViewer.getTextWidget();
+        if (textWidget !is null && !textWidget.isDisposed()) {
+            Display display= textWidget.getDisplay();
+            if (display is null)
+                return;
+
+            display.asyncExec(new Runnable() {
+                public void run() {
+                    doPresentInformation();
+                }
+            });
+        }
+    }
+
+    /*
+     * @see AbstractInformationControlManager#presentInformation()
+     */
+    protected void doPresentInformation() {
+        super.presentInformation();
+    }
+
+    /**
+     * Computes the document offset underlying the given text widget coordinates.
+     * This method uses a linear search as it cannot make any assumption about
+     * how the document is actually presented in the widget. (Covers cases such
+     * as bidirectional text.)
+     *
+     * @param x the horizontal coordinate inside the text widget
+     * @param y the vertical coordinate inside the text widget
+     * @return the document offset corresponding to the given point
+     */
+    private int computeOffsetAtLocation(int x, int y) {
+
+        try {
+
+            StyledText styledText= fTextViewer.getTextWidget();
+            int widgetOffset= styledText.getOffsetAtLocation(new Point(x, y));
+            Point p= styledText.getLocationAtOffset(widgetOffset);
+            if (p.x > x)
+                widgetOffset--;
+
+            if (fTextViewer instanceof ITextViewerExtension5) {
+                ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
+                return extension.widgetOffset2ModelOffset(widgetOffset);
+            }
+
+            return widgetOffset + fTextViewer._getVisibleRegionOffset();
+
+        } catch (IllegalArgumentException e) {
+            return -1;
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.AbstractInformationControlManager#showInformationControl(dwt.graphics.Rectangle)
+     */
+    protected void showInformationControl(Rectangle subjectArea) {
+        if (fTextViewer !is null && fTextViewer.requestWidgetToken(this, WIDGET_PRIORITY))
+            super.showInformationControl(subjectArea);
+        else
+            if (DEBUG)
+                System.out.println("TextViewerHoverManager#showInformationControl(..) did not get widget token"); //$NON-NLS-1$
+    }
+
+    /*
+     * @see dwtx.jface.text.AbstractInformationControlManager#hideInformationControl()
+     */
+    protected void hideInformationControl() {
+        try {
+            fTextHover= null;
+            super.hideInformationControl();
+        } finally {
+            if (fTextViewer !is null)
+                fTextViewer.releaseWidgetToken(this);
+        }
+    }
+    
+    /*
+     * @see dwtx.jface.text.AbstractInformationControlManager#replaceInformationControl(bool)
+     * @since 3.4
+     */
+    void replaceInformationControl(bool takeFocus) {
+        if (fTextViewer !is null)
+            fTextViewer.releaseWidgetToken(this);
+        super.replaceInformationControl(takeFocus);
+    }
+
+    /*
+     * @see dwtx.jface.text.AbstractInformationControlManager#handleInformationControlDisposed()
+     */
+    protected void handleInformationControlDisposed() {
+        try {
+            super.handleInformationControlDisposed();
+        } finally {
+            if (fTextViewer !is null)
+                fTextViewer.releaseWidgetToken(this);
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.IWidgetTokenKeeper#requestWidgetToken(dwtx.jface.text.IWidgetTokenOwner)
+     */
+    public bool requestWidgetToken(IWidgetTokenOwner owner) {
+        fTextHover= null;
+        super.hideInformationControl();
+        return true;
+    }
+
+    /*
+     * @see dwtx.jface.text.IWidgetTokenKeeperExtension#requestWidgetToken(dwtx.jface.text.IWidgetTokenOwner, int)
+     * @since 3.0
+     */
+    public bool requestWidgetToken(IWidgetTokenOwner owner, int priority) {
+        if (priority > WIDGET_PRIORITY) {
+            fTextHover= null;
+            super.hideInformationControl();
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * @see dwtx.jface.text.IWidgetTokenKeeperExtension#setFocus(dwtx.jface.text.IWidgetTokenOwner)
+     * @since 3.0
+     */
+    public bool setFocus(IWidgetTokenOwner owner) {
+        if (! hasInformationControlReplacer())
+            return false;
+        
+        IInformationControl iControl= getCurrentInformationControl();
+        if (canReplace(iControl)) {
+            if (cancelReplacingDelay())
+                replaceInformationControl(true);
+            
+            return true;
+        }
+        
+        return false;
+    }
+
+    /**
+     * Returns the currently shown text hover or <code>null</code> if no text
+     * hover is shown.
+     *
+     * @return the currently shown text hover or <code>null</code>
+     */
+    protected ITextHover getCurrentTextHover() {
+        return fTextHover;
+    }
+
+    /*
+     * @see dwtx.jface.text.AbstractHoverInformationControlManager#dispose()
+     * @since 3.0
+     */
+    public void dispose() {
+        if (fTextViewer !is null) {
+            fTextViewer.removeViewportListener(fViewportListener);
+            fViewportListener= null;
+
+            StyledText st= fTextViewer.getTextWidget();
+            if (st !is null && !st.isDisposed())
+                st.removeMouseMoveListener(fMouseMoveListener);
+            fMouseMoveListener= null;
+        }
+        super.dispose();
+    }
+}