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

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