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

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