/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
*
* 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 org.onebusaway.webapp.gwt.notification;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.onebusaway.presentation.client.RoutePresenter;
import org.onebusaway.transit_data.model.ArrivalAndDepartureBean;
import org.onebusaway.transit_data.model.RouteBean;
import org.onebusaway.transit_data.model.StopBean;
import org.onebusaway.transit_data.model.StopWithArrivalsAndDeparturesBean;
import org.onebusaway.transit_data.model.trips.TripBean;
import org.onebusaway.webapp.gwt.common.PageException;
import org.onebusaway.webapp.gwt.common.context.Context;
import org.onebusaway.webapp.gwt.common.context.ContextManager;
import org.onebusaway.webapp.gwt.common.widgets.DivPanel;
import org.onebusaway.webapp.gwt.common.widgets.SpanWidget;
import org.onebusaway.webapp.gwt.where_library.WhereLibrary;
import org.onebusaway.webapp.gwt.where_library.pages.WhereCommonPage;
import org.onebusaway.webapp.gwt.where_library.resources.WhereLibraryStandardStopCssResource;
import org.onebusaway.webapp.gwt.where_library.rpc.WebappServiceAsync;
import org.onebusaway.webapp.gwt.where_library.view.ArrivalsAndDeparturesPresentaion;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
public class NotificationWidgetPage extends WhereCommonPage {
private static DateTimeFormat _timeFormat = DateTimeFormat.getFormat("hh:mm");
private static NotificationCssResource _notificationCss = NotificationResources.INSTANCE.getCss();
private static WhereLibraryStandardStopCssResource _stopCss = WhereLibrary.INSTANCE.getStandardStopCss();
private static final int DEFAULT_NOTIFY_MINUTES_BEFORE = 5;
private static final String PARAM_TRIP_ID = "tripId";
private static final String PARAM_STOP_ID = "stopId";
private NotificationStateDAO _dao = new NotificationStateDAO();
private DataRequestTimer _dataRequestTimer = new DataRequestTimer();
private ArrivalsAndDeparturesHandler _arrivalsAndDeparturesHandler = new ArrivalsAndDeparturesHandler();
private NotificationTimer _notificationTimer = new NotificationTimer();
private String _tripId;
private String _stopId;
private DivPanel _stopPanel = new DivPanel(_stopCss.arrivalsStopInfo());
private Grid _arrivalsAndDeparturesTable = new Grid(2, 3);
private ArrivalsAndDeparturesPresentaion _methods;
private List<NotificationMethod> _notificationMethods = new ArrayList<NotificationMethod>();
private TextBox _minutesBeforeTextBox;
private int _minutesBefore = DEFAULT_NOTIFY_MINUTES_BEFORE;
private ArrivalAndDepartureBean _departureBean = null;
public NotificationWidgetPage(ContextManager contextManager) {
_methods = new ArrivalsAndDeparturesPresentaion(true);
_notificationMethods.add(new SoundNotificationMethod());
_notificationMethods.add(new PopupNotificationMethod());
}
public Widget create(final Context context) throws PageException {
prepArrivalsAndDeparturesTable();
DivPanel panel = new DivPanel();
panel.addStyleName("panel");
panel.add(_stopPanel);
panel.add(_arrivalsAndDeparturesTable);
DivPanel notificationOptionsPanel = new DivPanel(
_notificationCss.notificationOptions());
panel.add(notificationOptionsPanel);
DivPanel minutesRow = getNotificationMinutesBeforePanel();
notificationOptionsPanel.add(minutesRow);
DivPanel methodsPanel = getMethodsPanel();
notificationOptionsPanel.add(methodsPanel);
// DivPanel saveDefaultsPanel = getSaveDefaultsPanel();
// notificationOptionsPanel.add(saveDefaultsPanel);
update(context);
return panel;
}
private DivPanel getNotificationMinutesBeforePanel() {
DivPanel minutesRow = new DivPanel(
_notificationCss.notificationMinutesBeforePanel());
minutesRow.add(new SpanWidget("Notify me"));
_minutesBeforeTextBox = new TextBox();
_minutesBeforeTextBox.addStyleName(_notificationCss.notificationMinutesBeforeTextBox());
_minutesBeforeTextBox.setText(Integer.toString(DEFAULT_NOTIFY_MINUTES_BEFORE));
MinutesBeforeHandler handler = new MinutesBeforeHandler();
_minutesBeforeTextBox.addKeyUpHandler(handler);
minutesRow.add(_minutesBeforeTextBox);
minutesRow.add(new SpanWidget("minutes before arrival"));
return minutesRow;
}
private DivPanel getMethodsPanel() {
DivPanel methodsPanel = new DivPanel(
_notificationCss.notificationMethodsPanel());
for (NotificationMethod method : _notificationMethods) {
DivPanel row = new DivPanel(_notificationCss.notificationMethodPanel());
if (method.getSelectionRow(row))
methodsPanel.add(row);
}
return methodsPanel;
}
private DivPanel getSaveDefaultsPanel() {
DivPanel panel = new DivPanel(_notificationCss.notificationDefaultsPanel());
panel.add(new SpanWidget("Make this the default for:",
_notificationCss.notificationDefaultsText()));
Button justThisStop = new Button("This Stop");
justThisStop.addStyleName(_notificationCss.notificationDefaultsButton());
justThisStop.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent arg0) {
NotificationState state = getState();
_dao.setState(_stopId, state, false);
}
});
panel.add(justThisStop);
Button allStops = new Button("All Stops");
allStops.addStyleName(_notificationCss.notificationDefaultsButton());
allStops.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent arg0) {
NotificationState state = getState();
_dao.setState(_stopId, state, true);
}
});
panel.add(allStops);
return panel;
}
@Override
public Widget update(Context context) throws PageException {
_tripId = context.getParam(PARAM_TRIP_ID);
_stopId = context.getParam(PARAM_STOP_ID);
System.out.println("tripId=" + _tripId + " stopId=" + _stopId);
if (_tripId == null || _stopId == null) {
_dataRequestTimer.cancel();
} else {
NotificationState state = _dao.getState(_stopId);
_minutesBeforeTextBox.setText(Integer.toString(state.getMinutesBefore()));
_minutesBefore = state.getMinutesBefore();
Map<String, NotificationMethodState> methodStates = new HashMap<String, NotificationMethodState>();
for (NotificationMethodState methodState : state.getMethodStates())
methodStates.put(methodState.getId(), methodState);
for (NotificationMethod method : _notificationMethods) {
NotificationMethodState methodState = methodStates.get(method.getId());
if (methodState != null)
method.loadFromState(methodState);
}
_dataRequestTimer.run();
_dataRequestTimer.scheduleRepeating(60 * 1000);
refreshAlarm();
}
return null;
}
private void prepArrivalsAndDeparturesTable() {
_arrivalsAndDeparturesTable.addStyleName(_stopCss.arrivalsTable());
_arrivalsAndDeparturesTable.getRowFormatter().addStyleName(0,
_stopCss.arrivalsHeader());
_arrivalsAndDeparturesTable.getRowFormatter().addStyleName(1,
_stopCss.arrivalsRow());
_arrivalsAndDeparturesTable.getCellFormatter().addStyleName(0, 0,
_stopCss.arrivalsRouteColumn());
_arrivalsAndDeparturesTable.getCellFormatter().addStyleName(0, 1,
_stopCss.arrivalsDestinationColumn());
_arrivalsAndDeparturesTable.getCellFormatter().addStyleName(0, 2,
_stopCss.arrivalsStatusColumn());
_arrivalsAndDeparturesTable.getCellFormatter().addStyleName(1, 0,
_stopCss.arrivalsRouteEntry());
_arrivalsAndDeparturesTable.getCellFormatter().setStyleName(1, 2,
_stopCss.arrivalsStatusEntry());
_arrivalsAndDeparturesTable.setText(0, 0, "route");
_arrivalsAndDeparturesTable.setText(0, 1, "destination");
_arrivalsAndDeparturesTable.setText(0, 2, "minutes");
_arrivalsAndDeparturesTable.setText(1, 1, "loading arrival data...");
}
private void refreshInterface(StopWithArrivalsAndDeparturesBean bean) {
updateStopPanel(bean.getStop());
for (ArrivalAndDepartureBean dep : bean.getArrivalsAndDepartures()) {
TripBean trip = dep.getTrip();
if (trip.getId().equals(_tripId)) {
updateArrivalsAndDeparturePanel(dep);
_departureBean = dep;
}
}
}
private void updateStopPanel(StopBean stop) {
_stopPanel.clear();
String url = "index.html#p(index)m(location)lat(" + stop.getLat() + ")lon("
+ stop.getLon() + ")accuracy(8)stop(" + stop.getId() + ")";
DivPanel namePanel = new DivPanel(_stopCss.arrivalsStopAddress());
_stopPanel.add(namePanel);
Anchor nameLink = new Anchor(stop.getName(), url);
namePanel.add(nameLink);
DivPanel numberPanel = new DivPanel(_stopCss.arrivalsStopNumber());
_stopPanel.add(numberPanel);
Anchor numberLink = new Anchor("Stop # " + stop.getCode() + " - "
+ stop.getDirection() + " bound", url);
numberPanel.add(numberLink);
}
private void updateArrivalsAndDeparturePanel(ArrivalAndDepartureBean bean) {
long now = System.currentTimeMillis();
TripBean trip = bean.getTrip();
RouteBean route = trip.getRoute();
String routeName = RoutePresenter.getNameForRoute(route);
_arrivalsAndDeparturesTable.setText(1, 0, routeName);
if (RoutePresenter.isRouteNameLong(routeName))
_arrivalsAndDeparturesTable.getCellFormatter().addStyleName(1, 0,
_stopCss.arrivalsRouteLongNameEntry());
DivPanel divPanel = new DivPanel();
DivPanel destinationPanel = new DivPanel(
_stopCss.arrivalsDestinationEntry());
divPanel.add(destinationPanel);
String href = "trip.action?id=" + trip.getId() + "&stop="
+ bean.getStop().getId();
destinationPanel.add(new Anchor(trip.getTripHeadsign(), href));
DivPanel timeAndStatusPanel = new DivPanel(_stopCss.arrivalsTimePanel());
divPanel.add(timeAndStatusPanel);
String time = _timeFormat.format(new Date(bean.computeBestDepartureTime()));
timeAndStatusPanel.add(new SpanWidget(time, _stopCss.arrivalsTimeEntry()));
timeAndStatusPanel.add(new SpanWidget(" - "));
String arrivalStatusLabelStyle = _methods.getStatusLabelStyle(bean);
timeAndStatusPanel.add(new SpanWidget(_methods.getStatusLabel(bean),
arrivalStatusLabelStyle));
_arrivalsAndDeparturesTable.setWidget(1, 1, divPanel);
_arrivalsAndDeparturesTable.getCellFormatter().setStyleName(1, 2,
_stopCss.arrivalsStatusEntry());
_arrivalsAndDeparturesTable.getCellFormatter().addStyleName(1, 2,
arrivalStatusLabelStyle);
if (_methods.isNow(bean))
_arrivalsAndDeparturesTable.getCellFormatter().addStyleName(1, 2,
_stopCss.arrivalStatusNow());
_arrivalsAndDeparturesTable.setText(1, 2, _methods.getMinutesLabel(bean));
}
private void refreshAlarm() {
System.out.println("reseting alarm");
_notificationTimer.cancel();
_minutesBefore = getMinutesBefore();
if (_minutesBefore < 0) {
System.out.println("invalid minutes before: " + _minutesBefore);
_minutesBeforeTextBox.addStyleName("invalidValue");
} else {
System.out.println("good minutes before: " + _minutesBefore);
_minutesBeforeTextBox.removeStyleName("invalidValue");
_notificationTimer.schedule(5 * 1000);
}
}
private int getMinutesBefore() {
String minutesBeforeString = _minutesBeforeTextBox.getText();
System.out.println("minutes before raw=" + minutesBeforeString);
try {
return Integer.parseInt(minutesBeforeString);
} catch (NumberFormatException ex) {
return -1;
}
}
private void checkAlarm() {
System.out.println("checking alarm");
if (_departureBean != null) {
System.out.println("we have a bean");
if (_departureBean.computeBestDepartureTime() - _minutesBefore * 60
* 1000 < System.currentTimeMillis()) {
System.out.println("we have an alarm!");
_notificationTimer.cancel();
NotificationContextImpl context = new NotificationContextImpl();
for (NotificationMethod method : _notificationMethods)
method.handleNotification(context);
return;
}
}
_notificationTimer.schedule(30 * 1000);
}
private void resetNotifications() {
System.out.println("notification reset");
for (NotificationMethod method : _notificationMethods)
method.handleNotificationReset();
}
private NotificationState getState() {
NotificationState state = new NotificationState();
state.setMinutesBefore(_minutesBefore);
for (NotificationMethod method : _notificationMethods) {
NotificationMethodState methodState = new NotificationMethodState();
methodState.setId(method.getId());
methodState.setEnabled(method.isEnabled());
state.getMethodStates().add(methodState);
}
return state;
}
/****
* Internal Classes
****/
private class DataRequestTimer extends Timer {
@Override
public void run() {
WebappServiceAsync service = WebappServiceAsync.SERVICE;
service.getArrivalsByStopId(_stopId, _arrivalsAndDeparturesHandler);
}
}
private class ArrivalsAndDeparturesHandler implements
AsyncCallback<StopWithArrivalsAndDeparturesBean> {
public void onSuccess(StopWithArrivalsAndDeparturesBean bean) {
refreshInterface(bean);
}
public void onFailure(Throwable arg0) {
}
}
private class MinutesBeforeHandler implements KeyUpHandler {
public void onKeyUp(KeyUpEvent arg0) {
refreshAlarm();
}
}
private class NotificationTimer extends Timer {
@Override
public void run() {
checkAlarm();
}
}
private class NotificationContextImpl implements NotificationContext {
public void reset() {
resetNotifications();
}
}
}