/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.ui.util.tip;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
/**
* Browser tip
*
* @author Simon Templer
* @partner 01 / Fraunhofer Institute for Computer Graphics Research
*/
public class BrowserTip {
private static final ALogger log = ALoggerFactory.getLogger(BrowserTip.class);
private static final int HOVER_DELAY = 400;
private final int toolTipWidth;
private final int toolTipHeight;
private final boolean plainText;
private final ScheduledExecutorService scheduleService;
/**
* The height adjustment when using the computed size
*/
private int heightAdjustment = 50;
/**
* Create a browser tip using its own {@link ScheduledExecutorService}
*
* @param toolTipWidth the maximum with
* @param toolTipHeight the maximum height
* @param plainText if the content will be plain text instead of HTML
*/
public BrowserTip(int toolTipWidth, int toolTipHeight, boolean plainText) {
this(toolTipWidth, toolTipHeight, plainText, null);
}
/**
* Create a browser tip with using given {@link ScheduledExecutorService}
*
* @param toolTipWidth the maximum with
* @param toolTipHeight the maximum height
* @param plainText if the content will be plain text instead of HTML
* @param scheduleService the scheduled executor service to use, if
* <code>null</code> a service will be created
*/
public BrowserTip(int toolTipWidth, int toolTipHeight, boolean plainText,
ScheduledExecutorService scheduleService) {
super();
this.toolTipWidth = toolTipWidth;
this.toolTipHeight = toolTipHeight;
this.plainText = plainText;
if (scheduleService != null) {
this.scheduleService = scheduleService;
}
else {
this.scheduleService = Executors.newScheduledThreadPool(1);
}
}
/**
* Show the tool tip
*
* @param control the tip control
* @param posx the x-position
* @param posy the y-position
* @param toolTip the tool tip string
*
* @return the tool shell
*/
public Shell showToolTip(Control control, int posx, int posy, String toolTip) {
return showToolTip(control, posx, posy, toolTip, null, null);
}
/**
* Show the tool tip
*
* @param control the tip control
* @param posx the x-position
* @param posy the y-position
* @param toolTip the tool tip string
* @param addBounds additional bounds that will be treated as if in the
* tooltip (the tooltip won't hide if the cursor is inside these
* bounds), may be <code>null</code>
* @param addBoundsControl the control the addBounds coordinates are
* relative to, <code>null</code> if addBounds is in display
* coordinates or no addBounds is provided
*
* @return the tool shell
*/
public Shell showToolTip(Control control, int posx, int posy, String toolTip,
final Rectangle addBounds, final Control addBoundsControl) {
final Shell toolShell = new Shell(control.getShell(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
FillLayout layout = new FillLayout();
toolShell.setLayout(layout);
try {
if (plainText) {
Text text = new Text(toolShell, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
text.setFont(control.getDisplay().getSystemFont());
text.setForeground(control.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
text.setBackground(control.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
text.setText(toolTip);
}
else {
Browser browser = new Browser(toolShell, SWT.NONE);
browser.setFont(control.getDisplay().getSystemFont());
browser.setForeground(control.getDisplay()
.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
browser.setBackground(control.getDisplay()
.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
browser.setText(toolTip);
}
Point pt = control.toDisplay(posx, posy);
Rectangle bounds = control.getDisplay().getBounds();
Point size = toolShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
int width = Math.min(toolTipWidth, size.x);
/*
* On Windows XP (not on 7) computeSize seems to result in a size
* where the whole text is pressed into one line. We try to fix this
* by using the... "widthFactor"! (only for small computed heights)
*/
int widthFactor = (size.y < 30) ? (size.x / width) : (1);
int height = Math.min(toolTipHeight, size.y * widthFactor + heightAdjustment);
int x = (pt.x + width > bounds.x + bounds.width) ? (bounds.x + bounds.width - width)
: (pt.x);
int y = (pt.y + height > bounds.y + bounds.height) ? (bounds.y + bounds.height - height)
: (pt.y);
toolShell.setBounds(x, y, width, height);
final Point initCursor = toolShell.getDisplay().getCursorLocation();
toolShell.addMouseTrackListener(new MouseTrackAdapter() {
@Override
public void mouseExit(MouseEvent e) {
hideToolTip(toolShell);
}
});
final AtomicReference<ScheduledFuture<?>> closeTimerRef = new AtomicReference<ScheduledFuture<?>>();
ScheduledFuture<?> closeTimer = scheduleService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (!toolShell.isDisposed()) {
toolShell.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
// check if cursor is over tooltip
if (!toolShell.isDisposed()) {
Point cursor = toolShell.getDisplay().getCursorLocation();
if (!cursor.equals(initCursor)) {
Rectangle bounds = toolShell.getBounds();
if (addBounds != null) {
Rectangle add;
if (addBoundsControl != null) {
Point addP = addBoundsControl.toDisplay(
addBounds.x, addBounds.y);
add = new Rectangle(addP.x, addP.y,
addBounds.width, addBounds.height);
}
else {
add = addBounds;
}
bounds = bounds.union(add);
}
if (!bounds.contains(cursor)) {
hideToolTip(toolShell);
ScheduledFuture<?> closeTimer = closeTimerRef.get();
if (closeTimer != null)
closeTimer.cancel(true);
}
}
}
else {
ScheduledFuture<?> closeTimer = closeTimerRef.get();
if (closeTimer != null)
closeTimer.cancel(true);
}
}
});
}
else {
// disposed -> cancel timer
ScheduledFuture<?> closeTimer = closeTimerRef.get();
if (closeTimer != null)
closeTimer.cancel(true);
}
}
}, 2 * HOVER_DELAY, 1000, TimeUnit.MILLISECONDS);
closeTimerRef.set(closeTimer);
toolShell.setVisible(true);
toolShell.setFocus();
return toolShell;
} catch (SWTError err) {
log.error(err.getMessage(), err);
return null;
}
}
/**
* Hide the tool tip
*
* @param shell the tip shell
*/
public static void hideToolTip(Shell shell) {
if (shell != null && !shell.isDisposed()) {
shell.close();
shell.dispose();
}
}
/**
* Determines if the tool tip is visible
*
* @param shell the tip shell
*
* @return if the tool tip is visible
*/
public static boolean toolTipVisible(Shell shell) {
if (shell != null && !shell.isDisposed()) {
return true;
}
return false;
}
/**
* @return the heightAdjustment
*/
public int getHeightAdjustment() {
return heightAdjustment;
}
/**
* @param heightAdjustment the heightAdjustment to set
*/
public void setHeightAdjustment(int heightAdjustment) {
this.heightAdjustment = heightAdjustment;
}
}