001: /*
002: * MyGWT Widget Library
003: * Copyright(c) 2007, MyGWT.
004: * licensing@mygwt.net
005: *
006: * http://mygwt.net/license
007: */
008: package net.mygwt.ui.client.widget;
009:
010: import net.mygwt.ui.client.Events;
011: import net.mygwt.ui.client.MyDOM;
012: import net.mygwt.ui.client.event.BaseEvent;
013: import net.mygwt.ui.client.event.Listener;
014: import net.mygwt.ui.client.util.Format;
015: import net.mygwt.ui.client.util.Markup;
016: import net.mygwt.ui.client.util.Rectangle;
017:
018: import com.google.gwt.user.client.DOM;
019: import com.google.gwt.user.client.Element;
020: import com.google.gwt.user.client.Event;
021: import com.google.gwt.user.client.EventPreview;
022: import com.google.gwt.user.client.Timer;
023: import com.google.gwt.user.client.Window;
024: import com.google.gwt.user.client.ui.RootPanel;
025: import com.google.gwt.user.client.ui.Widget;
026:
027: /**
028: * Displays a information popup when a user hovers over a widget.
029: *
030: * <dl>
031: * <dt><b>Events:</b></dt>
032: *
033: * <dd><b>BeforeOpen</b> : (widget)<br>
034: * <div>Fires before the tooltip is displayed. Listeners can set the
035: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
036: * <ul>
037: * <li>widget : this</li>
038: * </ul>
039: * </dd>
040: *
041: * <dd><b>Open</b> : (widget)<br>
042: * <div>Fires after a tooltip is shown.</div>
043: * <ul>
044: * <li>widget : this</li>
045: * </ul>
046: * </dd>
047: *
048: * <dd><b>BeforeClose</b> : (widget)<br>
049: * <div>Fires before the tooltip is hidden. Listeners can set the
050: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
051: * <ul>
052: * <li>widget : this</li>
053: * </ul>
054: * </dd>
055: *
056: * <dd><b>Close</b> : (widget)<br>
057: * <div>Fires after a tooltip is hidden.</div>
058: * <ul>
059: * <li>widget : this</li>
060: * </ul>
061: * </dd>
062: *
063: * <dl>
064: * <dt><b>CSS Styles:</b></dt>
065: * <dd>.my-tooltip { the tool tip }</dd>
066: * <dd>.my-tooltip .my-tooltip-title { title text }</dd>
067: * <dd>.my-tooltip .my-tooltip-text { body text }</dd>
068: * </dl>
069: */
070: public class ToolTip extends Component implements Listener {
071:
072: private static WidgetContainer tooltip;
073: private static EventPreview preview;
074: private static int lastX, lastY;
075: private static boolean hovering, showing;
076: private static Timer timer;
077:
078: static {
079: timer = new Timer() {
080: public void run() {
081: if (hovering) {
082: ToolTip tip = (ToolTip) tooltip.getData("current");
083: if (tip.title == null && tip.text == null) {
084: return;
085: }
086: tip.showAt(lastX, lastY);
087: }
088: }
089: };
090: tooltip = new WidgetContainer();
091: tooltip.setLayoutOnChange(true);
092: DOM.setStyleAttribute(tooltip.getElement(), "position",
093: "absolute");
094: MyDOM.setLeftTop(tooltip.getElement(), -1000, -1000);
095: RootPanel.get().add(tooltip);
096:
097: preview = new EventPreview() {
098:
099: public boolean onEventPreview(Event event) {
100: Element target = DOM.eventGetTarget(event);
101:
102: ToolTip tip = (ToolTip) tooltip.getData("current");
103: if (tip.trackMouse) {
104: tip.setToolTipPosition(DOM.eventGetClientX(event),
105: DOM.eventGetClientY(event));
106: }
107:
108: Widget source = (Widget) tooltip.getData("source");
109: if (target == null
110: || !DOM.isOrHasChild(source.getElement(),
111: target)) {
112: hovering = false;
113: doHide();
114: }
115: return true;
116: }
117:
118: };
119: }
120:
121: /**
122: * Hides the tool tip.
123: */
124: protected static void doHide() {
125: DOM.removeEventPreview(preview);
126: timer.cancel();
127:
128: showing = false;
129: hovering = false;
130:
131: Component tip = (Component) tooltip.getData("current");
132: if (tip != null) {
133: tip.fireEvent(Events.Close);
134: }
135:
136: tooltip.setData("current", null);
137: tooltip.setData("source", null);
138: tooltip.setVisible(false);
139:
140: }
141:
142: private int delay = 700;
143: private boolean hideOnClick = true;
144: private boolean trackMouse = false;
145: private int xOffset = 5;
146: private int yOffset = 15;
147: private Component source;
148: private Element contentElem;
149: private String html = "<div class=my-tooltip-title>{0}</div><div class=my-tooltip-text>{1}</div>";
150: private String title, text;
151: private Element titleElem, textElem;
152: private Element mouseOver;
153:
154: /**
155: * Creates a new tooltip.
156: *
157: * @param source the source widget
158: */
159: public ToolTip(Component source) {
160: this .source = source;
161: MyDOM.addEventsSunk(source.getElement(), Event.MOUSEEVENTS);
162: source.addListener(Events.MouseOver, this );
163: source.addListener(Events.MouseOut, this );
164: source.addListener(Events.Click, this );
165: }
166:
167: /**
168: * Creates a new tooltip.
169: *
170: * @param source the source widget
171: * @param mouseOver the mouse over element
172: */
173: public ToolTip(Component source, Element mouseOver) {
174: this (source);
175: this .mouseOver = mouseOver;
176: }
177:
178: /**
179: * Returns the delay in milliseconds.
180: *
181: * @return the delay
182: */
183: public int getDelay() {
184: return delay;
185: }
186:
187: /**
188: * Returns <code>true</code> if hide on click is enabled.
189: *
190: * @return the hide on click state
191: */
192: public boolean getHideOnClick() {
193: return hideOnClick;
194: }
195:
196: /**
197: * Returns the tool tip source.
198: *
199: * @return the source
200: */
201: public Component getSource() {
202: return source;
203: }
204:
205: /**
206: * Returns <code>true</code> if track mouse is enabled.
207: *
208: * @return the track mouse state
209: */
210: public boolean getTrackMouse() {
211: return trackMouse;
212: }
213:
214: /**
215: * Returns the x offset.
216: *
217: * @return the x offset
218: */
219: public int getXOffset() {
220: return xOffset;
221: }
222:
223: /**
224: * Returns the y offset.
225: *
226: * @return the y offset
227: */
228: public int getYOffset() {
229: return yOffset;
230: }
231:
232: public void handleEvent(BaseEvent be) {
233: if (be.type == Events.MouseOver || be.type == Events.MouseOut) {
234: try {
235: lastX = be.getClientX();
236: lastY = be.getClientY();
237: } catch (Exception e) {
238: // TODO: why exception when paging
239: }
240:
241: if (isEnabled()) {
242: Element elem = mouseOver != null ? mouseOver : source
243: .getElement();
244: Rectangle rect = MyDOM.getBounds(elem);
245:
246: if (rect.contains(lastX, lastY)) {
247: if (!hovering) {
248: onMouseOver(be);
249: }
250: } else {
251: doHide();
252: }
253: }
254:
255: }
256:
257: if (hideOnClick && be.type == Events.Click) {
258: doHide();
259: }
260: }
261:
262: /**
263: * Hides the tool tip.
264: */
265: public void hide() {
266: if (!fireEvent(Events.BeforeClose)) {
267: return;
268: }
269: doHide();
270: }
271:
272: /**
273: * Returns whether or not the tooltip is currently showing.
274: *
275: * @return true if the tooltip is showing
276: */
277: public boolean isShowing() {
278: return showing;
279: }
280:
281: public void setDelay(int delay) {
282: this .delay = delay;
283: }
284:
285: /**
286: * Specifies if the tool tip should be hidden when the source widget is
287: * clicked. Default value is <code>true</code>.
288: *
289: * @param hideOnClick <code>true</code> to enable
290: */
291: public void setHideOnClick(boolean hideOnClick) {
292: this .hideOnClick = hideOnClick;
293: }
294:
295: /**
296: * Sets the tool tip's title and text.
297: *
298: * @param title the title
299: * @param text the text
300: */
301: public void setText(String title, String text) {
302: this .title = title;
303: this .text = text;
304: if (rendered) {
305: if (title != null && !title.equals("")) {
306: MyDOM.setInnerHTML(titleElem, title);
307: MyDOM.setVisible(titleElem, true);
308: } else {
309: MyDOM.setVisible(titleElem, false);
310: }
311: if (text != null && !text.equals("")) {
312: MyDOM.setInnerHTML(textElem, text);
313: }
314: }
315: }
316:
317: public void disable() {
318: super .disable();
319: setVisible(false);
320: }
321:
322: public void enable() {
323: super .enable();
324: setVisible(true);
325: }
326:
327: public void setToolTipPosition(int x, int y) {
328: MyDOM
329: .setLeftTop(tooltip.getElement(), x + xOffset, y
330: + yOffset);
331:
332: Rectangle r = MyDOM.getBounds(tooltip.getElement());
333:
334: int clientHeight = Window.getClientHeight()
335: + MyDOM.getBodyScrollTop();
336: int clientWidth = Window.getClientWidth()
337: + MyDOM.getBodyScrollLeft();
338:
339: x = r.x;
340: y = r.y;
341:
342: if (y + r.height > clientHeight) {
343: y = clientHeight - r.height - 30;
344: MyDOM.setTop(tooltip.getElement(), y);
345: }
346: if (x + r.width > clientWidth) {
347: x = clientWidth - r.width - 4;
348: MyDOM.setLeft(tooltip.getElement(), x);
349: }
350: }
351:
352: /**
353: * Specifies if the tool tip should move with the current cursor location.
354: * Default value is <code>false</code>.
355: *
356: * @param trackMouse <code>true</code> to enable mouse tracking
357: */
358: public void setTrackMouse(boolean trackMouse) {
359: this .trackMouse = trackMouse;
360: }
361:
362: /**
363: * Sets the x offset amount. Default value is 5.
364: *
365: * @param offset the offset
366: */
367: public void setXOffset(int offset) {
368: xOffset = offset;
369: }
370:
371: /**
372: * Sets the y offset amount. Default value is 15.
373: *
374: * @param offset the y offset
375: */
376: public void setYOffset(int offset) {
377: yOffset = offset;
378: }
379:
380: /**
381: * Displays the tool tip at the specified location. Offsets will be applie to
382: * values.
383: *
384: * @param x the x coordinate
385: * @param y the y coordinate
386: */
387: public void showAt(int x, int y) {
388: if (showing || !isEnabled()) {
389: return;
390: }
391: BaseEvent be = new BaseEvent();
392: be.x = x;
393: be.y = y;
394: if (!fireEvent(Events.BeforeOpen, be)) {
395: return;
396: }
397: showing = true;
398: onShowToolTip(x, y);
399: }
400:
401: protected void onMouseOut(BaseEvent be) {
402: if (hovering) {
403: hovering = false;
404: tooltip.setData("current", null);
405: timer.cancel();
406: }
407: }
408:
409: protected void onMouseOver(BaseEvent be) {
410: if (!hovering) {
411: DOM.setIntStyleAttribute(tooltip.getElement(), "zIndex",
412: MyDOM.getZIndex());
413: hovering = true;
414: tooltip.setData("current", this );
415: timer.schedule(delay);
416: } else {
417:
418: }
419: }
420:
421: protected void onRender() {
422: String html = Format.substitute(Markup.BOX, "my-tooltip");
423: setElement(MyDOM.create(html));
424: contentElem = MyDOM.findChild("my-tooltip-mc", getElement());
425:
426: if (title == null)
427: title = "";
428: if (text == null)
429: text = "";
430: String temp = Format.substitute(this .html, new String[] {
431: this .title, this .text });
432: MyDOM.setInnerHTML(contentElem, temp);
433:
434: titleElem = MyDOM.findChild("my-tooltip-title", getElement());
435: textElem = MyDOM.findChild("my-tooltip-text", getElement());
436: }
437:
438: protected void onShowToolTip(int x, int y) {
439: tooltip.removeAll();
440: tooltip.add(this );
441: tooltip.setVisible(true);
442: tooltip.setData("current", this );
443: tooltip.setData("source", source);
444: showing = true;
445: setToolTipPosition(x, y);
446:
447: DOM.addEventPreview(preview);
448:
449: fireEvent(Events.Open);
450: }
451:
452: }
|