/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2003 Arnaud Farine * * Copyright (C) 2003-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine 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. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.ui; import totalcross.sys.*; import totalcross.ui.event.*; import totalcross.ui.font.*; import totalcross.ui.gfx.*; /** * Displays a tooltip when user holds the pen in the control. * On Windows and Linux desktop, the tooltip is also shown when the mouse stays over a control. * <br><br> * The default popup delay is 1000ms and the amount of time it will be displayed is 2000 ms; * a pen up also hides the tip. * <p> * You can change some properties of the tooltip: borderColor, insideGap, distX, distY, * millisDelay, millisDisplay. See their javadocs for details. * <p> * Example:<br> * <pre> * ToolTip.distX = 10; // 0 by default * ToolTip.distY = 4; // 0 by default * ToolTip.insideGap = 8; // 4 by default * Button b; * add(b = new Button("Hello Tooltip!"),CENTER,BOTTOM);<br> * ToolTip t = new ToolTip(b, "Hi, this is a button");<br> * t.borderColor = 0x00FF00; // -1 (none) by default * t.millisDelay = 500; // 1000 by default * t.millisDisplay = 4000; // 2000 by default * t.setBackColor(Color.getRGB(250,0,0)); // same as control's container by default * </pre> * The tooltip can have multiple lines, just split them using the char '\n' in your message.<br> * Example : * <pre> * ToolTip t = new ToolTip(control, "Hi!\nIt's Me"); * </pre> * A ControlEvent.PRESSED event will be dispatched to the attached control right before * the text is shown. You can then set the tip to a new value using setText; setting to an empty * string will disable the tooltip at that moment. Calling setControlRect also changes the rectangle * around which the tooltip will be displayed. * * In Android UI, the ToolTip is displayed with a round border. **/ public class ToolTip extends Label implements PenListener, MouseListener { private static PenEvent outside; static { outside = new PenEvent(); outside.type = PenEvent.PEN_UP; } // attributes private Control control; // The control which supports the tip private boolean shown; // guich@tc120_22 // timers private TimerEvent delayTimer, displayTimer; /** The gap between the border and the text. Common to all tooltips. */ public static int insideGap = 4; /** The x distance between the tip and the control, 0 by default. Common to all tooltips. */ public static int distX; /** The y distance between the tip and the control, 0 by default. Common to all tooltips. */ public static int distY; /** The amount of time that the pen must be down until the tip pops up (by default, 1000ms) */ public int millisDelay = 1000; /** The amount of time that the tip will be shown (by default, 2000ms) */ public int millisDisplay = 2000; /** The border color. By default, it is -1 and no border is shown */ public int borderColor = -1; // the color around the rect private String msg0, msg0lines[]; /** * Constructor * @param control the control which supports the tip. * If null, you must call <code>setControlRect</code> and <code>show</code> by your own. * @param message the message which will be written in the tip. You can * make multiLine, using \n character like in the Label control. **/ public ToolTip(Control control, String message) { super(message==null?"":message, LEFT); msg0 = super.text; if (msg0.indexOf('\n') != -1) msg0lines = Convert.tokenizeString(msg0,'\n'); setVisible(false); this.control = control; setBackForeColors(UIColors.tooltipBack,UIColors.tooltipFore); transparentBackground = uiAndroid; if (control != null) { control.addPenListener(this); // i must admit: listeners simplified a LOT the ToolTip class control.addMouseListener(this); control.onEventFirst = false; } focusTraversable = false; } /** Stop using mouse events to show the tooltip. * @since TotalCross 1.27 */ public void dontShowTipOnMouseEvents() { if (control != null) control.removeMouseListener(this); } /** Use this handy method to split the text in order to correctly fit the window. * Here's a sample: * <pre> * String msg = "a very long text that will be split to fit in the window"; * new ToolTip(control, ToolTip.split(msg, fm)); * </pre> * Make sure that fm will be the font's FontMetrics (if you plan to change the font * after calling the constructor). * @deprecated The split is done automatically * @since TotalCross 1.2 */ public static String split(String msg, FontMetrics fm) { return Convert.insertLineBreak(Settings.screenWidth-insideGap*2, fm, msg); } /** Change the control rect to the given one. By default, its used the absolute rectangle * of the control passed in the constructor. The placement of the tooltip will * be defined based on it, in a way that the control is not obscured. */ public void setControlRect(Rect r) { int xx,yy; Window w = control == null ? null : control.getParentWindow(); if (w == null) w = Window.getTopMost(); w.add(this); // guich@tc100b4_19: must always be (re)added to the parent container to make sure we will be the last control that will be painted Coord size = w.getSize(); int ww = msg0lines != null ? fm.getMaxWidth(msg0lines,0,msg0lines.length) : fm.stringWidth(msg0); // guich@tc120_2: moved to after w.add(this) if (ww > Settings.screenWidth) { super.setText(Convert.insertLineBreakBalanced(Settings.screenWidth * 9 / 10, fm, msg0)); ww = super.getMaxTextWidth(); } int hh = getPreferredHeight()+ insideGap; // can we place it below the control? if (r.y2()+distY+hh < size.y && r.height != 0) yy = r.y2()+distY-w.y; // guich@tc126_48: decrease window's y else yy = Math.max(0,r.y-hh-distY-w.y); // guich@tc126_48: decrease window's y // check if we don't overflow the width if ((r.x+ww+distX) > size.x) // afe@20030617 - If position + width>screen size. xx = size.x - ww - distX; else xx = r.x + distX - w.x; // guich@tc126_48: decrease window's x setRect(xx,yy,ww+(super.lines.length == 1 ? insideGap : fmH),hh,null,false); // careful: xx,yy are relative to window position, but the control's rect passed as parameter is ABSOLUTE } public void onEvent(Event e) { if (e.type == TimerEvent.TRIGGERED) { if (delayTimer != null && delayTimer.triggered) { removeTimer(delayTimer); delayTimer = null; if (control != null) { setControlRect(control.getAbsoluteRect()); control._onEvent(getPressedEvent(this)); // tell the parent that we'll popup } if (text.length() > 0) // only show if there's something to be shown show(); } else if (displayTimer != null && displayTimer.triggered) { hide(); if (control != null) // guich@tc120_22 postOutside(); } } } private void postOutside() { outside.absoluteX = outside.x = outside.absoluteY = outside.y = -(Settings.touchTolerance+1); // use a small value because some controls (like Grid) may behave incorrectly if we use big values outside.target = control; outside.consumed = false; control.postEvent(outside); } /** Shows the tooltip. * If you want to show the tooltip programatically, you must do something like: * <pre> * toolTip.setText(msg); * toolTip.setControlRect(lbCompany.getAbsoluteRect()); * toolTip.show(); * </pre> */ public void show() { displayTimer = addTimer(millisDisplay); setVisible(true); shown = true; Window.needsPaint = true; } /** Hides the tooltip. */ public void hide() { if (displayTimer != null) removeTimer(displayTimer); displayTimer = null; setVisible(false); Window.needsPaint = true; // guich@570_93 } public void mouseMove(MouseEvent e) { } public void mouseIn(MouseEvent e) { penDown(e); } public void mouseOut(MouseEvent e) { penUp(e); } public void penDown(PenEvent e) { delayTimer = addTimer(millisDelay); shown = false; } public void penUp(PenEvent e) { if (e == outside) return; if (isVisible()) // tooltip not removed yet? { setVisible(false); Window.needsPaint = true; // guich@570_93 } if (delayTimer != null || displayTimer != null) // guich@503_1: if the user removes the pen before the popup, do not show the tooltip anymore - guich@570_93: if the user removes the pen before timeout, remove the timers as well { if (shown && control != null) // guich@tc120_22 postOutside(); if (displayTimer != null) removeTimer(displayTimer); if (delayTimer != null) removeTimer(delayTimer); displayTimer = delayTimer = null; } if (shown && e != null) e.consumed = true; } public void onPaint(Graphics g) { if (displayTimer != null) // only draw if we're visible { int dx = (insideGap>>1)+1; int dy = (dx>>1)-2; g.backColor = backColor; if (uiAndroid) g.fillRoundRect(0,0,width,height,fmH/2); else g.fillRect(0,0,dx,height); // since g will be translated, fill the part that the Label won't // draw the label, taking care to not overwrite the border g.translate(dx,dy); super.onPaint(g); g.translate(-dx,-dy); // draw the border if (borderColor != -1) { g.foreColor = borderColor; if (uiAndroid) g.drawRoundRect(0,0,width,height,fmH/2); else g.drawRect(0,0,width,height); } } } public void penDrag(DragEvent e) { } public void penDragEnd(DragEvent e) { } public void penDragStart(DragEvent e) { } public void reposition() { hide(); super.reposition(); } public void mouseWheel(MouseEvent e) { } }