/* -------------------------------------------------------------------------
OpenTripPlanner GWT Client
Copyright (C) 2015 Mecatran - info@mecatran.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
------------------------------------------------------------------------- */
package com.mecatran.otp.gwt.client.view;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.datepicker.client.DateBox;
import com.mecatran.otp.gwt.client.i18n.I18nUtils;
import com.mecatran.otp.gwt.client.model.ModeCapabilitiesBean;
import com.mecatran.otp.gwt.client.model.ModeSet;
import com.mecatran.otp.gwt.client.model.TransportMode;
public class DropDownModeSelectorWidget extends Composite implements
ModeSelectorWidget {
private static final float BIKE_COMFORT_FACTOR = 0.3f;
private static final float WALK_SPEED_WHEELCHAIR = 2.0f;
private static final float WALK_SPEED_SLOW = 4.0f;
private static final float WALK_SPEED_MEDIUM = 5.0f;
private static final float WALK_SPEED_FAST = 6.0f;
private FlowPanel dateTimePanel;
private ListBox departureOrArrivalListBox;
private DateBox dateBox;
private TimePicker timePicker;
private ExtValueListBox<ModeSet> modeDropdown;
private ExtValueListBox<Float> walkSpeedDropdown;
private ExtValueListBox<Float> bikeSpeedDropdown;
private ExtValueListBox<Float> bikeFactorsDropdown;
private ExtValueListBox<Integer> transfersDropdown;
private ExtValueListBox<Integer> maxWalkDistanceDropdown;
private Anchor showHideAdvOptsAnchor;
private Panel toHidePanel;
private Panel walkOptionsPanel;
private Panel bikeOptionsPanel;
private Panel transitOptionsPanel;
private float walkSpeedKph;
private float bikeSpeedKph;
private boolean wheelchairAccessible;
private float bikeSpeedFactor;
private int maxWalkDistanceMeters;
private int transferPenaltySeconds;
private ModeSelectorListener listener;
public DropDownModeSelectorWidget() {
// Build the components
Panel rootPanel = new VerticalPanel();
// Mode selector
Panel modePanel = new HorizontalPanel();
modePanel.addStyleName("modes-panel");
modePanel.addStyleName("modes-icon");
modeDropdown = new ExtValueListBox<ModeSet>();
modeDropdown.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
handleModeChange();
}
});
modeDropdown.setSelectedIndex(0);
modePanel.add(modeDropdown);
rootPanel.add(modePanel);
// Departure/arrival
dateTimePanel = new FlowPanel();
dateTimePanel.addStyleName("datetime-panel");
dateTimePanel.addStyleName("clock-icon");
departureOrArrivalListBox = new ListBox();
departureOrArrivalListBox.addItem(I18nUtils.tr("depart.at"));
departureOrArrivalListBox.addItem(I18nUtils.tr("arrive.at"));
departureOrArrivalListBox.setSelectedIndex(0);
departureOrArrivalListBox.getElement().getStyle()
.setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
dateTimePanel.add(departureOrArrivalListBox);
// Date & time
rootPanel.add(dateTimePanel);
HorizontalPanel datePanel = new HorizontalPanel();
datePanel.addStyleName("date-panel");
datePanel.getElement().getStyle()
.setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
dateTimePanel.add(datePanel);
DateTimeFormat dateFormat = DateTimeFormat.getFormat(I18nUtils
.tr("date.format.long"));
dateBox = new DateBox();
dateBox.setValue(new Date());
dateBox.setFormat(new DateBox.DefaultFormat(dateFormat));
datePanel.add(dateBox);
Label toLabel = new Label(I18nUtils.tr("at.time"));
toLabel.addStyleName("datetime-to-label");
toLabel.getElement().getStyle()
.setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
dateTimePanel.add(toLabel);
HorizontalPanel timePanel = new HorizontalPanel();
timePanel.addStyleName("time-panel");
timePanel.getElement().getStyle()
.setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
dateTimePanel.add(timePanel);
timePicker = new TimePicker(new Date());
timePanel.add(timePicker);
// Show/hide bar & panel
showHideAdvOptsAnchor = new Anchor(I18nUtils.tr("show.options"));
showHideAdvOptsAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
boolean visible = !toHidePanel.isVisible();
toHidePanel.setVisible(visible);
showHideAdvOptsAnchor.setText(I18nUtils
.tr(visible ? "hide.options" : "show.options"));
if (visible) {
showHideAdvOptsAnchor.removeStyleName("collapsed-icon");
showHideAdvOptsAnchor.addStyleName("expanded-icon");
} else {
showHideAdvOptsAnchor.addStyleName("collapsed-icon");
showHideAdvOptsAnchor.removeStyleName("expanded-icon");
}
}
});
showHideAdvOptsAnchor.addStyleName("show-hide-options");
showHideAdvOptsAnchor.addStyleName("collapsed-icon");
// showHideAdvOptsAnchor.setVisible(false);
rootPanel.add(showHideAdvOptsAnchor);
toHidePanel = new VerticalPanel();
toHidePanel.setVisible(false);
rootPanel.add(toHidePanel);
// Walk options panel
walkOptionsPanel = new HorizontalPanel();
walkOptionsPanel.addStyleName("walk-speed-panel");
walkOptionsPanel.addStyleName("walk-speed-icon");
walkSpeedDropdown = new ExtValueListBox<Float>();
// TODO Customize walk speeds in config
refillWalkSpeedDropdown();
walkOptionsPanel.add(walkSpeedDropdown);
maxWalkDistanceDropdown = new ExtValueListBox<Integer>();
maxWalkDistanceDropdown.addItem("< 200 m", 200);
maxWalkDistanceDropdown.addItem("< 500 m", 500);
maxWalkDistanceDropdown.addItem("< 1 km", 1000);
maxWalkDistanceDropdown.addItem("< 2 km", 2000);
maxWalkDistanceDropdown.addItem("< 5 km", 5000);
maxWalkDistanceDropdown.setSelectedValue(1000);
walkOptionsPanel.add(maxWalkDistanceDropdown);
toHidePanel.add(walkOptionsPanel);
// Bike options panel
bikeOptionsPanel = new HorizontalPanel();
bikeOptionsPanel.addStyleName("bike-speed-panel");
bikeOptionsPanel.addStyleName("bike-speed-icon");
bikeSpeedDropdown = new ExtValueListBox<Float>();
// TODO Customize bike speeds in config
bikeSpeedDropdown.addItem(I18nUtils.tr("bike.speed.slow"), 12.0f);
bikeSpeedDropdown.addItem(I18nUtils.tr("bike.speed.medium"), 16.0f);
bikeSpeedDropdown.addItem(I18nUtils.tr("bike.speed.fast"), 22.0f);
bikeSpeedDropdown.setSelectedIndex(1);
bikeOptionsPanel.add(bikeSpeedDropdown);
bikeFactorsDropdown = new ExtValueListBox<Float>();
bikeFactorsDropdown.addItem(I18nUtils.tr("bike.factor.safest"), 0.0f);
bikeFactorsDropdown.addItem(I18nUtils.tr("bike.factor.medium"),
(1.0f - BIKE_COMFORT_FACTOR) / 2.0f);
bikeFactorsDropdown.addItem(I18nUtils.tr("bike.factor.fastest"),
1.0f - BIKE_COMFORT_FACTOR);
bikeFactorsDropdown.setSelectedIndex(1);
bikeOptionsPanel.add(bikeFactorsDropdown);
toHidePanel.add(bikeOptionsPanel);
// Transit options panel
transitOptionsPanel = new HorizontalPanel();
transitOptionsPanel.addStyleName("transfers-options-panel");
transitOptionsPanel.addStyleName("transfers-options-icon");
transfersDropdown = new ExtValueListBox<Integer>();
transfersDropdown.addItem(I18nUtils.tr("transit.best.trip"), 120);
transfersDropdown.addItem(I18nUtils.tr("transit.least.transfers"), 240);
transfersDropdown.addItem(I18nUtils.tr("transit.least.walk"), 60);
transitOptionsPanel.add(transfersDropdown);
toHidePanel.add(transitOptionsPanel);
// Show/hide according to selected modes
handleModeChange();
initWidget(rootPanel);
}
@Override
public void setModeCapabilities(ModeCapabilitiesBean modeCapabilities) {
modeDropdown.clear();
for (ModeSet modeSet : buildModeSetList(modeCapabilities)) {
modeDropdown.addItem(modeSet.getHumanName(), modeSet);
}
showHideAdvOptsAnchor.setVisible(modeCapabilities
.isHasAdvancedOptions());
handleModeChange();
}
@Override
public Set<TransportMode> getSelectedModes() {
ModeSet selected = modeDropdown.getSelectedVal();
if (selected != null) {
return selected.getModes();
} else {
return Collections.emptySet();
}
}
@Override
public int getMaxWalkDistanceMeters() {
updateOptions();
if (shouldIgnoreLazynessParameters(getSelectedModes()))
return 1000000;
return maxWalkDistanceMeters;
}
@Override
public float getWalkReluctanceFactor() {
updateOptions();
if (shouldIgnoreLazynessParameters(getSelectedModes()))
return 1.0f;
return 2.0f;
}
/**
* Should ignore "lazyness parameters" (maxWalkDistance, walkReluctance) due
* to requested modes: bike-share or bike+transit.
*
* @param modes
* @return
*/
private boolean shouldIgnoreLazynessParameters(Set<TransportMode> modes) {
// If we have bike mode, always ignore lazyness :)
return modes.contains(TransportMode.BICYCLE)
|| modes.contains(TransportMode.BICYCLE_RENTAL);
}
@Override
public boolean isWheelchairAccessible() {
updateOptions();
return wheelchairAccessible;
}
@Override
public float getWalkSpeedKph() {
updateOptions();
return walkSpeedKph;
}
@Override
public float getBikeSpeedKph() {
updateOptions();
return bikeSpeedKph;
}
@Override
public int getTransferPenaltySeconds() {
updateOptions();
return transferPenaltySeconds;
}
@Override
public float getBikeSpeedFactor() {
updateOptions();
return bikeSpeedFactor;
}
@Override
public float getBikeSafetyFactor() {
updateOptions();
return 1.0f - BIKE_COMFORT_FACTOR - bikeSpeedFactor;
}
@Override
public float getBikeComfortFactor() {
return BIKE_COMFORT_FACTOR;
}
private void refillWalkSpeedDropdown() {
// TODO Make the values parametrable
Float oldWalkSpeed = walkSpeedDropdown.getSelectedVal();
Set<TransportMode> modes = getSelectedModes();
boolean hasBike = modes.contains(TransportMode.BICYCLE)
|| modes.contains(TransportMode.BICYCLE_RENTAL);
walkSpeedDropdown.clear();
if (oldWalkSpeed == null)
oldWalkSpeed = 5.0f;
if (hasBike) {
if (oldWalkSpeed == 2.0f)
oldWalkSpeed = 5.0f;
} else {
walkSpeedDropdown.addItem(I18nUtils.tr("walk.speed.handicap"),
WALK_SPEED_WHEELCHAIR);
}
walkSpeedDropdown.addItem(I18nUtils.tr("walk.speed.slow"),
WALK_SPEED_SLOW);
walkSpeedDropdown.addItem(I18nUtils.tr("walk.speed.medium"),
WALK_SPEED_MEDIUM);
walkSpeedDropdown.addItem(I18nUtils.tr("walk.speed.fast"),
WALK_SPEED_FAST);
// Reselect currently selected value if available
walkSpeedDropdown.setSelectedValue(oldWalkSpeed);
}
private void updateOptions() {
walkSpeedKph = walkSpeedDropdown.getSelectedVal();
// Wheelchair access is just a form a walk speed...
wheelchairAccessible = walkSpeedKph <= 2;
bikeSpeedKph = bikeSpeedDropdown.getSelectedVal();
bikeSpeedFactor = bikeFactorsDropdown.getSelectedVal();
transferPenaltySeconds = transfersDropdown.getSelectedVal();
maxWalkDistanceMeters = maxWalkDistanceDropdown.getSelectedVal();
}
private void handleModeChange() {
Set<TransportMode> modes = getSelectedModes();
refillWalkSpeedDropdown();
boolean hasWalk = modes.contains(TransportMode.WALK);
boolean hasTransit = modes.contains(TransportMode.BUS)
|| modes.contains(TransportMode.TRAM)
|| modes.contains(TransportMode.RAIL)
|| modes.contains(TransportMode.SUBWAY)
|| modes.contains(TransportMode.FERRY)
|| modes.contains(TransportMode.TROLLEY);
boolean hasBike = modes.contains(TransportMode.BICYCLE)
|| modes.contains(TransportMode.BICYCLE_RENTAL);
walkOptionsPanel.setVisible(hasWalk);
maxWalkDistanceDropdown.setVisible(hasWalk && hasTransit);
bikeOptionsPanel.setVisible(hasBike);
transitOptionsPanel.setVisible(hasTransit);
dateTimePanel.setVisible(hasTransit);
if (listener != null)
listener.onTransportModeChange(modes);
}
private List<ModeSet> buildModeSetList(ModeCapabilitiesBean modeCapabilities) {
List<ModeSet> retval = new ArrayList<ModeSet>();
if (modeCapabilities.isHasTransit()) {
// Transit only
retval.add(new ModeSet(I18nUtils.tr("mode.transit", "Casimir"),
TransportMode.WALK, TransportMode.BUS, TransportMode.FERRY,
TransportMode.GONDOLA, TransportMode.RAIL,
TransportMode.SUBWAY, TransportMode.TRAM,
TransportMode.TROLLEY));
}
if (modeCapabilities.isHasWalkOnly()) {
// Walk only
retval.add(new ModeSet(I18nUtils.tr("mode.walk"),
TransportMode.WALK));
}
if (modeCapabilities.isHasBikeOnly()) {
// Bike only
retval.add(new ModeSet(I18nUtils.tr("mode.bike"),
TransportMode.BICYCLE));
}
if (modeCapabilities.isHasBikeAndTransit()) {
// Bike + transit
retval.add(new ModeSet(I18nUtils.tr("mode.transit.bike"),
TransportMode.BICYCLE, TransportMode.BUS,
TransportMode.FERRY, TransportMode.GONDOLA,
TransportMode.RAIL, TransportMode.SUBWAY,
TransportMode.TRAM, TransportMode.TROLLEY));
}
if (modeCapabilities.isHasBikeRental()) {
// Rental bike only
retval.add(new ModeSet(I18nUtils.tr("mode.bike-rental"),
TransportMode.BICYCLE_RENTAL));
}
if (modeCapabilities.isHasBikeRental()
&& modeCapabilities.isHasTransit()
&& modeCapabilities.isHasBikeAndTransit()) {
// Rental bike + transit
retval.add(new ModeSet(I18nUtils.tr("mode.transit.bike-rental"),
TransportMode.BICYCLE_RENTAL, TransportMode.BUS,
TransportMode.FERRY, TransportMode.GONDOLA,
TransportMode.RAIL, TransportMode.SUBWAY,
TransportMode.TRAM, TransportMode.TROLLEY));
}
return retval;
}
@Override
public boolean isDateDeparture() {
return departureOrArrivalListBox.getSelectedIndex() == 0;
}
@Override
public void setDateDeparture(boolean dateDeparture) {
departureOrArrivalListBox.setSelectedIndex(dateDeparture ? 0 : 1);
}
@SuppressWarnings("deprecation")
@Override
public Date getDateTime() {
Date dateTime = dateBox.getValue();
Date time = new Date(timePicker.getValue());
dateTime.setHours(time.getHours());
dateTime.setMinutes(time.getMinutes());
dateTime.setSeconds(time.getSeconds());
return dateTime;
}
@Override
public void setDateTime(Date dateTime) {
dateBox.setValue(dateTime);
timePicker.setValue(dateTime.getTime());
}
@Override
public void setModes(Set<TransportMode> modes) {
/*
* Guess the best selection in the mode dropdown by computing the number
* of matches between the set of modes of each choice and the asked mode
* set. We select the best match. Please be aware that it could be not
* exactly what the called requested, but here we have to select amongst
* a restricted choice of mode sets anyway.
*/
int bestIndex = -1;
int bestMatch = -100;
for (int i = 0; i < modeDropdown.getItemCount(); i++) {
ModeSet modeSet = modeDropdown.getExtValue(i);
Set<TransportMode> modes2 = modeSet.getModes();
int n1 = 0;
int n2 = 0;
for (TransportMode mode : modes) {
if (modes2.contains(mode))
n1++;
}
for (TransportMode mode2 : modes2) {
if (!modes.contains(mode2))
n2++;
}
int match = n1 - n2;
if (match > bestMatch) {
bestMatch = match;
bestIndex = i;
}
}
if (bestIndex > -1) {
modeDropdown.setSelectedIndex(bestIndex);
/*
* The "on change" event is *not* called by setSelectedIndex(), so
* we have to call it ourselves.
*/
handleModeChange();
}
}
@Override
public void setWheelchairAccessible(boolean wheelchairAccessible) {
walkSpeedDropdown
.setSelectedValue(wheelchairAccessible ? WALK_SPEED_WHEELCHAIR
: WALK_SPEED_MEDIUM);
}
@Override
public void setModeSelectorListener(
ModeSelectorListener modeSelectorListener) {
this.listener = modeSelectorListener;
}
}