/** * This file is part of Archiv-Editor. * * The software Archiv-Editor serves as a client user interface for working with * the Person Data Repository. See: pdr.bbaw.de * * The software Archiv-Editor was developed at the Berlin-Brandenburg Academy * of Sciences and Humanities, Jägerstr. 22/23, D-10117 Berlin. * www.bbaw.de * * Copyright (C) 2010-2013 Berlin-Brandenburg Academy * of Sciences and Humanities * * The software Archiv-Editor was developed by @author: Christoph Plutte. * * Archiv-Editor is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Archiv-Editor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Archiv-Editor. * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>. */ package org.bbaw.pdr.ae.view.control.customSWTWidges; /******************************************************************************* * 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 *******************************************************************************/ import java.util.HashMap; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; /** * 32 * This class gives implementors to provide customized tooltips for any * control. 33 * 34 * @since 3.3 35 */ public abstract class CustomTooltipMouseListener { /** * The listener interface for receiving tooltipHide events. The class that * is interested in processing a tooltipHide event implements this * interface, and the object created with that class is registered with a * component using the component's <code>addTooltipHideListener</code> * method. When the tooltipHide event occurs, that object's appropriate * method is invoked. * @see TooltipHideEvent */ private class TooltipHideListener implements Listener { /** * @param event * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ @Override public void handleEvent(final Event event) { if (event.widget instanceof Control) { if (!event.widget.isDisposed()) { Control c = (Control) event.widget; Shell shell = c.getShell(); switch (event.type) { case SWT.MouseDown: if (isHideOnMouseDown()) { toolTipHide(shell, event); } break; case SWT.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); } default: break; // break; } } } } } /** * The listener interface for receiving toolTipOwnerControl events. The * class that is interested in processing a toolTipOwnerControl event * implements this interface, and the object created with that class is * registered with a component using the component's * <code>addToolTipOwnerControlListener</code> method. When the * toolTipOwnerControl event occurs, that object's appropriate method is * invoked. * @see ToolTipOwnerControlEvent */ private class ToolTipOwnerControlListener implements Listener { /** * @param event * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ @Override public void handleEvent(final Event event) { switch (event.type) { case SWT.Dispose: case SWT.KeyDown: case SWT.MouseDown: case SWT.MouseMove: toolTipHide(currentTooltip, event); break; case SWT.MouseHover: toolTipCreate(event); break; case SWT.MouseExit: /* * Check if the mouse exit happend because we move over the * tooltip */ if (currentTooltip != null && !currentTooltip.isDisposed()) { if (currentTooltip.getBounds().contains(_control.toDisplay(event.x, event.y))) { break; } } toolTipHide(currentTooltip, event); break; default: break; } } } /** The control. */ private Control _control; /** The x shift. */ private int _xShift = 3; /** The y shift. */ private int _yShift = 0; /** The popup delay. */ private int _popupDelay = 0; /** The hide delay. */ private int _hideDelay = 0; /** The listener. */ private ToolTipOwnerControlListener _listener; /** The data. */ private HashMap _data; // Ensure that only one tooltip is active in time /** The CURREN t_ tooltip. */ private static Shell currentTooltip; /** * Recreate the tooltip on every mouse move. */ public static final int RECREATE = 1; /** * Don't recreate the tooltip as long the mouse doesn't leave the area * triggering the Tooltip creation. */ public static final int NO_RECREATE = 1 << 1; /** The hide listener. */ private TooltipHideListener _hideListener = new TooltipHideListener(); /** The hide on mouse down. */ private boolean _hideOnMouseDown = true; /** The respect display bounds. */ private boolean _respectDisplayBounds = true; /** The respect monitor bounds. */ private boolean _respectMonitorBounds = true; /** The style. */ private int _style; /** The current area. */ private Object _currentArea; /** * Create new instance which add TooltipSupport to the widget. * @param control the control on whose action the tooltip is shown */ public CustomTooltipMouseListener(final 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 CustomTooltipMouseListener(final Control control, final int style, final boolean manualActivation) { this._control = control; this._style = style; this._control.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(final DisposeEvent e) { deactivate(); } }); this._listener = new ToolTipOwnerControlListener(); if (!manualActivation) { activate(); } } /** * Activate tooltip support for this control. */ public void activate() { deactivate(); _control.addListener(SWT.Dispose, _listener); // RAP auskommentiewrt _control.addListener(SWT.MouseHover, _listener); _control.addListener(SWT.MouseMove, _listener); _control.addListener(SWT.MouseExit, _listener); _control.addListener(SWT.MouseDown, _listener); } /** * 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(final Event event) { } /** * 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); /** * Deactivate tooltip support for the underlying control. */ public void deactivate() { _control.removeListener(SWT.Dispose, _listener); // RAP auskommentiewrt _control.removeListener(SWT.MouseHover, _listener); _control.removeListener(SWT.MouseMove, _listener); _control.removeListener(SWT.MouseExit, _listener); _control.removeListener(SWT.MouseDown, _listener); } /** * Fixup display bounds. * @param tipSize the tip size * @param location the location * @return the point */ private Point fixupDisplayBounds(final Point tipSize, final Point location) { if (_respectDisplayBounds || _respectMonitorBounds) { Rectangle bounds; Point rightBounds = new Point(tipSize.x + location.x, tipSize.y + location.y); 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 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(final String key) { if (_data != null) { return _data.get(key); } return null; } /** * 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(final Point tipSize, final Event event) { return _control.toDisplay(event.x + _xShift, event.y + _yShift); } /** * 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(final Event event) { return _control; } /** * Hide the currently active tool tip. */ public void hide() { toolTipHide(currentTooltip, null); } /** * Return if hiding on mouse down is set. * @return <code>true</code> if hiding on mouse down in the tool tip is on */ public boolean isHideOnMouseDown() { return _hideOnMouseDown; } /** * Return whther the tooltip respects bounds of the display. * @return <code>true</code> if the tooltip respects bounds of the display */ public boolean isRespectDisplayBounds() { return _respectDisplayBounds; } /** * Return whther the tooltip respects bounds of the monitor. * @return <code>true</code> if tooltip respects the bounds of the monitor */ public boolean isRespectMonitorBounds() { return _respectMonitorBounds; } /** * Restore arbitary data under the given key. * @param key the key * @param value the value */ @SuppressWarnings( {"unchecked", "rawtypes"}) public void setData(final String key, final Object value) { if (_data == null) { _data = new HashMap(); } _data.put(key, value); } /** * 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(final int hideDelay) { this._hideDelay = hideDelay; } /** * 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(final boolean hideOnMouseDown) { // Only needed if there's currently a tooltip active if (currentTooltip != null && !currentTooltip.isDisposed()) { // Only change if value really changed if (hideOnMouseDown != this._hideOnMouseDown) { _control.getDisplay().syncExec(new Runnable() { @Override public void run() { if (currentTooltip != null && currentTooltip.isDisposed()) { toolTipHookByTypeRecursively(currentTooltip, hideOnMouseDown, SWT.MouseDown); } } }); } } this._hideOnMouseDown = hideOnMouseDown; } /** * 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(final int popupDelay) { this._popupDelay = popupDelay; } /** * 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(final boolean respectDisplayBounds) { this._respectDisplayBounds = respectDisplayBounds; } /** * 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(final boolean respectMonitorBounds) { this._respectMonitorBounds = respectMonitorBounds; } /** * 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 153 */ public void setShift(final Point p) { _xShift = p.x; _yShift = p.y; } /** * 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 boolean shouldCreateToolTip(final Event event) { if ((_style & NO_RECREATE) != 0) { Object tmp = getToolTipArea(event); // No new area close the current tooltip if (tmp == null) { hide(); return false; } boolean rv = !tmp.equals(_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 boolean shouldHideToolTip(final Event event) { if (event != null && event.type == SWT.MouseMove && (NO_RECREATE) != 0) { Object tmp = getToolTipArea(event); // No new area close the current tooltip if (tmp == null) { hide(); return false; } boolean rv = !tmp.equals(_currentArea); return rv; } return true; } /** * Start up the tooltip programmatically. * @param location the location relative to the control the tooltip is shown */ public void show(final Point location) { Event event = new Event(); event.x = location.x; event.y = location.y; event.widget = _control; toolTipCreate(event); } /** * Tool tip create. * @param event the event * @return the shell */ private Shell toolTipCreate(final Event event) { if (_control != null && !_control.isDisposed() && shouldCreateToolTip(event)) { Shell shell = new Shell(_control.getShell(), SWT.ON_TOP | SWT.TOOL | SWT.NO_FOCUS); shell.setLayout(new GridLayout()); toolTipOpen(shell, event); return shell; } return null; } /** * Tool tip hide. * @param tip the tip * @param event the event */ private void toolTipHide(final Shell tip, final Event event) { if (tip != null && !tip.isDisposed() && shouldHideToolTip(event)) { _currentArea = null; tip.dispose(); currentTooltip = null; afterHideToolTip(event); } } /** * Tool tip hook both recursively. * @param c the c */ private void toolTipHookBothRecursively(final Control c) { c.addListener(SWT.MouseDown, _hideListener); c.addListener(SWT.MouseExit, _hideListener); if (c instanceof Composite) { Control[] children = ((Composite) c).getChildren(); for (int i = 0; i < children.length; i++) { toolTipHookBothRecursively(children[i]); } } } /** * Tool tip hook by type recursively. * @param c the c * @param add the add * @param type the type */ private void toolTipHookByTypeRecursively(final Control c, final boolean add, final int type) { if (add) { c.addListener(type, _hideListener); } else { c.removeListener(type, _hideListener); } if (c instanceof Composite) { Control[] children = ((Composite) c).getChildren(); for (int i = 0; i < children.length; i++) { toolTipHookByTypeRecursively(children[i], add, type); } } } /** * Tool tip open. * @param shell the shell * @param event the event */ private void toolTipOpen(final Shell shell, final Event event) { // Ensure that only one Tooltip is shown in time if (currentTooltip != null) { toolTipHide(currentTooltip, null); } currentTooltip = shell; if (_popupDelay > 0) { _control.getDisplay().timerExec(_popupDelay, new Runnable() { @Override public void run() { toolTipShow(shell, event); } }); } else { toolTipShow(currentTooltip, event); } if (_hideDelay > 0) { _control.getDisplay().timerExec(_popupDelay + _hideDelay, new Runnable() { @Override public void run() { toolTipHide(shell, null); } }); } } /** * Tool tip show. * @param tip the tip * @param event the event */ private void toolTipShow(final Shell tip, final Event event) { if (!tip.isDisposed()) { _currentArea = getToolTipArea(event); createToolTipContentArea(event, tip); if (isHideOnMouseDown()) { toolTipHookBothRecursively(tip); } else { toolTipHookByTypeRecursively(tip, true, SWT.MouseExit); } tip.pack(); tip.setLocation(fixupDisplayBounds(tip.getSize(), getLocation(tip.getSize(), event))); tip.setVisible(true); } } public boolean isVisible() { return (currentTooltip != null && !currentTooltip.isDisposed()); } }