Merge branch 'gwt' into spectator

This commit is contained in:
Lennart Hensler committed 2012-01-19 09:50:47 +01:00
commit 83632deee5
30 files changed
+1251 -336

No files matched your search

@@ -40,7 +40,7 @@ public class ExpeditionWindTrackerFactory implements WindTrackerFactory, BundleA
logger.info("Created "+getClass().getName()+" with default UDP port "+defaultPort);
}
public static WindTrackerFactory getInstance() {
public synchronized static WindTrackerFactory getInstance() {
if (defaultInstance == null) {
defaultInstance = new ExpeditionWindTrackerFactory();
}
@@ -5,6 +5,9 @@ import com.google.gwt.resources.client.ImageResource;
interface AdminConsoleResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/settings.png")
ImageResource settingsIcon();
@Source("com/sap/sailing/gwt/ui/client/images/remove.png")
ImageResource removeIcon();
@@ -3,12 +3,14 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
@@ -48,6 +50,8 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe
private final RaceMap raceMap;
private final QuickRanksListBoxComposite quickRanksListBox;
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
public RaceMapPanel(SailingServiceAsync sailingService, CompetitorSelectionProvider competitorSelectionProvider,
ErrorReporter errorReporter, final EventRefresher eventRefresher, final StringMessages stringMessages) {
this.sailingService = sailingService;
@@ -79,8 +83,9 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe
/* time interval between displays in milliseconds */5000, stringMessages, errorReporter);
raceListBox.addRaceSelectionChangeListener(windHistory);
grid.setWidget(1, 0, windHistory);
Anchor showConfigAnchor = new Anchor(new SafeHtmlBuilder().appendHtmlConstant(
"<img class=\"linkNoBorder\" src=\"/gwt/images/settings.png\"/>").toSafeHtml());
ImageResource settingsImage = resources.settingsIcon();
Anchor showConfigAnchor = new Anchor(AbstractImagePrototype.create(settingsImage).getSafeHtml());
showConfigAnchor.setTitle(stringMessages.configuration());
showConfigAnchor.addClickHandler(new ClickHandler() {
@Override
@@ -129,8 +129,8 @@ public class RaceMapResources {
Icon unknownManeuverIcon = Icon
.newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=?|FFFFFF|000000");
unknownManeuverIcon.setIconAnchor(Point.newInstance(10, 33));
maneuverIconsForTypeAndTargetTack.put(new Pair<ManeuverType, Tack>(ManeuverType.UNKNOWN, Tack.STARBOARD), tackToStarboardIcon);
maneuverIconsForTypeAndTargetTack.put(new Pair<ManeuverType, Tack>(ManeuverType.UNKNOWN, Tack.PORT), tackToStarboardIcon);
maneuverIconsForTypeAndTargetTack.put(new Pair<ManeuverType, Tack>(ManeuverType.UNKNOWN, Tack.STARBOARD), unknownManeuverIcon);
maneuverIconsForTypeAndTargetTack.put(new Pair<ManeuverType, Tack>(ManeuverType.UNKNOWN, Tack.PORT), unknownManeuverIcon);
Icon penaltyCircleToStarboardIcon = Icon
.newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|00FF00|000000");
penaltyCircleToStarboardIcon.setIconAnchor(Point.newInstance(10, 33));
@@ -0,0 +1,240 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ca.nanometrics.gflot.client.Axis;
import ca.nanometrics.gflot.client.DataPoint;
import ca.nanometrics.gflot.client.PlotItem;
import ca.nanometrics.gflot.client.PlotModelStrategy;
import ca.nanometrics.gflot.client.PlotPosition;
import ca.nanometrics.gflot.client.PlotWithOverview;
import ca.nanometrics.gflot.client.PlotWithOverviewModel;
import ca.nanometrics.gflot.client.SeriesHandler;
import ca.nanometrics.gflot.client.SeriesType;
import ca.nanometrics.gflot.client.event.PlotHoverListener;
import ca.nanometrics.gflot.client.event.SelectionListener;
import ca.nanometrics.gflot.client.jsni.Plot;
import ca.nanometrics.gflot.client.options.AxisOptions;
import ca.nanometrics.gflot.client.options.GridOptions;
import ca.nanometrics.gflot.client.options.LegendOptions;
import ca.nanometrics.gflot.client.options.LineSeriesOptions;
import ca.nanometrics.gflot.client.options.PlotOptions;
import ca.nanometrics.gflot.client.options.PointsSeriesOptions;
import ca.nanometrics.gflot.client.options.SelectionOptions;
import ca.nanometrics.gflot.client.options.SeriesOptions;
import ca.nanometrics.gflot.client.options.TickFormatter;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.gwt.ui.client.ColorMap;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.WindDAO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDAO;
import com.sap.sailing.gwt.ui.shared.WindTrackInfoDAO;
import com.sap.sailing.gwt.ui.shared.components.Component;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
public class WindChart implements Component<WindChartSettings> {
private final StringMessages stringMessages;
private final DateTimeFormat dateFormat;
private PlotWithOverviewModel model;
private final Set<WindSource> windSourcesToDisplay;
private final ColorMap<WindSource> colorMap;
private final Map<WindSource, SeriesHandler> stripChartSeries;
private final List<SeriesHandler> seriesHandlersInOrder;
private final Label selectedPointLabel;
private final ErrorReporter errorReporter;
private PlotWithOverview plot;
public WindChart(WindChartSettings settings, StringMessages stringMessages, ErrorReporter errorReporter) {
super();
this.stringMessages = stringMessages;
this.errorReporter = errorReporter;
this.windSourcesToDisplay = new HashSet<WindSource>(settings.getWindSourcesToDisplay());
dateFormat = DateTimeFormat.getFormat("HH:mm:ss");
this.colorMap = new ColorMap<WindSource>();
this.stripChartSeries = new HashMap<WindSource, SeriesHandler>();
this.seriesHandlersInOrder = new ArrayList<SeriesHandler>();
this.selectedPointLabel = new Label();
}
@Override
public String getLocalizedShortName() {
return stringMessages.wind();
}
@Override
public Widget getEntryWidget() {
VerticalPanel vp = new VerticalPanel();
HorizontalPanel hp = new HorizontalPanel();
hp.add(selectedPointLabel);
hp.setHeight("2em");
vp.add(hp);
model = new PlotWithOverviewModel(PlotModelStrategy.defaultStrategy());
PlotOptions plotOptions = new PlotOptions();
plotOptions.setDefaultLineSeriesOptions(new LineSeriesOptions().setLineWidth(1).setShow(true));
plotOptions.setDefaultPointsOptions(new PointsSeriesOptions().setShow(false));
plotOptions.setDefaultShadowSize(2);
AxisOptions hAxisOptions = new AxisOptions();
hAxisOptions.setTickFormatter(new TickFormatter() {
@Override
public String formatTickValue(double tickValue, Axis axis) {
return dateFormat.format(new Date((long) tickValue));
}
});
plotOptions.setXAxisOptions(hAxisOptions);
plotOptions.setLegendOptions(new LegendOptions().setShow(false));
plotOptions.setGridOptions(new GridOptions().setHoverable(true).setMouseActiveRadius(5).setAutoHighlight(true));
plotOptions.setSelectionOptions(new SelectionOptions().setDragging(true).setMode("x")); // select along x-axis only
for (WindSource windSource : windSourcesToDisplay) {
SeriesHandler series = addSeries(windSource);
series.setOptions(SeriesType.LINES, getLinesOptions(/* visible */ windSourcesToDisplay.contains(windSource)));
series.setOptions(SeriesType.POINTS, getPointsOptions(/* visible */ false));
series.setVisible(false);
}
this.plot = new PlotWithOverview(model, plotOptions) {
@Override
protected void onLoad() {
super.onLoad();
// onLoad is called immediately after a widget becomes attached to the browser's document.
// Use this time point to ask the chart to redraw
redraw();
}
};
// add hover listener
plot.addHoverListener(new PlotHoverListener() {
public void onPlotHover(Plot plot, PlotPosition position, PlotItem item) {
if (item != null) {
SeriesHandler seriesHandler = seriesHandlersInOrder.get(item.getSeriesIndex());
WindSource windSource = getWindSource(seriesHandler);
selectedPointLabel.setText(windSource.name()+": "+position.getY().intValue());
selectedPointLabel.setVisible(true);
} else {
selectedPointLabel.setVisible(false);
}
}
}, /* only on data point */ false);
plot.addSelectionListener(new SelectionListener() {
public void selected(double x1, double y1, double x2, double y2) {
/* TODO Remove not visible buoys from the series when user is zooming in or add them if he is zooming out.
for (CompetitorDAO competitor : competitorsAndTimePointsDAO.getCompetitor()){
long[] markPassingTimes = competitorsAndTimePointsDAO.getMarkPassings(competitor);
Double[] markPassingValues = chartData.getMarkPassings(competitor);
SeriesHandler markSeries = getCompetitorMarkPassingSeries(competitor);
markSeries.clear();
int visibleMarkPassings = 0;
for (int j = 0; j < markPassingTimes.length; j++){
if (markPassingValues[j] != null && markPassingTimes[j] > x1 && markPassingTimes[j] < x2) {
markSeries.add(new DataPoint(markPassingTimes[j],markPassingValues[j]));
visibleMarkPassings++;
}
}
if (visibleMarkPassings == 0){
markSeries.setVisible(false);
}
}*/
plot.setLinearSelection(x1, x2);
}
});
plot.setOverviewHeight(60);
vp.add(plot);
return vp;
}
private WindSource getWindSource(SeriesHandler seriesHandler) {
for (Map.Entry<WindSource, SeriesHandler> e : stripChartSeries.entrySet()) {
if (e.getValue() == seriesHandler) {
return e.getKey();
}
}
return null;
}
private SeriesHandler addSeries(WindSource windSource) {
SeriesHandler series = model.addSeries(windSource.name(), colorMap.getColorByID(windSource));
seriesHandlersInOrder.add(series);
stripChartSeries.put(windSource, series);
return series;
}
public void updateStripChartSeries(WindInfoForRaceDAO result) {
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
for (Map.Entry<WindSource, WindTrackInfoDAO> e : result.windTrackInfoByWindSource.entrySet()) {
WindSource windSource = e.getKey();
SeriesHandler seriesHandler = stripChartSeries.get(windSource);
if (e.getValue().windFixes.isEmpty()) {
if (seriesHandler != null) {
model.removeSeries(seriesHandler);
stripChartSeries.remove(windSource);
}
} else {
if (seriesHandler == null) {
seriesHandler = addSeries(windSource);
seriesHandler.setOptions(SeriesType.LINES, getLinesOptions(/* visible */ windSourcesToDisplay.contains(windSource)));
seriesHandler.setOptions(SeriesType.POINTS, getPointsOptions(/* visible */ false));
} else {
seriesHandler.clear();
}
for (WindDAO windFix : e.getValue().windFixes) {
seriesHandler.add(new DataPoint(windFix.timepoint, windFix.dampenedTrueWindFromDeg));
min = Math.min(min, windFix.timepoint);
max = Math.max(max, windFix.timepoint);
}
seriesHandler.setVisible(true);
}
}
if (plot != null && plot.isAttached()) {
try {
plot.setLinearSelection(min, max); // TODO maintain previous selection
plot.redraw();
} catch (Exception ex) {
errorReporter.reportError("Error trying to update strip chart: " + ex.getMessage());
}
}
}
private SeriesOptions getPointsOptions(boolean visible) {
return new PointsSeriesOptions().setLineWidth(0).setShow(visible);
}
private SeriesOptions getLinesOptions(boolean visible) {
return new LineSeriesOptions().setLineWidth(2.5).setShow(visible);
}
@Override
public boolean hasSettings() {
return true;
}
@Override
public SettingsDialogComponent<WindChartSettings> getSettingsDialogComponent() {
return new WindChartSettingsDialogComponent(new WindChartSettings(windSourcesToDisplay));
}
/**
* Sets the visibilities of the wind source series based on the new settings. Note that this does not
* re-load any wind data. This has to happen by calling {@link #updateStripChartSeries(WindInfoForRaceDAO)}.
*/
@Override
public void updateSettings(WindChartSettings newSettings) {
windSourcesToDisplay.clear();
windSourcesToDisplay.addAll(newSettings.getWindSourcesToDisplay());
for (Map.Entry<WindSource, SeriesHandler> e : stripChartSeries.entrySet()) {
e.getValue().setVisible(windSourcesToDisplay.contains(e.getKey()));
}
plot.redraw();
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.sap.sailing.domain.common.WindSource;
public class WindChartSettings {
private final Set<WindSource> windSourcesToDisplay;
public WindChartSettings(Set<WindSource> windSourcesToDisplay) {
this.windSourcesToDisplay = windSourcesToDisplay;
}
public WindChartSettings(WindSource... windSourcesToDisplay) {
this(new HashSet<WindSource>(windSourcesToDisplay==null?new ArrayList<WindSource>():Arrays.asList(windSourcesToDisplay)));
}
public Set<WindSource> getWindSourcesToDisplay() {
return windSourcesToDisplay;
}
}
@@ -0,0 +1,61 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
public class WindChartSettingsDialogComponent implements SettingsDialogComponent<WindChartSettings> {
private final WindChartSettings initialSettings;
private final Map<WindSource, CheckBox> checkboxes;
public WindChartSettingsDialogComponent(WindChartSettings initialSettings) {
super();
this.initialSettings = initialSettings;
checkboxes = new LinkedHashMap<WindSource, CheckBox>();
}
@Override
public Widget getAdditionalWidget(DataEntryDialog<WindChartSettings> dialog) {
VerticalPanel vp = new VerticalPanel();
for (WindSource windSource : WindSource.values()) {
CheckBox checkbox = dialog.createCheckbox(windSource.name());
checkboxes.put(windSource, checkbox);
checkbox.setValue(initialSettings.getWindSourcesToDisplay().contains(windSource));
vp.add(checkbox);
}
return vp;
}
@Override
public WindChartSettings getResult() {
Set<WindSource> windSourcesToDisplay = new HashSet<WindSource>();
for (Map.Entry<WindSource, CheckBox> e : checkboxes.entrySet()) {
if (e.getValue().getValue()) {
windSourcesToDisplay.add(e.getKey());
}
}
return new WindChartSettings(windSourcesToDisplay);
}
@Override
public Validator<WindChartSettings> getValidator() {
// with checkboxes only, nothing can go wrong :-)
return null;
}
@Override
public FocusWidget getFocusWidget() {
return checkboxes.values().iterator().next();
}
}
@@ -7,34 +7,16 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ca.nanometrics.gflot.client.Axis;
import ca.nanometrics.gflot.client.DataPoint;
import ca.nanometrics.gflot.client.PlotItem;
import ca.nanometrics.gflot.client.PlotModelStrategy;
import ca.nanometrics.gflot.client.PlotPosition;
import ca.nanometrics.gflot.client.PlotWithOverview;
import ca.nanometrics.gflot.client.PlotWithOverviewModel;
import ca.nanometrics.gflot.client.SeriesHandler;
import ca.nanometrics.gflot.client.SeriesType;
import ca.nanometrics.gflot.client.event.PlotHoverListener;
import ca.nanometrics.gflot.client.event.SelectionListener;
import ca.nanometrics.gflot.client.jsni.Plot;
import ca.nanometrics.gflot.client.options.AxisOptions;
import ca.nanometrics.gflot.client.options.GridOptions;
import ca.nanometrics.gflot.client.options.LegendOptions;
import ca.nanometrics.gflot.client.options.LineSeriesOptions;
import ca.nanometrics.gflot.client.options.PlotOptions;
import ca.nanometrics.gflot.client.options.PointsSeriesOptions;
import ca.nanometrics.gflot.client.options.SelectionOptions;
import ca.nanometrics.gflot.client.options.TickFormatter;
import com.google.gwt.cell.client.ActionCell;
import com.google.gwt.cell.client.ActionCell.Delegate;
import com.google.gwt.core.client.GWT;
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.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.ColumnSortEvent;
import com.google.gwt.user.cellview.client.ColumnSortEvent.Handler;
@@ -44,6 +26,8 @@ import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
import com.google.gwt.user.cellview.client.IdentityColumn;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
@@ -53,9 +37,8 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.gwt.ui.client.ColorMap;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.EventDisplayer;
import com.sap.sailing.gwt.ui.client.EventRefresher;
@@ -68,13 +51,14 @@ import com.sap.sailing.gwt.ui.shared.RegattaDAO;
import com.sap.sailing.gwt.ui.shared.WindDAO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDAO;
import com.sap.sailing.gwt.ui.shared.WindTrackInfoDAO;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialog;
import com.sap.sailing.server.api.EventNameAndRaceName;
public class WindPanel extends FormPanel implements EventDisplayer, WindShower, RaceSelectionChangeListener {
private final SailingServiceAsync sailingService;
private final ErrorReporter errorReporter;
private final Grid grid;
private final StringMessages stringConstants;
private final StringMessages stringMessages;
private final WindSettingPanel windSettingPanel;
private ColumnSortList columnSortList;
private final IdentityColumn<WindDAO> removeColumn;
@@ -87,19 +71,14 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
private final ListBox windSourceSelection;
private final Map<WindSource, ListDataProvider<WindDAO>> windLists;
private final CheckBox raceIsKnownToStartUpwindBox;
private final PlotWithOverview stripChart;
private final DateTimeFormat dateFormat;
private final ColorMap<WindSource> colorMap;
private final Map<WindSource, SeriesHandler> stripChartSeries;
private final WindChart windChart;
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
public WindPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
EventRefresher eventRefresher, StringMessages stringConstants) {
EventRefresher eventRefresher, final StringMessages stringMessages) {
this.sailingService = sailingService;
this.errorReporter = errorReporter;
this.stringConstants = stringConstants;
this.colorMap = new ColorMap<WindSource>();
this.stripChartSeries = new HashMap<WindSource, SeriesHandler>();
dateFormat = DateTimeFormat.getFormat("HH:mm:ss");
this.stringMessages = stringMessages;
windLists = new HashMap<WindSource, ListDataProvider<WindDAO>>();
windSourceSelection = new ListBox();
windSourceSelection.addChangeHandler(new ChangeHandler() {
@@ -108,7 +87,7 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
setWindSource(/* runOnSuccess */ null);
}
});
removeColumn = new IdentityColumn<WindDAO>(new ActionCell<WindDAO>(stringConstants.remove(), new Delegate<WindDAO>() {
removeColumn = new IdentityColumn<WindDAO>(new ActionCell<WindDAO>(stringMessages.remove(), new Delegate<WindDAO>() {
@Override
public void execute(final WindDAO wind) {
List<Triple<EventDAO, RegattaDAO, RaceDAO>> eventAndRaces = trackedEventsComposite.getSelectedEventAndRace();
@@ -122,7 +101,7 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
@Override
public void onFailure(Throwable caught) {
WindPanel.this.errorReporter.reportError(
WindPanel.this.stringConstants.errorSettingWindForRace()+ " "+eventAndRace.getC().name
WindPanel.this.stringMessages.errorSettingWindForRace()+ " "+eventAndRace.getC().name
+ ": "+ caught.getMessage());
}
});
@@ -159,16 +138,16 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
}
};
grid = new Grid(4, 2); // first row: event/race selection; second row: wind source selection; third row: wind display
trackedEventsComposite = new TrackedEventsComposite(sailingService, errorReporter, eventRefresher, stringConstants, false);
trackedEventsComposite = new TrackedEventsComposite(sailingService, errorReporter, eventRefresher, stringMessages, false);
trackedEventsComposite.addRaceSelectionChangeListener(this);
grid.setWidget(0, 0, trackedEventsComposite);
windSettingPanel = new WindSettingPanel(sailingService, errorReporter, trackedEventsComposite, this);
grid.setWidget(0, 1, windSettingPanel);
HorizontalPanel windSourceSelectionPanel = new HorizontalPanel();
windSourceSelectionPanel.setSpacing(10);
windSourceSelectionPanel.add(new Label(stringConstants.windSource()));
windSourceSelectionPanel.add(new Label(stringMessages.windSource()));
windSourceSelectionPanel.add(windSourceSelection);
raceIsKnownToStartUpwindBox = new CheckBox(stringConstants.raceIsKnownToStartUpwind());
raceIsKnownToStartUpwindBox = new CheckBox(stringMessages.raceIsKnownToStartUpwind());
windSourceSelectionPanel.add(raceIsKnownToStartUpwindBox);
raceIsKnownToStartUpwindBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
@@ -180,118 +159,23 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
});
}
});
ImageResource settingsImage = resources.settingsIcon();
Anchor showConfigAnchor = new Anchor(AbstractImagePrototype.create(settingsImage).getSafeHtml());
showConfigAnchor.setTitle(stringMessages.configuration());
showConfigAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
new SettingsDialog<WindChartSettings>(windChart, stringMessages).show();
}
});
windSourceSelectionPanel.add(showConfigAnchor);
grid.setWidget(1, 0, windSourceSelectionPanel);
stripChart = createStripChart();
grid.setWidget(2, 0, stripChart);
windChart = new WindChart(new WindChartSettings(WindSource.values()), stringMessages, errorReporter);
grid.setWidget(2, 0, windChart.getEntryWidget());
grid.getCellFormatter().setVerticalAlignment(1, 1, HasVerticalAlignment.ALIGN_TOP);
this.setWidget(grid);
}
private PlotWithOverview createStripChart() {
PlotWithOverviewModel model = new PlotWithOverviewModel(PlotModelStrategy.defaultStrategy());
PlotOptions plotOptions = new PlotOptions();
plotOptions.setDefaultLineSeriesOptions(new LineSeriesOptions().setLineWidth(1).setShow(true));
plotOptions.setDefaultPointsOptions(new PointsSeriesOptions().setShow(false));
plotOptions.setDefaultShadowSize(2);
AxisOptions hAxisOptions = new AxisOptions();
hAxisOptions.setTickFormatter(new TickFormatter() {
@Override
public String formatTickValue(double tickValue, Axis axis) {
return dateFormat.format(new Date((long) tickValue));
}
});
plotOptions.setXAxisOptions(hAxisOptions);
plotOptions.setLegendOptions(new LegendOptions().setShow(false));
plotOptions.setGridOptions(new GridOptions().setHoverable(true).setMouseActiveRadius(5).setAutoHighlight(true));
plotOptions.setSelectionOptions(new SelectionOptions().setDragging(true).setMode("x")); // select along x-axis only
for (WindSource windSource : WindSource.values()) {
SeriesHandler series = model.addSeries(""+windSource.name(), colorMap.getColorByID(windSource));
series.setOptions(SeriesType.LINES, new LineSeriesOptions().setLineWidth(2.5).setShow(true));
series.setOptions(SeriesType.POINTS, new PointsSeriesOptions().setLineWidth(0).setShow(false));
series.setVisible(false);
stripChartSeries.put(windSource, series);
}
final PlotWithOverview plot = new PlotWithOverview(model, plotOptions) {
@Override
protected void onLoad() {
super.onLoad();
// onLoad is called immediately after a widget becomes attached to the browser's document.
// Use this time point to ask the chart to redraw
redraw();
}
};
// add hover listener
plot.addHoverListener(new PlotHoverListener() {
public void onPlotHover(Plot plot, PlotPosition position, PlotItem item) {
/* TODO show some reasonable hover tooltip in wind strip chart
CompetitorDAO competitor = competitorID.get(seriesID.indexOf(item.getSeries()));
if (item != null && competitor != null) {
if (item.getSeries().getLabel().toLowerCase().contains("mark")){
selectedPointLabel.setText(competitor.name + " passed " + markPassingBuoyName.get(competitor.id + (long) item.getDataPoint().getX()) +" at " + dateFormat.format(new Date((long) item.getDataPoint().getX())));
}
else {
String unit = "";
switch (dataToShow){
case CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
unit = stringConstants.currentSpeedOverGroundInKnotsUnit();
break;
case DISTANCE_TRAVELED:
unit = stringConstants.distanceInMetersUnit();
break;
case GAP_TO_LEADER_IN_SECONDS:
unit = stringConstants.gapToLeaderInSecondsUnit();
break;
case VELOCITY_MADE_GOOD_IN_KNOTS:
unit = stringConstants.velocityMadeGoodInKnotsUnit();
break;
case WINDWARD_DISTANCE_TO_OVERALL_LEADER:
unit = stringConstants.windwardDistanceToGoInMetersUnit();
}
String decimalPlaces = "";
for (int i = 0; i < dataToShow.getPrecision(); i++){
if (i == 0){
decimalPlaces += ".";
}
decimalPlaces += "0";
}
NumberFormat numberFormat = NumberFormat.getFormat("0" +decimalPlaces);
selectedPointLabel.setText(competitor.name + " at " + dateFormat.format(new Date((long) item.getDataPoint().getX()))
+ ": " + numberFormat.format(item.getDataPoint().getY()) + unit);
}
} else {
selectedPointLabel.setText(stringConstants.noSelection());
}
*/
}
}, true);
plot.addSelectionListener(new SelectionListener() {
public void selected(double x1, double y1, double x2, double y2) {
/* TODO Remove not visible buoys from the series when user is zooming in or add them if he is zooming out.
for (CompetitorDAO competitor : competitorsAndTimePointsDAO.getCompetitor()){
long[] markPassingTimes = competitorsAndTimePointsDAO.getMarkPassings(competitor);
Double[] markPassingValues = chartData.getMarkPassings(competitor);
SeriesHandler markSeries = getCompetitorMarkPassingSeries(competitor);
markSeries.clear();
int visibleMarkPassings = 0;
for (int j = 0; j < markPassingTimes.length; j++){
if (markPassingValues[j] != null && markPassingTimes[j] > x1 && markPassingTimes[j] < x2) {
markSeries.add(new DataPoint(markPassingTimes[j],markPassingValues[j]));
visibleMarkPassings++;
}
}
if (visibleMarkPassings == 0){
markSeries.setVisible(false);
}
}*/
plot.setLinearSelection(x1, x2);
}
});
// plot.setHeight(height);
// plot.setWidth(width);
plot.setOverviewHeight(60);
return plot;
}
private void clearOrShowWindBasedOnRaceSelection(List<Triple<EventDAO, RegattaDAO, RaceDAO>> selectedRaces) {
if (selectedRaces.isEmpty()) {
clearWindDisplay(); // no wind known for untracked race
@@ -325,7 +209,7 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(WindPanel.this.stringConstants.errorFetchingWindInformationForRace()+" " + race.name + ": "
errorReporter.reportError(WindPanel.this.stringMessages.errorFetchingWindInformationForRace()+" " + race.name + ": "
+ caught.getMessage());
}
});
@@ -364,10 +248,10 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
grid.setWidget(3, 0, null);
VerticalPanel windDisplay = new VerticalPanel();
grid.setWidget(3, 0, windDisplay);
updateStripChartSeries(result);
windChart.updateStripChartSeries(result);
for (Map.Entry<WindSource, WindTrackInfoDAO> e : result.windTrackInfoByWindSource.entrySet()) {
Label windSourceLabel = new Label(stringConstants.windSource()+": "+e.getKey()+
", "+stringConstants.dampeningInterval()+" "+e.getValue().dampeningIntervalInMilliseconds+"ms");
Label windSourceLabel = new Label(stringMessages.windSource()+": "+e.getKey()+
", "+stringMessages.dampeningInterval()+" "+e.getValue().dampeningIntervalInMilliseconds+"ms");
windDisplay.add(windSourceLabel);
timeColumn.setSortable(true);
speedInKnotsColumn.setSortable(true);
@@ -409,45 +293,6 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
}
}
private void updateStripChartSeries(WindInfoForRaceDAO result) {
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
for (Map.Entry<WindSource, WindTrackInfoDAO> e : result.windTrackInfoByWindSource.entrySet()) {
WindSource windSource = e.getKey();
SeriesHandler seriesHandler = stripChartSeries.get(windSource);
if (e.getValue().windFixes.isEmpty()) {
if (seriesHandler != null) {
stripChart.getModel().removeSeries(seriesHandler);
stripChartSeries.remove(windSource);
}
} else {
if (seriesHandler == null) {
seriesHandler = stripChart.getModel().addSeries("" + windSource.name(),
colorMap.getColorByID(windSource));
seriesHandler.setOptions(SeriesType.LINES, new LineSeriesOptions().setLineWidth(2.5).setShow(true));
seriesHandler.setOptions(SeriesType.POINTS, new PointsSeriesOptions().setLineWidth(0).setShow(false));
stripChartSeries.put(windSource, seriesHandler);
} else {
seriesHandler.clear();
}
for (WindDAO windFix : e.getValue().windFixes) {
seriesHandler.add(new DataPoint(windFix.timepoint, windFix.dampenedTrueWindFromDeg));
min = Math.min(min, windFix.timepoint);
max = Math.max(max, windFix.timepoint);
}
seriesHandler.setVisible(true);
}
}
if (stripChart != null && stripChart.isAttached()) {
try {
stripChart.setLinearSelection(min, max); // TODO maintain previous selection
stripChart.redraw();
} catch (Exception ex) {
errorReporter.reportError("Error trying to update strip chart: " + ex.getMessage());
}
}
}
private Handler getWindTableColumnSortHandler(List<WindDAO> list, TextColumn<WindDAO> timeColumn,
TextColumn<WindDAO> speedInKnotsColumn, TextColumn<WindDAO> windDirectionInDegColumn,
TextColumn<WindDAO> dampenedSpeedInKnotsColumn, TextColumn<WindDAO> dampenedWindDirectionInDegColumn) {
@@ -498,9 +343,9 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
windSourceName, raceIsKnownToStartUpwindBox.getValue(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(WindPanel.this.stringConstants.errorWhileTryingToSetWindSourceForRace()+
" "+selectedRace.getC().name+" "+WindPanel.this.stringConstants.inEvent()+" "+selectedRace.getA().name+
" "+WindPanel.this.stringConstants.to()+" "+
errorReporter.reportError(WindPanel.this.stringMessages.errorWhileTryingToSetWindSourceForRace()+
" "+selectedRace.getC().name+" "+WindPanel.this.stringMessages.inEvent()+" "+selectedRace.getA().name+
" "+WindPanel.this.stringMessages.to()+" "+
windSourceName+": "+caught.getMessage());
}
@Override
@@ -3,7 +3,7 @@ kielWeel2011=Kiel Week 2011
stgAccount=STG Account
tracTracEvents=TracTrac Events
swissTimingEvents=SwissTiming Events
trackedBefore=History of tracked events
trackedBefore=Historie verfolgter Events
listRaces=Rennen anzeigen
wind=Wind
windPanelLabel=This is the Wind Panel which so far is completely empty.
@@ -14,14 +14,14 @@ dampeningInterval=Mittelungsintervall
map=Karte
race=Wettfahrt
races=Wettfahrten
tracked=getracked
tracked=verfolgt
time=Zeit
acceleration=Acceleration
accelerationHelp=Use "1" for regular play-back speed, "2" for double-speed playback and so on
delay=Delay (s)
delayHelp=Leave empty to replay at arbitrary times; enter seconds (e.g., "60" for one minute or "0" for none) of delay before live data feed
speedInKnots=Speed (kn)
fromDeg=From (deg)
acceleration=Abspielgeschwindigkeit
accelerationHelp="1" für normale Abspielgeschwindigkeit, "2" für doppelte Geschwindigkeit usw.
delay=Verzögerung (s)
delayHelp=Leer für Wiedergabe zu beliebigen Zeitpunkten; Verzögerung des Datenstroms in Sekunden eingeben (z.B. "60" für eine Minute oder "0" für keine)
speedInKnots=Geschwindigkeit (sm/h)
fromDeg=aus (Grad)
noRacesYet=Bisher keine Wettfahrten
total=Gesamt
carry=übertr.
@@ -107,12 +107,12 @@ livePort=Port Live Data
storedPort=Port Stored Data
liveUri=Live URI
storedUri=Stored URI
eventsConnectedTo=Events Currently Tracked
eventsConnectedTo=Momentan verfolgte Events
hostname=Hostname
declinationCheckbox=Correct Wind Bearing by Declination
declinationCheckbox=Windrichtung mit Missweisung korrigieren
raceStartTrackingColumn=Tracking Started
pauseAutomaticRefresh=Pause automatic refresh
autoRefresh=Refresh automatically
pauseAutomaticRefresh=Keine automatische Aktualisierung
autoRefresh=Automatisch aktualisieren
delayInSeconds=Verzögerung in Sekunden:
delayBetweenAutoAdvances=Intervall zwischen Auto-Updates in Sekunden:
chooseUpdateIntervalOfAtLeastOneSecond=Update-Intervall mindestens eine Sekunde
@@ -154,7 +154,7 @@ in=in
secondsUnit=[s]
metersUnit=[m]
failedToLoadRaceData=Renndaten konnten nicht geladen werden.
close=Close
close=Schließen
compareCompetitors=Kompetitoren vergleichen
description=Beschreibung
sailNumber=Segelnummer
@@ -73,7 +73,7 @@ import com.sap.sailing.server.api.RaceIdentifier;
* data. So {@code seriesID, competitorID and markSeriesID} are linked with the index. So if u know for example the
* seriesID-index, you can get the competitor by calling competitorID.get(index).
*
* @author Benjamin Ebling (D056866)
* @author Benjamin Ebling (D056866), Axel Uhl (d043530)
*
*/
public abstract class AbstractChartPanel<SettingsType extends ChartSettings> extends SimplePanel implements CompetitorSelectionChangeListener {
@@ -409,6 +409,7 @@ public abstract class AbstractChartPanel<SettingsType extends ChartSettings> ext
// add hover listener
plot.addHoverListener(new PlotHoverListener() {
public void onPlotHover(Plot plot, PlotPosition position, PlotItem item) {
// FIXME can the indexOf ever find anything? seriesID is a List<SeriesHandler>; item.getSeries() returns a Series object
CompetitorDAO competitor = competitorID.get(seriesID.indexOf(item.getSeries()));
if (item != null && competitor != null) {
if (item.getSeries().getLabel().toLowerCase().contains("mark")) {
@@ -0,0 +1,70 @@
package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.components.CollapsablePanel;
import com.sap.sailing.gwt.ui.shared.components.Component;
import com.sap.sailing.gwt.ui.shared.components.ComponentGroup;
import com.sap.sailing.gwt.ui.shared.components.ComponentToolbar;
/**
* A GWT component that visualizes a {@link Component} or a {@link Component} including menus to scroll quickly to the embedded view
* of the respective component, collapse/expand buttons for the views embedded and homogeneous settings buttons for
* those components that have settings.
*
* @author Axel Uhl (d043530)
*
*/
public class CollapsableComponentViewer<SettingsType> {
private final CollapsablePanel disclosurePanel;
private final Component<SettingsType> component;
private final StringMessages stringMessages;
public CollapsableComponentViewer(Component<SettingsType> component, StringMessages stringMessages) {
this.component = component;
this.stringMessages = stringMessages;
AbsolutePanel contentPanel = new AbsolutePanel();
disclosurePanel = createDisclosePanel(contentPanel, component.getLocalizedShortName(), 150);
}
public Widget getViewerWidget() {
return disclosurePanel;
}
public Component<?> getComponent() {
return component;
}
private CollapsablePanel createDisclosePanel(Panel contentPanel, String panelTitle, int heightInPx)
{
CollapsablePanel disclosurePanel = new CollapsablePanel (panelTitle);
disclosurePanel.setSize("100%", "100%");
disclosurePanel.setOpen(true);
ComponentToolbar<SettingsType> toolbar = new ComponentToolbar<SettingsType>(component, stringMessages);
toolbar.addSettingsButton();
disclosurePanel.setHeaderToolbar(toolbar);
contentPanel.setSize("100%", heightInPx + "px");
disclosurePanel.setContent(contentPanel);
if(component.getEntryWidget() != null) {
contentPanel.add(component.getEntryWidget());
} else {
if(component instanceof ComponentGroup) {
Iterable<Component<?>> components = ((ComponentGroup<?>) component).getComponents();
for(Component<?> c: components) {
if(c.getEntryWidget() != null)
contentPanel.add(c.getEntryWidget());
}
}
}
return disclosurePanel;
}
}
@@ -1,26 +1,20 @@
package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.DisclosurePanel;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.adminconsole.RaceMap;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.Timer;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.shared.components.Component;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
import com.sap.sailing.server.api.DefaultLeaderboardName;
public class RaceBoardPanel extends FormPanel implements Component<RaceBoardSettings> {
@@ -28,66 +22,45 @@ public class RaceBoardPanel extends FormPanel implements Component<RaceBoardSett
private final ErrorReporter errorReporter;
// TODO Frank to use these later
// private final StringMessages stringMessages;
private String raceBoardName;
private static RaceBoardResources resources = GWT.create(RaceBoardResources.class);
// TODO Frank to use these later
// private List<DisclosurePanel> collapsablePanels;
private final CompetitorSelectionProvider competitorSelectionProvider;
private final List<CollapsableComponentViewer<?>> collapsableViewers;
public RaceBoardPanel(SailingServiceAsync sailingService, String raceBoardName, ErrorReporter errorReporter,
final StringMessages stringMessages) {
this.sailingService = sailingService;
this.setRaceBoardName(raceBoardName);
this.errorReporter = errorReporter;
// TODO Frank to use these later
// this.stringMessages = stringMessages;
VerticalPanel mainPanel = new VerticalPanel();
mainPanel.setSize("100%", "100%");
setWidget(mainPanel);
competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true);
RaceMap raceMap = new RaceMap(sailingService, errorReporter, new Timer(
/* delayBetweenAutoAdvancesInMilliseconds */500), competitorSelectionProvider, stringMessages);
collapsableViewers = new ArrayList<CollapsableComponentViewer<?>>();
for(int i = 0; i < 4; i++) {
if(i == 0) {
AbsolutePanel contentPanel = new AbsolutePanel();
DisclosurePanel panel = createDisclosePanel(contentPanel, "Panel " + i, 300);
CompetitorSelectionModel competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true);
//final RaceMapPanel raceMapPanel = new RaceMapPanel(sailingService, competitorSelectionModel, this, this, stringMessages);
LeaderboardPanel defaultLeaderboardPanel = new LeaderboardPanel(sailingService, competitorSelectionModel,
DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME, errorReporter, stringMessages);
raceMap.loadMapsAPI(contentPanel);
mainPanel.add(panel);
CollapsableComponentViewer<LeaderboardSettings> viewer = new CollapsableComponentViewer<LeaderboardSettings>(
defaultLeaderboardPanel, stringMessages);
collapsableViewers.add(viewer);
} else {
VerticalPanel contentPanel = new VerticalPanel();
DisclosurePanel panel = createDisclosePanel(contentPanel, "Panel " + i, 100);
contentPanel.add(new Label("Content of panel " + i));
mainPanel.add(panel);
SimpleComponentGroup<Object> componentGroup = new SimpleComponentGroup<Object>("Component Group " + i);
componentGroup.addComponent(new SimpleComponent("My Component"));
componentGroup.addComponent(new SimpleComponent("My Component 2"));
componentGroup.addComponent(new SimpleComponent("My Component 3"));
//LeaderboardPanel leaderboardPanel = LeaderboardPanel();
CollapsableComponentViewer<Object> viewer = new CollapsableComponentViewer<Object>(componentGroup, stringMessages);
collapsableViewers.add(viewer);
}
}
}
private DisclosurePanel createDisclosePanel(Panel contentPanel, String panelTitle, int heightInPx) {
DisclosurePanel disclosurePanel = new DisclosurePanel (resources.openIcon(), resources.closeIcon(), panelTitle);
disclosurePanel.setSize("100%", "100%");
disclosurePanel.setOpen(true);
contentPanel.setSize("100%", heightInPx + "px");
disclosurePanel.setContent(contentPanel);
disclosurePanel.addOpenHandler(new OpenHandler<DisclosurePanel>() {
@Override
public void onOpen(OpenEvent<DisclosurePanel> event) {
}
});
disclosurePanel.addCloseHandler(new CloseHandler<DisclosurePanel>() {
@Override
public void onClose(CloseEvent<DisclosurePanel> event) {
}
});
return disclosurePanel;
for (CollapsableComponentViewer<?> viewer : collapsableViewers) {
mainPanel.add(viewer.getViewerWidget());
}
}
@Override
@@ -130,3 +103,4 @@ public class RaceBoardPanel extends FormPanel implements Component<RaceBoardSett
return null;
}
}
@@ -11,4 +11,6 @@ interface RaceBoardResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/close.png")
ImageResource closeIcon();
@Source("com/sap/sailing/gwt/ui/client/images/settings.png")
ImageResource settingsIcon();
}
@@ -0,0 +1,43 @@
package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.shared.components.Component;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
public class SimpleComponent implements Component<SimpleComponentSettings> {
private String componentName;
private final Label label;
public SimpleComponent(String componentName) {
this.componentName = componentName;
label = new Label("Ich bin eine Label component");
}
@Override
public String getLocalizedShortName() {
return componentName;
}
@Override
public Widget getEntryWidget() {
return label;
}
@Override
public boolean hasSettings() {
return true;
}
@Override
public SettingsDialogComponent<SimpleComponentSettings> getSettingsDialogComponent() {
return null;
}
@Override
public void updateSettings(SimpleComponentSettings newSettings) {
}
}
@@ -0,0 +1,62 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.shared.components.Component;
import com.sap.sailing.gwt.ui.shared.components.ComponentGroup;
import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent;
public class SimpleComponentGroup<SettingsType> implements ComponentGroup<SettingsType> {
private String groupName;
private final List<Component<?>> components;
public SimpleComponentGroup(String groupName) {
this.groupName = groupName;
components = new ArrayList<Component<?>>();
}
@Override
public boolean hasSettings() {
return false;
}
@Override
public SettingsDialogComponent<SettingsType> getSettingsDialogComponent() {
return null;
}
@Override
public void updateSettings(SettingsType newSettings) {
}
@Override
public String getLocalizedShortName() {
return groupName;
}
@Override
public Widget getEntryWidget() {
return null;
}
@Override
public boolean addComponent(Component<?> component) {
return components.add(component);
}
@Override
public boolean removeComponent(Component<?> component) {
return components.remove(component);
}
@Override
public Iterable<Component<?>> getComponents() {
return components;
}
}
@@ -0,0 +1,5 @@
package com.sap.sailing.gwt.ui.raceboard;
public class SimpleComponentSettings {
}
@@ -0,0 +1,439 @@
package com.sap.sailing.gwt.ui.shared.components;
import java.util.Iterator;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasAnimation;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A widget that consists of a header and a content panel that discloses the content when a user clicks on the header.
*
* <h3>CSS Style Rules</h3>
* <dl class="css">
* <dt>.gwt-DisclosurePanel
* <dd>the panel's primary style
* <dt>.gwt-DisclosurePanel-open
* <dd>dependent style set when panel is open
* <dt>.gwt-DisclosurePanel-closed
* <dd>dependent style set when panel is closed
* </dl>
* <p>
* The header and content sections can be easily selected using css with a child selector:<br/>
* .gwt-DisclosurePanel-open .header { ... }
* </p>
*/
public final class CollapsablePanel extends Composite implements HasWidgets.ForIsWidget, HasAnimation,
HasOpenHandlers<CollapsablePanel>, HasCloseHandlers<CollapsablePanel> {
private static ComponentResources resources = GWT.create(ComponentResources.class);
/**
* Used to wrap widgets in the header to provide click support. Effectively wraps the widget in an
* <code>anchor</code> to get automatic keyboard access.
*/
private final class ClickableHeader extends SimplePanel {
private ClickableHeader() {
// Anchor is used to allow keyboard access.
super(DOM.createAnchor());
Element elem = getElement();
DOM.setElementProperty(elem, "href", "javascript:void(0);");
// Avoids layout problems from having blocks in inlines.
DOM.setStyleAttribute(elem, "display", "block");
sinkEvents(Event.ONCLICK);
setStyleName(STYLENAME_HEADER);
}
@Override
public void onBrowserEvent(Event event) {
// no need to call super.
switch (DOM.eventGetType(event)) {
case Event.ONCLICK:
// Prevent link default action.
DOM.eventPreventDefault(event);
setOpen(!isOpen);
}
}
}
/**
* An {@link Animation} used to open the content.
*/
private static class ContentAnimation extends Animation {
/**
* Whether the item is being opened or closed.
*/
private boolean opening;
/**
* The {@link CollapsablePanel} being affected.
*/
private CollapsablePanel curPanel;
/**
* Open or close the content.
*
* @param panel
* the panel to open or close
* @param animate
* true to animate, false to open instantly
*/
public void setOpen(CollapsablePanel panel, boolean animate) {
// Immediately complete previous open
cancel();
// Open the new item
if (animate) {
curPanel = panel;
opening = panel.isOpen;
run(ANIMATION_DURATION);
} else {
panel.contentWrapper.setVisible(panel.isOpen);
if (panel.isOpen) {
// Special treatment on the visible case to ensure LazyPanel works
panel.getContent().setVisible(true);
}
}
}
@Override
protected void onComplete() {
if (!opening) {
curPanel.contentWrapper.setVisible(false);
}
DOM.setStyleAttribute(curPanel.contentWrapper.getElement(), "height", "auto");
curPanel = null;
}
@Override
protected void onStart() {
super.onStart();
if (opening) {
curPanel.contentWrapper.setVisible(true);
// Special treatment on the visible case to ensure LazyPanel works
curPanel.getContent().setVisible(true);
}
}
@Override
protected void onUpdate(double progress) {
int scrollHeight = DOM.getElementPropertyInt(curPanel.contentWrapper.getElement(), "scrollHeight");
int height = (int) (progress * scrollHeight);
if (!opening) {
height = scrollHeight - height;
}
height = Math.max(height, 1);
DOM.setStyleAttribute(curPanel.contentWrapper.getElement(), "height", height + "px");
DOM.setStyleAttribute(curPanel.contentWrapper.getElement(), "width", "auto");
}
}
public interface Imager {
Image makeImage();
void updateImage(boolean open, Image image);
}
/**
* The duration of the animation.
*/
private static final int ANIMATION_DURATION = 350;
// Stylename constants.
private static final String STYLENAME_DEFAULT = "gwt-DisclosurePanel";
private static final String STYLENAME_SUFFIX_OPEN = "open";
private static final String STYLENAME_SUFFIX_CLOSED = "closed";
private static final String STYLENAME_HEADER = "header";
private static final String STYLENAME_CONTENT = "content";
/**
* The {@link Animation} used to open and close the content.
*/
private static ContentAnimation contentAnimation;
/**
* top level widget. The first child will be a reference to {@link #header}. The second child will be a reference to
* {@link #contentWrapper}.
*/
private final VerticalPanel mainPanel = new VerticalPanel();
/**
* The wrapper around the content widget.
*/
private final SimplePanel contentWrapper = new SimplePanel();
/**
* holds the header widget.
*/
// private final DockPanel header = new DockPanel();
private final Grid header = new Grid(1,2);
private final ClickableHeader clickableHeader = new ClickableHeader();
private boolean isAnimationEnabled = false;
private boolean isOpen = false;
/**
* Creates an empty ThePanel that is initially closed.
*/
private CollapsablePanel() {
initWidget(mainPanel);
header.setWidth("100%");
header.getCellFormatter().setWidth(0, 0, "50%");
header.getCellFormatter().setWidth(0, 1, "50%");
header.getCellFormatter().setAlignment(0, 0, HasHorizontalAlignment.ALIGN_LEFT, HasVerticalAlignment.ALIGN_MIDDLE);
header.getCellFormatter().setAlignment(0, 1, HasHorizontalAlignment.ALIGN_RIGHT, HasVerticalAlignment.ALIGN_MIDDLE);
//header.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
mainPanel.add(header);
mainPanel.add(contentWrapper);
DOM.setStyleAttribute(contentWrapper.getElement(), "padding", "0px");
DOM.setStyleAttribute(contentWrapper.getElement(), "overflow", "hidden");
setStyleName(STYLENAME_DEFAULT);
setContentDisplay(false);
}
/**
* Creates a ThePanel with the specified header text, an initial open/close state and a bundle of images to be used
* in the default header widget.
*
* @param openImage
* the open state image resource
* @param closedImage
* the closed state image resource
* @param headerText
* the text to be displayed in the header
*/
public CollapsablePanel(ImageResource openImage, ImageResource closedImage, String headerText) {
this();
setClickableHeader(new CollapsablePanelDefaultHeader(this, openImage, closedImage, headerText));
}
/**
* Creates a ThePanel that will be initially closed using the specified text in the header.
*
* @param headerText
* the text to be displayed in the header
*/
public CollapsablePanel(String headerText) {
this(resources.openIcon(), resources.closeIcon(), headerText);
}
private void setClickableHeader(Widget widget) {
clickableHeader.add(widget);
header.setWidget(0, 0, clickableHeader);
// header.add(clickableHeader, DockPanel.WEST);
}
public void setHeaderToolbar(HorizontalPanel toolbar) {
// header.add(toolbar, DockPanel.EAST);
header.setWidget(0, 1, toolbar);
}
public void add(Widget w) {
if (this.getContent() == null) {
setContent(w);
} else {
throw new IllegalStateException("A DisclosurePanel can only contain two Widgets.");
}
}
/**
* Overloaded version for IsWidget.
*
* @see #add(Widget)
*/
public void add(IsWidget w) {
this.add(asWidgetOrNull(w));
}
public HandlerRegistration addCloseHandler(CloseHandler<CollapsablePanel> handler) {
return addHandler(handler, CloseEvent.getType());
}
public HandlerRegistration addOpenHandler(OpenHandler<CollapsablePanel> handler) {
return addHandler(handler, OpenEvent.getType());
}
public void clear() {
setContent(null);
}
/**
* Gets the widget that was previously set in {@link #setContent(Widget)}.
*
* @return the panel's current content widget
*/
public Widget getContent() {
return contentWrapper.getWidget();
}
/**
* Gets the widget that is currently being used as a header.
*
* @return the widget currently being used as a header
*/
public Widget getHeader() {
return header.asWidget();
}
/**
* Gets a {@link HasText} instance to provide access to the headers's text, if the header widget does provide such
* access.
*
* @return a reference to the header widget if it implements {@link HasText}, <code>null</code> otherwise
*/
public HasText getHeaderTextAccessor() {
Widget widget = header.asWidget();
return (widget instanceof HasText) ? (HasText) widget : null;
}
public boolean isAnimationEnabled() {
return isAnimationEnabled;
}
/**
* Determines whether the panel is open.
*
* @return <code>true</code> if panel is in open state
*/
public boolean isOpen() {
return isOpen;
}
public Iterator<Widget> iterator() {
return null;
// return WidgetIterators.createWidgetIterator(this,
// new Widget[] {getContent()});
}
public boolean remove(Widget w) {
if (w == getContent()) {
setContent(null);
return true;
}
return false;
}
/**
* Overloaded version for IsWidget.
*
* @see #remove(Widget)
*/
public boolean remove(IsWidget w) {
return this.remove(asWidgetOrNull(w));
}
public void setAnimationEnabled(boolean enable) {
isAnimationEnabled = enable;
}
/**
* Sets the content widget which can be opened and closed by this panel. If there is a preexisting content widget,
* it will be detached.
*
* @param content
* the widget to be used as the content panel
*/
public void setContent(Widget content) {
final Widget currentContent = getContent();
// Remove existing content widget.
if (currentContent != null) {
contentWrapper.setWidget(null);
currentContent.removeStyleName(STYLENAME_CONTENT);
}
// Add new content widget if != null.
if (content != null) {
contentWrapper.setWidget(content);
content.addStyleName(STYLENAME_CONTENT);
setContentDisplay(false);
}
}
/**
* Changes the visible state of this <code>ThePanel</code>.
*
* @param isOpen
* <code>true</code> to open the panel, <code>false</code> to close
*/
public void setOpen(boolean isOpen) {
if (this.isOpen != isOpen) {
this.isOpen = isOpen;
setContentDisplay(true);
fireEvent();
}
}
/**
* <b>Affected Elements:</b>
* <ul>
* <li>-header = the clickable header.</li>
* </ul>
*
* @see UIObject#onEnsureDebugId(String)
*/
@Override
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
header.ensureDebugId(baseID + "-header");
}
private void fireEvent() {
if (isOpen) {
OpenEvent.fire(this, this);
} else {
CloseEvent.fire(this, this);
}
}
private void setContentDisplay(boolean animate) {
if (isOpen) {
removeStyleDependentName(STYLENAME_SUFFIX_CLOSED);
addStyleDependentName(STYLENAME_SUFFIX_OPEN);
} else {
removeStyleDependentName(STYLENAME_SUFFIX_OPEN);
addStyleDependentName(STYLENAME_SUFFIX_CLOSED);
}
if (getContent() != null) {
if (contentAnimation == null) {
contentAnimation = new ContentAnimation();
}
contentAnimation.setOpen(this, animate && isAnimationEnabled);
}
}
}
@@ -0,0 +1,99 @@
package com.sap.sailing.gwt.ui.shared.components;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.shared.components.CollapsablePanel.Imager;
/**
* The default header widget used within a {@link CollapsablePanel}.
*/
public class CollapsablePanelDefaultHeader extends Widget implements HasText, OpenHandler<CollapsablePanel>,
CloseHandler<CollapsablePanel> {
/**
* imageTD holds the image for the icon, not null. labelTD holds the text for the label.
*/
private final Element labelTD;
private final Image iconImage;
private final Imager imager;
private final CollapsablePanel collapsablePanel;
protected CollapsablePanelDefaultHeader(CollapsablePanel collapsablePanel, Imager imager, String text) {
this.imager = imager;
this.collapsablePanel = collapsablePanel;
iconImage = imager.makeImage();
// I do not need any Widgets here, just a DOM structure.
Element root = DOM.createTable();
Element tbody = DOM.createTBody();
Element tr = DOM.createTR();
final Element imageTD = DOM.createTD();
labelTD = DOM.createTD();
setElement(root);
DOM.appendChild(root, tbody);
DOM.appendChild(tbody, tr);
DOM.appendChild(tr, imageTD);
DOM.appendChild(tr, labelTD);
// set image TD to be same width as image.
DOM.setElementProperty(imageTD, "align", "center");
DOM.setElementProperty(imageTD, "valign", "middle");
DOM.setStyleAttribute(imageTD, "width", iconImage.getWidth() + "px");
DOM.appendChild(imageTD, iconImage.getElement());
setText(text);
collapsablePanel.addOpenHandler(this);
collapsablePanel.addCloseHandler(this);
setStyle();
}
protected CollapsablePanelDefaultHeader(CollapsablePanel collapsablePanel, final ImageResource openImage,
final ImageResource closedImage, String text) {
this(collapsablePanel, new Imager() {
public Image makeImage() {
return new Image(closedImage);
}
public void updateImage(boolean open, Image image) {
if (open) {
image.setResource(openImage);
} else {
image.setResource(closedImage);
}
}
}, text);
}
public final String getText() {
return DOM.getInnerText(labelTD);
}
public final void onClose(CloseEvent<CollapsablePanel> event) {
setStyle();
}
public final void onOpen(OpenEvent<CollapsablePanel> event) {
setStyle();
}
public final void setText(String text) {
DOM.setInnerText(labelTD, text);
}
private void setStyle() {
imager.updateImage(collapsablePanel.isOpen(), iconImage);
}
}
@@ -1,5 +1,7 @@
package com.sap.sailing.gwt.ui.shared.components;
import com.google.gwt.user.client.ui.Widget;
/**
*
* @author Axel Uhl (d043530)
@@ -8,23 +10,17 @@ package com.sap.sailing.gwt.ui.shared.components;
* the type that describes this component's settings (if any). Use {@link Object} in case your components
* doesn't support any settings.
*/
public interface Component<SettingsType> extends ComponentForestEntry {
public interface Component<SettingsType> extends ComponentSettings<SettingsType> {
/**
* @return whether this component has settings that a user may change; if so,
* @return the name to display to a user for quick navigation to this component
*/
boolean hasSettings();
String getLocalizedShortName();
/**
* If this component {@link #hasSettings has settings}, this method may return a component for the settings dialog.
* It will be used to obtain a widget shown in the settings dialog, a validator for the component-specific settings,
* as well as to produce a result from the widget's state when the settings dialog wants to validate or return the
* settings.
* A component may or may not have a {@link Widget} used to render its contents. Typically,
* entries with sub-entries don't afford a widget of their own but let the framework choose a container
* in which to aggregate their entries' widgets.
*/
SettingsDialogComponent<SettingsType> getSettingsDialogComponent();
/**
* Updates the settings of this component. Expected to be called when a settings dialog using this component's
* {@link #getSettingsDialogComponent()} has been confirmed.
*/
void updateSettings(SettingsType newSettings);
Widget getEntryWidget();
}
@@ -1,12 +0,0 @@
package com.sap.sailing.gwt.ui.shared.components;
/**
* {@link Component}s are arranged in an "ordered forest" structure (multi-rooted tree with the roots ordered in a sequence).
* Each hierarchy level can optionally provide a localized name. Each hierarchy level consists of one or more
*
* @author Axel Uhl (d043530)
*
*/
public interface ComponentForest extends ComponentForestEntry {
Iterable<ComponentForest> getEntries();
}
@@ -1,17 +0,0 @@
package com.sap.sailing.gwt.ui.shared.components;
import com.google.gwt.user.client.ui.Widget;
public interface ComponentForestEntry {
/**
* @return the name to display to a user for quick navigation to this component
*/
String getLocalizedShortName();
/**
* A component forest entry may or may not have a {@link Widget} used to render its contents. Typically,
* entries with sub-entries don't afford a widget of their own but let the framework choose a container
* in which to aggregate their entries' widgets.
*/
Widget getEntryWidget();
}
@@ -0,0 +1,10 @@
package com.sap.sailing.gwt.ui.shared.components;
public interface ComponentGroup<SettingsType> extends Component<SettingsType> {
boolean addComponent(Component<?> component);
boolean removeComponent(Component<?> component);
Iterable<Component<?>> getComponents();
}
@@ -0,0 +1,16 @@
package com.sap.sailing.gwt.ui.shared.components;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
interface ComponentResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/open.png")
ImageResource openIcon();
@Source("com/sap/sailing/gwt/ui/client/images/close.png")
ImageResource closeIcon();
@Source("com/sap/sailing/gwt/ui/client/images/settings.png")
ImageResource settingsIcon();
}
@@ -0,0 +1,30 @@
package com.sap.sailing.gwt.ui.shared.components;
/**
*
* @author Axel Uhl (d043530)
*
* @param <SettingsType>
* the type that describes this component's settings (if any). Use {@link Object} in case your components
* doesn't support any settings.
*/public interface ComponentSettings<SettingsType> {
/**
* @return whether this component has settings that a user may change; if so,
*/
boolean hasSettings();
/**
* If this component {@link #hasSettings has settings}, this method may return a component for the settings dialog.
* It will be used to obtain a widget shown in the settings dialog, a validator for the component-specific settings,
* as well as to produce a result from the widget's state when the settings dialog wants to validate or return the
* settings.
*/
SettingsDialogComponent<SettingsType> getSettingsDialogComponent();
/**
* Updates the settings of this component. Expected to be called when a settings dialog using this component's
* {@link #getSettingsDialogComponent()} has been confirmed.
*/
void updateSettings(SettingsType newSettings);
}
@@ -1,7 +0,0 @@
package com.sap.sailing.gwt.ui.shared.components;
import com.google.gwt.user.client.ui.HorizontalPanel;
public class ComponentTitleBar extends HorizontalPanel {
}
@@ -0,0 +1,45 @@
package com.sap.sailing.gwt.ui.shared.components;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.sap.sailing.gwt.ui.client.StringMessages;
public class ComponentToolbar<SettingsType> extends HorizontalPanel {
private static ComponentResources resources = GWT.create(ComponentResources.class);
private final Component<SettingsType> component;
final StringMessages stringMessages;
public ComponentToolbar(final Component<SettingsType> component, final StringMessages stringMessages) {
super();
this.setSpacing(10);
this.component = component;
this.stringMessages = stringMessages;
}
public void addSettingsButton() {
if(component.hasSettings()) {
ImageResource settingsImage = resources.settingsIcon();
Anchor showConfigAnchor = new Anchor(AbstractImagePrototype.create(settingsImage).getSafeHtml());
showConfigAnchor.setTitle(stringMessages.configuration());
showConfigAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
new SettingsDialog<SettingsType>(component, stringMessages).show();
}
});
this.add(showConfigAnchor);
}
}
}
@@ -1,21 +0,0 @@
package com.sap.sailing.gwt.ui.shared.components;
import com.google.gwt.user.client.ui.VerticalPanel;
/**
* A GWT component that visualizes a {@link ComponentForest} including menus to scroll quickly to the embedded view
* of the respective component, collapse/expand buttons for the views embedded and homogeneous settings buttons for
* those components that have settings.
*
* @author Axel Uhl (d043530)
*
*/
public class CompositePanel extends VerticalPanel {
public CompositePanel(ComponentForest components) {
super();
for (ComponentForestEntry entry : components.getEntries()) {
entry.toString(); // TODO continue here...
}
}
}
@@ -46,6 +46,7 @@
<!-- Specify the paths for translatable code -->
<extend-property name="locale" values="de"/>
<source path='client'/>
<source path='components'/>
<source path='adminconsole'/>
<source path='leaderboard'/>
<source path='shared'/>
@@ -34,6 +34,7 @@
<extend-property name="locale" values="de"/>
<source path='client'/>
<source path='raceboard'/>
<source path='leaderboard'/>
<source path='adminconsole'/>
<source path='shared'/>
</module>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB