Mercurial > projects > dwt-addons
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; + } + } + } + } +}