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

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