You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VTooltip.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. /*
  2. * Copyright 2011 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client;
  17. import com.google.gwt.event.dom.client.ClickEvent;
  18. import com.google.gwt.event.dom.client.ClickHandler;
  19. import com.google.gwt.event.dom.client.KeyDownEvent;
  20. import com.google.gwt.event.dom.client.KeyDownHandler;
  21. import com.google.gwt.event.dom.client.MouseMoveEvent;
  22. import com.google.gwt.event.dom.client.MouseMoveHandler;
  23. import com.google.gwt.user.client.DOM;
  24. import com.google.gwt.user.client.Element;
  25. import com.google.gwt.user.client.Event;
  26. import com.google.gwt.user.client.Timer;
  27. import com.google.gwt.user.client.Window;
  28. import com.google.gwt.user.client.ui.FlowPanel;
  29. import com.google.gwt.user.client.ui.RootPanel;
  30. import com.google.gwt.user.client.ui.Widget;
  31. import com.vaadin.client.ui.VOverlay;
  32. /**
  33. * TODO open for extension
  34. */
  35. public class VTooltip extends VOverlay {
  36. private static final String CLASSNAME = "v-tooltip";
  37. private static final int MARGIN = 4;
  38. public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN
  39. | Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONMOUSEMOVE
  40. | Event.ONCLICK;
  41. protected static final int MAX_WIDTH = 500;
  42. private static final int QUICK_OPEN_TIMEOUT = 1000;
  43. private static final int CLOSE_TIMEOUT = 300;
  44. private static final int OPEN_DELAY = 750;
  45. private static final int QUICK_OPEN_DELAY = 100;
  46. VErrorMessage em = new VErrorMessage();
  47. Element description = DOM.createDiv();
  48. private boolean closing = false;
  49. private boolean opening = false;
  50. private ApplicationConnection ac;
  51. // Open next tooltip faster. Disabled after 2 sec of showTooltip-silence.
  52. private boolean justClosed = false;
  53. public VTooltip(ApplicationConnection client) {
  54. super(false, false, true);
  55. ac = client;
  56. setStyleName(CLASSNAME);
  57. FlowPanel layout = new FlowPanel();
  58. setWidget(layout);
  59. layout.add(em);
  60. DOM.setElementProperty(description, "className", CLASSNAME + "-text");
  61. DOM.appendChild(layout.getElement(), description);
  62. setSinkShadowEvents(true);
  63. }
  64. /**
  65. * Show a popup containing the information in the "info" tooltip
  66. *
  67. * @param info
  68. */
  69. private void show(TooltipInfo info) {
  70. boolean hasContent = false;
  71. if (info.getErrorMessage() != null) {
  72. em.setVisible(true);
  73. em.updateMessage(info.getErrorMessage());
  74. hasContent = true;
  75. } else {
  76. em.setVisible(false);
  77. }
  78. if (info.getTitle() != null && !"".equals(info.getTitle())) {
  79. DOM.setInnerHTML(description, info.getTitle());
  80. DOM.setStyleAttribute(description, "display", "");
  81. hasContent = true;
  82. } else {
  83. DOM.setInnerHTML(description, "");
  84. DOM.setStyleAttribute(description, "display", "none");
  85. }
  86. if (hasContent) {
  87. // Issue #8454: With IE7 the tooltips size is calculated based on
  88. // the last tooltip's position, causing problems if the last one was
  89. // in the right or bottom edge. For this reason the tooltip is moved
  90. // first to 0,0 position so that the calculation goes correctly.
  91. setPopupPosition(0, 0);
  92. setPopupPositionAndShow(new PositionCallback() {
  93. @Override
  94. public void setPosition(int offsetWidth, int offsetHeight) {
  95. if (offsetWidth > MAX_WIDTH) {
  96. setWidth(MAX_WIDTH + "px");
  97. // Check new height and width with reflowed content
  98. offsetWidth = getOffsetWidth();
  99. offsetHeight = getOffsetHeight();
  100. }
  101. int x = tooltipEventMouseX + 10 + Window.getScrollLeft();
  102. int y = tooltipEventMouseY + 10 + Window.getScrollTop();
  103. if (x + offsetWidth + MARGIN - Window.getScrollLeft() > Window
  104. .getClientWidth()) {
  105. x = Window.getClientWidth() - offsetWidth - MARGIN;
  106. }
  107. if (y + offsetHeight + MARGIN - Window.getScrollTop() > Window
  108. .getClientHeight()) {
  109. y = tooltipEventMouseY - 5 - offsetHeight;
  110. if (y - Window.getScrollTop() < 0) {
  111. // tooltip does not fit on top of the mouse either,
  112. // put it at the top of the screen
  113. y = Window.getScrollTop();
  114. }
  115. }
  116. setPopupPosition(x, y);
  117. sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT);
  118. }
  119. });
  120. } else {
  121. hide();
  122. }
  123. }
  124. private void showTooltip() {
  125. // Close current tooltip
  126. if (isShowing()) {
  127. closeNow();
  128. }
  129. // Schedule timer for showing the tooltip according to if it was
  130. // recently closed or not.
  131. int timeout = justClosed ? QUICK_OPEN_DELAY : OPEN_DELAY;
  132. showTimer.schedule(timeout);
  133. opening = true;
  134. }
  135. private void closeNow() {
  136. hide();
  137. setWidth("");
  138. closing = false;
  139. }
  140. private Timer showTimer = new Timer() {
  141. @Override
  142. public void run() {
  143. TooltipInfo info = tooltipEventHandler.getTooltipInfo();
  144. if (null != info) {
  145. show(info);
  146. }
  147. opening = false;
  148. }
  149. };
  150. private Timer closeTimer = new Timer() {
  151. @Override
  152. public void run() {
  153. closeNow();
  154. justClosedTimer.schedule(2000);
  155. justClosed = true;
  156. }
  157. };
  158. private Timer justClosedTimer = new Timer() {
  159. @Override
  160. public void run() {
  161. justClosed = false;
  162. }
  163. };
  164. public void hideTooltip() {
  165. if (opening) {
  166. showTimer.cancel();
  167. opening = false;
  168. }
  169. if (!isAttached()) {
  170. return;
  171. }
  172. if (closing) {
  173. // already about to close
  174. return;
  175. }
  176. closeTimer.schedule(CLOSE_TIMEOUT);
  177. closing = true;
  178. justClosed = true;
  179. justClosedTimer.schedule(QUICK_OPEN_TIMEOUT);
  180. }
  181. private int tooltipEventMouseX;
  182. private int tooltipEventMouseY;
  183. public void updatePosition(Event event) {
  184. tooltipEventMouseX = DOM.eventGetClientX(event);
  185. tooltipEventMouseY = DOM.eventGetClientY(event);
  186. }
  187. @Override
  188. public void onBrowserEvent(Event event) {
  189. final int type = DOM.eventGetType(event);
  190. // cancel closing event if tooltip is mouseovered; the user might want
  191. // to scroll of cut&paste
  192. if (type == Event.ONMOUSEOVER) {
  193. // Cancel closing so tooltip stays open and user can copy paste the
  194. // tooltip
  195. closeTimer.cancel();
  196. closing = false;
  197. }
  198. }
  199. /**
  200. * Replace current open tooltip with new content
  201. */
  202. public void replaceCurrentTooltip() {
  203. if (closing) {
  204. closeTimer.cancel();
  205. closeNow();
  206. }
  207. TooltipInfo info = tooltipEventHandler.getTooltipInfo();
  208. if (null != info) {
  209. show(info);
  210. }
  211. opening = false;
  212. }
  213. private class TooltipEventHandler implements MouseMoveHandler,
  214. ClickHandler, KeyDownHandler {
  215. /**
  216. * Current element hovered
  217. */
  218. private com.google.gwt.dom.client.Element currentElement = null;
  219. /**
  220. * Current tooltip active
  221. */
  222. private TooltipInfo currentTooltipInfo = null;
  223. /**
  224. * Get current active tooltip information
  225. *
  226. * @return Current active tooltip information or null
  227. */
  228. public TooltipInfo getTooltipInfo() {
  229. return currentTooltipInfo;
  230. }
  231. /**
  232. * Locate connector and it's tooltip for given element
  233. *
  234. * @param element
  235. * Element used in search
  236. * @return true if connector and tooltip found
  237. */
  238. private boolean resolveConnector(Element element) {
  239. ComponentConnector connector = Util.getConnectorForElement(ac,
  240. RootPanel.get(), element);
  241. // Try to find first connector with proper tooltip info
  242. TooltipInfo info = null;
  243. while (connector != null) {
  244. info = connector.getTooltipInfo(element);
  245. if (info != null && info.hasMessage()) {
  246. break;
  247. }
  248. if (!(connector.getParent() instanceof ComponentConnector)) {
  249. connector = null;
  250. info = null;
  251. break;
  252. }
  253. connector = (ComponentConnector) connector.getParent();
  254. }
  255. if (connector != null && info != null) {
  256. currentTooltipInfo = info;
  257. return true;
  258. }
  259. return false;
  260. }
  261. /**
  262. * Handle hide event
  263. *
  264. * @param event
  265. * Event causing hide
  266. */
  267. private void handleHideEvent() {
  268. hideTooltip();
  269. currentTooltipInfo = null;
  270. }
  271. @Override
  272. public void onMouseMove(MouseMoveEvent mme) {
  273. Event event = Event.as(mme.getNativeEvent());
  274. com.google.gwt.dom.client.Element element = Element.as(event
  275. .getEventTarget());
  276. // We can ignore move event if it's handled by move or over already
  277. if (currentElement == element) {
  278. return;
  279. }
  280. currentElement = element;
  281. boolean connectorAndTooltipFound = resolveConnector((com.google.gwt.user.client.Element) element);
  282. if (!connectorAndTooltipFound) {
  283. if (isShowing()) {
  284. handleHideEvent();
  285. } else {
  286. currentTooltipInfo = null;
  287. }
  288. } else {
  289. updatePosition(event);
  290. if (isShowing()) {
  291. replaceCurrentTooltip();
  292. } else {
  293. showTooltip();
  294. }
  295. }
  296. }
  297. @Override
  298. public void onClick(ClickEvent event) {
  299. handleHideEvent();
  300. }
  301. @Override
  302. public void onKeyDown(KeyDownEvent event) {
  303. handleHideEvent();
  304. }
  305. }
  306. private final TooltipEventHandler tooltipEventHandler = new TooltipEventHandler();
  307. /**
  308. * Connects DOM handlers to widget that are needed for tooltip presentation.
  309. *
  310. * @param widget
  311. * Widget which DOM handlers are connected
  312. */
  313. public void connectHandlersToWidget(Widget widget) {
  314. widget.addDomHandler(tooltipEventHandler, MouseMoveEvent.getType());
  315. widget.addDomHandler(tooltipEventHandler, ClickEvent.getType());
  316. widget.addDomHandler(tooltipEventHandler, KeyDownEvent.getType());
  317. }
  318. }