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 11KB

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