/******************************************************************************* * Copyright (c) 2000, 2009 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.swt.widgets; import org.eclipse.swt.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.carbon.HMHelpContentRec; import org.eclipse.swt.internal.carbon.OS; import org.eclipse.swt.events.*; /** * Instances of this class represent popup windows that are used * to inform or warn the user. * <p> * <dl> * <dt><b>Styles:</b></dt> * <dd>BALLOON, ICON_ERROR, ICON_INFORMATION, ICON_WARNING</dd> * <dt><b>Events:</b></dt> * <dd>Selection</dd> * </dl> * </p><p> * Note: Only one of the styles ICON_ERROR, ICON_INFORMATION, * and ICON_WARNING may be specified. * </p><p> * IMPORTANT: This class is <em>not</em> intended to be subclassed. * </p> * * @see <a href="http://www.eclipse.org/swt/snippets/#tooltips">Tool Tips snippets</a> * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a> * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> * * @since 3.2 * @noextend This class is not intended to be subclassed by clients. */ public class ToolTip extends Widget { Shell parent, tip; int x, y; boolean spikeAbove, autohide; TextLayout layoutText, layoutMessage; String text, message; TrayItem item; Region region; Font boldFont; Runnable runnable; int helpString; static final int BORDER = 5; static final int PADDING = 5; static final int INSET = 4; static final int TIP_HEIGHT = 20; static final int IMAGE_SIZE = 16; static final int DELAY = 10000; /** * Constructs a new instance of this class given its parent * and a style value describing its behavior and appearance. * <p> * The style value is either one of the style constants defined in * class <code>SWT</code> which is applicable to instances of this * class, or must be built by <em>bitwise OR</em>'ing together * (that is, using the <code>int</code> "|" operator) two or more * of those <code>SWT</code> style constants. The class description * lists the style constants that are applicable to the class. * Style bits are also inherited from superclasses. * </p> * * @param parent a composite control which will be the parent of the new instance (cannot be null) * @param style the style of control to construct * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> * </ul> * * @see SWT#BALLOON * @see SWT#ICON_ERROR * @see SWT#ICON_INFORMATION * @see SWT#ICON_WARNING * @see Widget#checkSubclass * @see Widget#getStyle */ public ToolTip (Shell parent, int style) { super (parent, checkStyle (style)); this.parent = parent; createWidget (); parent.addToolTip (this); } static int checkStyle (int style) { int mask = SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING; if ((style & mask) == 0) return style; return checkBits (style, SWT.ICON_INFORMATION, SWT.ICON_WARNING, SWT.ICON_ERROR, 0, 0, 0); } /** * Adds the listener to the collection of listeners who will * be notified when the receiver is selected by the user, by sending * it one of the messages defined in the <code>SelectionListener</code> * interface. * <p> * <code>widgetSelected</code> is called when the receiver is selected. * <code>widgetDefaultSelected</code> is not called. * </p> * * @param listener the listener which should be notified when the receiver is selected by the user * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see SelectionListener * @see #removeSelectionListener * @see SelectionEvent */ public void addSelectionListener (SelectionListener listener) { checkWidget (); if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); TypedListener typedListener = new TypedListener (listener); addListener (SWT.Selection,typedListener); addListener (SWT.DefaultSelection,typedListener); } void configure () { Display display = parent.getDisplay (); int x = this.x; int y = this.y; if (x == -1 || y == -1) { Point point; if (item != null) { point = item.getLocation (); } else { point = display.getCursorLocation (); } x = point.x; y = point.y; } Monitor monitor = parent.getMonitor (); Rectangle dest = monitor.getBounds (); Point size = getSize (dest.width / 4); int w = size.x; int h = size.y; int t = (style & SWT.BALLOON) != 0 ? TIP_HEIGHT : 0; int i = (style & SWT.BALLOON) != 0 ? 17 : 0; tip.setSize (w, h + t); int [] polyline; spikeAbove = dest.height >= y + size.y + t; if (dest.width >= x + size.x) { if (dest.height >= y + size.y + t) { polyline = new int [] { 0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t, 5, 1+t, 5, t, 16, t, 16, 0, 35, t, w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t, w, h-5+t, w-1, h-5+t, w-1, h-3+t, w-2, h-3+t, w-2, h-2+t, w-3, h-2+t, w-3, h-1+t, w-5, h-1+t, w-5, h+t, 5, h+t, 5, h-1+t, 3, h-1+t, 3, h-2+t, 2, h-2+t, 2, h-3+t, 1, h-3+t, 1, h-5+t, 0, h-5+t, 0, 5+t}; tip.setLocation (Math.max (0, x - i), y); } else { polyline = new int [] { 0, 5, 1, 5, 1, 3, 3, 1, 5, 1, 5, 0, w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5, w, h-5, w-1, h-5, w-1, h-3, w-2, h-3, w-2, h-2, w-3, h-2, w-3, h-1, w-5, h-1, w-5, h, 35, h, 16, h+t, 16, h, 5, h, 5, h-1, 3, h-1, 3, h-2, 2, h-2, 2, h-3, 1, h-3, 1, h-5, 0, h-5, 0, 5}; tip.setLocation (Math.max (0, x - i), y - size.y - t); } } else { if (dest.height >= y + size.y + t) { polyline = new int [] { 0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t, 5, 1+t, 5, t, w-35, t, w-16, 0, w-16, t, w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t, w, h-5+t, w-1, h-5+t, w-1, h-3+t, w-2, h-3+t, w-2, h-2+t, w-3, h-2+t, w-3, h-1+t, w-5, h-1+t, w-5, h+t, 5, h+t, 5, h-1+t, 3, h-1+t, 3, h-2+t, 2, h-2+t, 2, h-3+t, 1, h-3+t, 1, h-5+t, 0, h-5+t, 0, 5+t}; tip.setLocation (Math.min (dest.width - size.x, x - size.x + i), y); } else { polyline = new int [] { 0, 5, 1, 5, 1, 3, 3, 1, 5, 1, 5, 0, w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5, w, h-5, w-1, h-5, w-1, h-3, w-2, h-3, w-2, h-2, w-3, h-2, w-3, h-1, w-5, h-1, w-5, h, w-16, h, w-16, h+t, w-35, h, 5, h, 5, h-1, 3, h-1, 3, h-2, 2, h-2, 2, h-3, 1, h-3, 1, h-5, 0, h-5, 0, 5}; tip.setLocation (Math.min (dest.width - size.x, x - size.x + i), y - size.y - t); } } if ((style & SWT.BALLOON) != 0) { if (region != null) region.dispose (); region = new Region (display); region.add (polyline); tip.setRegion (region); } } void createWidget () { super.createWidget (); this.autohide = true; x = y = -1; text = ""; message = ""; } void disposeTip () { if (tip != null) tip.dispose (); tip = null; if (region != null) region.dispose (); region = null; if (layoutText != null) layoutText.dispose (); layoutText = null; if (layoutMessage != null) layoutMessage.dispose (); layoutMessage = null; if (boldFont != null) boldFont.dispose (); boldFont = null; } /** * Returns <code>true</code> if the receiver is automatically * hidden by the platform, and <code>false</code> otherwise. * * @return the receiver's auto hide state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * */ public boolean getAutoHide () { checkWidget (); return autohide; } /** * Returns the receiver's message, which will be an empty * string if it has never been set. * * @return the receiver's message * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public String getMessage () { checkWidget (); return message; } /** * Returns the receiver's parent, which must be a <code>Shell</code>. * * @return the receiver's parent * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public Shell getParent () { checkWidget (); return parent; } Point getSize (int maxWidth) { int textWidth = 0, messageWidth = 0; if (layoutText != null) { layoutText.setWidth (-1); textWidth = layoutText.getBounds ().width; } if (layoutMessage != null) { layoutMessage.setWidth (-1); messageWidth = layoutMessage.getBounds ().width; } int messageTrim = 2 * INSET + 2 * BORDER + 2 * PADDING; boolean hasImage = layoutText != null && (style & SWT.BALLOON) != 0 && (style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING)) != 0; int textTrim = messageTrim + (hasImage ? IMAGE_SIZE : 0); int width = Math.min (maxWidth, Math.max (textWidth + textTrim, messageWidth + messageTrim)); int textHeight = 0, messageHeight = 0; if (layoutText != null) { layoutText.setWidth (maxWidth - textTrim); textHeight = layoutText.getBounds ().height; } if (layoutMessage != null) { layoutMessage.setWidth (maxWidth - messageTrim); messageHeight = layoutMessage.getBounds ().height; } int height = 2 * BORDER + 2 * PADDING + messageHeight; if (layoutText != null) height += Math.max (IMAGE_SIZE, textHeight) + 2 * PADDING; return new Point (width, height); } /** * Returns the receiver's text, which will be an empty * string if it has never been set. * * @return the receiver's text * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public String getText () { checkWidget (); return text; } /** * Returns <code>true</code> if the receiver is visible, and * <code>false</code> otherwise. * <p> * If one of the receiver's ancestors is not visible or some * other condition makes the receiver not visible, this method * may still indicate that it is considered visible even though * it may not actually be showing. * </p> * * @return the receiver's visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getVisible () { checkWidget (); if (tip != null) return tip.getVisible (); if (display.helpWidget == this) { int window = OS.FrontWindow (); int [] windowClass = new int [1]; OS.GetWindowClass (window, windowClass); return windowClass [0] == OS.kHelpWindowClass; } return false; } /** * Returns <code>true</code> if the receiver is visible and all * of the receiver's ancestors are visible and <code>false</code> * otherwise. * * @return the receiver's visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getVisible */ public boolean isVisible () { checkWidget (); return getVisible (); } void onMouseDown (Event event) { sendSelectionEvent (SWT.Selection, null, true); setVisible (false); } void onPaint (Event event) { GC gc = event.gc; int x = BORDER + PADDING; int y = BORDER + PADDING; if ((style & SWT.BALLOON) != 0 && spikeAbove) y += TIP_HEIGHT; if (layoutText != null) { int id = style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING); if ((style & SWT.BALLOON) != 0 && id != 0) { Display display = getDisplay (); Image image = display.getSystemImage (id); Rectangle rect = image.getBounds (); gc.drawImage (image, 0, 0, rect.width, rect.height, x, y, IMAGE_SIZE, IMAGE_SIZE); x += IMAGE_SIZE; } x += INSET; layoutText.draw (gc, x, y); y += 2 * PADDING + Math.max (IMAGE_SIZE, layoutText.getBounds ().height); } if (layoutMessage != null) { x = BORDER + PADDING + INSET; layoutMessage.draw (gc, x, y); } } void releaseWidget () { super.releaseWidget (); if (parent != null) parent.removeTooTip (this); if (runnable != null) { Display display = getDisplay (); display.timerExec (-1, runnable); } runnable = null; disposeTip (); if (helpString != 0) OS.CFRelease (helpString); helpString = 0; } /** * Removes the listener from the collection of listeners who will * be notified when the receiver is selected by the user. * * @param listener the listener which should no longer be notified * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see SelectionListener * @see #addSelectionListener */ public void removeSelectionListener (SelectionListener listener) { checkWidget(); if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); if (eventTable == null) return; eventTable.unhook (SWT.Selection, listener); eventTable.unhook (SWT.DefaultSelection,listener); } /** * Makes the receiver hide automatically when <code>true</code>, * and remain visible when <code>false</code>. * * @param autoHide the auto hide state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see #getVisible * @see #setVisible */ public void setAutoHide (boolean autohide) { checkWidget (); this.autohide = autohide; //TODO - update when visible } /** * Sets the location of the receiver, which must be a tooltip, * to the point specified by the arguments which are relative * to the display. * <p> * Note that this is different from most widgets where the * location of the widget is relative to the parent. * </p> * * @param x the new x coordinate for the receiver * @param y the new y coordinate for the receiver * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setLocation (int x, int y) { checkWidget (); if (this.x == x && this.y == y) return; this.x = x; this.y = y; if (tip != null && tip.getVisible ()) configure (); } /** * Sets the location of the receiver, which must be a tooltip, * to the point specified by the argument which is relative * to the display. * <p> * Note that this is different from most widgets where the * location of the widget is relative to the parent. * </p><p> * Note that the platform window manager ultimately has control * over the location of tooltips. * </p> * * @param location the new location for the receiver * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the point is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setLocation (Point location) { checkWidget (); if (location == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); setLocation (location.x, location.y); } /** * Sets the receiver's message. * * @param string the new message * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the text is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setMessage (String string) { checkWidget (); if (string == null) error (SWT.ERROR_NULL_ARGUMENT); message = string; if (tip != null) { layoutMessage.setText (string); if (tip.getVisible ()) configure (); } } /** * Sets the receiver's text. * * @param string the new text * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the text is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setText (String string) { checkWidget (); if (string == null) error (SWT.ERROR_NULL_ARGUMENT); text = string; if (tip != null) { layoutText.setText (string); TextStyle style = new TextStyle (boldFont, null, null); layoutText.setStyle (style, 0, string.length ()); if (tip.getVisible ()) configure (); } } /** * Marks the receiver as visible if the argument is <code>true</code>, * and marks it invisible otherwise. * <p> * If one of the receiver's ancestors is not visible or some * other condition makes the receiver not visible, marking * it visible may not actually cause it to be displayed. * </p> * * @param visible the new visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setVisible (boolean visible) { checkWidget (); if (helpString != 0) OS.CFRelease (helpString); helpString = 0; if (runnable != null) display.timerExec (-1, runnable); runnable = null; if (visible) { OS.HMHideTag (); display.helpWidget = this; if (!autohide || (style & SWT.BALLOON) != 0) { // Show tip if (tip == null) { tip = new Shell (parent, SWT.ON_TOP | SWT.NO_TRIM | SWT.TOOL); Color background = display.getSystemColor (SWT.COLOR_INFO_BACKGROUND); tip.setBackground (background); Listener listener = new Listener () { public void handleEvent (Event event) { switch (event.type) { case SWT.Paint: onPaint (event); break; case SWT.MouseDown: onMouseDown (event); break; } } }; tip.addListener (SWT.Paint, listener); tip.addListener (SWT.MouseDown, listener); layoutText = new TextLayout (display); layoutText.setText (text); Font font = display.getSystemFont (); FontData data = font.getFontData () [0]; boldFont = new Font (display, data.getName (), data.getHeight (), SWT.BOLD); TextStyle style = new TextStyle (boldFont, null, null); layoutText.setStyle (style, 0, text.length ()); layoutMessage = new TextLayout (display); layoutMessage.setText(message); } configure (); tip.setVisible (true); if (autohide) { runnable = new Runnable () { public void run () { if (!isDisposed ()) setVisible (false); } }; display.timerExec(DELAY, runnable); } } else { // Show HMTag if (tip != null) disposeTip (); if (x == -1 || y == -1) { Point point; if (item != null) { point = item.getLocation (); x = point.x; y = point.y; } else { org.eclipse.swt.internal.carbon.Point pt = new org.eclipse.swt.internal.carbon.Point (); OS.GetGlobalMouse (pt); x = pt.h; y = pt.v; } } StringBuffer string = new StringBuffer (text); if (text.length () > 0) string.append ("\n\n"); string.append (message); char [] buffer = new char [string.length ()]; string.getChars (0, buffer.length, buffer, 0); helpString = OS.CFStringCreateWithCharacters (OS.kCFAllocatorDefault, buffer, buffer.length); HMHelpContentRec helpContent = new HMHelpContentRec (); helpContent.tagSide = (short) OS.kHMAbsoluteCenterAligned; helpContent.absHotRect_left = (short)x; helpContent.absHotRect_top = (short)y; helpContent.absHotRect_right = (short)(x + 1); helpContent.absHotRect_bottom = (short) (y + 1); helpContent.content0_contentType = OS.kHMCFStringContent; helpContent.content0_tagCFString = helpString; helpContent.content1_contentType = OS.kHMCFStringContent; helpContent.content1_tagCFString = helpString; OS.HMDisplayTag(helpContent); } } else { if (display.helpWidget == this) { display.helpWidget = null; OS.HMHideTag (); if (tip != null) tip.setVisible (false); } } } }