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.

VNotification.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.Date;
  7. import java.util.EventObject;
  8. import java.util.Iterator;
  9. import com.google.gwt.core.client.GWT;
  10. import com.google.gwt.event.dom.client.KeyCodes;
  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.ui.HTML;
  16. import com.google.gwt.user.client.ui.Widget;
  17. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  18. import com.vaadin.terminal.gwt.client.BrowserInfo;
  19. import com.vaadin.terminal.gwt.client.UIDL;
  20. import com.vaadin.terminal.gwt.client.Util;
  21. public class VNotification extends VOverlay {
  22. public static final int CENTERED = 1;
  23. public static final int CENTERED_TOP = 2;
  24. public static final int CENTERED_BOTTOM = 3;
  25. public static final int TOP_LEFT = 4;
  26. public static final int TOP_RIGHT = 5;
  27. public static final int BOTTOM_LEFT = 6;
  28. public static final int BOTTOM_RIGHT = 7;
  29. public static final int DELAY_FOREVER = -1;
  30. public static final int DELAY_NONE = 0;
  31. private static final String STYLENAME = "v-Notification";
  32. private static final int mouseMoveThreshold = 7;
  33. private static final int Z_INDEX_BASE = 20000;
  34. public static final String STYLE_SYSTEM = "system";
  35. private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps
  36. private int startOpacity = 90;
  37. private int fadeMsec = 400;
  38. private int delayMsec = 1000;
  39. private Timer fader;
  40. private Timer delay;
  41. private int x = -1;
  42. private int y = -1;
  43. private String temporaryStyle;
  44. private ArrayList<EventListener> listeners;
  45. private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
  46. public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style";
  47. public static final String ATTRIBUTE_NOTIFICATION_CAPTION = "caption";
  48. public static final String ATTRIBUTE_NOTIFICATION_MESSAGE = "message";
  49. public static final String ATTRIBUTE_NOTIFICATION_ICON = AbstractComponentConnector.ATTRIBUTE_ICON;
  50. public static final String ATTRIBUTE_NOTIFICATION_POSITION = "position";
  51. public static final String ATTRIBUTE_NOTIFICATION_DELAY = "delay";
  52. /**
  53. * Default constructor. You should use GWT.create instead.
  54. */
  55. public VNotification() {
  56. setStyleName(STYLENAME);
  57. sinkEvents(Event.ONCLICK);
  58. DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);
  59. }
  60. /**
  61. * @deprecated Use static {@link #createNotification(int)} instead to enable
  62. * GWT deferred binding.
  63. *
  64. * @param delayMsec
  65. */
  66. @Deprecated
  67. public VNotification(int delayMsec) {
  68. this();
  69. this.delayMsec = delayMsec;
  70. if (BrowserInfo.get().isTouchDevice()) {
  71. new Timer() {
  72. @Override
  73. public void run() {
  74. if (isAttached()) {
  75. fade();
  76. }
  77. }
  78. }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  79. }
  80. }
  81. /**
  82. * @deprecated Use static {@link #createNotification(int, int, int)} instead
  83. * to enable GWT deferred binding.
  84. *
  85. * @param delayMsec
  86. * @param fadeMsec
  87. * @param startOpacity
  88. */
  89. @Deprecated
  90. public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
  91. this(delayMsec);
  92. this.fadeMsec = fadeMsec;
  93. this.startOpacity = startOpacity;
  94. }
  95. public void startDelay() {
  96. DOM.removeEventPreview(this);
  97. if (delayMsec > 0) {
  98. if (delay == null) {
  99. delay = new Timer() {
  100. @Override
  101. public void run() {
  102. fade();
  103. }
  104. };
  105. delay.schedule(delayMsec);
  106. }
  107. } else if (delayMsec == 0) {
  108. fade();
  109. }
  110. }
  111. @Override
  112. public void show() {
  113. show(CENTERED);
  114. }
  115. public void show(String style) {
  116. show(CENTERED, style);
  117. }
  118. public void show(int position) {
  119. show(position, null);
  120. }
  121. public void show(Widget widget, int position, String style) {
  122. setWidget(widget);
  123. show(position, style);
  124. }
  125. public void show(String html, int position, String style) {
  126. setWidget(new HTML(html));
  127. show(position, style);
  128. }
  129. public void show(int position, String style) {
  130. setOpacity(getElement(), startOpacity);
  131. if (style != null) {
  132. temporaryStyle = style;
  133. addStyleName(style);
  134. addStyleDependentName(style);
  135. }
  136. super.show();
  137. setPosition(position);
  138. }
  139. @Override
  140. public void hide() {
  141. DOM.removeEventPreview(this);
  142. cancelDelay();
  143. cancelFade();
  144. if (temporaryStyle != null) {
  145. removeStyleName(temporaryStyle);
  146. removeStyleDependentName(temporaryStyle);
  147. temporaryStyle = null;
  148. }
  149. super.hide();
  150. fireEvent(new HideEvent(this));
  151. }
  152. public void fade() {
  153. DOM.removeEventPreview(this);
  154. cancelDelay();
  155. if (fader == null) {
  156. fader = new Timer() {
  157. private final long start = new Date().getTime();
  158. @Override
  159. public void run() {
  160. /*
  161. * To make animation smooth, don't count that event happens
  162. * on time. Reduce opacity according to the actual time
  163. * spent instead of fixed decrement.
  164. */
  165. long now = new Date().getTime();
  166. long timeEplaced = now - start;
  167. float remainingFraction = 1 - timeEplaced
  168. / (float) fadeMsec;
  169. int opacity = (int) (startOpacity * remainingFraction);
  170. if (opacity <= 0) {
  171. cancel();
  172. hide();
  173. if (BrowserInfo.get().isOpera()) {
  174. // tray notification on opera needs to explicitly
  175. // define
  176. // size, reset it
  177. DOM.setStyleAttribute(getElement(), "width", "");
  178. DOM.setStyleAttribute(getElement(), "height", "");
  179. }
  180. } else {
  181. setOpacity(getElement(), opacity);
  182. }
  183. }
  184. };
  185. fader.scheduleRepeating(FADE_ANIMATION_INTERVAL);
  186. }
  187. }
  188. public void setPosition(int position) {
  189. final Element el = getElement();
  190. DOM.setStyleAttribute(el, "top", "");
  191. DOM.setStyleAttribute(el, "left", "");
  192. DOM.setStyleAttribute(el, "bottom", "");
  193. DOM.setStyleAttribute(el, "right", "");
  194. switch (position) {
  195. case TOP_LEFT:
  196. DOM.setStyleAttribute(el, "top", "0px");
  197. DOM.setStyleAttribute(el, "left", "0px");
  198. break;
  199. case TOP_RIGHT:
  200. DOM.setStyleAttribute(el, "top", "0px");
  201. DOM.setStyleAttribute(el, "right", "0px");
  202. break;
  203. case BOTTOM_RIGHT:
  204. DOM.setStyleAttribute(el, "position", "absolute");
  205. if (BrowserInfo.get().isOpera()) {
  206. // tray notification on opera needs explicitly defined size
  207. DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");
  208. DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");
  209. }
  210. DOM.setStyleAttribute(el, "bottom", "0px");
  211. DOM.setStyleAttribute(el, "right", "0px");
  212. break;
  213. case BOTTOM_LEFT:
  214. DOM.setStyleAttribute(el, "bottom", "0px");
  215. DOM.setStyleAttribute(el, "left", "0px");
  216. break;
  217. case CENTERED_TOP:
  218. center();
  219. DOM.setStyleAttribute(el, "top", "0px");
  220. break;
  221. case CENTERED_BOTTOM:
  222. center();
  223. DOM.setStyleAttribute(el, "top", "");
  224. DOM.setStyleAttribute(el, "bottom", "0px");
  225. break;
  226. default:
  227. case CENTERED:
  228. center();
  229. break;
  230. }
  231. }
  232. private void cancelFade() {
  233. if (fader != null) {
  234. fader.cancel();
  235. fader = null;
  236. }
  237. }
  238. private void cancelDelay() {
  239. if (delay != null) {
  240. delay.cancel();
  241. delay = null;
  242. }
  243. }
  244. private void setOpacity(Element el, int opacity) {
  245. DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));
  246. if (BrowserInfo.get().isIE()) {
  247. DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity
  248. + ")");
  249. }
  250. }
  251. @Override
  252. public void onBrowserEvent(Event event) {
  253. DOM.removeEventPreview(this);
  254. if (fader == null) {
  255. fade();
  256. }
  257. }
  258. @Override
  259. public boolean onEventPreview(Event event) {
  260. int type = DOM.eventGetType(event);
  261. // "modal"
  262. if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) {
  263. if (type == Event.ONCLICK) {
  264. if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
  265. fade();
  266. return false;
  267. }
  268. } else if (type == Event.ONKEYDOWN
  269. && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
  270. fade();
  271. return false;
  272. }
  273. if (temporaryStyle == STYLE_SYSTEM) {
  274. return true;
  275. } else {
  276. return false;
  277. }
  278. }
  279. // default
  280. switch (type) {
  281. case Event.ONMOUSEMOVE:
  282. if (x < 0) {
  283. x = DOM.eventGetClientX(event);
  284. y = DOM.eventGetClientY(event);
  285. } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
  286. || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
  287. startDelay();
  288. }
  289. break;
  290. case Event.ONMOUSEDOWN:
  291. case Event.ONMOUSEWHEEL:
  292. case Event.ONSCROLL:
  293. startDelay();
  294. break;
  295. case Event.ONKEYDOWN:
  296. if (event.getRepeat()) {
  297. return true;
  298. }
  299. startDelay();
  300. break;
  301. default:
  302. break;
  303. }
  304. return true;
  305. }
  306. public void addEventListener(EventListener listener) {
  307. if (listeners == null) {
  308. listeners = new ArrayList<EventListener>();
  309. }
  310. listeners.add(listener);
  311. }
  312. public void removeEventListener(EventListener listener) {
  313. if (listeners == null) {
  314. return;
  315. }
  316. listeners.remove(listener);
  317. }
  318. private void fireEvent(HideEvent event) {
  319. if (listeners != null) {
  320. for (Iterator<EventListener> it = listeners.iterator(); it
  321. .hasNext();) {
  322. EventListener l = it.next();
  323. l.notificationHidden(event);
  324. }
  325. }
  326. }
  327. public static void showNotification(ApplicationConnection client,
  328. final UIDL notification) {
  329. boolean onlyPlainText = notification
  330. .hasAttribute(VView.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
  331. String html = "";
  332. if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_ICON)) {
  333. final String parsedUri = client.translateVaadinUri(notification
  334. .getStringAttribute(ATTRIBUTE_NOTIFICATION_ICON));
  335. html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
  336. }
  337. if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_CAPTION)) {
  338. String caption = notification
  339. .getStringAttribute(ATTRIBUTE_NOTIFICATION_CAPTION);
  340. if (onlyPlainText) {
  341. caption = Util.escapeHTML(caption);
  342. caption = caption.replaceAll("\\n", "<br />");
  343. }
  344. html += "<h1>" + caption + "</h1>";
  345. }
  346. if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE)) {
  347. String message = notification
  348. .getStringAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE);
  349. if (onlyPlainText) {
  350. message = Util.escapeHTML(message);
  351. message = message.replaceAll("\\n", "<br />");
  352. }
  353. html += "<p>" + message + "</p>";
  354. }
  355. final String style = notification
  356. .hasAttribute(ATTRIBUTE_NOTIFICATION_STYLE) ? notification
  357. .getStringAttribute(ATTRIBUTE_NOTIFICATION_STYLE) : null;
  358. final int position = notification
  359. .getIntAttribute(ATTRIBUTE_NOTIFICATION_POSITION);
  360. final int delay = notification
  361. .getIntAttribute(ATTRIBUTE_NOTIFICATION_DELAY);
  362. createNotification(delay).show(html, position, style);
  363. }
  364. public static VNotification createNotification(int delayMsec) {
  365. final VNotification notification = GWT.create(VNotification.class);
  366. notification.delayMsec = delayMsec;
  367. if (BrowserInfo.get().isTouchDevice()) {
  368. new Timer() {
  369. @Override
  370. public void run() {
  371. if (notification.isAttached()) {
  372. notification.fade();
  373. }
  374. }
  375. }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  376. }
  377. return notification;
  378. }
  379. public class HideEvent extends EventObject {
  380. public HideEvent(Object source) {
  381. super(source);
  382. }
  383. }
  384. public interface EventListener extends java.util.EventListener {
  385. public void notificationHidden(HideEvent event);
  386. }
  387. }