All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jface.window.ToolTip Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2006, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Tom Schindl  - initial API and implementation
 *                                                 bugfix in: 195137, 198089, 225190
 *******************************************************************************/

package org.eclipse.jface.window;

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.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
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;

/**
 * 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 data;

	// Ensure that only one tooltip is active in time
	private static Shell CURRENT_TOOLTIP;

	/**
	 * 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;

	private TooltipHideListener hideListener = new TooltipHideListener();

	private Listener shellListener;

	private boolean hideOnMouseDown = true;

	private boolean respectDisplayBounds = true;

	private boolean 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 ToolTip(Control control) {
		this(control, RECREATE, false);
	}

	/**
	 * @param control
	 *            the control to which the tooltip is bound
	 * @param style
	 *            style passed to control tooltip behavior
	 *
	 * @param manualActivation
	 *            true if the activation is done manually using
	 *            {@link #show(Point)}
	 * @see #RECREATE
	 * @see #NO_RECREATE
	 */
	public ToolTip(Control control, int style, boolean manualActivation) {
		this.control = control;
		this.style = style;
		this.listener = new ToolTipOwnerControlListener();
		this.shellListener = event -> {
			if (ToolTip.this.control != null
					&& !ToolTip.this.control.isDisposed()) {
				ToolTip.this.control.getDisplay().asyncExec(() -> {
					// Check if the new active shell is the tooltip
					// itself
					if (ToolTip.this.control != null && !ToolTip.this.control.isDisposed()
							&& ToolTip.this.control.getDisplay().getActiveShell() != CURRENT_TOOLTIP) {
						toolTipHide(CURRENT_TOOLTIP, event);
					}
				});
			}
		};

		if (!manualActivation) {
			activate();
		}
	}

	/**
	 * Restore arbitrary data under the given key
	 *
	 * @param key
	 *            the key
	 * @param value
	 *            the value
	 */
	public void setData(String key, Object value) {
		if (data == null) {
			data = new HashMap<>();
		}
		data.put(key, value);
	}

	/**
	 * Get the data restored under the key
	 *
	 * @param key
	 *            the key
	 * @return data or null if no entry is restored under the key
	 */
	public Object getData(String key) {
		if (data != 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 right. *

* * @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(SWT.Dispose, listener); control.addListener(SWT.MouseHover, listener); control.addListener(SWT.MouseMove, listener); control.addListener(SWT.MouseExit, listener); control.addListener(SWT.MouseDown, listener); control.addListener(SWT.MouseWheel, listener); } /** * Deactivate tooltip support for the underlying control */ public void deactivate() { control.removeListener(SWT.Dispose, listener); control.removeListener(SWT.MouseHover, listener); control.removeListener(SWT.MouseMove, listener); control.removeListener(SWT.MouseExit, listener); control.removeListener(SWT.MouseDown, listener); control.removeListener(SWT.MouseWheel, listener); } /** * Return whether the tooltip respects bounds of the display. * * @return true if the tooltip respects bounds of the display */ public boolean isRespectDisplayBounds() { return respectDisplayBounds; } /** * Set to false if display bounds should not be respected or to * true if the tooltip is should repositioned to not overlap the * display bounds. *

* Default is true *

* * @param respectDisplayBounds false if tooltip is allowed to * overlap display bounds */ public void setRespectDisplayBounds(boolean respectDisplayBounds) { this.respectDisplayBounds = respectDisplayBounds; } /** * Return whether the tooltip respects bounds of the monitor. * * @return true if tooltip respects the bounds of the monitor */ public boolean isRespectMonitorBounds() { return respectMonitorBounds; } /** * Set to false if monitor bounds should not be respected or to * true 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. *

* Default is true *

* * @param respectMonitorBounds false if tooltip is allowed to * overlap monitor bounds */ public void setRespectMonitorBounds(boolean respectMonitorBounds) { this.respectMonitorBounds = respectMonitorBounds; } /** * Should the tooltip displayed because of the given event. *

* Subclasses may overwrite this to get custom behavior *

* * @param event * the event * @return true if tooltip should be displayed */ protected boolean shouldCreateToolTip(Event event) { if ((style & NO_RECREATE) != 0) { Object tmp = getToolTipArea(event); // No new area close the current tooltip if (tmp == null) { hide(); return false; } return !tmp.equals(currentArea); } return true; } /** * This method is called before the tooltip is hidden * * @param event * the event trying to hide the tooltip * @return true if the tooltip should be hidden */ private boolean shouldHideToolTip(Event event) { if (event != null && event.type == SWT.MouseMove && (style & NO_RECREATE) != 0) { Object tmp = getToolTipArea(event); // No new area close the current tooltip if (tmp == null) { hide(); return false; } return !tmp.equals(currentArea); } 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. * * 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. * * @param event * the event * @return the area responsible for the tooltip creation or * null 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(final Event event) { if (shouldCreateToolTip(event)) { Shell shell = new Shell(control.getShell(), SWT.ON_TOP | SWT.TOOL | SWT.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, SWT.MouseExit); } tip.pack(); Point size = tip.getSize(); Point location = fixupDisplayBounds(size, getLocation(size, event)); // Need to adjust a bit more if the mouse cursor.y == tip.y and // the cursor.x is inside the tip Point cursorLocation = tip.getDisplay().getCursorLocation(); if (cursorLocation.y == location.y && location.x < cursorLocation.x && location.x + size.x > cursorLocation.x) { location.y -= 2; } tip.setLocation(location); 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); 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 (Monitor element : ms) { tmp = element.getBounds(); if (tmp.contains(p)) { bounds = tmp; break; } } } else { bounds = control.getDisplay().getBounds(); } if (!(bounds.contains(location) && bounds.contains(rightBounds))) { if (rightBounds.x > bounds.x + bounds.width) { location.x -= rightBounds.x - (bounds.x + bounds.width); } if (rightBounds.y > bounds.y + bounds.height) { location.y -= rightBounds.y - (bounds.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 != null && !tip.isDisposed() && shouldHideToolTip(event)) { control.getShell().removeListener(SWT.Deactivate, shellListener); currentArea = null; passOnEvent(tip, event); tip.dispose(); CURRENT_TOOLTIP = null; afterHideToolTip(event); } if (event != null && event.type == SWT.Dispose) { deactivate(); data = null; } } private void passOnEvent(Shell tip, Event event) { if (control != null && !control.isDisposed() && event != null && event.widget != control && event.type == SWT.MouseDown) { // the following was left in order to fix bug 298770 with minimal change. In 3.7, the complete method should be removed. tip.close(); } } private void toolTipOpen(final Shell shell, final Event event) { // Ensure that only one Tooltip is shown in time if (CURRENT_TOOLTIP != null) { toolTipHide(CURRENT_TOOLTIP, null); } CURRENT_TOOLTIP = shell; control.getShell().addListener(SWT.Deactivate, shellListener); if (popupDelay > 0) { control.getDisplay().timerExec(popupDelay, () -> toolTipShow(shell, event)); } else { toolTipShow(CURRENT_TOOLTIP, event); } if (hideDelay > 0) { control.getDisplay().timerExec(popupDelay + hideDelay, () -> toolTipHide(shell, null)); } } private void toolTipHookByTypeRecursively(Control c, boolean add, int type) { if (add) { c.addListener(type, hideListener); } else { c.removeListener(type, hideListener); } if (c instanceof Composite) { Control[] children = ((Composite) c).getChildren(); for (Control element : children) { toolTipHookByTypeRecursively(element, add, type); } } } private void toolTipHookBothRecursively(Control c) { c.addListener(SWT.MouseDown, hideListener); c.addListener(SWT.MouseExit, hideListener); if (c instanceof Composite) { Control[] children = ((Composite) c).getChildren(); for (Control element : children) { toolTipHookBothRecursively(element); } } } /** * 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. *

* Subclasses may override to clean up requested system resources *

* * @param event * event triggered the hiding action (may be null * 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 0 * 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 * 0 the tooltip is shown immediately */ public void setPopupDelay(int popupDelay) { this.popupDelay = popupDelay; } /** * Return if hiding on mouse down is set. * * @return true if hiding on mouse down in the tool tip is on */ public boolean 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 false. 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 (CURRENT_TOOLTIP != null && !CURRENT_TOOLTIP.isDisposed()) { // Only change if value really changed if (hideOnMouseDown != this.hideOnMouseDown) { control.getDisplay().syncExec(() -> { if (CURRENT_TOOLTIP != null && CURRENT_TOOLTIP.isDisposed()) { toolTipHookByTypeRecursively(CURRENT_TOOLTIP, hideOnMouseDown, SWT.MouseDown); } }); } } this.hideOnMouseDown = hideOnMouseDown; } /** * Hide the currently active tool tip */ public void hide() { toolTipHide(CURRENT_TOOLTIP, null); } private class ToolTipOwnerControlListener implements Listener { @Override public void handleEvent(Event event) { switch (event.type) { case SWT.Dispose: case SWT.KeyDown: case SWT.MouseDown: case SWT.MouseMove: case SWT.MouseWheel: toolTipHide(CURRENT_TOOLTIP, event); break; case SWT.MouseHover: toolTipCreate(event); break; case SWT.MouseExit: /* * Check if the mouse exit happened because we move over the * tooltip */ if (CURRENT_TOOLTIP != null && !CURRENT_TOOLTIP.isDisposed()) { if (CURRENT_TOOLTIP.getBounds().contains( control.toDisplay(event.x, event.y))) { break; } } toolTipHide(CURRENT_TOOLTIP, event); break; } } } private class TooltipHideListener implements Listener { @Override public void handleEvent(Event event) { if (event.widget instanceof Control) { Control c = (Control) event.widget; Shell shell = c.getShell(); switch (event.type) { case SWT.MouseDown: if (isHideOnMouseDown()) { toolTipHide(shell, event); } break; case SWT.MouseExit: Rectangle rect = shell.getBounds(); if (!rect.contains(c.getDisplay().getCursorLocation())) { toolTipHide(shell, event); } break; } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy