/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.notification;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
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.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import org.eclipse.che.ide.DelayedTask;
import org.eclipse.che.ide.Resources;
import org.eclipse.che.ide.api.notification.Notification;
import org.eclipse.che.ide.api.notification.NotificationObserver;
import org.eclipse.che.ide.api.notification.StatusNotification;
import org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode;
import org.eclipse.che.ide.api.notification.StatusNotification.Status;
import org.eclipse.che.ide.notification.NotificationManagerView.NotificationActionDelegate;
import org.vectomatic.dom.svg.ui.SVGImage;
import org.vectomatic.dom.svg.ui.SVGResource;
import javax.validation.constraints.NotNull;
import static com.google.gwt.user.client.Event.ONCLICK;
import static com.google.gwt.user.client.Event.ONDBLCLICK;
import static com.google.gwt.user.client.Event.ONMOUSEOUT;
import static com.google.gwt.user.client.Event.ONMOUSEOVER;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.PROGRESS;
/**
* Widget for showing notification popup.
* <p/>
* {@link Notification} in this case must extends {@link StatusNotification}. Status may have one of three values {@link
* Status}.
* By default, if the {@link Notification} is set to <code>Status.PROGRESS</code>, then hide timer won't be handle popup closing, but if
* the popup has status <code>Status.SUCCESS</code> or <code>Status.FAIL</code> then hide timer will perform automatically hide popup
* widget in 5 seconds.
* <p/>
* Popup's showing and hiding is controlled by two animations.
* Showing animation simply sets opacity value from 0 to 1 in 1 second.
* Hiding animation simply sets opacity value from 1 to 0 and margin-left from element offset width to negative value.
* <p/>
* Possible improvements:
* <ul>
* <li>Widget should handle {@link com.google.gwt.user.client.Window} onBlur and onFocus events. This need to stop hide timer if window
* becomes blur.</li>
* </ul>
*
* @author Andrey Plotnikov
* @author Vlad Zhukovskyi
* @see {@link Notification}.
* @see {@link StatusNotification}.
* @see {@link Status}.
*/
public class NotificationPopup extends SimplePanel implements NotificationObserver {
public static final String MESSAGE_WRAPPER_DBG_ID = "popup-message-wrapper";
public static final String NOTIFICATION_WRAPPER_DBG_ID = "popup-notification-wrapper";
public static final String TITLE_DBG_ID = "popup-title";
public static final String CONTENT_DBG_ID = "popup-content";
public static final String ICON_DBG_ID = "popup-icon";
public static final String CLOSE_ICON_DBG_ID = "popup-close-icon";
/**
* Value for the automatically hiding. By default it 5 seconds.
*/
public static final int DEFAULT_TIME = 5000;
private StatusNotification notification;
private NotificationActionDelegate delegate;
private Resources resources;
private SimplePanel titlePanel;
private SimplePanel messagePanel;
private SimplePanel iconPanel;
private int clickCount = 0;
private final Timer hideTimer = new Timer() {
/** {@inheritDoc} */
@Override
public void run() {
delegate.onClose(notification);
}
};
/**
* Create notification message.
*
* @param notification
* the notification
* @param resources
* the resources
* @param delegate
* the delegate
*/
public NotificationPopup(@NotNull StatusNotification notification,
@NotNull Resources resources,
@NotNull NotificationActionDelegate delegate) {
this.notification = notification;
this.resources = resources;
this.delegate = delegate;
setStyleName(resources.notificationCss().notificationPopupPanel());
notification.addObserver(this);
FlowPanel contentWrapper = new FlowPanel();
contentWrapper.add(titlePanel = createTitleWidget());
contentWrapper.add(messagePanel = createContentWidget());
contentWrapper.setStyleName(resources.notificationCss().notificationPopupContentWrapper());
contentWrapper.ensureDebugId(CONTENT_DBG_ID + notification.getId());
FlowPanel notificationWrapper = new FlowPanel();
notificationWrapper.add(iconPanel = createIconWidget());
notificationWrapper.add(contentWrapper);
notificationWrapper.add(createCloseWidget());
notificationWrapper.setStyleName(resources.notificationCss().notificationPopup());
notificationWrapper.ensureDebugId(NOTIFICATION_WRAPPER_DBG_ID + notification.getId());
setWidget(notificationWrapper);
}
/** {@inheritDoc} */
@Override
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
switch (DOM.eventGetType(event)) {
case ONCLICK:
clickCount++;
if (clickCount == 1) {
Timer timer = new Timer() {
@Override
public void run() {
if (clickCount == 1) {
clickCount = 0;
delegate.onClick(notification);
}
}
};
timer.schedule(200);
}
break;
case ONDBLCLICK:
clickCount = 0;
delegate.onDoubleClick(notification);
break;
case ONMOUSEOVER:
hideTimer.cancel();
break;
case ONMOUSEOUT:
if (notification.getStatus() == PROGRESS) {
hideTimer.cancel();
} else {
hideTimer.schedule(DEFAULT_TIME);
}
break;
}
}
/**
* Return an icon based on {@link Status}.
*
* @return SVG image that represents icon status
*/
private SVGImage getIconBaseOnStatus() {
final SVGResource icon;
final String status;
switch (notification.getStatus()) {
case PROGRESS:
icon = resources.progress();
status = "progress";
break;
case SUCCESS:
icon = resources.success();
status = "success";
break;
case FAIL:
icon = resources.fail();
status = "fail";
break;
case WARNING:
icon = resources.warning();
status = "warning";
break;
default:
throw new IllegalArgumentException("Can't determine notification icon");
}
SVGImage image = new SVGImage(icon);
image.getElement().setAttribute("name", status);
return image;
}
/**
* Create icon wrapper that contains an icon.
*
* @return {@link SimplePanel} as icon wrapper
*/
private SimplePanel createIconWidget() {
SimplePanel iconWrapper = new SimplePanel();
iconWrapper.setStyleName(resources.notificationCss().notificationPopupIconWrapper());
iconWrapper.ensureDebugId(ICON_DBG_ID + notification.getId());
return iconWrapper;
}
/**
* Create close icon widget that contains an close notification icon.
*
* @return {@link SimplePanel} as close icon wrapper
*/
private SimplePanel createCloseWidget() {
SimplePanel closeWrapper = new SimplePanel();
SVGImage closeImage = new SVGImage(resources.closeIcon());
closeImage.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
delegate.onClose(notification);
}
});
closeWrapper.add(closeImage);
closeWrapper.setStyleName(resources.notificationCss().notificationPopupCloseButtonWrapper());
closeWrapper.ensureDebugId(CLOSE_ICON_DBG_ID + notification.getId());
return closeWrapper;
}
/**
* Create title widget that contains notification title.
*
* @return {@link SimplePanel} as title wrapper
*/
private SimplePanel createTitleWidget() {
SimplePanel titleWrapper = new SimplePanel();
Label titleLabel = new Label();
titleWrapper.add(titleLabel);
titleWrapper.setStyleName(resources.notificationCss().notificationPopupTitleWrapper());
titleWrapper.ensureDebugId(TITLE_DBG_ID + notification.getId());
return titleWrapper;
}
/**
* Create message widget that contains notification message.
*
* @return {@link SimplePanel} as message wrapper
*/
private SimplePanel createContentWidget() {
SimplePanel messageWrapper = new SimplePanel();
Label messageLabel = new Label();
messageWrapper.add(messageLabel);
messageWrapper.setStyleName(resources.notificationCss().notificationPopupMessageWrapper());
messageWrapper.ensureDebugId(MESSAGE_WRAPPER_DBG_ID + notification.getId());
return messageWrapper;
}
/** {@inheritDoc} */
@Override
public void onValueChanged() {
update();
}
/**
* Update notification's widget values.
* <p/>
* Widget consumes:
* <ul>
* <li>{@link Notification#title}</li>
* <li>{@link Notification#content}</li>
* <li>Icon and background color based on {@link Status}</li>
* </ul>
*/
private void update() {
Widget titleWidget = titlePanel.getWidget();
if (titleWidget != null && titleWidget instanceof Label) {
((Label)titleWidget).setText(notification.getTitle());
titleWidget.setTitle(notification.getTitle());
}
Widget messageWidget = messagePanel.getWidget();
if (messageWidget != null && messageWidget instanceof Label) {
((Label)messageWidget).setText(notification.getContent());
}
iconPanel.setWidget(getIconBaseOnStatus());
removeStyleName(resources.notificationCss().notificationStatusProgress());
removeStyleName(resources.notificationCss().notificationStatusSuccess());
removeStyleName(resources.notificationCss().notificationStatusFail());
removeStyleName(resources.notificationCss().notificationStatusWarning());
DisplayMode displayMode = notification.getDisplayMode();
Status status = notification.getStatus();
switch (status) {
case PROGRESS:
setStyleName(resources.notificationCss().notificationStatusProgress(), true);
break;
case SUCCESS:
setStyleName(resources.notificationCss().notificationStatusSuccess(), true);
break;
case FAIL:
setStyleName(resources.notificationCss().notificationStatusFail(), true);
break;
case WARNING:
setStyleName(resources.notificationCss().notificationStatusWarning(), true);
break;
}
if (FLOAT_MODE == displayMode && PROGRESS == status) {
hideTimer.cancel();
return;
}
hideTimer.schedule(DEFAULT_TIME);
}
/** {@inheritDoc} */
@Override
protected void onAttach() {
boolean isOrWasAttached = isOrWasAttached();
super.onAttach();
if (!isOrWasAttached) {
sinkEvents(ONCLICK | ONDBLCLICK | ONMOUSEOUT | ONMOUSEOVER);
update();
addStyleName(resources.notificationCss().notificationShowingAnimation());
}
}
/**
* Return notification related to this widget.
*
* @return the {@link StatusNotification}
*/
public StatusNotification getNotification() {
return notification;
}
/** {@inheritDoc} */
@Override
public void removeFromParent() {
if (isOrWasAttached()) {
addStyleName(resources.notificationCss().notificationHidingAnimation());
new DelayedTask() {
@Override
public void onExecute() {
NotificationPopup.super.removeFromParent();
}
}.delay(500);
} else {
super.removeFromParent();
}
}
}