123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- /*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
- package com.vaadin.client.ui;
-
- import java.util.ArrayList;
- import java.util.Date;
- import java.util.EventObject;
- import java.util.Iterator;
-
- import com.google.gwt.aria.client.Roles;
- import com.google.gwt.core.client.GWT;
- import com.google.gwt.event.dom.client.KeyCodes;
- import com.google.gwt.user.client.DOM;
- import com.google.gwt.user.client.Event;
- import com.google.gwt.user.client.Timer;
- import com.google.gwt.user.client.ui.FlowPanel;
- import com.google.gwt.user.client.ui.HTML;
- import com.google.gwt.user.client.ui.Label;
- import com.google.gwt.user.client.ui.Widget;
- import com.vaadin.client.ApplicationConnection;
- import com.vaadin.client.BrowserInfo;
- import com.vaadin.client.UIDL;
- import com.vaadin.client.Util;
- import com.vaadin.client.ui.aria.AriaHelper;
- import com.vaadin.shared.Position;
- import com.vaadin.shared.ui.ui.NotificationConfigurationBean;
- import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role;
- import com.vaadin.shared.ui.ui.UIConstants;
-
- public class VNotification extends VOverlay {
-
- public static final Position CENTERED = Position.MIDDLE_CENTER;
- public static final Position CENTERED_TOP = Position.TOP_CENTER;
- public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
- public static final Position TOP_LEFT = Position.TOP_LEFT;
- public static final Position TOP_RIGHT = Position.TOP_RIGHT;
- public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
- public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
-
- /**
- * Position that is only accessible for assistive devices, invisible for
- * visual users.
- */
- public static final Position ASSISTIVE = Position.ASSISTIVE;
-
- public static final int DELAY_FOREVER = -1;
- public static final int DELAY_NONE = 0;
-
- private static final String STYLENAME = "v-Notification";
- private static final int mouseMoveThreshold = 7;
- private static final int Z_INDEX_BASE = 20000;
- public static final String STYLE_SYSTEM = "system";
- private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps
-
- private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>();
-
- private int startOpacity = 90;
- private int fadeMsec = 400;
- private int delayMsec = 1000;
-
- private Timer fader;
- private Timer delay;
-
- private int x = -1;
- private int y = -1;
-
- private String temporaryStyle;
-
- private ArrayList<EventListener> listeners;
- private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
-
- /**
- * Default constructor. You should use GWT.create instead.
- */
- public VNotification() {
- setStyleName(STYLENAME);
- sinkEvents(Event.ONCLICK);
- DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);
- }
-
- /**
- * @deprecated Use static {@link #createNotification(int)} instead to enable
- * GWT deferred binding.
- *
- * @param delayMsec
- */
- @Deprecated
- public VNotification(int delayMsec) {
- this();
- this.delayMsec = delayMsec;
- if (BrowserInfo.get().isTouchDevice()) {
- new Timer() {
- @Override
- public void run() {
- if (isAttached()) {
- fade();
- }
- }
- }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
- }
- }
-
- /**
- * @deprecated Use static {@link #createNotification(int, int, int)} instead
- * to enable GWT deferred binding.
- *
- * @param delayMsec
- * @param fadeMsec
- * @param startOpacity
- */
- @Deprecated
- public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
- this(delayMsec);
- this.fadeMsec = fadeMsec;
- this.startOpacity = startOpacity;
- }
-
- public void startDelay() {
- DOM.removeEventPreview(this);
- if (delayMsec > 0) {
- if (delay == null) {
- delay = new Timer() {
- @Override
- public void run() {
- fade();
- }
- };
- delay.schedule(delayMsec);
- }
- } else if (delayMsec == 0) {
- fade();
- }
- }
-
- @Override
- public void show() {
- show(CENTERED);
- }
-
- public void show(String style) {
- show(CENTERED, style);
- }
-
- public void show(com.vaadin.shared.Position position) {
- show(position, null);
- }
-
- public void show(Widget widget, Position position, String style) {
- NotificationConfigurationBean styleSetup = getUiState(style);
- setWaiAriaRole(styleSetup);
-
- FlowPanel panel = new FlowPanel();
- if (styleSetup.hasAssistivePrefix()) {
- panel.add(new Label(styleSetup.getAssistivePrefix()));
- AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
- true);
- }
-
- panel.add(widget);
-
- if (styleSetup.hasAssistivePostfix()) {
- panel.add(new Label(styleSetup.getAssistivePostfix()));
- AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
- true);
- }
- setWidget(panel);
- show(position, style);
- }
-
- public void show(String html, Position position, String style) {
- NotificationConfigurationBean styleSetup = getUiState(style);
- String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE;
-
- setWaiAriaRole(styleSetup);
-
- String type = "";
- String usage = "";
-
- if (styleSetup != null && styleSetup.hasAssistivePrefix()) {
- type = "<span class='" + assistiveDeviceOnlyStyle + "'>"
- + styleSetup.getAssistivePrefix() + "</span>";
- }
-
- if (styleSetup != null && styleSetup.hasAssistivePostfix()) {
- usage = "<span class='" + assistiveDeviceOnlyStyle + "'>"
- + styleSetup.getAssistivePostfix() + "</span>";
- }
-
- setWidget(new HTML(type + html + usage));
- show(position, style);
- }
-
- private NotificationConfigurationBean getUiState(String style) {
- NotificationConfigurationBean styleSetup = getApplicationConnection()
- .getUIConnector().getState().notificationConfiguration.setup
- .get(style);
- return styleSetup;
- }
-
- private void setWaiAriaRole(NotificationConfigurationBean styleSetup) {
- Roles.getAlertRole().set(getElement());
-
- if (styleSetup != null && styleSetup.getAssistiveRole() != null) {
- if (Role.STATUS == styleSetup.getAssistiveRole()) {
- Roles.getStatusRole().set(getElement());
- }
- }
- }
-
- public void show(Position position, String style) {
- setOpacity(getElement(), startOpacity);
- if (style != null) {
- temporaryStyle = style;
- addStyleName(style);
- addStyleDependentName(style);
- }
- super.show();
- notifications.add(this);
- setPosition(position);
- positionOrSizeUpdated();
- /**
- * Android 4 fails to render notifications correctly without a little
- * nudge (#8551)
- */
- if (BrowserInfo.get().isAndroid()) {
- Util.setStyleTemporarily(getElement(), "display", "none");
- }
- }
-
- @Override
- public void hide() {
- DOM.removeEventPreview(this);
- cancelDelay();
- cancelFade();
- if (temporaryStyle != null) {
- removeStyleName(temporaryStyle);
- removeStyleDependentName(temporaryStyle);
- temporaryStyle = null;
- }
- super.hide();
- notifications.remove(this);
- fireEvent(new HideEvent(this));
- }
-
- public void fade() {
- DOM.removeEventPreview(this);
- cancelDelay();
- if (fader == null) {
- fader = new Timer() {
- private final long start = new Date().getTime();
-
- @Override
- public void run() {
- /*
- * To make animation smooth, don't count that event happens
- * on time. Reduce opacity according to the actual time
- * spent instead of fixed decrement.
- */
- long now = new Date().getTime();
- long timeEplaced = now - start;
- float remainingFraction = 1 - timeEplaced
- / (float) fadeMsec;
- int opacity = (int) (startOpacity * remainingFraction);
- if (opacity <= 0) {
- cancel();
- hide();
- } else {
- setOpacity(getElement(), opacity);
- }
- }
- };
- fader.scheduleRepeating(FADE_ANIMATION_INTERVAL);
- }
- }
-
- public void setPosition(com.vaadin.shared.Position position) {
- final com.google.gwt.user.client.Element el = getElement();
- DOM.setStyleAttribute(el, "top", "");
- DOM.setStyleAttribute(el, "left", "");
- DOM.setStyleAttribute(el, "bottom", "");
- DOM.setStyleAttribute(el, "right", "");
- switch (position) {
- case TOP_LEFT:
- DOM.setStyleAttribute(el, "top", "0px");
- DOM.setStyleAttribute(el, "left", "0px");
- break;
- case TOP_RIGHT:
- DOM.setStyleAttribute(el, "top", "0px");
- DOM.setStyleAttribute(el, "right", "0px");
- break;
- case BOTTOM_RIGHT:
- DOM.setStyleAttribute(el, "position", "absolute");
- DOM.setStyleAttribute(el, "bottom", "0px");
- DOM.setStyleAttribute(el, "right", "0px");
- break;
- case BOTTOM_LEFT:
- DOM.setStyleAttribute(el, "bottom", "0px");
- DOM.setStyleAttribute(el, "left", "0px");
- break;
- case TOP_CENTER:
- center();
- DOM.setStyleAttribute(el, "top", "0px");
- break;
- case BOTTOM_CENTER:
- center();
- DOM.setStyleAttribute(el, "top", "");
- DOM.setStyleAttribute(el, "bottom", "0px");
- break;
- case ASSISTIVE:
- DOM.setStyleAttribute(el, "top", "-2000px");
- DOM.setStyleAttribute(el, "left", "-2000px");
- break;
- default:
- case MIDDLE_CENTER:
- center();
- break;
- }
- }
-
- private void cancelFade() {
- if (fader != null) {
- fader.cancel();
- fader = null;
- }
- }
-
- private void cancelDelay() {
- if (delay != null) {
- delay.cancel();
- delay = null;
- }
- }
-
- private void setOpacity(com.google.gwt.user.client.Element el, int opacity) {
- DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));
- if (BrowserInfo.get().isIE()) {
- DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity
- + ")");
- }
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- DOM.removeEventPreview(this);
- if (fader == null) {
- fade();
- }
- }
-
- @Override
- public boolean onEventPreview(Event event) {
- int type = DOM.eventGetType(event);
- // "modal"
- if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) {
- if (type == Event.ONCLICK) {
- if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
- fade();
- return false;
- }
- } else if (type == Event.ONKEYDOWN
- && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
- fade();
- return false;
- }
- if (temporaryStyle == STYLE_SYSTEM) {
- return true;
- } else {
- return false;
- }
- }
- // default
- switch (type) {
- case Event.ONMOUSEMOVE:
-
- if (x < 0) {
- x = DOM.eventGetClientX(event);
- y = DOM.eventGetClientY(event);
- } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
- || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
- startDelay();
- }
- break;
- case Event.ONMOUSEDOWN:
- case Event.ONMOUSEWHEEL:
- case Event.ONSCROLL:
- startDelay();
- break;
- case Event.ONKEYDOWN:
- if (event.getRepeat()) {
- return true;
- }
- startDelay();
- break;
- default:
- break;
- }
- return true;
- }
-
- public void addEventListener(EventListener listener) {
- if (listeners == null) {
- listeners = new ArrayList<EventListener>();
- }
- listeners.add(listener);
- }
-
- public void removeEventListener(EventListener listener) {
- if (listeners == null) {
- return;
- }
- listeners.remove(listener);
- }
-
- private void fireEvent(HideEvent event) {
- if (listeners != null) {
- for (Iterator<EventListener> it = listeners.iterator(); it
- .hasNext();) {
- EventListener l = it.next();
- l.notificationHidden(event);
- }
- }
- }
-
- public static void showNotification(ApplicationConnection client,
- final UIDL notification) {
- boolean onlyPlainText = notification
- .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
- String html = "";
- if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
- String iconUri = notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON);
- html += client.getIcon(iconUri).getElement().getString();
- }
- if (notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
- String caption = notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
- if (onlyPlainText) {
- caption = Util.escapeHTML(caption);
- caption = caption.replaceAll("\\n", "<br />");
- }
- html += "<h1>" + caption + "</h1>";
- }
- if (notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
- String message = notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
- if (onlyPlainText) {
- message = Util.escapeHTML(message);
- message = message.replaceAll("\\n", "<br />");
- }
- html += "<p>" + message + "</p>";
- }
-
- final String style = notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
- : null;
-
- final int pos = notification
- .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
- Position position = Position.values()[pos];
-
- final int delay = notification
- .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
- createNotification(delay, client.getUIConnector().getWidget()).show(
- html, position, style);
- }
-
- public static VNotification createNotification(int delayMsec, Widget owner) {
- final VNotification notification = GWT.create(VNotification.class);
- notification.setWaiAriaRole(null);
-
- notification.delayMsec = delayMsec;
- if (BrowserInfo.get().isTouchDevice()) {
- new Timer() {
- @Override
- public void run() {
- if (notification.isAttached()) {
- notification.fade();
- }
- }
- }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY);
- }
- notification.setOwner(owner);
- return notification;
- }
-
- public class HideEvent extends EventObject {
-
- public HideEvent(Object source) {
- super(source);
- }
- }
-
- public interface EventListener extends java.util.EventListener {
- public void notificationHidden(HideEvent event);
- }
-
- /**
- * Moves currently visible notifications to the top of the event preview
- * stack. Can be called when opening other overlays such as subwindows to
- * ensure the notifications receive the events they need and don't linger
- * indefinitely. See #7136.
- *
- * TODO Should this be a generic Overlay feature instead?
- */
- public static void bringNotificationsToFront() {
- for (VNotification notification : notifications) {
- DOM.removeEventPreview(notification);
- DOM.addEventPreview(notification);
- }
- }
- }
|