Merge branch 'master' into bug3344

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
This commit is contained in:
Benjamin Barth committed 2015-12-07 15:09:12 +01:00
commit 34740f12ab
30 files changed
+5346 -5099

No files matched your search

@@ -20,7 +20,7 @@ public class LeaderboardPanelMock extends LeaderboardPanel {
String leaderboardName, ErrorReporter errorReporter,
StringMessages stringMessages) {
super(sailingService, new AsyncActionsExecutor(), LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
/* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true), new CompetitorSelectionModel(
/* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true), new CompetitorSelectionModel(
/* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, new UserAgentDetails("gecko1_8"), /* showRaceDetails */ true);
}
+2 -2
View File
@@ -98,13 +98,13 @@ body, table td, select, button {
padding: 49px 0 0 80px;
width: 300px;
color: #7c7c7c;
background: url(images/important-message.png) left center no-repeat;
background: url(images/important-message.png) left center no-repeat;
}
.LeaderboardHeader {
position: relative;
margin: 0 auto;
width: 400px;
width: 100%;
text-align: center;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-size: 18px;
+45 -2
View File
@@ -6,6 +6,13 @@
height: 0;
clear: both;
visibility: hidden;
RegattaRaceInformation-Header
RaceName-Label
RaceSeriesAndFleet-Label
RegattaAndRaceTime-Header???
RegattaName-Anchor
*/
@@ -87,6 +94,14 @@ table {
padding-top: 10px;
padding-left: 10px;
}
.CombinedWindPanelParentDiv.indentsmall,
.TrueNorthIndicatorPanelParentDiv.indentsmall {
padding-left: 30px;
}
.CombinedWindPanelParentDiv.indentbig,
.TrueNorthIndicatorPanelParentDiv.indentbig {
padding-left: 45px;
}
.MapSimulationLegendParentDiv {
padding-bottom: 10px;
@@ -573,7 +588,8 @@ img.openColumn {
url(images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png),
url(images/arrows_top_black_small.png);
background-repeat: no-repeat, no-repeat;
background-position: 4% 50%, 96% 50%
background-position: 4% 50%, 96% 50%;
background-position: 5px center, calc(100% - 6px) center;
}
.gwt-SplitLayoutPanel-EastToggleButton-Closed-leaderboard {
@@ -582,7 +598,8 @@ img.openColumn {
url(images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png),
url(images/arrows_bottom_black_small.png);
background-repeat: no-repeat, no-repeat;
background-position: 4% 50%, 96% 50%
background-position: 4% 50%, 96% 50%;
background-position: 5px center, calc(100% - 6px) center;
}
.gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-media,
@@ -664,6 +681,32 @@ img.openColumn {
margin-top: 5px;
}
.compactHeader .RegattaRaceInformation-Header {
left: 110px;
}
.compactHeader .RegattaRaceInformation-Header .RaceName-Label {
display: block;
font-size: 16px;
padding: 2px 0;
}
.compactHeader .RegattaRaceInformation-Header .RaceSeriesAndFleet-Label {
display: block;
margin-top: 5px;
white-space: nowrap;
}
.compactHeader .RegattaAndRaceTime-Header {
top: 11px;
}
.compactHeader .RegattaAndRaceTime-Header .RegattaName-Anchor {
width: 115px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
white-space: nowrap;
padding: 2px 0 2px 20px;
background-position: 4px -1px;
}
.VideoPopup-Close-Button {
border: none;
}
@@ -107,7 +107,7 @@ public class AutoPlayController implements RaceTimesInfoProviderListener {
asyncActionsExecutor = new AsyncActionsExecutor();
showWindChart = false;
leaderboard = null;
leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true);
leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
leaderboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l);
leaderboardTimer.setLivePlayDelayInMillis(delayToLiveInMillis);
@@ -170,10 +170,10 @@ public class AutoPlayController implements RaceTimesInfoProviderListener {
RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(raceToShow);
raceSelectionModel.setSelection(singletonList);
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, userService, asyncActionsExecutor,
competitorsAndTheirBoats, raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */null, raceboardViewConfig,
errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */false);
competitorsAndTheirBoats, raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */ null, raceboardViewConfig,
errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */ false,
/* isScreenLargeEnoughToOfferChartSupport */ true);
return raceBoardPanel;
}
@@ -108,7 +108,7 @@ public class EventParamUtils {
.createNewDefaultSettings(null, null,
/* overallDetails */overallDetails, null,
/* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow,
raceColumnSelectionStrategy);
raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true);
}
return result;
}
@@ -44,7 +44,6 @@ import com.sap.sailing.gwt.home.shared.places.fakeseries.SeriesDefaultPlace;
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration;
import com.sap.sailing.gwt.ui.shared.util.NullSafeComparableComparator;
public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> extends AbstractActivity implements Presenter {
@@ -248,7 +247,6 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
}
linkParams.put("raceName", trackedRaceName);
linkParams.put("regattaName", regattaName);
linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_MODE, "simple");
// TODO this must only be forwarded if there is a logged-on user
// linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true");
return linkParams;
@@ -54,7 +54,7 @@ public class ExperimentalFeatures {
/**
* Enables/disables the link to the race board on mobile races view (competition format), in case of a tracked race
*/
public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = false;
public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = true;
/**
* Provide selection to filter regattas by boat category on mobile multiregatta event overview
*/
@@ -237,7 +237,7 @@ public class RegattaOverviewPanel extends SimplePanel {
if (leaderboardsTabPanel != null) {
if (showLeaderboard) {
final CompetitorSelectionModel competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true);
final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true);
final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
sailingService.getLeaderboardsByEvent(eventDTO, new MarkedAsyncCallback<List<StrippedLeaderboardDTO>>(
new AsyncCallback<List<StrippedLeaderboardDTO>>() {
@Override
@@ -30,8 +30,8 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
private boolean redrawAllMarkersPendingForMinMaxBeingInitialized;
public RaceTimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages,
RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible) {
super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible);
RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport) {
super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible, isScreenLargeEnoughToOfferChartSupport);
this.raceTimesInfoProvider = raceTimesInfoProvider;
selectedRace = null;
autoAdjustPlayMode = true;
@@ -89,7 +89,13 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
private static ClientResources resources = GWT.create(ClientResources.class);
protected static TimePanelCss timePanelCss = TimePanelCssResources.INSTANCE.css();
public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, boolean canReplayWhileLiveIsPossible) {
/**
* @param isScreenLargeEnoughToOfferChartSupport
* if <code>true</code>, the right padding will be set such that the time panel lines up with charts such
* as the competitor chart or the wind chart shown above it
*/
public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages,
boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport) {
this.timer = timer;
this.timeRangeProvider = timeRangeProvider;
this.stringMessages = stringMessages;
@@ -105,7 +111,9 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
timePanelSliderFlowWrapper = new FlowPanel();
timePanelSlider.setStyleName("timePanelSlider");
timePanelSlider.getElement().getStyle().setPaddingLeft(66, Unit.PX);
timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX);
if (isScreenLargeEnoughToOfferChartSupport) {
timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX);
}
timePanelSliderFlowWrapper.add(timePanelSlider);
playSpeedImg = resources.timesliderPlaySpeedIcon();
@@ -1,5 +1,6 @@
.competitorFilterContainer {
width: 400px;
max-width: 400px;
width: 100%;
height: 2.14em;
background-color: #FFFFFF;
margin-left: 0px;
@@ -22,7 +23,7 @@
.searchInput {
font-size: .933333333333333em;
height: 2.142857142857143em;
width: 280px;
width: 70%;
border: none;
background-color: #eaeaea;
padding: 0;
@@ -258,7 +258,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint {
result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null,
/* overallDetails */ overallDetails, null,
/* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow,
raceColumnSelectionStrategy);
raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true);
}
return result;
}
@@ -344,7 +344,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
private Element elementToBlur;
private boolean showSelectionCheckbox;
private List<LeaderboardUpdateListener> leaderboardUpdateListener;
private final List<LeaderboardUpdateListener> leaderboardUpdateListener;
private boolean initialCompetitorFilterHasBeenApplied = false;
private final boolean showCompetitorFilterStatus;
@@ -668,7 +668,14 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
CompetitorDTO competitor = competitorFetcher.getCompetitor(object);
final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode();
final String flagImageURL = competitor.getFlagImageURL();
boolean showBoatColor = !isShowCompetitorFullName() && LeaderboardPanel.this.isEmbedded && preSelectedRace != null;
if (showBoatColor) {
String competitorColor = LeaderboardPanel.this.competitorSelectionProvider.getColor(
competitorFetcher.getCompetitor(object), LeaderboardPanel.this.preSelectedRace).getAsHtml();
sb.appendHtmlConstant("<div style=\"border-bottom: 2px solid " + competitorColor + ";\">");
}
if (flagImageURL != null && !flagImageURL.isEmpty()) {
sb.appendHtmlConstant("<img src=\"" + flagImageURL + "\" width=\"18px\" height=\"12px\" title=\"" + competitor.getName() + "\"/>");
sb.appendHtmlConstant("&nbsp;");
@@ -685,6 +692,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
}
}
sb.appendEscaped(competitor.getSailID());
if (showBoatColor) {
sb.appendHtmlConstant("</div>");
}
}
@Override
@@ -3178,7 +3188,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
if (timer != null) {
timer.removeTimeListener(this);
}
if(leaderboardUpdateListener != null) {
if (leaderboardUpdateListener != null) {
leaderboardUpdateListener.clear();
}
}
@@ -78,11 +78,11 @@ public class LeaderboardSettingsFactory {
/* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort,
/* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelection.getType(),
/*showAddedScores*/ false, /*showOverallRacesCompleted*/ false,
/*showCompetitorSailIdColumns*/showCompetitorSailIdColumn, /*showCompetitorFullNameColumn*/showCompetitorNameColumn);
showCompetitorSailIdColumn, showCompetitorNameColumn);
break;
case Replay:
settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */
nameOfRaceColumnToShow != null, showRegattaRank);
nameOfRaceColumnToShow != null, showRegattaRank, showCompetitorSailIdColumn, showCompetitorNameColumn);
break;
}
return settings;
@@ -127,28 +127,32 @@ public class LeaderboardSettingsFactory {
* such a meta leaderboard exists; if multiple such meta leaderboards exist, they are all shown
*/
public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
List<String> namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank) {
List<String> namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank,
boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn) {
final List<DetailType> overallDetails = createOverallDetailsForShowRegattaRank(showRegattaRank);
return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetails,
nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null);
nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null,
showCompetitorSailIdColumn, showCompetitorFullNameColumn);
}
/**
* Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean)}, only that an additional refresh interval for auto-refresh
* Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean, boolean, boolean)}, only that an additional refresh interval for auto-refresh
* may be specified; if <code>null</code>, no auto-refresh shall be performed
*/
public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
private LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
List<String> namesOfRacesToShow, List<DetailType> overallDetailsToShow, String nameOfRaceToSort,
boolean autoExpandPreSelectedRace, Long refreshIntervalMillis) {
boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, boolean showCompetitorSailIdColumns, boolean showCompetitorFullNameColumn) {
return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetailsToShow,
nameOfRaceToSort, autoExpandPreSelectedRace, refreshIntervalMillis,
/* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT);
/* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT,
showCompetitorSailIdColumns, showCompetitorFullNameColumn);
}
public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
List<String> namesOfRacesToShow, List<DetailType> overallDetailsToShow, String nameOfRaceToSort,
boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, Integer numberOfLastRacesToShow,
RaceColumnSelectionStrategies raceColumnSelectionStrategy) {
RaceColumnSelectionStrategies raceColumnSelectionStrategy, boolean showCompetitorSailIdColumns,
boolean showCompetitorFullNameColumn) {
if (namesOfRaceColumnsToShow != null && namesOfRacesToShow != null) {
throw new IllegalArgumentException("Can specify race columns either by column or by race name, not both");
}
@@ -168,7 +172,7 @@ public class LeaderboardSettingsFactory {
autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ nameOfRaceToSort,
/* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelectionStrategy,
/*showAddedScores*/ false, /*showOverallRacesCompleted*/ false,
/*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true);
showCompetitorSailIdColumns, showCompetitorFullNameColumn);
}
public LeaderboardSettings mergeLeaderboardSettings(LeaderboardSettings settingsWithRaceSelection, LeaderboardSettings settingsWithDetails) {
@@ -106,7 +106,7 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog<Leaderboar
}
LeaderboardSettings settings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
namesOfRaceColumnsToShow, /* namesOfRacesToShow */null, /* nameOfRaceToSort */null, /* autoExpandPreSelectedRace */
false, /* showRegattaRank */ true);
false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
leaderboardSettingsDialogComponent = new LeaderboardSettingsDialogComponent(settings.getManeuverDetailsToShow(),
settings.getLegDetailsToShow(), settings.getRaceDetailsToShow(), settings.getOverallDetailsToShow(), raceList,
/* select all races by default */ raceList, new ExplicitRaceColumnSelection(),
@@ -87,7 +87,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
private LeaderboardSettings getOrCreateLeaderboardSettings(String leaderboardName, LeaderboardSettings currentLeaderboardSettings) {
LeaderboardSettings newLeaderboardSettings = leaderboardNamesAndSettings.get(leaderboardName);
if(newLeaderboardSettings == null) {
newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true);
newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
}
if(currentLeaderboardSettings != null) {
newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().mergeLeaderboardSettings(newLeaderboardSettings, currentLeaderboardSettings);
@@ -584,7 +584,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
String leaderboardName, String leaderboardGroupName, final ErrorReporter errorReporter,
final StringMessages stringMessages, UserAgentDetails userAgent) {
super(sailingService, asyncActionsExecutor, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
/* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true),
/* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true),
new CompetitorSelectionModel(/* hasMultiSelection */true),
leaderboardName, errorReporter, stringMessages, userAgent, /* showRaceDetails */ true);
suppressedCompetitorsShown = new ListDataProvider<CompetitorDTO>(new ArrayList<CompetitorDTO>());
@@ -151,7 +151,8 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint
// Use a TimePanel to manage wind chart zoom, although the TimePanel itself is not being displayed;
// let the time panel always return to "live" mode.
final TimePanel<TimePanelSettings> timePanel = new TimePanel<TimePanelSettings>(
timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false) {
timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false,
/* isScreenLargeEnoughToOfferChartSupport set to true iff wind chart will be displayed */ showWindChart) {
protected boolean isLiveModeToBeMadePossible() {
return true;
}
@@ -1,197 +1,205 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel;
import com.sap.sailing.gwt.ui.client.MediaService;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsDTO;
import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
private RaceWithCompetitorsDTO selectedRace;
private static final String PARAM_REGATTA_NAME = "regattaName";
private static final String PARAM_RACE_NAME = "raceName";
private static final String PARAM_LEADERBOARD_NAME = "leaderboardName";
private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
private static final String PARAM_EVENT_ID = "eventId";
private String regattaName;
private String raceName;
private String leaderboardName;
private String leaderboardGroupName;
private UUID eventId;
private RaceBoardViewConfiguration raceboardViewConfig;
private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
@Override
protected void doOnModuleLoad() {
super.doOnModuleLoad();
EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath);
// read mandatory parameters
regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME);
raceName = Window.Location.getParameter(PARAM_RACE_NAME);
String leaderboardNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
String leaderboardGroupNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME);
if (leaderboardNameParamValue != null && !leaderboardNameParamValue.isEmpty()) {
leaderboardName = leaderboardNameParamValue;
}
if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
leaderboardGroupName = leaderboardGroupNameParamValue;
}
String eventIdParamValue = Window.Location.getParameter(PARAM_EVENT_ID);
if (eventIdParamValue != null && !eventIdParamValue.isEmpty()) {
eventId = UUID.fromString(eventIdParamValue);
}
if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
leaderboardGroupName = leaderboardGroupNameParamValue;
}
if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty() ||
leaderboardName == null || leaderboardName.isEmpty()) {
createErrorPage("This page requires a valid regatta name, race name and leaderboard name.");
return;
}
// read optional parameters
boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_LEADERBOARD, true /* default*/);
boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_WINDCHART, false /* default*/);
boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default*/);
boolean showViewStreamlets = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLETS, false /* default*/);
boolean showViewStreamletColors = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLET_COLORS, false /* default*/);
boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, true /* default*/);
String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/);
final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false);
final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, true);
final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default*/);
final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/);
raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard,
showWindChart, showCompetitorsChart, showViewStreamlets, showViewStreamletColors, showViewSimulation, canReplayWhileLiveIsPossible, autoSelectMedia, defaultMedia);
sailingService.getRaceboardData(regattaName, raceName, leaderboardName, leaderboardGroupName, eventId, new AsyncCallback<RaceboardDataDTO>() {
@Override
public void onSuccess(RaceboardDataDTO result) {
checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls);
}
@Override
public void onFailure(Throwable caught) {
reportError("Error trying to create the raceboard: " + caught.getMessage());
}
});
}
private void createErrorPage(String message) {
final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX);
LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(getStringMessages(), this, getUserService());
logoAndTitlePanel.addStyleName("LogoAndTitlePanel");
RootLayoutPanel.get().add(vp);
vp.addNorth(logoAndTitlePanel, 100);
vp.add(new Label(message));
}
private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls) {
if (!raceboardData.isValidLeaderboard()) {
createErrorPage(getStringMessages().noSuchLeaderboard());
return;
}
if (eventId != null && !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().noSuchEvent());
}
if (leaderboardGroupName != null) {
if(!raceboardData.isValidLeaderboardGroup()) {
createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(leaderboardName, leaderboardGroupName));
return;
}
if (eventId != null && raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(leaderboardGroupName, eventId.toString()));
return;
}
}
if (raceboardData.getRace() == null) {
createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName);
return;
}
selectedRace = raceboardData.getRace();
Window.setTitle(selectedRace.getName());
RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier());
raceSelectionModel.setSelection(singletonList);
Timer timer = new Timer(PlayModes.Replay, 1000l);
AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/);
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, getUserService(), asyncActionsExecutor,
raceboardData.getCompetitorAndTheirBoats(), timer, raceSelectionModel, leaderboardName, leaderboardGroupName, eventId,
raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls);
createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig);
}
private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) {
FlowPanel timeLineInnerBgPanel = new FlowPanel();
timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel");
timeLineInnerBgPanel.add(raceBoardPanel.getTimePanel());
FlowPanel timeLineInnerPanel = new FlowPanel();
timeLineInnerPanel.add(timeLineInnerBgPanel);
timeLineInnerPanel.addStyleName("timeLineInnerPanel");
FlowPanel timelinePanel = new FlowPanel();
timelinePanel.add(timeLineInnerPanel);
timelinePanel.addStyleName("timeLinePanel");
return timelinePanel;
}
private void createRaceBoardInOneScreenMode(final RaceBoardPanel raceBoardPanel,
RaceBoardViewConfiguration raceboardViewConfiguration) {
final DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
RootLayoutPanel.get().add(p);
final FlowPanel timePanel = createTimePanel(raceBoardPanel);
final Button toggleButton = raceBoardPanel.getTimePanel().getAdvancedToggleButton();
toggleButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
boolean advancedModeShown = raceBoardPanel.getTimePanel().toggleAdvancedMode();
if (advancedModeShown) {
p.setWidgetSize(timePanel, 96);
toggleButton.removeStyleDependentName("Closed");
toggleButton.addStyleDependentName("Open");
} else {
p.setWidgetSize(timePanel, 67);
toggleButton.addStyleDependentName("Closed");
toggleButton.removeStyleDependentName("Open");
}
}
});
p.addSouth(timePanel, 67);
p.add(raceBoardPanel);
p.addStyleName("dockLayoutPanel");
}
}
package com.sap.sailing.gwt.ui.raceboard;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel;
import com.sap.sailing.gwt.ui.client.MediaService;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsDTO;
import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
private RaceWithCompetitorsDTO selectedRace;
private static final String PARAM_REGATTA_NAME = "regattaName";
private static final String PARAM_RACE_NAME = "raceName";
private static final String PARAM_LEADERBOARD_NAME = "leaderboardName";
private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
private static final String PARAM_EVENT_ID = "eventId";
private String regattaName;
private String raceName;
private String leaderboardName;
private String leaderboardGroupName;
private UUID eventId;
private RaceBoardViewConfiguration raceboardViewConfig;
private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
@Override
protected void doOnModuleLoad() {
super.doOnModuleLoad();
EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath);
// read mandatory parameters
regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME);
raceName = Window.Location.getParameter(PARAM_RACE_NAME);
String leaderboardNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
String leaderboardGroupNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME);
if (leaderboardNameParamValue != null && !leaderboardNameParamValue.isEmpty()) {
leaderboardName = leaderboardNameParamValue;
}
if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
leaderboardGroupName = leaderboardGroupNameParamValue;
}
String eventIdParamValue = Window.Location.getParameter(PARAM_EVENT_ID);
if (eventIdParamValue != null && !eventIdParamValue.isEmpty()) {
eventId = UUID.fromString(eventIdParamValue);
}
if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
leaderboardGroupName = leaderboardGroupNameParamValue;
}
if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty() ||
leaderboardName == null || leaderboardName.isEmpty()) {
createErrorPage("This page requires a valid regatta name, race name and leaderboard name.");
return;
}
// read optional parameters
boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_LEADERBOARD, true /* default*/);
boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_WINDCHART, false /* default*/);
boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default*/);
boolean showViewStreamlets = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLETS, false /* default*/);
boolean showViewStreamletColors = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLET_COLORS, false /* default*/);
boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, true /* default*/);
String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/);
final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false);
final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, true);
final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default*/);
final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/);
raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard,
showWindChart, showCompetitorsChart, showViewStreamlets, showViewStreamletColors, showViewSimulation, canReplayWhileLiveIsPossible, autoSelectMedia, defaultMedia);
sailingService.getRaceboardData(regattaName, raceName, leaderboardName, leaderboardGroupName, eventId, new AsyncCallback<RaceboardDataDTO>() {
@Override
public void onSuccess(RaceboardDataDTO result) {
// Determine if the screen is large enough to display charts such as the competitor chart or the wind chart.
// This decision is made once based on the initial screen height. Resizing the window afterwards will have
// no impact on the chart support, i.e. they are available/unavailable based on the initial decision.
boolean isScreenLargeEnoughToOfferChartSupport = Document.get().getClientHeight() >= 600;
checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls,
isScreenLargeEnoughToOfferChartSupport);
}
@Override
public void onFailure(Throwable caught) {
reportError("Error trying to create the raceboard: " + caught.getMessage());
}
});
}
private void createErrorPage(String message) {
final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX);
LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(getStringMessages(), this, getUserService());
logoAndTitlePanel.addStyleName("LogoAndTitlePanel");
RootLayoutPanel.get().add(vp);
vp.addNorth(logoAndTitlePanel, 100);
vp.add(new Label(message));
}
private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls,
boolean isScreenLargeEnoughToOfferChartSupport) {
if (!raceboardData.isValidLeaderboard()) {
createErrorPage(getStringMessages().noSuchLeaderboard());
return;
}
if (eventId != null && !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().noSuchEvent());
}
if (leaderboardGroupName != null) {
if(!raceboardData.isValidLeaderboardGroup()) {
createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(leaderboardName, leaderboardGroupName));
return;
}
if (eventId != null && raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(leaderboardGroupName, eventId.toString()));
return;
}
}
if (raceboardData.getRace() == null) {
createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName);
return;
}
selectedRace = raceboardData.getRace();
Window.setTitle(selectedRace.getName());
RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier());
raceSelectionModel.setSelection(singletonList);
Timer timer = new Timer(PlayModes.Replay, 1000l);
AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/);
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, getUserService(), asyncActionsExecutor,
raceboardData.getCompetitorAndTheirBoats(), timer, raceSelectionModel, leaderboardName, leaderboardGroupName, eventId,
raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls,
isScreenLargeEnoughToOfferChartSupport);
createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig);
}
private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) {
FlowPanel timeLineInnerBgPanel = new ResizableFlowPanel();
timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel");
timeLineInnerBgPanel.add(raceBoardPanel.getTimePanel());
FlowPanel timeLineInnerPanel = new ResizableFlowPanel();
timeLineInnerPanel.add(timeLineInnerBgPanel);
timeLineInnerPanel.addStyleName("timeLineInnerPanel");
FlowPanel timelinePanel = new ResizableFlowPanel();
timelinePanel.add(timeLineInnerPanel);
timelinePanel.addStyleName("timeLinePanel");
return timelinePanel;
}
private void createRaceBoardInOneScreenMode(final RaceBoardPanel raceBoardPanel,
RaceBoardViewConfiguration raceboardViewConfiguration) {
final DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
RootLayoutPanel.get().add(p);
final FlowPanel timePanel = createTimePanel(raceBoardPanel);
final Button toggleButton = raceBoardPanel.getTimePanel().getAdvancedToggleButton();
toggleButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
boolean advancedModeShown = raceBoardPanel.getTimePanel().toggleAdvancedMode();
if (advancedModeShown) {
p.setWidgetSize(timePanel, 96);
toggleButton.removeStyleDependentName("Closed");
toggleButton.addStyleDependentName("Open");
} else {
p.setWidgetSize(timePanel, 67);
toggleButton.addStyleDependentName("Closed");
toggleButton.removeStyleDependentName("Open");
}
}
});
p.addSouth(timePanel, 67);
p.add(raceBoardPanel);
p.addStyleName("dockLayoutPanel");
}
}
@@ -1,437 +1,483 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.BoatDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.CompetitorColorProvider;
import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider;
import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
import com.sap.sailing.gwt.ui.client.shared.charts.WindChart;
import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings;
import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI;
import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel;
import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
import com.sap.sse.common.filter.FilterSet;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.URLEncoder;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.ComponentViewer;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
import com.sap.sse.gwt.client.useragent.UserAgentDetails;
import com.sap.sse.security.ui.client.UserService;
/**
* A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}.
* The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}.
* {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier}
* map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only.
*
* @author Frank Mittag, Axel Uhl (d043530)
*
*/
public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeListener, LeaderboardUpdateListener, PopupPositionProvider {
private final SailingServiceAsync sailingService;
private final MediaServiceAsync mediaService;
private final UUID eventId;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private final RaceBoardViewConfiguration raceboardViewConfiguration;
private String raceBoardName;
private final List<ComponentViewer> componentViewers;
private RaceTimePanel timePanel;
private final Timer timer;
private final RaceSelectionProvider raceSelectionProvider;
private final UserAgentDetails userAgent;
private final CompetitorSelectionProvider competitorSelectionProvider;
private final TimeRangeWithZoomModel timeRangeWithZoomModel;
private final RegattaAndRaceIdentifier selectedRaceIdentifier;
private final String leaderboardName;
private final LeaderboardPanel leaderboardPanel;
private WindChart windChart;
private MultiCompetitorRaceChart competitorChart;
private EditMarkPassingsPanel editMarkPassingPanel;
/**
* The component viewer in <code>ONESCREEN</code> view mode. <code>null</code> if in <code>CASCADE</code> view mode
*/
private SideBySideComponentViewer leaderboardAndMapViewer;
private final AsyncActionsExecutor asyncActionsExecutor;
private final RaceTimesInfoProvider raceTimesInfoProvider;
private RaceMap raceMap;
private final FlowPanel raceInformationHeader;
private final FlowPanel regattaAndRaceTimeInformationHeader;
private boolean currentRaceHasBeenSelectedOnce;
private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class);
/**
* @param eventId
* an optional event that can be used for "back"-navigation in case the race board shows a race in the
* context of an event; may be <code>null</code>.
*/
public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService,
UserService userService, AsyncActionsExecutor asyncActionsExecutor, Map<CompetitorDTO, BoatDTO> competitorsAndTheirBoats,
Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName,
String leaderboardGroupName, UUID eventId, RaceBoardViewConfiguration raceboardViewConfiguration,
ErrorReporter errorReporter, final StringMessages stringMessages,
UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) {
this.sailingService = sailingService;
this.mediaService = mediaService;
this.stringMessages = stringMessages;
this.raceboardViewConfiguration = raceboardViewConfiguration;
this.raceSelectionProvider = theRaceSelectionProvider;
this.raceTimesInfoProvider = raceTimesInfoProvider;
this.errorReporter = errorReporter;
this.userAgent = userAgent;
this.timer = timer;
this.eventId = eventId;
this.currentRaceHasBeenSelectedOnce = false;
this.leaderboardName = leaderboardName;
raceSelectionProvider.addRaceSelectionChangeListener(this);
selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
this.setRaceBoardName(selectedRaceIdentifier.getRaceName());
this.asyncActionsExecutor = asyncActionsExecutor;
FlowPanel mainPanel = new FlowPanel();
mainPanel.setSize("100%", "100%");
setWidget(mainPanel);
raceInformationHeader = new FlowPanel();
raceInformationHeader.setStyleName("RegattaRaceInformation-Header");
regattaAndRaceTimeInformationHeader = new FlowPanel();
regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header");
timeRangeWithZoomModel = new TimeRangeWithZoomModel();
componentViewers = new ArrayList<ComponentViewer>();
final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(selectedRaceIdentifier, competitorsAndTheirBoats);
competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true, colorProvider);
raceMapResources.combinedWindPanelStyle().ensureInjected();
raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer,
competitorSelectionProvider, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewStreamletColors(), getConfiguration().isShowViewSimulation(),
selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true);
CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap,
new LeaderboardFetcher() {
@Override
public LeaderboardDTO getLeaderboard() {
return leaderboardPanel.getLeaderboard();
}
}, selectedRaceIdentifier);
raceMap.getLeftHeaderPanel().add(raceInformationHeader);
raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader);
leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox);
leaderboardPanel.setTitle(stringMessages.leaderboard());
leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX);
leaderboardPanel.getElement().getStyle().setMarginTop(10, Unit.PX);
createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, raceMap, userService); // initializes the raceMap field
leaderboardPanel.addLeaderboardUpdateListener(this);
// in case the URL configuration contains the name of a competitors filter set we try to activate it
// FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it
if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) {
for (FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> filterSet : competitorSearchTextBox.getCompetitorsFilterSets()
.getFilterSets()) {
if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) {
competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet);
break;
}
}
}
timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider,
raceboardViewConfiguration.isCanReplayDuringLiveRaces());
timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel);
raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel);
raceSelectionProvider.addRaceSelectionChangeListener(timePanel);
timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
}
/**
* @param event an optional event; may be <code>null</code> or else can be used to show some context information in
* the {@link GlobalNavigationPanel}.
*/
private void createOneScreenView(String leaderboardName, String leaderboardGroupName, UUID event, FlowPanel mainPanel,
boolean showMapControls, RaceMap raceMap, UserService userService) {
// create the default leaderboard and select the right race
raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier));
List<Component<?>> components = new ArrayList<Component<?>>();
competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, raceSelectionProvider,
timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName);
competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts());
competitorChart.setVisible(false);
components.add(competitorChart);
windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(),
stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true);
windChart.setVisible(false);
windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
windChart.getEntryWidget().setTitle(stringMessages.windChart());
components.add(windChart);
editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier,
stringMessages, competitorSelectionProvider, errorReporter, timer);
editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard());
editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings());
components.add(editMarkPassingPanel);
boolean autoSelectMedia = getConfiguration().isAutoSelectMedia();
MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent(
selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages,
errorReporter, userAgent, this, autoSelectMedia);
leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, userService, editMarkPassingPanel);
componentViewers.add(leaderboardAndMapViewer);
for (ComponentViewer componentViewer : componentViewers) {
mainPanel.add(componentViewer.getViewerWidget());
}
boolean showLeaderboard = getConfiguration().isShowLeaderboard();
if (Document.get().getClientWidth() <= 1024) {
showLeaderboard = false;
}
setLeaderboardVisible(showLeaderboard);
setWindChartVisible(getConfiguration().isShowWindChart());
setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart());
// make sure to load leaderboard data for filtering to work
if (!showLeaderboard) {
leaderboardPanel.setVisible(true);
leaderboardPanel.setVisible(false);
}
}
@SuppressWarnings("unused")
private <SettingsType extends AbstractSettings> void addSettingsMenuItem(MenuBar settingsMenu, final Component<SettingsType> component) {
if (component.hasSettings()) {
settingsMenu.addItem(component.getLocalizedShortName(), new Command() {
public void execute() {
new SettingsDialog<SettingsType>(component, stringMessages).show();
}
});
}
}
private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, CompetitorFilterPanel competitorSearchTextBox) {
LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance()
.createNewSettingsForPlayMode(timer.getPlayMode(),
/* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(),
/* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(),
new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false,
/*showCompetitorSailIdColumn*/true, /*showCompetitorFullNameColumn*/true);
return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier,
competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages,
userAgent, /* showRaceDetails */ true, competitorSearchTextBox,
/* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false,
/* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false,
/*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false);
}
private void setComponentVisible(ComponentViewer componentViewer, Component<?> component, boolean visible) {
component.setVisible(visible);
componentViewer.forceLayout();
}
/**
* Sets the collapsable panel for the leaderboard open or close, if in <code>CASCADE</code> view mode.<br />
* Displays or hides the leaderboard, if in <code>ONESCREEN</code> view mode.<br /><br />
*
* The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
*
* @param visible <code>true</code> if the leaderboard shall be open/visible
*/
public void setLeaderboardVisible(boolean visible) {
setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible);
}
/**
* Sets the collapsable panel for the wind chart open or close, if in <code>CASCADE</code> view mode.<br />
* Displays or hides the wind chart, if in <code>ONESCREEN</code> view mode.<br /><br />
*
* The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
*
* @param visible <code>true</code> if the wind chart shall be open/visible
*/
public void setWindChartVisible(boolean visible) {
setComponentVisible(leaderboardAndMapViewer, windChart, visible);
}
/**
* Sets the collapsable panel for the competitor chart open or close, if in <code>CASCADE</code> view mode.<br />
* Displays or hides the competitor chart, if in <code>ONESCREEN</code> view mode.<br /><br />
*
* The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
*
* @param visible <code>true</code> if the competitor chart shall be open/visible
*/
public void setCompetitorChartVisible(boolean visible) {
setComponentVisible(leaderboardAndMapViewer, competitorChart, visible);
}
public RaceTimePanel getTimePanel() {
return timePanel;
}
protected SailingServiceAsync getSailingService() {
return sailingService;
}
protected String getRaceBoardName() {
return raceBoardName;
}
protected void setRaceBoardName(String raceBoardName) {
this.raceBoardName = raceBoardName;
}
protected ErrorReporter getErrorReporter() {
return errorReporter;
}
@Override
public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
}
public RaceBoardViewConfiguration getConfiguration() {
return raceboardViewConfiguration;
}
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth());
editMarkPassingPanel.setLeaderboard(leaderboard);
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
if (!currentRaceHasBeenSelectedOnce) {
FleetDTO fleet = raceColumn.getFleet(raceIdentifier);
String seriesName = raceColumn.getSeriesName();
if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) {
seriesName = "";
}
String fleetForRaceName = fleet==null?"":fleet.getName();
if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) {
fleetForRaceName = "";
} else {
fleetForRaceName = " - "+fleetForRaceName;
}
final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName());
raceNameLabel.setStyleName("RaceName-Label");
final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName);
raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label");
raceInformationHeader.clear();
raceInformationHeader.add(raceNameLabel);
raceInformationHeader.add(raceAdditionalInformationLabel);
final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
if (eventId != null) {
// we don't use the EntryPointLinkFactory here, because of lacking support for Places
String debugParam = Window.Location.getParameter("gwt.codesvr");
String link = "/gwt/Home.html";
if (debugParam != null && !debugParam.isEmpty()) {
link += "?gwt.codesvr=" + debugParam;
}
link += "#EventPlace:eventId="+eventId.toString();
link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName);
regattaNameAnchor.setHref(link);
} else {
String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName");
if(leaderboardGroupNameParam != null) {
Map<String, String> leaderboardGroupLinkParameters = new HashMap<String, String>();
leaderboardGroupLinkParameters.put("showRaceDetails", "true");
leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam);
String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters);
regattaNameAnchor.setHref(leaderBoardGroupLink);
} else {
// fallback
regattaNameAnchor.setHref("javascript:window.history.back();");
}
}
regattaNameAnchor.setStyleName("RegattaName-Anchor");
// TODO: Strange behavior... check
// Window.addResizeHandler(new ResizeHandler() {
// @Override
// public void onResize(ResizeEvent event) {
// int headerPanelWidth = raceMap.getRightHeaderPanel().getOffsetWidth() - 150; // 150px is the width of the sapLogo
// int raceNameAndFleetLabelWidth = raceInformationHeader.getOffsetWidth();
// int regattaAnchorWidth = regattaNameAnchor.getOffsetWidth();
// boolean overlap = raceNameAndFleetLabelWidth + regattaAnchorWidth > headerPanelWidth;
// raceInformationHeader.setVisible(!overlap);
// }
// });
Label raceTimeLabel = computeRaceInformation(raceColumn, fleet);
raceTimeLabel.setStyleName("RaceTime-Label");
regattaAndRaceTimeInformationHeader.clear();
regattaAndRaceTimeInformationHeader.add(regattaNameAnchor);
regattaAndRaceTimeInformationHeader.add(raceTimeLabel);
currentRaceHasBeenSelectedOnce = true;
}
}
@Override
public UIObject getXPositionUiObject() {
return timePanel;
}
@Override
public UIObject getYPositionUiObject() {
return timePanel;
}
private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) {
Label raceInformationLabel = new Label();
raceInformationLabel.setStyleName("Race-Time-Label");
DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y");
raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet)));
return raceInformationLabel;
}
}
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.BoatDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.CompetitorColorProvider;
import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider;
import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
import com.sap.sailing.gwt.ui.client.shared.charts.WindChart;
import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings;
import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI;
import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel;
import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
import com.sap.sse.common.filter.FilterSet;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.URLEncoder;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.ComponentViewer;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
import com.sap.sse.gwt.client.useragent.UserAgentDetails;
import com.sap.sse.security.ui.client.UserService;
/**
* A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}.
* The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}.
* {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier}
* map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only.
*
* @author Frank Mittag, Axel Uhl (d043530)
*
*/
public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeListener,
LeaderboardUpdateListener, PopupPositionProvider, RequiresResize {
private final SailingServiceAsync sailingService;
private final MediaServiceAsync mediaService;
private final UUID eventId;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private final RaceBoardViewConfiguration raceboardViewConfiguration;
private String raceBoardName;
private final List<ComponentViewer> componentViewers;
private RaceTimePanel timePanel;
private final Timer timer;
private final RaceSelectionProvider raceSelectionProvider;
private final UserAgentDetails userAgent;
private final CompetitorSelectionProvider competitorSelectionProvider;
private final TimeRangeWithZoomModel timeRangeWithZoomModel;
private final RegattaAndRaceIdentifier selectedRaceIdentifier;
private final String leaderboardName;
private final LeaderboardPanel leaderboardPanel;
private WindChart windChart;
private MultiCompetitorRaceChart competitorChart;
private EditMarkPassingsPanel editMarkPassingPanel;
/**
* The component viewer in <code>ONESCREEN</code> view mode. <code>null</code> if in <code>CASCADE</code> view mode
*/
private SideBySideComponentViewer leaderboardAndMapViewer;
private final AsyncActionsExecutor asyncActionsExecutor;
private final RaceTimesInfoProvider raceTimesInfoProvider;
private RaceMap raceMap;
private final FlowPanel raceInformationHeader;
private final FlowPanel regattaAndRaceTimeInformationHeader;
private boolean currentRaceHasBeenSelectedOnce;
private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class);
/**
* @param eventId
* an optional event that can be used for "back"-navigation in case the race board shows a race in the
* context of an event; may be <code>null</code>.
* @param isScreenLargeEnoughToOfferChartSupport
* if the screen is large enough to display charts such as the competitor chart or the wind chart, a
* padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and
* the charts are created. This decision is made once on startup in the {@link RaceBoardEntryPoint} class.
*/
public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService,
UserService userService, AsyncActionsExecutor asyncActionsExecutor, Map<CompetitorDTO, BoatDTO> competitorsAndTheirBoats,
Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName,
String leaderboardGroupName, UUID eventId, RaceBoardViewConfiguration raceboardViewConfiguration,
ErrorReporter errorReporter, final StringMessages stringMessages,
UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport) {
this.sailingService = sailingService;
this.mediaService = mediaService;
this.stringMessages = stringMessages;
this.raceboardViewConfiguration = raceboardViewConfiguration;
this.raceSelectionProvider = theRaceSelectionProvider;
this.raceTimesInfoProvider = raceTimesInfoProvider;
this.errorReporter = errorReporter;
this.userAgent = userAgent;
this.timer = timer;
this.eventId = eventId;
this.currentRaceHasBeenSelectedOnce = false;
this.leaderboardName = leaderboardName;
raceSelectionProvider.addRaceSelectionChangeListener(this);
selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
this.setRaceBoardName(selectedRaceIdentifier.getRaceName());
this.asyncActionsExecutor = asyncActionsExecutor;
FlowPanel mainPanel = new ResizableFlowPanel();
mainPanel.setSize("100%", "100%");
setWidget(mainPanel);
raceInformationHeader = new FlowPanel();
raceInformationHeader.setStyleName("RegattaRaceInformation-Header");
regattaAndRaceTimeInformationHeader = new FlowPanel();
regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header");
timeRangeWithZoomModel = new TimeRangeWithZoomModel();
componentViewers = new ArrayList<ComponentViewer>();
final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(selectedRaceIdentifier, competitorsAndTheirBoats);
competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true, colorProvider);
raceMapResources.combinedWindPanelStyle().ensureInjected();
raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer,
competitorSelectionProvider, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewStreamletColors(), getConfiguration().isShowViewSimulation(),
selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true) {
private static final String INDENT_SMALL_CONTROL_STYLE = "indentsmall";
private static final String INDENT_BIG_CONTROL_STYLE = "indentbig";
@Override
public void onResize() {
super.onResize();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
// Show/hide the leaderboard panels toggle button text based on the race map height
leaderboardAndMapViewer.setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(raceMap.getOffsetHeight() > 400);
}
});
}
@Override
protected String getLeftControlsIndentStyle() {
// Calculate style name for left control indent based on race map height an leaderboard panel visibility
if (raceMap.getOffsetHeight() <= 300) {
return INDENT_BIG_CONTROL_STYLE;
}
if (leaderboardPanel.isVisible() && raceMap.getOffsetHeight() <= 500) {
return INDENT_SMALL_CONTROL_STYLE;
}
return super.getLeftControlsIndentStyle();
}
};
CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap,
new LeaderboardFetcher() {
@Override
public LeaderboardDTO getLeaderboard() {
return leaderboardPanel.getLeaderboard();
}
}, selectedRaceIdentifier);
raceMap.getLeftHeaderPanel().add(raceInformationHeader);
raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader);
// Determine if the screen is large enough to initially display the leaderboard panel on the left side of the
// map based on the initial screen width. Afterwards, the leaderboard panel visibility can be toggled as usual.
boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard = Document.get().getClientWidth() >= 1024;
leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox, isScreenLargeEnoughToInitiallyDisplayLeaderboard);
leaderboardPanel.setTitle(stringMessages.leaderboard());
leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX);
leaderboardPanel.getElement().getStyle().setMarginTop(10, Unit.PX);
createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, isScreenLargeEnoughToOfferChartSupport, isScreenLargeEnoughToInitiallyDisplayLeaderboard, raceMap, userService); // initializes the raceMap field
leaderboardPanel.addLeaderboardUpdateListener(this);
// in case the URL configuration contains the name of a competitors filter set we try to activate it
// FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it
if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) {
for (FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> filterSet : competitorSearchTextBox.getCompetitorsFilterSets()
.getFilterSets()) {
if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) {
competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet);
break;
}
}
}
timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider,
raceboardViewConfiguration.isCanReplayDuringLiveRaces(), isScreenLargeEnoughToOfferChartSupport);
timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel);
raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel);
raceSelectionProvider.addRaceSelectionChangeListener(timePanel);
timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
}
/**
* @param event
* an optional event; may be <code>null</code> or else can be used to show some context information in
* the {@link GlobalNavigationPanel}.
* @param isScreenLargeEnoughToOfferChartSupport
* if the screen is large enough to display charts such as the competitor chart or the wind chart, a
* padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and
* the charts are created.
* @param isScreenLargeEnoughToInitiallyDisplayLeaderboard TODO
*/
private void createOneScreenView(String leaderboardName, String leaderboardGroupName, UUID event, FlowPanel mainPanel,
boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard, RaceMap raceMap, UserService userService) {
// create the default leaderboard and select the right race
raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier));
List<Component<?>> components = new ArrayList<Component<?>>();
if (isScreenLargeEnoughToOfferChartSupport) {
competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, raceSelectionProvider,
timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName);
competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts());
competitorChart.setVisible(false);
components.add(competitorChart);
windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(),
stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true);
windChart.setVisible(false);
windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
windChart.getEntryWidget().setTitle(stringMessages.windChart());
components.add(windChart);
editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier,
stringMessages, competitorSelectionProvider, errorReporter, timer);
editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard());
editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings());
components.add(editMarkPassingPanel);
}
boolean autoSelectMedia = getConfiguration().isAutoSelectMedia();
MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent(
selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages,
errorReporter, userAgent, this, autoSelectMedia);
leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, userService, editMarkPassingPanel);
componentViewers.add(leaderboardAndMapViewer);
for (ComponentViewer componentViewer : componentViewers) {
mainPanel.add(componentViewer.getViewerWidget());
}
boolean showLeaderboard = getConfiguration().isShowLeaderboard() && isScreenLargeEnoughToInitiallyDisplayLeaderboard;
setLeaderboardVisible(showLeaderboard);
if (isScreenLargeEnoughToOfferChartSupport) {
setWindChartVisible(getConfiguration().isShowWindChart());
setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart());
}
// make sure to load leaderboard data for filtering to work
if (!showLeaderboard) {
leaderboardPanel.setVisible(true);
leaderboardPanel.setVisible(false);
}
}
@SuppressWarnings("unused")
private <SettingsType extends AbstractSettings> void addSettingsMenuItem(MenuBar settingsMenu, final Component<SettingsType> component) {
if (component.hasSettings()) {
settingsMenu.addItem(component.getLocalizedShortName(), new Command() {
public void execute() {
new SettingsDialog<SettingsType>(component, stringMessages).show();
}
});
}
}
private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName,
CompetitorFilterPanel competitorSearchTextBox, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard) {
LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance()
.createNewSettingsForPlayMode(timer.getPlayMode(),
/* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(),
/* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(),
new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false,
/*showCompetitorSailIdColumn*/true,
/* don't showCompetitorFullNameColumn in case screen is so small that we don't
* even display the leaderboard initially */ isScreenLargeEnoughToInitiallyDisplayLeaderboard);
return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier,
competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages,
userAgent, /* showRaceDetails */ true, competitorSearchTextBox,
/* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false,
/* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false,
/*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false);
}
private void setComponentVisible(ComponentViewer componentViewer, Component<?> component, boolean visible) {
component.setVisible(visible);
componentViewer.forceLayout();
}
/**
* Sets the collapsable panel for the leaderboard open or close, if in <code>CASCADE</code> view mode.<br />
* Displays or hides the leaderboard, if in <code>ONESCREEN</code> view mode.<br /><br />
*
* The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
*
* @param visible <code>true</code> if the leaderboard shall be open/visible
*/
public void setLeaderboardVisible(boolean visible) {
setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible);
}
/**
* Sets the collapsable panel for the wind chart open or close, if in <code>CASCADE</code> view mode.<br />
* Displays or hides the wind chart, if in <code>ONESCREEN</code> view mode.<br /><br />
*
* The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
*
* @param visible <code>true</code> if the wind chart shall be open/visible
*/
public void setWindChartVisible(boolean visible) {
setComponentVisible(leaderboardAndMapViewer, windChart, visible);
}
/**
* Sets the collapsable panel for the competitor chart open or close, if in <code>CASCADE</code> view mode.<br />
* Displays or hides the competitor chart, if in <code>ONESCREEN</code> view mode.<br /><br />
*
* The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
*
* @param visible <code>true</code> if the competitor chart shall be open/visible
*/
public void setCompetitorChartVisible(boolean visible) {
setComponentVisible(leaderboardAndMapViewer, competitorChart, visible);
}
public RaceTimePanel getTimePanel() {
return timePanel;
}
protected SailingServiceAsync getSailingService() {
return sailingService;
}
protected String getRaceBoardName() {
return raceBoardName;
}
protected void setRaceBoardName(String raceBoardName) {
this.raceBoardName = raceBoardName;
}
protected ErrorReporter getErrorReporter() {
return errorReporter;
}
@Override
public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
}
public RaceBoardViewConfiguration getConfiguration() {
return raceboardViewConfiguration;
}
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth());
if (editMarkPassingPanel != null) {
editMarkPassingPanel.setLeaderboard(leaderboard);
}
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
if (!currentRaceHasBeenSelectedOnce) {
FleetDTO fleet = raceColumn.getFleet(raceIdentifier);
String seriesName = raceColumn.getSeriesName();
if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) {
seriesName = "";
}
String fleetForRaceName = fleet==null?"":fleet.getName();
if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) {
fleetForRaceName = "";
} else {
fleetForRaceName = (seriesName.isEmpty() ? "" : " - ") + fleetForRaceName;
}
final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName());
raceNameLabel.setStyleName("RaceName-Label");
final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName);
raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label");
raceInformationHeader.clear();
raceInformationHeader.add(raceNameLabel);
raceInformationHeader.add(raceAdditionalInformationLabel);
final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
regattaNameAnchor.setTitle(raceIdentifier.getRegattaName());
if (eventId != null) {
// we don't use the EntryPointLinkFactory here, because of lacking support for Places
String debugParam = Window.Location.getParameter("gwt.codesvr");
String link = "/gwt/Home.html";
if (debugParam != null && !debugParam.isEmpty()) {
link += "?gwt.codesvr=" + debugParam;
}
link += "#EventPlace:eventId="+eventId.toString();
link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName);
regattaNameAnchor.setHref(link);
} else {
String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName");
if(leaderboardGroupNameParam != null) {
Map<String, String> leaderboardGroupLinkParameters = new HashMap<String, String>();
leaderboardGroupLinkParameters.put("showRaceDetails", "true");
leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam);
String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters);
regattaNameAnchor.setHref(leaderBoardGroupLink);
} else {
// fallback
regattaNameAnchor.setHref("javascript:window.history.back();");
}
}
regattaNameAnchor.setStyleName("RegattaName-Anchor");
Label raceTimeLabel = computeRaceInformation(raceColumn, fleet);
raceTimeLabel.setStyleName("RaceTime-Label");
regattaAndRaceTimeInformationHeader.clear();
regattaAndRaceTimeInformationHeader.add(regattaNameAnchor);
regattaAndRaceTimeInformationHeader.add(raceTimeLabel);
currentRaceHasBeenSelectedOnce = true;
}
}
@Override
public UIObject getXPositionUiObject() {
return timePanel;
}
@Override
public UIObject getYPositionUiObject() {
return timePanel;
}
private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) {
Label raceInformationLabel = new Label();
raceInformationLabel.setStyleName("Race-Time-Label");
DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y");
raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet)));
return raceInformationLabel;
}
@Override
public void onResize() {
Widget child = getWidget();
if (child != null && child instanceof RequiresResize) {
((RequiresResize) child).onResize();
}
}
}
@@ -0,0 +1,23 @@
package com.sap.sailing.gwt.ui.raceboard;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;
/**
* Utility class extending {@link FlowPanel} and implementing {@link RequiresResize} interface. All
* {@link #getChildren() children} also implementing {@link RequiresResize} are informed by calling their
* {@link RequiresResize#onResize()} method.
*/
class ResizableFlowPanel extends FlowPanel implements RequiresResize {
@Override
public void onResize() {
WidgetCollection children = getChildren();
for (Widget widget : children) {
if (widget instanceof RequiresResize) {
((RequiresResize) widget).onResize();
}
}
}
}
@@ -1,305 +1,334 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;
import com.sap.sailing.domain.common.security.Roles;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.media.MediaManagementControl;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl;
import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.ComponentViewer;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
import com.sap.sse.security.shared.DefaultRoles;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.UserStatusEventHandler;
import com.sap.sse.security.ui.shared.UserDTO;
/**
* Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components.
*
* TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging
* too deep into components and setting titles or even creating video buttons.
*/
public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler {
private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200;
private final int MIN_LEADERBOARD_WIDTH = 432; // works well for 505 and ESS
/**
* Absolute Panel that informs its children about a resize
*/
class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize {
public void onResize() {
WidgetCollection children = getChildren();
for (Widget widget : children) {
if (widget instanceof RequiresResize) {
((RequiresResize) widget).onResize();
}
}
}
}
private final LeaderboardPanel leftComponent;
private final Component<?> rightComponent;
private final List<Component<?>> components;
private final ScrollPanel leftScrollPanel;
private final StringMessages stringMessages;
private final Button mediaSelectionButton;
private final Button mediaManagementButton;
private final EditMarkPassingsPanel markPassingsPanel;
private LayoutPanel mainPanel;
private TouchSplitLayoutPanel splitLayoutPanel;
private int savedSplitPosition = -1;
private boolean layoutForLeftComponentForcedOnce = false;
public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component<?> rightComponentP,
final MediaPlayerManagerComponent mediaPlayerManagerComponent, List<Component<?>> components,
final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) {
this.stringMessages = stringMessages;
this.leftComponent = leftComponentP;
this.rightComponent = rightComponentP;
this.components = components;
this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent);
this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent);
this.markPassingsPanel = markPassingsPanel;
userService.addUserStatusEventHandler(this);
mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() {
public void notifyStateChange(){
String caption;
String tooltip;
switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) {
case 0:
caption = stringMessages.mediaNoVideosCaption();
tooltip = caption;
mediaSelectionButton.setVisible(false);
break;
case 1:
mediaSelectionButton.setVisible(true);
if(mediaPlayerManagerComponent.isPlaying()){
caption = stringMessages.mediaHideVideoCaption();
tooltip = stringMessages.mediaHideVideoTooltip();
}
else{
caption = stringMessages.mediaShowVideoCaption();
tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title);
}
break;
default:
mediaSelectionButton.setVisible(true);
caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size());
tooltip = stringMessages.mediaSelectVideoTooltip();
break;
}
if (mediaPlayerManagerComponent.isPlaying()) {
mediaSelectionButton.addStyleDependentName("mediaplaying");
} else {
mediaSelectionButton.removeStyleDependentName("mediaplaying");
}
mediaSelectionButton.setText(caption);
mediaSelectionButton.setTitle(tooltip);
mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing());
}
});
this.leftScrollPanel = new ScrollPanel();
this.leftScrollPanel.add(leftComponentP.getEntryWidget());
this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle());
this.mainPanel = new LayoutPanel();
this.mainPanel.setSize("100%", "100%");
this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX);
this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel");
this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */
25);
this.mainPanel.add(splitLayoutPanel);
// initialize components - they need to be added before other widgets to get the right width
initializeComponents();
// initialize the leaderboard component
savedSplitPosition = MIN_LEADERBOARD_WIDTH;
splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition);
// create a panel that will contain the horizontal toggle buttons
ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel();
panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget());
splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0);
// add additional toggle buttons panel that currently only contains the video button
List<Pair<Button, String>> additionalVerticalButtons = new ArrayList<Pair<Button, String>>();
additionalVerticalButtons.add(new Pair<Button, String>(mediaSelectionButton,
mediaPlayerManagerComponent.getDependentCssClassName()));
if (/* TODO check for correct role; was: user != null */ true) {
additionalVerticalButtons.add(new Pair<Button, String>(mediaManagementButton,
"managemedia"));
}
onUserStatusChange(userService.getCurrentUser());
// ensure that toggle buttons are positioned right
splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons,
additionalVerticalButtons);
}
/**
* Create the video control button that shows or hides the video popup
*
* @param userAgent
*/
private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) {
final Button result = new Button();
final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager,
result, stringMessages);
result.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) {
if (mediaPlayerManager.isPlaying()) {
mediaPlayerManager.stopAll();
} else {
mediaPlayerManager.playDefault();
}
} else {
multiSelectionControl.show();
}
}
});
// hide button initially as we defer showing the button to the asynchroneous
// task that gets launched by the media service to get video tracks
result.setVisible(false);
return result;
}
/**
* Create the video control button that shows or hides the video popup
*
* @param userAgent
*/
private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) {
final Button result = new Button(stringMessages.mediaManageMediaCaption());
result.setTitle(stringMessages.mediaManageMediaTooltip());
// onClick
final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager,
result, stringMessages);
result.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
multiSelectionControl.show();
}
});
// hide button initially as we defer showing the button to the asynchroneous
// task that gets launched by the media service to get video tracks
result.setVisible(false);
return result;
}
private void initializeComponents() {
for (final Component<?> component : components) {
splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200);
}
}
public <SettingsType extends AbstractSettings> void showSettingsDialog(Component<SettingsType> component) {
if (component.hasSettings()) {
new SettingsDialog<SettingsType>(component, stringMessages).show();
}
}
/**
* Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility
* based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update
* itself when it is not visible.
*/
public void forceLayout() {
if (!leftComponent.isVisible() && rightComponent.isVisible()) {
// the leaderboard is not visible, but the map is
if (isWidgetInSplitPanel(leftScrollPanel)) {
if (leftScrollPanel.getOffsetWidth() > 0) {
savedSplitPosition = leftScrollPanel.getOffsetWidth();
}
splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true,
savedSplitPosition);
}
} else if (leftComponent.isVisible() && rightComponent.isVisible()) {
// the leaderboard and the map are visible
splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition);
} else if (!leftComponent.isVisible() && !rightComponent.isVisible()) {
}
for (Component<?> component : components) {
final boolean isComponentVisible = component.isVisible();
splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible,
DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT);
}
splitLayoutPanel.forceLayout();
}
private boolean isWidgetInSplitPanel(Widget widget) {
int widgetIndex = splitLayoutPanel.getWidgetIndex(widget);
return widgetIndex >= 0;
}
public Panel getViewerWidget() {
return mainPanel;
}
public Component<?> getRootComponent() {
return null;
}
public String getViewerName() {
return "";
}
public void setLeftComponentWidth(int width) {
// TODO: The information provided by width is wrong
// need to find a way to get the correct information
if (!layoutForLeftComponentForcedOnce) {
savedSplitPosition = MIN_LEADERBOARD_WIDTH;
forceLayout();
}
layoutForLeftComponentForcedOnce = true;
}
@Override
public void onUserStatusChange(UserDTO user) {
Button toggleButton = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel).getToggleButton();
if (user != null
&& (user.hasRole(DefaultRoles.ADMIN.getRolename()) || user.hasRole(Roles.eventmanager.getRolename()))) {
toggleButton.setVisible(true);
forceLayout();
} else {
markPassingsPanel.setVisible(false);
toggleButton.setVisible(false);
forceLayout();
}
}
}
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;
import com.sap.sailing.domain.common.security.Roles;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.media.MediaManagementControl;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener;
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl;
import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.raceboard.TouchSplitLayoutPanel.Splitter;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.ComponentViewer;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
import com.sap.sse.security.shared.DefaultRoles;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.UserStatusEventHandler;
import com.sap.sse.security.ui.shared.UserDTO;
/**
* Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components.
*
* TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging
* too deep into components and setting titles or even creating video buttons.
*/
public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler {
private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200;
private final int MIN_LEADERBOARD_WIDTH = Math.min(432, Window.getClientWidth() - 40); // fallback value "432" works well for 505 and ESS
/**
* Absolute Panel that informs its children about a resize
*/
class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize {
public void onResize() {
WidgetCollection children = getChildren();
for (Widget widget : children) {
if (widget instanceof RequiresResize) {
((RequiresResize) widget).onResize();
}
}
}
}
private final LeaderboardPanel leftComponent;
private final Component<?> rightComponent;
private final List<Component<?>> components;
private final ScrollPanel leftScrollPanel;
private final StringMessages stringMessages;
private final Button mediaSelectionButton;
private final Button mediaManagementButton;
private final EditMarkPassingsPanel markPassingsPanel;
private LayoutPanel mainPanel;
private TouchSplitLayoutPanel splitLayoutPanel;
private int savedSplitPosition = -1;
private boolean layoutForLeftComponentForcedOnce = false;
public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component<?> rightComponentP,
final MediaPlayerManagerComponent mediaPlayerManagerComponent, List<Component<?>> components,
final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) {
this.stringMessages = stringMessages;
this.leftComponent = leftComponentP;
this.rightComponent = rightComponentP;
this.components = components;
this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent);
this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent);
this.markPassingsPanel = markPassingsPanel;
userService.addUserStatusEventHandler(this);
mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() {
public void notifyStateChange() {
String caption;
String tooltip;
switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) {
case 0:
caption = stringMessages.mediaNoVideosCaption();
tooltip = caption;
mediaSelectionButton.setVisible(false);
break;
case 1:
mediaSelectionButton.setVisible(true);
if(mediaPlayerManagerComponent.isPlaying()){
caption = stringMessages.mediaHideVideoCaption();
tooltip = stringMessages.mediaHideVideoTooltip();
}
else{
caption = stringMessages.mediaShowVideoCaption();
tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title);
}
break;
default:
mediaSelectionButton.setVisible(true);
caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size());
tooltip = stringMessages.mediaSelectVideoTooltip();
break;
}
if (mediaPlayerManagerComponent.isPlaying()) {
mediaSelectionButton.addStyleDependentName("mediaplaying");
} else {
mediaSelectionButton.removeStyleDependentName("mediaplaying");
}
mediaSelectionButton.setText(caption);
mediaSelectionButton.setTitle(tooltip);
mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing());
}
});
this.leftScrollPanel = new ScrollPanel();
this.leftScrollPanel.add(leftComponentP.getEntryWidget());
this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle());
this.mainPanel = new LayoutPanel() {
@Override
public void onResize() {
int leftWidth = leftScrollPanel.getOffsetWidth();
// The left scroll panel is potentially resized to ensure it is not too wide when the screen gets narrower,
// e.g. when resizing browser window or changing mobile device orientation. An offset of 40px is used, so
// the panels size slider and its toggle button is always accessable if it is open.
savedSplitPosition = Math.min(leftWidth > 0 ? leftWidth : savedSplitPosition, Window.getClientWidth() - 40);
splitLayoutPanel.setWidgetSize(leftScrollPanel, savedSplitPosition);
super.onResize();
}
};
this.mainPanel.setSize("100%", "100%");
this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX);
this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel");
this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */ 25);
this.mainPanel.add(splitLayoutPanel);
// initialize components - they need to be added before other widgets to get the right width
initializeComponents();
// initialize the leaderboard component
savedSplitPosition = MIN_LEADERBOARD_WIDTH;
splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition);
// create a panel that will contain the horizontal toggle buttons
ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel();
panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget());
splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0);
// add additional toggle buttons panel that currently only contains the video button
List<Pair<Button, String>> additionalVerticalButtons = new ArrayList<Pair<Button, String>>();
additionalVerticalButtons.add(new Pair<Button, String>(mediaSelectionButton,
mediaPlayerManagerComponent.getDependentCssClassName()));
if (/* TODO check for correct role; was: user != null */ true) {
additionalVerticalButtons.add(new Pair<Button, String>(mediaManagementButton,
"managemedia"));
}
onUserStatusChange(userService.getCurrentUser());
// ensure that toggle buttons are positioned right
splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons,
additionalVerticalButtons);
}
/**
* Create the video control button that shows or hides the video popup
*
* @param userAgent
*/
private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) {
final Button result = new Button();
final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager,
result, stringMessages);
result.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) {
if (mediaPlayerManager.isPlaying()) {
mediaPlayerManager.stopAll();
} else {
mediaPlayerManager.playDefault();
}
} else {
multiSelectionControl.show();
}
}
});
// hide button initially as we defer showing the button to the asynchroneous
// task that gets launched by the media service to get video tracks
result.setVisible(false);
return result;
}
/**
* Create the video control button that shows or hides the video popup
*
* @param userAgent
*/
private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) {
final Button result = new Button(stringMessages.mediaManageMediaCaption());
result.setTitle(stringMessages.mediaManageMediaTooltip());
// onClick
final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager,
result, stringMessages);
result.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
multiSelectionControl.show();
}
});
// hide button initially as we defer showing the button to the asynchronous
// task that gets launched by the media service to get video tracks
result.setVisible(false);
return result;
}
private void initializeComponents() {
for (final Component<?> component : components) {
splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200);
}
}
public <SettingsType extends AbstractSettings> void showSettingsDialog(Component<SettingsType> component) {
if (component.hasSettings()) {
new SettingsDialog<SettingsType>(component, stringMessages).show();
}
}
/**
* Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility
* based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update
* itself when it is not visible.
*/
public void forceLayout() {
if (!leftComponent.isVisible() && rightComponent.isVisible()) {
// the leaderboard is not visible, but the map is
if (isWidgetInSplitPanel(leftScrollPanel)) {
if (leftScrollPanel.getOffsetWidth() > 0) {
savedSplitPosition = Math.min(savedSplitPosition, leftScrollPanel.getOffsetWidth());
}
splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true,
savedSplitPosition);
}
} else if (leftComponent.isVisible() && rightComponent.isVisible()) {
// the leaderboard and the map are visible
splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition);
} else if (!leftComponent.isVisible() && !rightComponent.isVisible()) {
}
for (Component<?> component : components) {
final boolean isComponentVisible = component.isVisible();
splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible,
DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT);
}
splitLayoutPanel.forceLayout();
}
private boolean isWidgetInSplitPanel(Widget widget) {
int widgetIndex = splitLayoutPanel.getWidgetIndex(widget);
return widgetIndex >= 0;
}
public Panel getViewerWidget() {
return mainPanel;
}
public Component<?> getRootComponent() {
return null;
}
public String getViewerName() {
return "";
}
public void setLeftComponentWidth(int width) {
// TODO: The information provided by width is wrong
// need to find a way to get the correct information
if (!layoutForLeftComponentForcedOnce) {
savedSplitPosition = MIN_LEADERBOARD_WIDTH;
forceLayout();
}
layoutForLeftComponentForcedOnce = true;
}
@Override
public void onUserStatusChange(UserDTO user) {
final Splitter associatedSplitter = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel);
if (associatedSplitter != null) { // if the panel is not present, the splitter may not be found
final Button toggleButton = associatedSplitter.getToggleButton();
if (user != null
&& (user.hasRole(DefaultRoles.ADMIN.getRolename()) ||
user.hasRole(Roles.eventmanager.getRolename()))) {
toggleButton.setVisible(true);
forceLayout();
} else {
markPassingsPanel.setVisible(false);
toggleButton.setVisible(false);
forceLayout();
}
}
}
/**
* Shows/hides the text on left components toggle button by modifying CSS <code>font-size</code> property and adjust
* the dragger position by modifying CSS <code>margin-top</code> property.
*
* @param visible
* <code>true</code> to show the button text, <code>false</code> to hide it
*/
void setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(final boolean visible) {
Splitter leftScrollPanelSplitter = splitLayoutPanel.getAssociatedSplitter(leftScrollPanel);
if (leftScrollPanelSplitter != null) {
Style toggleButtonStyle = leftScrollPanelSplitter.getToggleButton().getElement().getStyle();
if (visible) toggleButtonStyle.clearFontSize();
else toggleButtonStyle.setFontSize(0, Unit.PX);
Style drapperStyle = leftScrollPanelSplitter.getDragger().getElement().getStyle();
if (visible) drapperStyle.clearMarginTop();
else drapperStyle.setMarginTop(-25, Unit.PX);
}
}
}
@@ -613,7 +613,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
if (lastVerticalSplitter != null) {
Panel panel = createToggleButtonPanel(allVerticalSplitters,
"gwt-SplitLayoutPanel-NorthSouthToggleButton-Panel", "gwt-SplitLayoutPanel-NorthSouthToggleButton",
cm, additionalVerticalButtonsAndStyles);
cm, additionalVerticalButtonsAndStyles); // TODO additional buttons will only be displayed if at least one vertical splitter is... on purpose?
lastVerticalSplitter.addToogleButtons(panel);
lastVerticalSplitter.setVisible(true);
lastVerticalSplitter.setDraggerVisible(false);
@@ -621,7 +621,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
ensureVerticalToggleButtonPosition();
if (lastHorizontalSplitter != null) {
Panel horizontalButtonsPanel = createToggleButtonPanel(allHorizontalSplitters,
"gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, null);
"gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, /* additional buttons and styles */ null);
panelForHorizontalButtons.add(horizontalButtonsPanel);
lastHorizontalSplitter.setVisible(false);
lastHorizontalSplitter.setDraggerVisible(false);
@@ -842,6 +842,24 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
}
}
}
/**
* Sets the size of the given {@link Widget} if it is {@link Widget#isVisible() visible}. Otherwise, the given size
* will be persisted as the widgets {@link LayoutData#oldSize old size}.
*
* @param widget the {@link Widget} to resize
* @param size the new size
*/
public void setWidgetSize(Widget widget, int size) {
final Splitter splitter = getAssociatedSplitter(widget);
if (splitter != null) {
if (widget.isVisible()) {
splitter.setAssociatedWidgetSize(size, /* defer */false);
} else {
((LayoutData) widget.getLayoutData()).oldSize = size;
}
}
}
private void assertIsChild2(Widget widget) {
assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel";
@@ -309,7 +309,9 @@ public class SimulatorMainPanel extends SimplePanel {
timer.setTime(windParams.getStartTime().getTime());
int secondsTimeStep = (int) windParams.getTimeStep().asSeconds();
timer.setPlaySpeedFactor(secondsTimeStep);
timePanel = new TimePanel<TimePanelSettings>(timer, timeRangeProvider, stringMessages, false);
timePanel = new TimePanel<TimePanelSettings>(timer, timeRangeProvider, stringMessages, false,
/* isScreenLargeEnoughToOfferChartSupport: no wind or competitor chart is shown; use full horizontal
* extension of time panel */ false);
busyIndicator = new SimpleBusyIndicator(false, 0.8f);
simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, streamletPars,
timer, timePanel, windParams, busyIndicator, mode, this, showMapControls,
@@ -13,7 +13,17 @@
by unticking the corresponding checkbox in the settings of the race map. Furthermore the stroke weight of the wider lines can be
configured in the same settings dialog.
</li>
<li>The race viewer (RaceBoard.html) now intelligently chooses a layout that suits the screen size.
When only little horizontal space is available, only the sail number and nationality but not the
competitor's long name is displayed by default. When vertical space is scarce, competitor and wind
charts are disabled and more horizontal space can be given to the time slider at the bottom. The
leaderboard section can then be scaled to smaller widths and will reduce the column widths better
than in the past.
</li>
<li>With the changes described above, the RaceBoard is now also accessible from the races page on mobile devices.
</li>
</ul>
<h5 class="articleSubheadline">November 2015</h5>
<ul class="bulletList">
File diff suppressed because it is too large. Load diff
@@ -26,7 +26,9 @@ public class TimeSlider extends SliderBar {
private List<TickPosition> calculatedTimeTicks;
private boolean isZoomed;
private int visibleLabelsInterval = 1;
private final int TICKCOUNT = 10;
public TimeSlider() {
@@ -84,7 +86,7 @@ public class TimeSlider extends SliderBar {
int tickLeftOffset = lineLeftOffset + (int) pos - (tickWidth / 2);
tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth);
tick.getStyle().setLeft(tickLeftOffset, Unit.PX);
tick.getStyle().setVisibility(Visibility.VISIBLE);
tick.getStyle().setVisibility(isTickInVisibleRange(tickPosition) ? Visibility.VISIBLE : Visibility.HIDDEN);
}
}
@@ -106,7 +108,7 @@ public class TimeSlider extends SliderBar {
int lineWidth = lineElement.getOffsetWidth();
// Create the labels or make them visible
Double previousValue = null;
for (int i = 0; i < calculatedTimeTicks.size(); i++) {
for (int i = 0, ticksInVisibleRange = 0; i < calculatedTimeTicks.size(); i++) {
TickPosition tickPosition = calculatedTimeTicks.get(i);
Element label = null;
if (i < tickLabelElements.size()) {
@@ -142,7 +144,8 @@ public class TimeSlider extends SliderBar {
labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset);
label.getStyle().setLeft(labelLeftOffset, Unit.PX);
label.getStyle().setVisibility(Visibility.VISIBLE);
boolean visible = isTickInVisibleRange(tickPosition) && ticksInVisibleRange++ % visibleLabelsInterval == 0;
label.getStyle().setVisibility(visible ? Visibility.VISIBLE : Visibility.HIDDEN);
}
previousValue = value;
}
@@ -152,6 +155,11 @@ public class TimeSlider extends SliderBar {
tickLabelElements.get(i).getStyle().setDisplay(Display.NONE);
}
}
private boolean isTickInVisibleRange(TickPosition tickPosition) {
long tickValue = tickPosition.getPosition().getTime();
return tickValue >= minValue && tickValue <= maxValue;
}
public void clearMarkersAndLabelsAndTicks() {
clearMarkers();
@@ -245,4 +253,10 @@ public class TimeSlider extends SliderBar {
public void setZoomed(boolean isZoomed) {
this.isZoomed = isZoomed;
}
}
@Override
public void onResize() {
visibleLabelsInterval = (TICKCOUNT / Math.max(getOffsetWidth() / 35, 1)) + 1;
super.onResize();
}
}
@@ -1,246 +1,247 @@
package com.sap.sse.gwt.client.controls.slider;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* This calculator emulates the way the HighCharts charting library is computing the chart ticks
* in case of a x-axis based on time values.
*/
public class TimeTicksCalculator {
public TimeTicksCalculator() {
}
/**
* Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and
* name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks
* now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over
* again for each segment having the same interval.
*/
public NormalizedInterval normalizeTimeTickInterval(long tickInterval) {
TimeUnits[] units = TimeUnits.values();
TimeUnits unit = units[units.length - 1]; // default unit is years
long interval = unit.unitInMs;
int[] multiples = unit.allowedMultiples;
int count;
// loop through the units to find the one that best fits the tickInterval
for (int i = 0; i < units.length; i++) {
unit = units[i];
interval = unit.unitInMs;
multiples = unit.allowedMultiples;
if (i + 1 < units.length) {
// lessThan is in the middle between the highest multiple and the next unit.
long lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2;
// break and keep the current unit
if (tickInterval <= lessThan) {
break;
}
}
}
// prevent 2.5 years intervals, though 25, 250 etc. are allowed
if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) {
multiples = new int[] { 1, 2, 5 };
}
// get the count
count = (int) normalizeTickInterval(tickInterval / interval, multiples, 1);
return new NormalizedInterval(unit.name(), interval, count);
}
/**
* Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
*
* @param {Number} interval
* @param {Array} multiples
* @param {Number} magnitude
* @param {Object} options
*/
public long normalizeTickInterval(long interval, int[] multiples, int magnitude) {
long normalized;
// round to a tenfold of 1, 2, 2.5 or 5
normalized = interval / magnitude;
// normalize the interval to the nearest multiple
for (int i = 0; i < multiples.length; i++) {
interval = multiples[i];
int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i];
if (normalized <= (multiples[i] + addedValue) / 2) {
break;
}
}
// multiply back to the correct magnitude
interval *= magnitude;
return interval;
}
private long makeTime(int year, int month) {
return makeTime(year, month, 1, 0, 0, 0);
}
private long makeTime(int year, int month, int date) {
return makeTime(year, month, date, 0, 0, 0);
}
@SuppressWarnings("deprecation")
private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) {
return new Date(year, month, date, hours, minutes, seconds).getTime();
}
/**
* Set the tick positions to a time unit that makes sense, for example on the first of each month or on every
* Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime
* axis.
*
* @param {Object} normalizedInterval The interval in axis values (ms) and the count
* @param {Number} min The minimum in axis values
* @param {Number} max The maximum in axis values
* @param {Number} startOfWeek
*/
@SuppressWarnings("deprecation")
public List<TickPosition> calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max,
long startOfWeek) {
List<TickPosition> tickPositions = new ArrayList<TickPosition>();
int i;
boolean useUTC = true;
int minYear = 0; // used in months and years as a basis for Date.UTC()
Date minDate = new Date(min);
long interval = normalizedInterval.unitRange;
int count = (int) normalizedInterval.count;
if (interval >= TimeUnits.SECOND.unitInMs) { // second
minDate.setTime(min - (min % 1000));
minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count
* Math.round(minDate.getSeconds() / count));
}
if (interval >= TimeUnits.MINUTE.unitInMs) { // minute
minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count
* Math.round(minDate.getMinutes() / count));
}
if (interval >= TimeUnits.HOUR.unitInMs) { // hour
minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count));
}
if (interval >= TimeUnits.DAY.unitInMs) { // day
minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count));
}
if (interval >= TimeUnits.MONTH.unitInMs) { // month
minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1);
minYear = minDate.getYear() + 1900;
}
if (interval >= TimeUnits.YEAR.unitInMs) { // year
minYear -= minYear % count;
minDate.setYear(minYear - 1900);
}
// week is a special case that runs outside the hierarchy
if (interval == TimeUnits.WEEK.unitInMs) {
// get start of current week, independent of count
long value = minDate.getDate() - minDate.getDay() + startOfWeek;
minDate.setDate((int) value);
}
// get tick positions
i = 1;
minYear = minDate.getYear() + 1900;
int minMonth = minDate.getMonth() + 1;
int minDateDate = minDate.getDate();
long time = minDate.getTime();
// iterate and add tick positions at appropriate values
while (time < max) {
if(time >= min)
tickPositions.add(new TickPosition(time));
// if the interval is years, use Date.UTC to increase years
if (interval == TimeUnits.YEAR.unitInMs) {
time = makeTime(minYear + i * count, 0);
// if the interval is months, use Date.UTC to increase months
} else if (interval == TimeUnits.MONTH.unitInMs) {
time = makeTime(minYear, minMonth + i * count);
// if we're using global time, the interval is not fixed as it jumps
// one hour at the DST crossover
} else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) {
time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7));
// else, the interval is fixed and we use simple addition
} else {
time += interval * count;
}
i++;
}
// push the last time
if(time <= max)
tickPositions.add(new TickPosition(time));
return tickPositions;
}
public class NormalizedInterval {
public String unitName;
public long unitRange;
public int count;
public NormalizedInterval(String unitName, long unitRange, int count) {
this.unitName = unitName;
this.unitRange = unitRange;
this.count = count;
}
@Override
public String toString() {
return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]";
}
}
public class TickPosition {
private Date position;
public TickPosition(long time) {
this.position = new Date(time);
}
public Date getPosition() {
return position;
}
}
enum TimeUnits {
MILLISECOND(1, new int[] { 1, 2, 5, 10, 20, 25, 50, 100, 200, 500 }),
SECOND(1000, new int[] { 1, 2, 5, 10, 15, 30 }),
MINUTE(60 * 1000, new int[] { 1, 2, 5, 10, 15, 30 }),
HOUR(60 * 60 * 1000, new int[] { 1, 2, 3, 4, 6, 8, 12 }),
DAY(24 * 3600000l, new int[] { 1, 2 }),
WEEK(7 * 24 * 3600000l, new int[] { 1, 2 }),
MONTH(30 * 24 * 3600000l, new int[] { 1, 2, 3, 4, 6 }),
YEAR(31556952000l, new int[] {});
int[] allowedMultiples;
long unitInMs;
TimeUnits(long unitInMs, int[] allowedMultiples) {
this.unitInMs = unitInMs;
this.allowedMultiples = allowedMultiples;
}
}
}
package com.sap.sse.gwt.client.controls.slider;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* This calculator emulates the way the HighCharts charting library is computing the chart ticks
* in case of a x-axis based on time values.
*/
public class TimeTicksCalculator {
public TimeTicksCalculator() {
}
/**
* Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and
* name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks
* now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over
* again for each segment having the same interval.
*/
public NormalizedInterval normalizeTimeTickInterval(long tickInterval) {
TimeUnits[] units = TimeUnits.values();
TimeUnits unit = units[units.length - 1]; // default unit is years
long interval = unit.unitInMs;
int[] multiples = unit.allowedMultiples;
int count;
// loop through the units to find the one that best fits the tickInterval
for (int i = 0; i < units.length; i++) {
unit = units[i];
interval = unit.unitInMs;
multiples = unit.allowedMultiples;
if (i + 1 < units.length) {
// lessThan is in the middle between the highest multiple and the next unit.
double lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2;
// break and keep the current unit
if (tickInterval <= lessThan) {
break;
}
}
}
// prevent 2.5 years intervals, though 25, 250 etc. are allowed
if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) {
multiples = new int[] { 1, 2, 5 };
}
// get the count
double temp = ((double) tickInterval) / ((double) interval);
count = (int) normalizeTickInterval(temp, multiples, 1);
return new NormalizedInterval(unit.name(), interval, count);
}
/**
* Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
*
* @param {Number} interval
* @param {Array} multiples
* @param {Number} magnitude
* @param {Object} options
*/
public long normalizeTickInterval(double interval, int[] multiples, int magnitude) {
double normalized;
// round to a tenfold of 1, 2, 2.5 or 5
normalized = interval / magnitude;
// normalize the interval to the nearest multiple
for (int i = 0; i < multiples.length; i++) {
interval = multiples[i];
int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i];
if (normalized <= (multiples[i] + addedValue) / 2) {
break;
}
}
// multiply back to the correct magnitude
interval *= magnitude;
return Math.round(interval);
}
private long makeTime(int year, int month) {
return makeTime(year, month, 1, 0, 0, 0);
}
private long makeTime(int year, int month, int date) {
return makeTime(year, month, date, 0, 0, 0);
}
@SuppressWarnings("deprecation")
private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) {
return new Date(year, month, date, hours, minutes, seconds).getTime();
}
/**
* Set the tick positions to a time unit that makes sense, for example on the first of each month or on every
* Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime
* axis.
*
* @param {Object} normalizedInterval The interval in axis values (ms) and the count
* @param {Number} min The minimum in axis values
* @param {Number} max The maximum in axis values
* @param {Number} startOfWeek
*/
@SuppressWarnings("deprecation")
public List<TickPosition> calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max,
long startOfWeek) {
List<TickPosition> tickPositions = new ArrayList<TickPosition>();
int i;
boolean useUTC = true;
int minYear = 0; // used in months and years as a basis for Date.UTC()
Date minDate = new Date(min);
long interval = normalizedInterval.unitRange;
int count = (int) normalizedInterval.count;
if (interval >= TimeUnits.SECOND.unitInMs) { // second
minDate.setTime(min - (min % 1000));
minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count
* Math.round(minDate.getSeconds() / count));
}
if (interval >= TimeUnits.MINUTE.unitInMs) { // minute
minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count
* Math.round(minDate.getMinutes() / count));
}
if (interval >= TimeUnits.HOUR.unitInMs) { // hour
minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count));
}
if (interval >= TimeUnits.DAY.unitInMs) { // day
minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count));
}
if (interval >= TimeUnits.MONTH.unitInMs) { // month
minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1);
minYear = minDate.getYear() + 1900;
}
if (interval >= TimeUnits.YEAR.unitInMs) { // year
minYear -= minYear % count;
minDate.setYear(minYear - 1900);
}
// week is a special case that runs outside the hierarchy
if (interval == TimeUnits.WEEK.unitInMs) {
// get start of current week, independent of count
long value = minDate.getDate() - minDate.getDay() + startOfWeek;
minDate.setDate((int) value);
}
// get tick positions
i = 1;
minYear = minDate.getYear() + 1900;
int minMonth = minDate.getMonth() + 1;
int minDateDate = minDate.getDate();
long time = minDate.getTime();
// iterate and add tick positions at appropriate values
while (time < max) {
if(time >= min)
tickPositions.add(new TickPosition(time));
// if the interval is years, use Date.UTC to increase years
if (interval == TimeUnits.YEAR.unitInMs) {
time = makeTime(minYear + i * count, 0);
// if the interval is months, use Date.UTC to increase months
} else if (interval == TimeUnits.MONTH.unitInMs) {
time = makeTime(minYear, minMonth + i * count);
// if we're using global time, the interval is not fixed as it jumps
// one hour at the DST crossover
} else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) {
time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7));
// else, the interval is fixed and we use simple addition
} else {
time += interval * count;
}
i++;
}
// push the last time
if(time <= max)
tickPositions.add(new TickPosition(time));
return tickPositions;
}
public class NormalizedInterval {
public String unitName;
public long unitRange;
public int count;
public NormalizedInterval(String unitName, long unitRange, int count) {
this.unitName = unitName;
this.unitRange = unitRange;
this.count = count;
}
@Override
public String toString() {
return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]";
}
}
public class TickPosition {
private Date position;
public TickPosition(long time) {
this.position = new Date(time);
}
public Date getPosition() {
return position;
}
}
enum TimeUnits {
MILLISECOND(1l, 1, 2, 5, 10, 20, 25, 50, 100, 200, 500),
SECOND(1000l, 1, 2, 5, 10, 15, 30),
MINUTE(60 * 1000l, 1, 2, 5, 10, 15, 30),
HOUR(60 * 60 * 1000l, 1, 2, 3, 4, 6, 8, 12),
DAY(24 * 3600000l, 1, 2),
WEEK(7 * 24 * 3600000l, 1, 2),
MONTH(30 * 24 * 3600000l, 1, 2, 3, 4, 6),
YEAR(31556952000l);
int[] allowedMultiples;
long unitInMs;
TimeUnits(long unitInMs, int... allowedMultiples) {
this.unitInMs = unitInMs;
this.allowedMultiples = allowedMultiples;
}
}
}
@@ -169,8 +169,7 @@ body ::-webkit-scrollbar-track {
.gwt-SplitLayoutPanel-EastToggleButton-Panel {
position: absolute;
bottom: 77px;
margin-left: -35px;
bottom: -10px;
}
.gwt-SplitLayoutPanel-EastToggleButton {
@@ -181,9 +180,14 @@ body ::-webkit-scrollbar-track {
-moz-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
transform: rotate(-90deg);
-webkit-transform-origin: 0 0;
-moz-transform-origin: 0 0;
-o-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
padding-right: 25px;
padding-left: 30px;
margin-left: -15px;
border-bottom: 1px solid #cecece;
border-right: 1px solid #cecece;
border-left: 1px solid #cecece;