/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.common;
import static org.libreplan.web.I18nHelper._;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.common.exceptions.ValidationException.InvalidValue;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.event.MouseEvent;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.EventInterceptor;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Div;
import org.zkoss.zul.Image;
import org.zkoss.zul.Label;
/**
* <p>
* It shows messages from the application to the user with different
* {@link Level levels} of severity. Once the user does some action in the page,
* like clicking a button or changing some input the messages automatically
* disappear.
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public class MessagesForUser extends GenericForwardComposer implements
IMessagesForUser {
// 2 seconds
private static final long DEFAULT_MINIMUM_VISUALIZATION_TIME_MILLIS = 1000 * 2;
private static final Log LOG = LogFactory.getLog(MessagesForUser.class);
private static final class PreviousMessagesDiscarder implements
EventInterceptor {
private final WeakReference<MessagesForUser> messagesForUserRef;
public PreviousMessagesDiscarder(MessagesForUser messagesForUser) {
this.messagesForUserRef = new WeakReference<MessagesForUser>(
messagesForUser);
}
@Override
public void afterProcessEvent(Event event) {
}
@Override
public Event beforePostEvent(Event event) {
return event;
}
@Override
public Event beforeProcessEvent(Event event) {
MessagesForUser messagesForUser = messagesForUserRef.get();
if (messagesForUser == null
|| messagesForUser.pendingToDetach.isEmpty()
|| !eventIndicatesUserActivity(event)) {
return event;
}
long currentTime = System.currentTimeMillis();
ComponentHolderTimestamped currrent = null;
while ((currrent = messagesForUser.pendingToDetach.peek()) != null
&& currrent.minimumVisualizationTimeSurpased(currentTime)) {
currrent.component.detach();
messagesForUser.pendingToDetach.poll();
}
return event;
}
private boolean eventIndicatesUserActivity(Event event) {
if (event instanceof MouseEvent) {
MouseEvent e = (MouseEvent) event;
return e.getName().equals("onClick");
}
if (event instanceof InputEvent) {
InputEvent e = (InputEvent) event;
return !e.getName().equals("onBlur");
}
return event instanceof CheckEvent || event instanceof SelectEvent;
}
@Override
public Event beforeSendEvent(Event event) {
return event;
}
}
private class ComponentHolderTimestamped {
private final Component component;
private final long timestamp;
ComponentHolderTimestamped(Component component) {
this.component = component;
this.timestamp = System.currentTimeMillis();
}
public boolean minimumVisualizationTimeSurpased(long currentTime) {
return currentTime - timestamp > minimumVisualizationTimeMilliseconds;
}
}
private Component container;
private final long minimumVisualizationTimeMilliseconds;
private Queue<ComponentHolderTimestamped> pendingToDetach = new ConcurrentLinkedQueue<ComponentHolderTimestamped>();
private static final String DETACH_EVENT_NAME = "onMarkDetached";
public MessagesForUser(Component container) {
this(container, DEFAULT_MINIMUM_VISUALIZATION_TIME_MILLIS);
}
public MessagesForUser(Component container,
long minimumVisualizationTimeMilliseconds) {
this.container = container;
this.minimumVisualizationTimeMilliseconds = minimumVisualizationTimeMilliseconds;
container.getPage().getDesktop()
.addListener(new PreviousMessagesDiscarder(this));
}
@Override
public void invalidValue(InvalidValue invalidValue, ICustomLabelCreator customLabelCreator) {
if (customLabelCreator == null) {
invalidValue(invalidValue);
} else {
addMessage(Level.WARNING, customLabelCreator
.createLabelFor(invalidValue));
}
}
@Override
public void invalidValue(InvalidValue invalidValue) {
addMessage(Level.WARNING, createLabelFor(invalidValue));
}
public static Label createLabelFor(InvalidValue invalidValue) {
Label result = new Label();
result.setValue(_(invalidValue.getMessage()));
return result;
}
@Override
public void showMessage(Level level, String message) {
addMessage(level, new Label(message));
}
private void addMessage(Level level, final Component label) {
final Div messageEntry = createMessage(level, label);
container.appendChild(messageEntry);
Events.echoEvent(DETACH_EVENT_NAME, messageEntry, "");
messageEntry.addEventListener(DETACH_EVENT_NAME, new EventListener() {
@Override
public void onEvent(Event event) {
pendingToDetach.offer(new ComponentHolderTimestamped(
messageEntry));
}
});
}
private Div createMessage(Level level, final Component label) {
Div div = new Div();
Image tick = new Image("/common/img/ico_ok.png");
tick.setSclass("tick");
div.setSclass("message_" + level.toString());
div.appendChild(tick);
div.appendChild(label);
return div;
}
@Override
public void clearMessages() {
List<Object> children = new ArrayList<Object>(container.getChildren());
for (Object child : children) {
Component c = (Component) child;
c.detach();
}
}
@Override
public void showInvalidValues(ValidationException e) {
showInvalidValues(e, null);
}
@Override
public void showInvalidValues(ValidationException e, ICustomLabelCreator customLabelCreator) {
for (InvalidValue invalidValue : e.getInvalidValues()) {
invalidValue(invalidValue, customLabelCreator);
}
if (!StringUtils.isEmpty(e.getMessage())
&& e.getInvalidValues().isEmpty()) {
showMessage(Level.WARNING, e.getMessage());
}
LOG.warn(e.getMessage());
}
}