diff dwtx/jface/window/ToolTip.d @ 7:8a302fdb4140

Jface some window and resource classes
author Frank Benoit <benoit@tionex.de>
date Fri, 28 Mar 2008 23:32:40 +0100
parents
children 644f1334b451
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/window/ToolTip.d	Fri Mar 28 23:32:40 2008 +0100
@@ -0,0 +1,643 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+
+module dwtx.jface.window.ToolTip;
+
+import tango.util.collection.HashMap;
+
+import dwt.DWT;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.layout.FillLayout;
+import dwt.widgets.Composite;
+import dwt.widgets.Control;
+import dwt.widgets.Event;
+import dwt.widgets.Listener;
+import dwt.widgets.Monitor;
+import dwt.widgets.Shell;
+// import dwtx.jface.viewers.ColumnViewer;
+// import dwtx.jface.viewers.ViewerCell;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+
+/**
+ * This class gives implementors to provide customized tooltips for any control.
+ *
+ * @since 3.3
+ */
+public abstract class ToolTip {
+    private Control control;
+
+    private int xShift = 3;
+
+    private int yShift = 0;
+
+    private int popupDelay = 0;
+
+    private int hideDelay = 0;
+
+    private ToolTipOwnerControlListener listener;
+
+    private HashMap!(String,Object) data;
+
+    // Ensure that only one tooltip is active in time
+    private static Shell CURRENT_TOOLTIP;
+
+    /**
+     * Recreate the tooltip on every mouse move
+     */
+    public static const int RECREATE = 1;
+
+    /**
+     * Don't recreate the tooltip as long the mouse doesn't leave the area
+     * triggering the Tooltip creation
+     */
+    public static const int NO_RECREATE = 1 << 1;
+
+    private TooltipHideListener hideListener;
+
+    private bool hideOnMouseDown = true;
+
+    private bool respectDisplayBounds = true;
+
+    private bool respectMonitorBounds = true;
+
+    private int style;
+
+    private Object currentArea;
+
+    /**
+     * Create new instance which add TooltipSupport to the widget
+     *
+     * @param control
+     *            the control on whose action the tooltip is shown
+     */
+    public this(Control control) {
+        this(control, RECREATE, false);
+    }
+
+    /**
+     * @param control
+     *            the control to which the tooltip is bound
+     * @param style
+     *            style passed to control tooltip behaviour
+     *
+     * @param manualActivation
+     *            <code>true</code> if the activation is done manually using
+     *            {@link #show(Point)}
+     * @see #RECREATE
+     * @see #NO_RECREATE
+     */
+    public this(Control control, int style, bool manualActivation) {
+        this.control = control;
+        this.style = style;
+        this.hideListener = new TooltipHideListener();
+        this.control.addDisposeListener(new class DisposeListener {
+
+            public void widgetDisposed(DisposeEvent e) {
+                deactivate();
+            }
+
+        });
+
+        this.listener = new ToolTipOwnerControlListener();
+
+        if (!manualActivation) {
+            activate();
+        }
+    }
+
+    /**
+     * Restore arbitary data under the given key
+     *
+     * @param key
+     *            the key
+     * @param value
+     *            the value
+     */
+    public void setData(String key, Object value) {
+        if (data is null) {
+            data = new HashMap!(String,Object);
+        }
+        data.add(key, value);
+    }
+
+    /**
+     * Get the data restored under the key
+     *
+     * @param key
+     *            the key
+     * @return data or <code>null</code> if no entry is restored under the key
+     */
+    public Object getData(String key) {
+        if (data !is null) {
+            return data.get(key);
+        }
+        return null;
+    }
+
+    /**
+     * Set the shift (from the mouse position triggered the event) used to
+     * display the tooltip. By default the tooltip is shifted 3 pixels to the
+     * left
+     *
+     * @param p
+     *            the new shift
+     */
+    public void setShift(Point p) {
+        xShift = p.x;
+        yShift = p.y;
+    }
+
+    /**
+     * Activate tooltip support for this control
+     */
+    public void activate() {
+        deactivate();
+        control.addListener(DWT.Dispose, listener);
+        control.addListener(DWT.MouseHover, listener);
+        control.addListener(DWT.MouseMove, listener);
+        control.addListener(DWT.MouseExit, listener);
+        control.addListener(DWT.MouseDown, listener);
+    }
+
+    /**
+     * Deactivate tooltip support for the underlying control
+     */
+    public void deactivate() {
+        control.removeListener(DWT.Dispose, listener);
+        control.removeListener(DWT.MouseHover, listener);
+        control.removeListener(DWT.MouseMove, listener);
+        control.removeListener(DWT.MouseExit, listener);
+        control.removeListener(DWT.MouseDown, listener);
+    }
+
+    /**
+     * Return whther the tooltip respects bounds of the display.
+     *
+     * @return <code>true</code> if the tooltip respects bounds of the display
+     */
+    public bool isRespectDisplayBounds() {
+        return respectDisplayBounds;
+    }
+
+    /**
+     * Set to <code>false</code> if display bounds should not be respected or
+     * to <code>true</code> if the tooltip is should repositioned to not
+     * overlap the display bounds.
+     * <p>
+     * Default is <code>true</code>
+     * </p>
+     *
+     * @param respectDisplayBounds
+     */
+    public void setRespectDisplayBounds(bool respectDisplayBounds) {
+        this.respectDisplayBounds = respectDisplayBounds;
+    }
+
+    /**
+     * Return whther the tooltip respects bounds of the monitor.
+     *
+     * @return <code>true</code> if tooltip respects the bounds of the monitor
+     */
+    public bool isRespectMonitorBounds() {
+        return respectMonitorBounds;
+    }
+
+    /**
+     * Set to <code>false</code> if monitor bounds should not be respected or
+     * to <code>true</code> if the tooltip is should repositioned to not
+     * overlap the monitors bounds. The monitor the tooltip belongs to is the
+     * same is control's monitor the tooltip is shown for.
+     * <p>
+     * Default is <code>true</code>
+     * </p>
+     *
+     * @param respectMonitorBounds
+     */
+    public void setRespectMonitorBounds(bool respectMonitorBounds) {
+        this.respectMonitorBounds = respectMonitorBounds;
+    }
+
+    /**
+     * Should the tooltip displayed because of the given event.
+     * <p>
+     * <b>Subclasses may overwrite this to get custom behaviour</b>
+     * </p>
+     *
+     * @param event
+     *            the event
+     * @return <code>true</code> if tooltip should be displayed
+     */
+    protected bool shouldCreateToolTip(Event event) {
+        if ((style & NO_RECREATE) !is 0) {
+            Object tmp = getToolTipArea(event);
+
+            // No new area close the current tooltip
+            if (tmp is null) {
+                hide();
+                return false;
+            }
+
+            bool rv = !tmp.opEquals(currentArea);
+            return rv;
+        }
+
+        return true;
+    }
+
+    /**
+     * This method is called before the tooltip is hidden
+     *
+     * @param event
+     *            the event trying to hide the tooltip
+     * @return <code>true</code> if the tooltip should be hidden
+     */
+    private bool shouldHideToolTip(Event event) {
+        if (event !is null && event.type is DWT.MouseMove
+                && (style & NO_RECREATE) !is 0) {
+            Object tmp = getToolTipArea(event);
+
+            // No new area close the current tooltip
+            if (tmp is null) {
+                hide();
+                return false;
+            }
+
+            bool rv = !tmp.opEquals(currentArea);
+            return rv;
+        }
+
+        return true;
+    }
+
+    /**
+     * This method is called to check for which area the tooltip is
+     * created/hidden for. In case of {@link #NO_RECREATE} this is used to
+     * decide if the tooltip is hidden recreated.
+     *
+     * <code>By the default it is the widget the tooltip is created for but could be any object. To decide if
+     * the area changed the {@link Object#equals(Object)} method is used.</code>
+     *
+     * @param event
+     *            the event
+     * @return the area responsible for the tooltip creation or
+     *         <code>null</code> this could be any object describing the area
+     *         (e.g. the {@link Control}  onto which the tooltip is bound to, a part of
+     *         this area e.g. for {@link ColumnViewer} this could be a
+     *         {@link ViewerCell})
+     */
+    protected Object getToolTipArea(Event event) {
+        return control;
+    }
+
+    /**
+     * Start up the tooltip programmatically
+     *
+     * @param location
+     *            the location relative to the control the tooltip is shown
+     */
+    public void show(Point location) {
+        Event event = new Event();
+        event.x = location.x;
+        event.y = location.y;
+        event.widget = control;
+        toolTipCreate(event);
+    }
+
+    private Shell toolTipCreate(Event event) {
+        if (shouldCreateToolTip(event)) {
+            Shell shell = new Shell(control.getShell(), DWT.ON_TOP | DWT.TOOL
+                    | DWT.NO_FOCUS);
+            shell.setLayout(new FillLayout());
+
+            toolTipOpen(shell, event);
+
+            return shell;
+        }
+
+        return null;
+    }
+
+    private void toolTipShow(Shell tip, Event event) {
+        if (!tip.isDisposed()) {
+            currentArea = getToolTipArea(event);
+            createToolTipContentArea(event, tip);
+            if (isHideOnMouseDown()) {
+                toolTipHookBothRecursively(tip);
+            } else {
+                toolTipHookByTypeRecursively(tip, true, DWT.MouseExit);
+            }
+
+            tip.pack();
+            tip.setLocation(fixupDisplayBounds(tip.getSize(), getLocation(tip
+                    .getSize(), event)));
+            tip.setVisible(true);
+        }
+    }
+
+    private Point fixupDisplayBounds(Point tipSize, Point location) {
+        if (respectDisplayBounds || respectMonitorBounds) {
+            Rectangle bounds;
+            Point rightBounds = new Point(tipSize.x + location.x, tipSize.y
+                    + location.y);
+
+            dwt.widgets.Monitor.Monitor[] ms = control.getDisplay().getMonitors();
+
+            if (respectMonitorBounds && ms.length > 1) {
+                // By default present in the monitor of the control
+                bounds = control.getMonitor().getBounds();
+                Point p = new Point(location.x, location.y);
+
+                // Search on which monitor the event occurred
+                Rectangle tmp;
+                for (int i = 0; i < ms.length; i++) {
+                    tmp = ms[i].getBounds();
+                    if (tmp.contains(p)) {
+                        bounds = tmp;
+                        break;
+                    }
+                }
+
+            } else {
+                bounds = control.getDisplay().getBounds();
+            }
+
+            if (!(bounds.contains(location) && bounds.contains(rightBounds))) {
+                if (rightBounds.x > bounds.width) {
+                    location.x -= rightBounds.x - bounds.width;
+                }
+
+                if (rightBounds.y > bounds.height) {
+                    location.y -= rightBounds.y - bounds.height;
+                }
+
+                if (location.x < bounds.x) {
+                    location.x = bounds.x;
+                }
+
+                if (location.y < bounds.y) {
+                    location.y = bounds.y;
+                }
+            }
+        }
+
+        return location;
+    }
+
+    /**
+     * Get the display relative location where the tooltip is displayed.
+     * Subclasses may overwrite to implement custom positioning.
+     *
+     * @param tipSize
+     *            the size of the tooltip to be shown
+     * @param event
+     *            the event triggered showing the tooltip
+     * @return the absolute position on the display
+     */
+    public Point getLocation(Point tipSize, Event event) {
+        return control.toDisplay(event.x + xShift, event.y + yShift);
+    }
+
+    private void toolTipHide(Shell tip, Event event) {
+        if (tip !is null && !tip.isDisposed() && shouldHideToolTip(event)) {
+            currentArea = null;
+            tip.dispose();
+            CURRENT_TOOLTIP = null;
+            afterHideToolTip(event);
+        }
+    }
+
+    private void toolTipOpen(Shell shell, Event event) {
+        // Ensure that only one Tooltip is shown in time
+        if (CURRENT_TOOLTIP !is null) {
+            toolTipHide(CURRENT_TOOLTIP, null);
+        }
+
+        CURRENT_TOOLTIP = shell;
+
+        if (popupDelay > 0) {
+            control.getDisplay().timerExec(popupDelay, new class Runnable {
+                Shell shell_;
+                Event event_;
+                this(){ shell_=shell; event_=event; }
+                public void run() {
+                    toolTipShow(shell_, event_);
+                }
+            });
+        } else {
+            toolTipShow(CURRENT_TOOLTIP, event);
+        }
+
+        if (hideDelay > 0) {
+            control.getDisplay().timerExec(popupDelay + hideDelay,
+                    new class Runnable {
+                        Shell shell_;
+                        this(){ shell_=shell; }
+                        public void run() {
+                            toolTipHide(shell, null);
+                        }
+                    });
+        }
+    }
+
+    private void toolTipHookByTypeRecursively(Control c, bool add, int type) {
+        if (add) {
+            c.addListener(type, hideListener);
+        } else {
+            c.removeListener(type, hideListener);
+        }
+
+        if ( auto c2 = cast(Composite)c ) {
+            Control[] children = c2.getChildren();
+            for (int i = 0; i < children.length; i++) {
+                toolTipHookByTypeRecursively(children[i], add, type);
+            }
+        }
+    }
+
+    private void toolTipHookBothRecursively(Control c) {
+        c.addListener(DWT.MouseDown, hideListener);
+        c.addListener(DWT.MouseExit, hideListener);
+
+        if ( auto comp = cast(Composite) c ) {
+            Control[] children = comp.getChildren();
+            for (int i = 0; i < children.length; i++) {
+                toolTipHookBothRecursively(children[i]);
+            }
+        }
+    }
+
+    /**
+     * Creates the content area of the the tooltip.
+     *
+     * @param event
+     *            the event that triggered the activation of the tooltip
+     * @param parent
+     *            the parent of the content area
+     * @return the content area created
+     */
+    protected abstract Composite createToolTipContentArea(Event event,
+            Composite parent);
+
+    /**
+     * This method is called after a Tooltip is hidden.
+     * <p>
+     * <b>Subclasses may override to clean up requested system resources</b>
+     * </p>
+     *
+     * @param event
+     *            event triggered the hiding action (may be <code>null</code>
+     *            if event wasn't triggered by user actions directly)
+     */
+    protected void afterHideToolTip(Event event) {
+
+    }
+
+    /**
+     * Set the hide delay.
+     *
+     * @param hideDelay
+     *            the delay before the tooltip is hidden. If <code>0</code>
+     *            the tooltip is shown until user moves to other item
+     */
+    public void setHideDelay(int hideDelay) {
+        this.hideDelay = hideDelay;
+    }
+
+    /**
+     * Set the popup delay.
+     *
+     * @param popupDelay
+     *            the delay before the tooltip is shown to the user. If
+     *            <code>0</code> the tooltip is shown immediately
+     */
+    public void setPopupDelay(int popupDelay) {
+        this.popupDelay = popupDelay;
+    }
+
+    /**
+     * Return if hiding on mouse down is set.
+     *
+     * @return <code>true</code> if hiding on mouse down in the tool tip is on
+     */
+    public bool isHideOnMouseDown() {
+        return hideOnMouseDown;
+    }
+
+    /**
+     * If you don't want the tool tip to be hidden when the user clicks inside
+     * the tool tip set this to <code>false</code>. You maybe also need to
+     * hide the tool tip yourself depending on what you do after clicking in the
+     * tooltip (e.g. if you open a new {@link Shell})
+     *
+     * @param hideOnMouseDown
+     *            flag to indicate of tooltip is hidden automatically on mouse
+     *            down inside the tool tip
+     */
+    public void setHideOnMouseDown(bool hideOnMouseDown) {
+        // Only needed if there's currently a tooltip active
+        if (CURRENT_TOOLTIP !is null && !CURRENT_TOOLTIP.isDisposed()) {
+            // Only change if value really changed
+            if (hideOnMouseDown !is this.hideOnMouseDown) {
+                control.getDisplay().syncExec(new class Runnable {
+                    bool hideOnMouseDown_;
+                    this(){ hideOnMouseDown_=hideOnMouseDown; }
+                    public void run() {
+                        if (CURRENT_TOOLTIP !is null
+                                && CURRENT_TOOLTIP.isDisposed()) {
+                            toolTipHookByTypeRecursively(CURRENT_TOOLTIP,
+                                    hideOnMouseDown_, DWT.MouseDown);
+                        }
+                    }
+
+                });
+            }
+        }
+
+        this.hideOnMouseDown = hideOnMouseDown;
+    }
+
+    /**
+     * Hide the currently active tool tip
+     */
+    public void hide() {
+        toolTipHide(CURRENT_TOOLTIP, null);
+    }
+
+    private class ToolTipOwnerControlListener : Listener {
+        public void handleEvent(Event event) {
+            switch (event.type) {
+            case DWT.Dispose:
+            case DWT.KeyDown:
+            case DWT.MouseDown:
+            case DWT.MouseMove:
+                toolTipHide(CURRENT_TOOLTIP, event);
+                break;
+            case DWT.MouseHover:
+                toolTipCreate(event);
+                break;
+            case DWT.MouseExit:
+                /*
+                 * Check if the mouse exit happend because we move over the
+                 * tooltip
+                 */
+                if (CURRENT_TOOLTIP !is null && !CURRENT_TOOLTIP.isDisposed()) {
+                    if (CURRENT_TOOLTIP.getBounds().contains(
+                            control.toDisplay(event.x, event.y))) {
+                        break;
+                    }
+                }
+
+                toolTipHide(CURRENT_TOOLTIP, event);
+                break;
+            }
+        }
+    }
+
+    private class TooltipHideListener : Listener {
+        public void handleEvent(Event event) {
+            if ( auto c = cast(Control)event.widget ) {
+
+                Shell shell = c.getShell();
+
+                switch (event.type) {
+                case DWT.MouseDown:
+                    if (isHideOnMouseDown()) {
+                        toolTipHide(shell, event);
+                    }
+                    break;
+                case DWT.MouseExit:
+                    /*
+                     * Give some insets to ensure we get exit informations from
+                     * a wider area ;-)
+                     */
+                    Rectangle rect = shell.getBounds();
+                    rect.x += 5;
+                    rect.y += 5;
+                    rect.width -= 10;
+                    rect.height -= 10;
+
+                    if (!rect.contains(c.getDisplay().getCursorLocation())) {
+                        toolTipHide(shell, event);
+                    }
+
+                    break;
+                }
+            }
+        }
+    }
+}