Merge branch 'master' into bug3212

This commit is contained in:
Axel Uhl committed 2015-12-08 14:39:44 +01:00
commit 6dd4665cfc
124 files changed
+13197 -12230

No files matched your search

@@ -94,8 +94,8 @@ public class TracTrac {
new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()),
new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()),
raceLogStore, regattaLogStore,
RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, /* simulateWithStartTimeNow */
false, /*useTracTracMarkPassings*/ false,
RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, /* offsetToStartTimeOfSimulatedRace */
null, /*useTracTracMarkPassings*/ false,
/* tracTracUsername */"", /* tracTracPassword */"", record.getRaceStatus(),
record.getRaceVisibility());
} else {
+40 -40
View File
@@ -1,40 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.common</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.common</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -21,7 +21,7 @@ public abstract class AbstractSpeedImpl implements Speed {
@Override
public Duration getDuration(Distance distance) {
return new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
return distance == null ? null : new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
}
@Override
@@ -1,430 +1,433 @@
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sse.common.Util;
public abstract class AbstractLeaderboardDTO implements Serializable {
private static final long serialVersionUID = -205106531931903527L;
public String name;
private List<RaceColumnDTO> races;
public Map<CompetitorDTO, String> competitorDisplayNames;
public Map<CompetitorDTO, LeaderboardRowDTO> rows;
public boolean hasCarriedPoints;
public int[] discardThresholds;
/**
* Set to the non-<code>null</code> regatta name if this DTO represents a <code>RegattaLeaderboard</code>.
*/
public String regattaName;
public String displayName;
public UUID defaultCourseAreaId;
public String defaultCourseAreaName;
public ScoringSchemeType scoringScheme;
public LeaderboardType type;
private Long delayToLiveInMillisForLatestRace;
public AbstractLeaderboardDTO() {
races = new ArrayList<RaceColumnDTO>();
}
public Set<BoatClassDTO> getBoatClasses() {
Set<BoatClassDTO> result = new HashSet<BoatClassDTO>();
if(rows != null) {
for (CompetitorDTO competitor : rows.keySet()) {
result.add(competitor.getBoatClass());
}
}
return result;
}
public String getDisplayName() {
return displayName;
}
public String getDisplayName(CompetitorDTO competitor) {
if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) {
return competitor.getName();
} else {
return competitorDisplayNames.get(competitor);
}
}
/**
* If the race column whose name is specified in <code>raceColumnName</code> has at least one competitor who has valid
* {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in
* the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned.
* If you specify an non null <code>preselectedRace</code> the leg count is calculated only for that race for the column
* holding that race. All other columns will be unaffected by <code>preselectedRace</code> and will return their regular
* leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because
* their fleet association may have varied across races.<p>
*
* See also bug 2604 and bug 2035.
*/
public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) {
int result = -1;
final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName);
for (LeaderboardRowDTO row : rows.values()) {
LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null
// when no race is pre-selected, always use the actual leg count;
// otherwise, if the pre-selected race is in the column identified by
// raceColumnName, use the entry only if it's in the pre-selected race;
// otherwise (a race is pre-selected but a different column's leg count
// is requested), use the regular leg count as is.
&& (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) {
result = Math.max(result, leaderboardEntryDTO.legDetails.size());
}
}
return result;
}
private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) {
RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName);
return raceColumn.containsRace(preselectedRace);
}
/**
* Tells if the <code>competitor</code> scored (and therefore presumably participated) in a medal race represented
* in this leaderboard.
*/
public boolean scoredInMedalRace(CompetitorDTO competitor) {
LeaderboardRowDTO row = rows.get(competitor);
for (RaceColumnDTO race : races) {
if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) {
return true;
}
}
return false;
}
public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) {
Double netPoints = null;
LeaderboardRowDTO row = rows.get(competitor);
if (row != null) {
LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar);
if (field != null && field.netPoints != null) {
netPoints = field.netPoints;
}
}
return netPoints;
}
public boolean raceIsTracked(String raceColumnName) {
for (RaceColumnDTO race : races) {
if (race.getRaceColumnName().equals(raceColumnName)) {
for (FleetDTO fleet : race.getFleets()) {
if (race.isTrackedRace(fleet)) {
return true;
}
}
}
}
return false;
}
public boolean raceIsMedalRace(String raceColumnName) {
return getRaceColumnByName(raceColumnName).isMedalRace();
}
/**
* If the {@link RaceColumnDTO} by the name <code>raceColumnName</code> doesn't exist yet within this leaderboard
* DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to <code>true</code>. This method
* ensures that a fleet named <code>fleetName</code> is present. If it's not present yet, it's added to the race
* column's fleet name list. The <code>trackedRaceIdentifier</code> and <code>race</code> are associated with the
* column for the fleet identified by <code>fleetName</code>.
*
* @param explicitFactor
* factor by which to multiply the race column's points for the overall score; if <code>null</code>, the
* default will be determined by whether or not the column is marked as medal race
* @param effectiveFactor
* is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting
* {@link RaceColumnDTO} represents
* @param regattaName
* must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>; specified
* separately from <code>trackedRaceIdentifier</code> because a column may belong to regatta/series
* despite not having a tracked race associated
* @param seriesName
* if <code>null</code>, this method will produce a {@link RaceColumnDTO}, otherwise a
* {@link RaceColumnInSeriesDTO}
* @param fleetDTO
* must not be null
*/
public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor,
String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace, RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race) {
assert fleetDTO != null;
RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName);
if (raceColumnDTO == null) {
raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace,
explicitFactor, regattaName, seriesName);
races.add(raceColumnDTO);
}
raceColumnDTO.setEffectiveFactor(effectiveFactor);
boolean contains = false;
for (FleetDTO fleet : raceColumnDTO.getFleets()) {
if (fleet.getName().equals(fleetDTO.getName())) {
contains = true;
break;
}
}
if (!contains) {
raceColumnDTO.addFleet(fleetDTO);
}
raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier);
raceColumnDTO.setRace(fleetDTO, race);
return raceColumnDTO;
}
public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName, String seriesName) {
final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName,
medalRace, /* explicit factor */ null, regattaName, seriesName);
races.add(raceColumn);
return raceColumn;
}
public RaceColumnDTO getRaceColumnByName(String raceColumnName) {
for (RaceColumnDTO race : races) {
if (race.getRaceColumnName().equals(raceColumnName)) {
return race;
}
}
return null;
}
public int getRaceColumnsCount() {
return races.size();
}
public int getRacesCount() {
int result = 0;
for (RaceColumnDTO race : getRaceList()) {
result += race.getFleets().size();
}
return result;
}
public int getTrackedRacesCount() {
int result = 0;
for (RaceColumnDTO raceColumn : getRaceList()) {
for (FleetDTO fleet : raceColumn.getFleets()) {
RaceDTO race = raceColumn.getRace(fleet);
if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) {
result++;
}
}
}
return result;
}
public List<RaceColumnDTO> getRaceList() {
return races;
}
public void setRaceList(List<RaceColumnDTO> raceList) {
this.races = raceList;
}
public boolean raceListContains(String raceColumnName) {
return getRaceColumnByName(raceColumnName) != null;
}
public void moveRaceUp(String raceColumnName) {
RaceColumnDTO race = getRaceColumnByName(raceColumnName);
int index = races.indexOf(race);
index--;
if (index >= 0) {
races.remove(index + 1);
races.add(index, race);
}
}
public void moveRaceDown(String raceColumnName) {
RaceColumnDTO race = getRaceColumnByName(raceColumnName);
int index = races.indexOf(race);
if (index != -1) {
index++;
if (index < races.size()) {
races.remove(index - 1);
races.add(index, race);
}
}
}
public void setIsMedalRace(String raceColumnName, boolean medalRace) {
getRaceColumnByName(raceColumnName).setMedalRace(medalRace);
}
/**
* @return The earliest start date of the races, or <code>null</code> if no start dates of the races are available.
*/
public Date getStartDate() {
Date leaderboardStart = null;
for (RaceColumnDTO race : getRaceList()) {
for (FleetDTO fleet: race.getFleets()) {
Date raceStart = race.getStartDate(fleet);
if (raceStart != null) {
if (leaderboardStart == null) {
leaderboardStart = new Date();
} else {
leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart;
}
}
}
}
return leaderboardStart;
}
/**
* Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is
* available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.<br />
* The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO.
*
* @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or <code>null</code> if the
* {@link PlacemarkOrderDTO places} of no race are available
*/
public PlacemarkOrderDTO getPlaces() {
PlacemarkOrderDTO leaderboardPlaces = null;
for (RaceColumnDTO race : getRaceList()) {
PlacemarkOrderDTO racePlaces = race.getPlaces();
if (racePlaces != null) {
if (leaderboardPlaces == null) {
leaderboardPlaces = new PlacemarkOrderDTO();
}
leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks());
}
}
return leaderboardPlaces;
}
/**
* @return <code>true</code> if the leaderboard contains a race which is live
*/
public boolean hasLiveRace(long serverTimePointAsMillis) {
for (RaceColumnDTO race : getRaceList()) {
for (FleetDTO fleet : race.getFleets()) {
if (race.isLive(fleet, serverTimePointAsMillis)) {
return true;
}
}
}
return false;
}
public List<Util.Pair<RaceColumnDTO, FleetDTO>> getLiveRaces(long serverTimePointAsMillis) {
List<Util.Pair<RaceColumnDTO, FleetDTO>> result = new ArrayList<Util.Pair<RaceColumnDTO, FleetDTO>>();
for (RaceColumnDTO race : getRaceList()) {
for (FleetDTO fleet : race.getFleets()) {
if (race.isLive(fleet, serverTimePointAsMillis)) {
result.add(new Util.Pair<RaceColumnDTO, FleetDTO>(race, fleet));
}
}
}
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode());
result = prime * result + Arrays.hashCode(discardThresholds);
result = prime * result + (hasCarriedPoints ? 1231 : 1237);
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode());
if (races == null) {
result = prime * result;
} else {
List<String> raceNames = new ArrayList<String>();
for (RaceColumnDTO race : races) {
raceNames.add(race.getName());
}
result = prime * result + raceNames.hashCode();
}
result = prime * result + ((rows == null) ? 0 : rows.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj;
if (competitorDisplayNames == null) {
if (other.competitorDisplayNames != null)
return false;
} else if (!competitorDisplayNames.equals(other.competitorDisplayNames))
return false;
if (!Arrays.equals(discardThresholds, other.discardThresholds))
return false;
if (hasCarriedPoints != other.hasCarriedPoints)
return false;
if (type != other.type)
return false;
if (scoringScheme != other.scoringScheme)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (races == null) {
if (other.races != null)
return false;
} else {
// compare race column names only, not the contents
if (races.size() != (other.races==null?0:other.races.size())) {
return false;
}
List<String> raceColumnNames = new ArrayList<String>(races.size());
List<String> otherRaceColumnNames = new ArrayList<String>(races.size());
for (RaceColumnDTO race : races) {
raceColumnNames.add(race.getName());
}
if (other.races != null) {
for (RaceColumnDTO otherRace : other.races) {
otherRaceColumnNames.add(otherRace.getName());
}
}
if (!raceColumnNames.equals(otherRaceColumnNames))
return false;
if (rows == null) {
if (other.rows != null)
return false;
} else if (!rows.equals(other.rows))
return false;
}
return true;
}
public boolean isDisplayNameSet(CompetitorDTO competitor) {
return competitorDisplayNames.get(competitor) != null;
}
public Long getDelayToLiveInMillisForLatestRace() {
return delayToLiveInMillisForLatestRace;
}
public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) {
this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace;
}
}
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sse.common.Util;
public abstract class AbstractLeaderboardDTO implements Serializable {
private static final long serialVersionUID = -205106531931903527L;
public String name;
private List<RaceColumnDTO> races;
public Map<CompetitorDTO, String> competitorDisplayNames;
public Map<CompetitorDTO, LeaderboardRowDTO> rows;
public boolean hasCarriedPoints;
public int[] discardThresholds;
/**
* Set to the non-<code>null</code> regatta name if this DTO represents a <code>RegattaLeaderboard</code>.
*/
public String regattaName;
public String displayName;
public UUID defaultCourseAreaId;
public String defaultCourseAreaName;
public ScoringSchemeType scoringScheme;
public LeaderboardType type;
private Long delayToLiveInMillisForLatestRace;
public AbstractLeaderboardDTO() {
races = new ArrayList<RaceColumnDTO>();
}
public Set<BoatClassDTO> getBoatClasses() {
Set<BoatClassDTO> result = new HashSet<BoatClassDTO>();
if(rows != null) {
for (CompetitorDTO competitor : rows.keySet()) {
result.add(competitor.getBoatClass());
}
}
return result;
}
public String getDisplayName() {
return displayName;
}
public String getDisplayName(CompetitorDTO competitor) {
if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) {
return competitor.getName();
} else {
return competitorDisplayNames.get(competitor);
}
}
/**
* If the race column whose name is specified in <code>raceColumnName</code> has at least one competitor who has valid
* {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in
* the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned.
* If you specify an non null <code>preselectedRace</code> the leg count is calculated only for that race for the column
* holding that race. All other columns will be unaffected by <code>preselectedRace</code> and will return their regular
* leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because
* their fleet association may have varied across races.<p>
*
* See also bug 2604 and bug 2035.
*/
public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) {
int result = -1;
final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName);
for (LeaderboardRowDTO row : rows.values()) {
LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null
// when no race is pre-selected, always use the actual leg count;
// otherwise, if the pre-selected race is in the column identified by
// raceColumnName, use the entry only if it's in the pre-selected race;
// otherwise (a race is pre-selected but a different column's leg count
// is requested), use the regular leg count as is.
&& (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) {
result = Math.max(result, leaderboardEntryDTO.legDetails.size());
}
}
return result;
}
private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) {
RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName);
return raceColumn.containsRace(preselectedRace);
}
/**
* Tells if the <code>competitor</code> scored (and therefore presumably participated) in a medal race represented
* in this leaderboard.
*/
public boolean scoredInMedalRace(CompetitorDTO competitor) {
LeaderboardRowDTO row = rows.get(competitor);
for (RaceColumnDTO race : races) {
if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) {
return true;
}
}
return false;
}
public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) {
Double netPoints = null;
LeaderboardRowDTO row = rows.get(competitor);
if (row != null) {
LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar);
if (field != null && field.netPoints != null) {
netPoints = field.netPoints;
}
}
return netPoints;
}
public boolean raceIsTracked(String raceColumnName) {
for (RaceColumnDTO race : races) {
if (race.getRaceColumnName().equals(raceColumnName)) {
for (FleetDTO fleet : race.getFleets()) {
if (race.isTrackedRace(fleet)) {
return true;
}
}
}
}
return false;
}
public boolean raceIsMedalRace(String raceColumnName) {
return getRaceColumnByName(raceColumnName).isMedalRace();
}
/**
* If the {@link RaceColumnDTO} by the name <code>raceColumnName</code> doesn't exist yet within this leaderboard
* DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to <code>true</code>. This method
* ensures that a fleet named <code>fleetName</code> is present. If it's not present yet, it's added to the race
* column's fleet name list. The <code>trackedRaceIdentifier</code> and <code>race</code> are associated with the
* column for the fleet identified by <code>fleetName</code>.
*
* @param explicitFactor
* factor by which to multiply the race column's points for the overall score; if <code>null</code>, the
* default will be determined by whether or not the column is marked as medal race
* @param effectiveFactor
* is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting
* {@link RaceColumnDTO} represents
* @param regattaName
* must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>; specified
* separately from <code>trackedRaceIdentifier</code> because a column may belong to regatta/series
* despite not having a tracked race associated
* @param seriesName
* if <code>null</code>, this method will produce a {@link RaceColumnDTO}, otherwise a
* {@link RaceColumnInSeriesDTO}
* @param fleetDTO
* must not be null
*/
public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor,
String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace,
RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race, boolean isMetaLeaderboardColumn) {
assert fleetDTO != null;
RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName);
if (raceColumnDTO == null) {
raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace,
explicitFactor, regattaName, seriesName, isMetaLeaderboardColumn);
races.add(raceColumnDTO);
}
raceColumnDTO.setEffectiveFactor(effectiveFactor);
boolean contains = false;
for (FleetDTO fleet : raceColumnDTO.getFleets()) {
if (fleet.getName().equals(fleetDTO.getName())) {
contains = true;
break;
}
}
if (!contains) {
raceColumnDTO.addFleet(fleetDTO);
}
raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier);
raceColumnDTO.setRace(fleetDTO, race);
return raceColumnDTO;
}
public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName,
String seriesName, boolean isMetaLeaderboardColumn) {
final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName,
medalRace, /* explicit factor */ null, regattaName, seriesName, isMetaLeaderboardColumn);
races.add(raceColumn);
return raceColumn;
}
public RaceColumnDTO getRaceColumnByName(String raceColumnName) {
for (RaceColumnDTO race : races) {
if (race.getRaceColumnName().equals(raceColumnName)) {
return race;
}
}
return null;
}
public int getRaceColumnsCount() {
return races.size();
}
public int getRacesCount() {
int result = 0;
for (RaceColumnDTO race : getRaceList()) {
result += race.getFleets().size();
}
return result;
}
public int getTrackedRacesCount() {
int result = 0;
for (RaceColumnDTO raceColumn : getRaceList()) {
for (FleetDTO fleet : raceColumn.getFleets()) {
RaceDTO race = raceColumn.getRace(fleet);
if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) {
result++;
}
}
}
return result;
}
public List<RaceColumnDTO> getRaceList() {
return races;
}
public void setRaceList(List<RaceColumnDTO> raceList) {
this.races = raceList;
}
public boolean raceListContains(String raceColumnName) {
return getRaceColumnByName(raceColumnName) != null;
}
public void moveRaceUp(String raceColumnName) {
RaceColumnDTO race = getRaceColumnByName(raceColumnName);
int index = races.indexOf(race);
index--;
if (index >= 0) {
races.remove(index + 1);
races.add(index, race);
}
}
public void moveRaceDown(String raceColumnName) {
RaceColumnDTO race = getRaceColumnByName(raceColumnName);
int index = races.indexOf(race);
if (index != -1) {
index++;
if (index < races.size()) {
races.remove(index - 1);
races.add(index, race);
}
}
}
public void setIsMedalRace(String raceColumnName, boolean medalRace) {
getRaceColumnByName(raceColumnName).setMedalRace(medalRace);
}
/**
* @return The earliest start date of the races, or <code>null</code> if no start dates of the races are available.
*/
public Date getStartDate() {
Date leaderboardStart = null;
for (RaceColumnDTO race : getRaceList()) {
for (FleetDTO fleet: race.getFleets()) {
Date raceStart = race.getStartDate(fleet);
if (raceStart != null) {
if (leaderboardStart == null) {
leaderboardStart = new Date();
} else {
leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart;
}
}
}
}
return leaderboardStart;
}
/**
* Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is
* available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.<br />
* The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO.
*
* @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or <code>null</code> if the
* {@link PlacemarkOrderDTO places} of no race are available
*/
public PlacemarkOrderDTO getPlaces() {
PlacemarkOrderDTO leaderboardPlaces = null;
for (RaceColumnDTO race : getRaceList()) {
PlacemarkOrderDTO racePlaces = race.getPlaces();
if (racePlaces != null) {
if (leaderboardPlaces == null) {
leaderboardPlaces = new PlacemarkOrderDTO();
}
leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks());
}
}
return leaderboardPlaces;
}
/**
* @return <code>true</code> if the leaderboard contains a race which is live
*/
public boolean hasLiveRace(long serverTimePointAsMillis) {
for (RaceColumnDTO race : getRaceList()) {
for (FleetDTO fleet : race.getFleets()) {
if (race.isLive(fleet, serverTimePointAsMillis)) {
return true;
}
}
}
return false;
}
public List<Util.Pair<RaceColumnDTO, FleetDTO>> getLiveRaces(long serverTimePointAsMillis) {
List<Util.Pair<RaceColumnDTO, FleetDTO>> result = new ArrayList<Util.Pair<RaceColumnDTO, FleetDTO>>();
for (RaceColumnDTO race : getRaceList()) {
for (FleetDTO fleet : race.getFleets()) {
if (race.isLive(fleet, serverTimePointAsMillis)) {
result.add(new Util.Pair<RaceColumnDTO, FleetDTO>(race, fleet));
}
}
}
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode());
result = prime * result + Arrays.hashCode(discardThresholds);
result = prime * result + (hasCarriedPoints ? 1231 : 1237);
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode());
if (races == null) {
result = prime * result;
} else {
List<String> raceNames = new ArrayList<String>();
for (RaceColumnDTO race : races) {
raceNames.add(race.getName());
}
result = prime * result + raceNames.hashCode();
}
result = prime * result + ((rows == null) ? 0 : rows.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj;
if (competitorDisplayNames == null) {
if (other.competitorDisplayNames != null)
return false;
} else if (!competitorDisplayNames.equals(other.competitorDisplayNames))
return false;
if (!Arrays.equals(discardThresholds, other.discardThresholds))
return false;
if (hasCarriedPoints != other.hasCarriedPoints)
return false;
if (type != other.type)
return false;
if (scoringScheme != other.scoringScheme)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (races == null) {
if (other.races != null)
return false;
} else {
// compare race column names only, not the contents
if (races.size() != (other.races==null?0:other.races.size())) {
return false;
}
List<String> raceColumnNames = new ArrayList<String>(races.size());
List<String> otherRaceColumnNames = new ArrayList<String>(races.size());
for (RaceColumnDTO race : races) {
raceColumnNames.add(race.getName());
}
if (other.races != null) {
for (RaceColumnDTO otherRace : other.races) {
otherRaceColumnNames.add(otherRace.getName());
}
}
if (!raceColumnNames.equals(otherRaceColumnNames))
return false;
if (rows == null) {
if (other.rows != null)
return false;
} else if (!rows.equals(other.rows))
return false;
}
return true;
}
public boolean isDisplayNameSet(CompetitorDTO competitor) {
return competitorDisplayNames.get(competitor) != null;
}
public Long getDelayToLiveInMillisForLatestRace() {
return delayToLiveInMillisForLatestRace;
}
public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) {
this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace;
}
}
@@ -0,0 +1,112 @@
package com.sap.sailing.domain.common.dto;
import java.util.Date;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.TimingConstants;
/**
* Master data about a single race that is to be transferred to the client. Holds only timing and a bit
* of information about the tracked race, if any. See also {@link RaceDTO} for a more comprehensive set
* of data about a race to be serialized to a client.<p>
*
* @author Axel Uhl (d043530)
*
*/
public class BasicRaceDTO extends NamedDTO {
private static final long serialVersionUID = -7884808503795229609L;
public Date startOfRace;
public Date endOfRace;
public TrackedRaceDTO trackedRace;
public BasicRaceDTO() {} // for GWT serialization only
public BasicRaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace) {
super(raceIdentifier.getRaceName());
this.trackedRace = trackedRace;
}
/**
* @see {@link TrackedRace#isLive} for further explanation.
* @param serverTimePointAsMillis
* the time point (in server clock time) at which to determine whether the race is/was live
* @return <code>true</code> if <code>serverTimePointAsMillis</code> is between (inclusively) the start and end time
* point of the "live" interval as defined above.
*/
public boolean isLive(long serverTimePointAsMillis) {
final Date startOfLivePeriod;
final Date endOfLivePeriod;
if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) {
startOfLivePeriod = null;
endOfLivePeriod = null;
} else {
if (startOfRace == null) {
startOfLivePeriod = trackedRace.startOfTracking;
} else {
startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
}
if (endOfRace == null) {
if (trackedRace.timePointOfNewestEvent != null) {
endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime()
+ TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
} else {
endOfLivePeriod = null;
}
} else {
endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
}
}
// if an empty timepoint is given then take the start of the race
if (serverTimePointAsMillis == 0) {
serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
}
// whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
// when this DTO was assembled, or there were no GPS or wind data
final boolean result =
startOfLivePeriod != null &&
endOfLivePeriod != null &&
startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
serverTimePointAsMillis <= endOfLivePeriod.getTime();
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode());
result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode());
result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
BasicRaceDTO other = (BasicRaceDTO) obj;
if (endOfRace == null) {
if (other.endOfRace != null)
return false;
} else if (!endOfRace.equals(other.endOfRace))
return false;
if (startOfRace == null) {
if (other.startOfRace != null)
return false;
} else if (!startOfRace.equals(other.startOfRace))
return false;
if (trackedRace == null) {
if (other.trackedRace != null)
return false;
} else if (!trackedRace.equals(other.trackedRace))
return false;
return true;
}
}
@@ -62,7 +62,6 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab
/**
* @param uuidGenerator used to provide the {@link #id ID} for this object (see also {@link #getId()}) and for any clones produced
* from it by the {@link #clone()} operation.
* @param hasOverallDetails TODO
*/
public LeaderboardDTO(Date timePointOfLastCorrectionsValidity, String comment, ScoringSchemeType scoringScheme, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator, boolean hasOverallDetails) {
initCollections();
@@ -79,7 +78,7 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab
this.suppressedCompetitors = new ArrayList<CompetitorDTO>();
}
public LeaderboardDTO(String id) {
protected LeaderboardDTO(String id) {
initCollections();
this.id = id;
}
@@ -0,0 +1,49 @@
package com.sap.sailing.domain.common.dto;
import java.util.ArrayList;
public class MetaLeaderboardRaceColumnDTO extends RaceColumnDTO {
private static final long serialVersionUID = 6152752963316150432L;
private ArrayList<BasicRaceDTO> raceList = new ArrayList<BasicRaceDTO>();
public boolean isLive(FleetDTO fleet, long serverTimePointAsMillis) {
for (BasicRaceDTO race : raceList) {
if (race.isLive(serverTimePointAsMillis)) {
return true;
}
}
return false;
}
public void addRace(BasicRaceDTO race) {
raceList.add(race);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((raceList == null) ? 0 : raceList.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
MetaLeaderboardRaceColumnDTO other = (MetaLeaderboardRaceColumnDTO) obj;
if (raceList == null) {
if (other.raceList != null)
return false;
} else if (!raceList.equals(other.raceList))
return false;
return true;
}
}
@@ -7,15 +7,17 @@ public class RaceColumnDTOFactory {
* @param regattaName
* must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>
* @param seriesName
* when <code>null</code>, a regular {@link RaceColumnDTO} will be produced and initialized from the
* properties passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created.
* when <code>null</code>, a regular {@link RaceColumnDTO} or {@link MetaLeaderboardRaceColumnDTO} will
* be produced (depending on <code>isMetaLeaderboardColumn</code>) and initialized from the properties
* passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created.
*/
public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor, String regattaName, String seriesName) {
public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor,
String regattaName, String seriesName, boolean isMetaLeaderboardColumn) {
final RaceColumnDTO raceColumnDTO;
if (seriesName != null) {
raceColumnDTO = new RaceColumnInSeriesDTO(seriesName, regattaName);
} else {
raceColumnDTO = new RaceColumnDTO();
raceColumnDTO = isMetaLeaderboardColumn ? new MetaLeaderboardRaceColumnDTO() : new RaceColumnDTO();
}
fillRaceColumnDTO(raceColumnDTO, columnName, isMedal, explicitFactor);
return raceColumnDTO;
@@ -1,10 +1,7 @@
package com.sap.sailing.domain.common.dto;
import java.util.Date;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.TimingConstants;
/**
* Master data about a single race that is to be transferred to the client.<p>
@@ -12,7 +9,7 @@ import com.sap.sailing.domain.common.TimingConstants;
* @author Axel Uhl (d043530)
*
*/
public class RaceDTO extends NamedDTO {
public class RaceDTO extends BasicRaceDTO {
private static final long serialVersionUID = 2613189982608149975L;
public enum RaceLiveState { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE, TRACKED_BUT_NOT_SCHEDULED };
@@ -24,13 +21,10 @@ public class RaceDTO extends NamedDTO {
*/
public boolean isTracked;
public Date startOfRace;
public Date endOfRace;
public RaceStatusDTO status;
public PlacemarkOrderDTO places;
public TrackedRaceDTO trackedRace;
public TrackedRaceStatisticsDTO trackedRaceStatistics;
private String regattaName;
@@ -43,21 +37,20 @@ public class RaceDTO extends NamedDTO {
}
public RaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace, boolean isCurrentlyTracked) {
super(raceIdentifier.getRaceName());
super(raceIdentifier, trackedRace);
this.regattaName = raceIdentifier.getRegattaName();
this.trackedRace = trackedRace;
this.isTracked = isCurrentlyTracked;
}
public RaceLiveState getLiveState(long serverTimePointAsMillis) {
RaceLiveState result = RaceLiveState.NOT_TRACKED;
if(trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) {
RaceLiveState result = RaceLiveState.NOT_TRACKED;
if (trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) {
result = RaceLiveState.TRACKED;
if(isLive(serverTimePointAsMillis)) {
if (isLive(serverTimePointAsMillis)) {
result = RaceLiveState.TRACKED_AND_LIVE;
if(startOfRace == null) {
if (startOfRace == null) {
result = RaceLiveState.TRACKED_BUT_NOT_SCHEDULED;
}
}
}
}
return result;
@@ -71,64 +64,15 @@ public class RaceDTO extends NamedDTO {
return regattaName;
}
/**
* @see {@link TrackedRace#isLive} for further explanation.
* @param serverTimePointAsMillis
* the time point (in server clock time) at which to determine whether the race is/was live
* @return <code>true</code> if <code>serverTimePointAsMillis</code> is between (inclusively) the start and end time
* point of the "live" interval as defined above.
*/
public boolean isLive(long serverTimePointAsMillis) {
final Date startOfLivePeriod;
final Date endOfLivePeriod;
if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) {
startOfLivePeriod = null;
endOfLivePeriod = null;
} else {
if (startOfRace == null) {
startOfLivePeriod = trackedRace.startOfTracking;
} else {
startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
}
if (endOfRace == null) {
if (trackedRace.timePointOfNewestEvent != null) {
endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime()
+ TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
} else {
endOfLivePeriod = null;
}
} else {
endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
}
}
// if an empty timepoint is given then take the start of the race
if (serverTimePointAsMillis == 0) {
serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
}
// whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
// when this DTO was assembled, or there were no GPS or wind data
final boolean result =
startOfLivePeriod != null &&
endOfLivePeriod != null &&
startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
serverTimePointAsMillis <= endOfLivePeriod.getTime();
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode());
result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode());
result = prime * result + (isTracked ? 1231 : 1237);
result = prime * result + ((places == null) ? 0 : places.hashCode());
result = prime * result + ((regattaName == null) ? 0 : regattaName.hashCode());
result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode());
result = prime * result + ((status == null) ? 0 : status.hashCode());
result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode());
return result;
}
@@ -146,11 +90,6 @@ public class RaceDTO extends NamedDTO {
return false;
} else if (!boatClass.equals(other.boatClass))
return false;
if (endOfRace == null) {
if (other.endOfRace != null)
return false;
} else if (!endOfRace.equals(other.endOfRace))
return false;
if (isTracked != other.isTracked)
return false;
if (places == null) {
@@ -163,21 +102,11 @@ public class RaceDTO extends NamedDTO {
return false;
} else if (!regattaName.equals(other.regattaName))
return false;
if (startOfRace == null) {
if (other.startOfRace != null)
return false;
} else if (!startOfRace.equals(other.startOfRace))
return false;
if (status == null) {
if (other.status != null)
return false;
} else if (!status.equals(other.status))
return false;
if (trackedRace == null) {
if (other.trackedRace != null)
return false;
} else if (!trackedRace.equals(other.trackedRace))
return false;
return true;
}
}
@@ -1,39 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.shared.android</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.shared.android</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -60,7 +60,7 @@ public class MultipleClassesInRegattaTest {
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
@@ -71,7 +71,7 @@ public class MultipleClassesInRegattaTest {
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
@@ -82,7 +82,7 @@ public class MultipleClassesInRegattaTest {
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
@@ -93,7 +93,7 @@ public class MultipleClassesInRegattaTest {
httpAndHost
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"),
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
@@ -67,7 +67,7 @@ public class UnicodeCharactersInCompetitorNamesTest {
+ TracTracConnectionConstants.PORT_STORED), new URI(
"http://tracms.traclive.dk/update_course"),
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l,
/* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
/* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, "tracTest", "tracTest", "", "",
new DummyTrackedRegattaRegistry(), mock(RaceLogResolver.class));
@@ -44,6 +44,7 @@ import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl;
import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver;
import com.sap.sailing.domain.tractracadapter.impl.Simulator;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.data.IPosition;
@@ -95,7 +96,6 @@ public interface DomainFactory {
* Fetch a race definition previously created by a call to {@link #getOrCreateRaceDefinitionAndTrackedRace}. If no such
* race definition was created so far, the call blocks until such a definition is provided by a call to
* {@link #getOrCreateRaceDefinitionAndTrackedRace}.
* @param raceId TODO
*/
RaceDefinition getAndWaitForRaceDefinition(UUID raceId);
@@ -105,7 +105,6 @@ public interface DomainFactory {
* A new {@link com.sap.sailing.domain.base.Regatta} is created if no event by
* an equal name with a boat class with an equal name as the <code>event</code>'s
* boat class exists yet.
* @param trackedRegattaRegistry TODO
*/
com.sap.sailing.domain.base.Regatta getOrCreateDefaultRegatta(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
IRace race, TrackedRegattaRegistry trackedRegattaRegistry);
@@ -134,27 +133,30 @@ public interface DomainFactory {
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has
* to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this end time.
* @param offsetToStartTimeOfSimulatedRace
* if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
* @param windStore
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
* available but loses track of the wind, e.g., during server restarts.
* @param raceLogResolver TODO
*/
TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException;
/**
* Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)}, only that
* a predefined {@link Regatta} is used to hold the resulting races.
* @param raceLogResolver TODO
* Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)},
* only that a predefined {@link Regatta} is used to hold the resulting races.
*
* @param offsetToStartTimeOfSimulatedRace
* if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
*/
RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
@@ -262,9 +264,13 @@ public interface DomainFactory {
Util.Pair<Iterable<com.sap.sailing.domain.base.Competitor>, BoatClass> getCompetitorsAndDominantBoatClass(IRace race);
/**
* @param offsetToStartTimeOfSimulatedRace
* if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
*/
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility);
/**
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
@@ -17,6 +17,8 @@ import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.TrackerManager;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tractracadapter.impl.Simulator;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -65,12 +67,13 @@ public interface TracTracAdapter {
* created, with a single default series and a single default fleet. If a valid {@link RegattaIdentifier}
* is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used
* to add the races to. Otherwise, a default regatta as described above will be created and used.
* @param raceStatus
* @param offsetToStartTimeOfSimulatedRace
* if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
*/
RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL,
URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint trackingStartTime,
TimePoint trackingEndTime, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
long timeoutForReceivingRaceDefinitionInMilliseconds, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm,
long timeoutForReceivingRaceDefinitionInMilliseconds, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm,
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility)
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
@@ -74,6 +74,7 @@ import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sailing.domain.tractracadapter.TracTracConfiguration;
import com.sap.sailing.domain.tractracadapter.TracTracControlPoint;
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
@@ -652,27 +653,27 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
String tracTracPassword, String raceStatus, String raceVisibility,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws MalformedURLException, FileNotFoundException,
URISyntaxException, CreateModelException, SubscriberInitializationException {
return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI,
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
String tracTracPassword, String raceStatus, String raceVisibility,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
SubscriberInitializationException {
return new TracTracRaceTrackerImpl(regatta, this, paramURL, liveURI, storedURI, courseDesignUpdateURI,
startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
raceVisibility, trackedRegattaRegistry, raceLogResolver);
}
@@ -690,11 +691,11 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI,
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, String tracTracUsername, String tracTracPassword, String raceStatus,
String raceVisibility) {
return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, courseDesignUpdateURI,
startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
regattaLogStore, this, tracTracUsername, tracTracPassword, raceStatus, raceVisibility);
}
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -31,7 +32,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
private final RegattaLogStore regattaLogStore;
private final DomainFactory domainFactory;
private final long delayToLiveInMillis;
private final boolean simulateWithStartTimeNow;
private final Duration offsetToStartTimeOfSimulatedRace;
private final String tracTracUsername;
private final String tracTracPassword;
private final String raceStatus;
@@ -40,7 +41,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
public RaceTrackingConnectivityParametersImpl(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
DomainFactory domainFactory, String tracTracUsername, String tracTracPassword, String raceStatus,
String raceVisibility) {
super();
@@ -52,7 +53,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
this.endOfTracking = endOfTracking;
this.delayToLiveInMillis = delayToLiveInMillis;
this.domainFactory = domainFactory;
this.simulateWithStartTimeNow = simulateWithStartTimeNow;
this.offsetToStartTimeOfSimulatedRace = offsetToStartTimeOfSimulatedRace;
this.raceLogStore = raceLogStore;
this.regattaLogStore = regattaLogStore;
this.tracTracUsername = tracTracUsername;
@@ -67,7 +68,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
GPSFixStore gpsFixStore, RaceLogResolver raceLogResolver) throws MalformedURLException, FileNotFoundException, URISyntaxException,
CreateModelException, SubscriberInitializationException {
RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, courseDesignUpdateURI,
startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
raceVisibility, trackedRegattaRegistry, raceLogResolver);
return tracker;
@@ -77,7 +78,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
WindStore windStore, GPSFixStore gpsFixStore, RaceLogResolver raceLogResolver) throws Exception {
RaceTracker tracker = domainFactory.createRaceTracker(regatta, paramURL, liveURI, storedURI,
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm,
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm,
raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
raceVisibility, trackedRegattaRegistry, raceLogResolver);
return tracker;
@@ -4,7 +4,6 @@ import java.util.logging.Logger;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
@@ -57,13 +56,13 @@ public class RawPositionReceiver extends AbstractReceiverWithQueue<IRaceCompetit
IRace race = event.getA().getRace();
DynamicTrackedRace trackedRace = getTrackedRace(race);
if (trackedRace != null) {
GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB());
if (getSimulator() != null) {
fix = new GPSFixMovingImpl(fix.getPosition(), getSimulator().delay(
fix.getTimePoint()), fix.getSpeed());
}
final GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB());
Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor());
trackedRace.recordFix(competitor, fix);
if (getSimulator() != null) {
getSimulator().scheduleCompetitorPosition(competitor, fix);
} else {
trackedRace.recordFix(competitor, fix);
}
} else {
logger.warning("Couldn't find tracked race for race " + race.getName()
+ ". Dropping raw position event " + event);
@@ -24,7 +24,9 @@ import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class Simulator {
@@ -33,13 +35,15 @@ public class Simulator {
private DynamicTrackedRace trackedRace;
private final WindStore windStore;
private boolean stopped;
private long advanceInMillis = -1;
private Duration advanceInMillis = Duration.NULL.minus(1);
private Timer timer = new Timer("Timer for TracTrac Simulator");
private final Duration offsetToStart;
public Simulator(WindStore windStore) {
public Simulator(WindStore windStore, Duration offsetToStart) {
super();
assert windStore != null;
this.windStore = windStore;
this.offsetToStart = offsetToStart;
}
/**
@@ -69,7 +73,7 @@ public class Simulator {
/**
* This is what everybody is waiting for :-). Notifies all waiters.
*/
public synchronized void setAdvanceInMillis(long advanceInMillis) {
public synchronized void setAdvanceInMillis(Duration advanceInMillis) {
this.advanceInMillis = advanceInMillis;
notifyAll();
}
@@ -114,7 +118,7 @@ public class Simulator {
* Waits until {@link #advanceInMillis} is set to something not equal to -1 which is its initial value. Unblocked by
* {@link #setAdvanceInMillis(long)}.
*/
private synchronized long getAdvanceInMillis() {
private synchronized Duration getAdvance() {
while (!isAdvanceInMillisSet()) {
try {
wait(2000); // wait for two seconds, then re-evaluate whether there is a start time
@@ -125,6 +129,16 @@ public class Simulator {
}
return advanceInMillis;
}
private Duration getOffsetToStart() {
final Duration result;
if (offsetToStart != null) {
result = offsetToStart;
} else {
result = Duration.NULL;
}
return result;
}
/**
* Transforms <code>timed</code>'s time point according to this simulator's delay and waits roughly until this time
@@ -157,12 +171,12 @@ public class Simulator {
* Like {@link #delay}, only that it doesn't wait until <code>timePoint</code> is reached in wall time.
*/
public TimePoint advance(TimePoint timePoint) {
return new MillisecondsTimePoint(timePoint.asMillis()+getAdvanceInMillis());
return timePoint.plus(getAdvance());
}
/**
* If {@link #advanceInMillis} is already set to a non-negative value, it is left alone, and
* {@link #delay(TimePoint)} is called. Otherwise, <code>time</code> is taken to be the original start time of the
* {@link #advance(TimePoint)} is called. Otherwise, <code>time</code> is taken to be the original start time of the
* race which is then used to compute {@link #advanceInMillis} such that
* <code>time.asMillis() + advanceInMillis == System.currentTimeMillis()</code>.
*/
@@ -183,13 +197,13 @@ public class Simulator {
if (isAdvanceInMillisSet()) {
return advance(time);
} else {
setAdvanceInMillis(System.currentTimeMillis() - time.asMillis());
setAdvanceInMillis(new MillisecondsDurationImpl(MillisecondsTimePoint.now().minus(time.asMillis()).plus(getOffsetToStart()).asMillis()));
return advance(time);
}
}
private boolean isAdvanceInMillisSet() {
return advanceInMillis != -1;
return !advanceInMillis.equals(Duration.NULL.minus(1));
}
/**
@@ -238,21 +252,35 @@ public class Simulator {
// deliver an empty list now
trackedRace.updateMarkPassings(competitor, markPassings);
}
}
public void scheduleCompetitorPosition(final Competitor competitor, GPSFixMoving competitorFix) {
final RecordGPSFix<Competitor> recorder = (c, f)->trackedRace.recordFix(c, f);
scheduleFixRecording(competitor, competitorFix, recorder);
}
@FunctionalInterface
private static interface RecordGPSFix<T> {
void recordFix(T objectForFix, GPSFixMoving fix);
}
public void scheduleMarkPosition(final Mark mark, GPSFixMoving markFix) {
final TimePoint transformedTimepoint = advance(markFix.getTimePoint());
final GPSFixMoving transformedMarkFix = new GPSFixMovingImpl(markFix.getPosition(), transformedTimepoint, markFix.getSpeed());
final RecordGPSFix<Mark> recorder = (m, f)->trackedRace.recordFix(mark, f);
scheduleFixRecording(mark, markFix, recorder);
}
private <T> void scheduleFixRecording(final T object, GPSFixMoving fix, final RecordGPSFix<T> recorder) {
final TimePoint transformedTimepoint = advance(fix.getTimePoint());
final GPSFixMoving transformedMarkFix = new GPSFixMovingImpl(fix.getPosition(), transformedTimepoint, fix.getSpeed());
long waitTime = getWaitTimeInMillisUntil(transformedMarkFix.getTimePoint());
if (waitTime <= 0) {
trackedRace.recordFix(mark, transformedMarkFix);
recorder.recordFix(object, transformedMarkFix);
} else {
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
trackedRace.recordFix(mark, transformedMarkFix);
recorder.recordFix(object, transformedMarkFix);
} catch (Exception e) {
logger.throwing(Simulator.class.getName(), "scheduleMarkPosition", e);
}
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.tractracadapter.JSONService;
import com.sap.sailing.domain.tractracadapter.RaceRecord;
import com.sap.sailing.domain.tractracadapter.TracTracAdapter;
import com.sap.sailing.domain.tractracadapter.TracTracConfiguration;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -56,7 +57,7 @@ public class TracTracAdapterImpl implements TracTracAdapter {
getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
courseDesignUpdateURI,
/* startOfTracking */null,
/* endOfTracking */null, delayToLiveInMillis, /* simulateWithStartTimeNow */false, /* ignoreTracTracMarkPassings */ false,
/* endOfTracking */null, delayToLiveInMillis, /* offsetToStartTimeOfSimulatedRace */null, /* ignoreTracTracMarkPassings */ false,
raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility),
timeoutInMilliseconds);
}
@@ -65,13 +66,13 @@ public class TracTracAdapterImpl implements TracTracAdapter {
public RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL,
URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus,
String raceVisibility) throws Exception {
return trackerManager.addRace(
regattaToAddTo,
getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword,
offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword,
raceStatus, raceVisibility), timeoutInMilliseconds);
}
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.ModelLocator;
@@ -253,26 +254,26 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
*/
protected TracTracRaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI,
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
throws URISyntaxException, MalformedURLException, FileNotFoundException, CreateModelException,
SubscriberInitializationException {
this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), domainFactory, paramURL, liveURI,
storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
}
private TracTracRaceTrackerImpl(IRace tractracRace, DomainFactory domainFactory, URL paramURL, URI liveURI,
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
String tracTracPassword, String raceStatus, String raceVisibility,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException, MalformedURLException,
FileNotFoundException, SubscriberInitializationException {
this(tractracRace, null, domainFactory, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
}
@@ -284,14 +285,14 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
*/
protected TracTracRaceTrackerImpl(Regatta regatta, DomainFactory domainFactory, URL paramURL, URI liveURI,
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings, RaceLogStore raceLogStore,
long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean ignoreTracTracMarkPassings, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
String tracTracPassword, String raceStatus, String raceVisibility,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException,
MalformedURLException, FileNotFoundException, CreateModelException, SubscriberInitializationException {
this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), regatta, domainFactory, paramURL,
liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
simulateWithStartTimeNow, ignoreTracTracMarkPassings, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassings, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
}
@@ -300,15 +301,15 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
* @param regatta
* if <code>null</code>, then <code>domainFactory.getOrCreateRegatta(tractracEvent)</code> will be used
* to obtain a default regatta
* @param simulateWithStartTimeNow
* if <code>true</code>, the connector will adjust the time stamps of all events received such that the
* @param offsetToStartTimeOfSimulatedRace
* if not <code>null</code>, the connector will adjust the time stamps of all events received such that the
* first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all
* events received such that they seem to be sent in "real-time." So, more or less the time points
* events received such that they seem to be sent in "real-time" + <code>offsetToStartTimeOfSimulatedRace</code> So, more or less the time points
* attached to the events sent to the receivers will again approximate the wall time.
*/
private TracTracRaceTrackerImpl(IRace tractracRace, final Regatta regatta, DomainFactory domainFactory,
URL paramURL, URI liveURI, URI storedURI, URI tracTracUpdateURI, TimePoint startOfTracking,
TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm,
TimePoint endOfTracking, long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore,
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException, MalformedURLException,
@@ -323,8 +324,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
this.gpsFixStore = gpsFixStore;
this.domainFactory = domainFactory;
this.lastProgressPerID = new HashMap<>();
if (simulateWithStartTimeNow) {
simulator = new Simulator(windStore);
if (offsetToStartTimeOfSimulatedRace != null) {
simulator = new Simulator(windStore, offsetToStartTimeOfSimulatedRace);
// don't write the transformed wind fixes into the DB again... see also bug 1974
this.windStore = EmptyWindStore.INSTANCE;
} else {
@@ -43,4 +43,6 @@ public interface Course extends CourseBase {
void update(Iterable<Util.Pair<ControlPoint, PassingInstruction>> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException;
int getNumberOfWaypoints();
Leg getLeg(int zeroBasedIndexOfWaypoint);
}
@@ -246,6 +246,11 @@ public class CourseImpl extends NamedImpl implements Course {
return waypoints.size();
}
@Override
public Leg getLeg(int zeroBasedIndexOfWaypoint) {
return legs.get(zeroBasedIndexOfWaypoint);
}
@Override
public String toString() {
lockForRead();
@@ -1,336 +1,342 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl;
import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeListener;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Duration;
/**
* A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding
* the columns belonging to the same regatta or even boat class.<p>
*
* The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to
* all {@link RaceColumnListener}s subscribed with this leaderboard.
*
* @author Axel Uhl (D043530)
*
*/
public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl();
private static final long serialVersionUID = -5708971849158747846L;
private final List<FlexibleRaceColumn> races;
private final ScoringScheme scoringScheme;
private String name;
private transient RaceLogStore raceLogStore;
private CourseArea courseArea;
private RaceExecutionOrderProvider raceExecutionOrderProvider;
/**
* @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog}
*/
private final IsRegattaLike regattaLikeHelper;
public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
ScoringScheme scoringScheme, CourseArea courseArea) {
this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
name, resultDiscardingRule, scoringScheme, courseArea);
}
public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
ScoringScheme scoringScheme, CourseArea courseArea) {
super(resultDiscardingRule);
this.scoringScheme = scoringScheme;
if (name == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = name;
this.races = new ArrayList<>();
this.raceLogStore = raceLogStore;
this.courseArea = courseArea;
this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) {
private static final long serialVersionUID = 4082392360832548953L;
@Override
public RaceColumn getRaceColumnByName(String raceColumnName) {
return getRaceColumnByName(raceColumnName);
}
};
this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
}
/**
* Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}.
* When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an
* {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is
* assembled for this empty race log and applied to all columns.
*/
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
raceLogStore = EmptyRaceLogStore.INSTANCE;
for (RaceColumn column : getRaceColumns()) {
column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
}
}
protected Object readResolve() throws ObjectStreamException {
raceExecutionOrderProvider.triggerUpdate();
return this;
}
@Override
public String getName() {
return name;
}
/**
* @param newName must not be <code>null</code>
*/
public void setName(String newName) {
if (newName == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = newName;
}
@Override
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false);
column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as
// race column listener on the column
return column;
}
@Override
public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) {
return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true);
}
private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
FlexibleRaceColumn column = getRaceColumnByName(name);
if (column != null) {
if (logAlreadyExistingColumn) {
final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName();
logger.severe(msg);
}
} else {
column = createRaceColumn(name, medalRace);
column.addRaceColumnListener(this);
races.add(column);
column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column);
}
return column;
}
@Override
public FlexibleRaceColumn getRaceColumnByName(String columnName) {
return (FlexibleRaceColumn) super.getRaceColumnByName(columnName);
}
@Override
public Fleet getFleet(String fleetName) {
Fleet result;
if (fleetName == null) {
result = defaultFleet;
} else {
result = super.getFleet(fleetName);
}
return result;
}
@Override
public void removeRaceColumn(String columnName) {
final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName);
if (raceColumn != null) {
for (Fleet fleet : raceColumn.getFleets()) {
raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
if (raceColumn.getTrackedRace(fleet) != null) {
raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider);
}
}
races.remove(raceColumn);
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
raceColumn.removeRaceColumnListener(this);
}
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
final Iterable<RaceColumn> result;
if (races != null) {
result = Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
} else {
result = null;
}
return result;
}
protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider);
}
protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
Iterable<Fleet> theFleets;
if (fleets == null || fleets.length == 0) {
Fleet defaultfleetCasted = defaultFleet;
theFleets = Collections.singleton(defaultfleetCasted);
} else {
theFleets = Arrays.asList(fleets);
}
return theFleets;
}
@Override
public void moveRaceColumnUp(String name) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(name)) {
race = r;
}
}
if (race != null) {
int index = 0;
index = races.lastIndexOf(race);
index--;
if (index >= 0) {
races.remove(race);
races.add(index, race);
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
}
}
}
@Override
public void moveRaceColumnDown(String name) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(name)) {
race = r;
}
}
if (race != null) {
int index = 0;
index = races.lastIndexOf(race);
if (index != -1) {
index++;
if (index < races.size()) {
races.remove(race);
races.add(index, race);
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
}
}
}
}
@Override
public void updateIsMedalRace(String raceName, boolean isMedalRace) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(raceName))
race = r;
}
if (race != null) {
race.setIsMedalRace(isMedalRace);
}
}
@Override
public ScoringScheme getScoringScheme() {
return scoringScheme;
}
@Override
public CourseArea getDefaultCourseArea() {
return courseArea;
}
@Override
public void setDefaultCourseArea(CourseArea newCourseArea) {
this.courseArea = newCourseArea;
}
@Override
public IsRegattaLike getRegattaLike() {
return regattaLikeHelper;
}
@Override
public RegattaLog getRegattaLog() {
return regattaLikeHelper.getRegattaLog();
}
@Override
public RegattaLikeIdentifier getRegattaLikeIdentifier() {
return regattaLikeHelper.getRegattaLikeIdentifier();
}
@Override
public void addListener(RegattaLikeListener listener) {
regattaLikeHelper.addListener(listener);
}
@Override
public void removeListener(RegattaLikeListener listener) {
regattaLikeHelper.removeListener(listener);
}
@Override
public Double getTimeOnTimeFactor(Competitor competitor) {
return regattaLikeHelper.getTimeOnTimeFactor(competitor);
}
@Override
public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) {
return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor);
}
private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider {
private static final long serialVersionUID = 652833386555762661L;
public RaceExecutionOrderCache() {
super();
addRaceColumnListener(this);
}
@Override
protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() {
final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns());
return result;
}
}
}
package com.sap.sailing.domain.leaderboard.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl;
import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeListener;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Duration;
/**
* A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding
* the columns belonging to the same regatta or even boat class.<p>
*
* The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to
* all {@link RaceColumnListener}s subscribed with this leaderboard.
*
* @author Axel Uhl (D043530)
*
*/
public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl();
private static final long serialVersionUID = -5708971849158747846L;
private final List<FlexibleRaceColumn> races;
private final ScoringScheme scoringScheme;
private String name;
private transient RaceLogStore raceLogStore;
private CourseArea courseArea;
private RaceExecutionOrderProvider raceExecutionOrderProvider;
/**
* @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog}
*/
private final IsRegattaLike regattaLikeHelper;
public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
ScoringScheme scoringScheme, CourseArea courseArea) {
this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
name, resultDiscardingRule, scoringScheme, courseArea);
}
public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
ScoringScheme scoringScheme, CourseArea courseArea) {
super(resultDiscardingRule);
this.scoringScheme = scoringScheme;
if (name == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = name;
this.races = new ArrayList<>();
this.raceLogStore = raceLogStore;
this.courseArea = courseArea;
this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) {
private static final long serialVersionUID = 4082392360832548953L;
@Override
public RaceColumn getRaceColumnByName(String raceColumnName) {
return getRaceColumnByName(raceColumnName);
}
};
this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
}
/**
* Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}.
* When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an
* {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is
* assembled for this empty race log and applied to all columns.
*/
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
raceLogStore = EmptyRaceLogStore.INSTANCE;
for (RaceColumn column : getRaceColumns()) {
column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
}
}
protected Object readResolve() throws ObjectStreamException {
raceExecutionOrderProvider.triggerUpdate();
return this;
}
@Override
public String getName() {
return name;
}
/**
* @param newName must not be <code>null</code>
*/
public void setName(String newName) {
if (newName == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = newName;
}
@Override
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false);
column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as
// race column listener on the column
return column;
}
@Override
public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) {
return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true);
}
private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
FlexibleRaceColumn column = getRaceColumnByName(name);
if (column != null) {
if (logAlreadyExistingColumn) {
final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName();
logger.severe(msg);
}
} else {
column = createRaceColumn(name, medalRace);
column.addRaceColumnListener(this);
races.add(column);
column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column);
}
return column;
}
@Override
public FlexibleRaceColumn getRaceColumnByName(String columnName) {
return (FlexibleRaceColumn) super.getRaceColumnByName(columnName);
}
@Override
public Fleet getFleet(String fleetName) {
Fleet result;
if (fleetName == null) {
result = defaultFleet;
} else {
result = super.getFleet(fleetName);
}
return result;
}
@Override
public void removeRaceColumn(String columnName) {
final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName);
if (raceColumn != null) {
for (Fleet fleet : raceColumn.getFleets()) {
raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
if (raceColumn.getTrackedRace(fleet) != null) {
raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider);
}
}
races.remove(raceColumn);
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
raceColumn.removeRaceColumnListener(this);
}
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
final Iterable<RaceColumn> result;
if (races != null) {
result = Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
} else {
result = null;
}
return result;
}
protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider);
}
protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
Iterable<Fleet> theFleets;
if (fleets == null || fleets.length == 0) {
Fleet defaultfleetCasted = defaultFleet;
theFleets = Collections.singleton(defaultfleetCasted);
} else {
theFleets = Arrays.asList(fleets);
}
return theFleets;
}
@Override
public void moveRaceColumnUp(String name) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(name)) {
race = r;
}
}
if (race != null) {
int index = 0;
index = races.lastIndexOf(race);
index--;
if (index >= 0) {
races.remove(race);
races.add(index, race);
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
}
}
}
@Override
public void moveRaceColumnDown(String name) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(name)) {
race = r;
}
}
if (race != null) {
int index = 0;
index = races.lastIndexOf(race);
if (index != -1) {
index++;
if (index < races.size()) {
races.remove(race);
races.add(index, race);
getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
}
}
}
}
@Override
public void updateIsMedalRace(String raceName, boolean isMedalRace) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(raceName))
race = r;
}
if (race != null) {
race.setIsMedalRace(isMedalRace);
}
}
@Override
public ScoringScheme getScoringScheme() {
return scoringScheme;
}
@Override
public CourseArea getDefaultCourseArea() {
return courseArea;
}
@Override
public void setDefaultCourseArea(CourseArea newCourseArea) {
this.courseArea = newCourseArea;
}
@Override
public IsRegattaLike getRegattaLike() {
return regattaLikeHelper;
}
@Override
public RegattaLog getRegattaLog() {
return regattaLikeHelper.getRegattaLog();
}
@Override
public RegattaLikeIdentifier getRegattaLikeIdentifier() {
return regattaLikeHelper.getRegattaLikeIdentifier();
}
@Override
public void addListener(RegattaLikeListener listener) {
regattaLikeHelper.addListener(listener);
}
@Override
public void removeListener(RegattaLikeListener listener) {
regattaLikeHelper.removeListener(listener);
}
@Override
public Double getTimeOnTimeFactor(Competitor competitor) {
return regattaLikeHelper.getTimeOnTimeFactor(competitor);
}
@Override
public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) {
return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor);
}
private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider {
private static final long serialVersionUID = 652833386555762661L;
public RaceExecutionOrderCache() {
super();
addRaceColumnListener(this);
}
@Override
protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() {
final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns());
return result;
}
}
@Override
protected LeaderboardType getLeaderboardType() {
return LeaderboardType.FlexibleLeaderboard;
}
}
@@ -1,117 +1,123 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta
* leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for
* {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with
* this leaderboard.
*
* @author Axel Uhl (D043530)
*
*/
public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard {
private static final long serialVersionUID = 2370461218294770084L;
private final Regatta regatta;
public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
super(resultDiscardingRule);
this.regatta = regatta;
regatta.addRaceColumnListener(this);
}
/**
* Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name.
*/
@Override
public void setName(String newName) {
setDisplayName(newName);
}
@Override
public Regatta getRegatta() {
return regatta;
}
@Override
public String getName() {
return getRegatta().getName();
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
List<RaceColumn> result = new ArrayList<RaceColumn>();
for (Series series : getRegatta().getSeries()) {
for (RaceColumn raceColumn : series.getRaceColumns()) {
result.add(raceColumn);
}
}
return result;
}
@Override
public RaceColumnInSeries getRaceColumnByName(String columnName) {
return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName);
}
@Override
public ScoringScheme getScoringScheme() {
return regatta.getScoringScheme();
}
@Override
public CourseArea getDefaultCourseArea() {
return regatta.getDefaultCourseArea();
}
/**
* If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses
* a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according
* to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class
* implementation.
*/
@Override
public ResultDiscardingRule getResultDiscardingRule() {
if (regatta.definesSeriesDiscardThresholds()) {
return new PerSeriesResultDiscardingRuleImpl(regatta);
} else {
return super.getResultDiscardingRule();
}
}
/**
* Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this
* leaderboard's regatta which includes those belonging to {@link RaceDefinition races}
* {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's
* {@link RegattaLog}.
*/
@Override
public Iterable<Competitor> getAllCompetitors() {
return regatta.getAllCompetitors();
}
@Override
public IsRegattaLike getRegattaLike() {
return regatta;
}
}
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta
* leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for
* {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with
* this leaderboard.
*
* @author Axel Uhl (D043530)
*
*/
public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard {
private static final long serialVersionUID = 2370461218294770084L;
private final Regatta regatta;
public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
super(resultDiscardingRule);
this.regatta = regatta;
regatta.addRaceColumnListener(this);
}
/**
* Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name.
*/
@Override
public void setName(String newName) {
setDisplayName(newName);
}
@Override
public Regatta getRegatta() {
return regatta;
}
@Override
public String getName() {
return getRegatta().getName();
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
List<RaceColumn> result = new ArrayList<RaceColumn>();
for (Series series : getRegatta().getSeries()) {
for (RaceColumn raceColumn : series.getRaceColumns()) {
result.add(raceColumn);
}
}
return result;
}
@Override
public RaceColumnInSeries getRaceColumnByName(String columnName) {
return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName);
}
@Override
public ScoringScheme getScoringScheme() {
return regatta.getScoringScheme();
}
@Override
public CourseArea getDefaultCourseArea() {
return regatta.getDefaultCourseArea();
}
/**
* If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses
* a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according
* to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class
* implementation.
*/
@Override
public ResultDiscardingRule getResultDiscardingRule() {
if (regatta.definesSeriesDiscardThresholds()) {
return new PerSeriesResultDiscardingRuleImpl(regatta);
} else {
return super.getResultDiscardingRule();
}
}
/**
* Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this
* leaderboard's regatta which includes those belonging to {@link RaceDefinition races}
* {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's
* {@link RegattaLog}.
*/
@Override
public Iterable<Competitor> getAllCompetitors() {
return regatta.getAllCompetitors();
}
@Override
public IsRegattaLike getRegattaLike() {
return regatta;
}
@Override
protected LeaderboardType getLeaderboardType() {
return LeaderboardType.RegattaLeaderboard;
}
}
@@ -1,64 +1,70 @@
package com.sap.sailing.domain.leaderboard.meta;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
private static final long serialVersionUID = 789076326144062944L;
private final List<Leaderboard> leaderboards;
private final NamedReentrantReadWriteLock leaderboardsLock;
public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme,
ThresholdBasedResultDiscardingRule resultDiscardingRule) {
super(name, scoringScheme, resultDiscardingRule);
leaderboards = new ArrayList<Leaderboard>();
leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(),
/* fair */ false);
}
@Override
public Iterable<Leaderboard> getLeaderboards() {
LockUtil.lockForRead(leaderboardsLock);
try {
return new ArrayList<Leaderboard>(leaderboards);
} finally {
LockUtil.unlockAfterRead(leaderboardsLock);
}
}
public void addLeaderboard(Leaderboard leaderboard) {
LockUtil.lockForWrite(leaderboardsLock);
try {
leaderboards.add(leaderboard);
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
} finally {
LockUtil.unlockAfterWrite(leaderboardsLock);
}
}
public void removeLeaderboard(Leaderboard leaderboard) {
LockUtil.lockForWrite(leaderboardsLock);
try {
leaderboards.remove(leaderboard);
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
unregisterScoreCorrectionChangeForwarder(leaderboard);
} finally {
LockUtil.unlockAfterWrite(leaderboardsLock);
}
}
@Override
public CourseArea getDefaultCourseArea() {
return null;
}
}
package com.sap.sailing.domain.leaderboard.meta;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
private static final long serialVersionUID = 789076326144062944L;
private final List<Leaderboard> leaderboards;
private final NamedReentrantReadWriteLock leaderboardsLock;
public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme,
ThresholdBasedResultDiscardingRule resultDiscardingRule) {
super(name, scoringScheme, resultDiscardingRule);
leaderboards = new ArrayList<Leaderboard>();
leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(),
/* fair */ false);
}
@Override
public Iterable<Leaderboard> getLeaderboards() {
LockUtil.lockForRead(leaderboardsLock);
try {
return new ArrayList<Leaderboard>(leaderboards);
} finally {
LockUtil.unlockAfterRead(leaderboardsLock);
}
}
public void addLeaderboard(Leaderboard leaderboard) {
LockUtil.lockForWrite(leaderboardsLock);
try {
leaderboards.add(leaderboard);
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
} finally {
LockUtil.unlockAfterWrite(leaderboardsLock);
}
}
public void removeLeaderboard(Leaderboard leaderboard) {
LockUtil.lockForWrite(leaderboardsLock);
try {
leaderboards.remove(leaderboard);
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
unregisterScoreCorrectionChangeForwarder(leaderboard);
} finally {
LockUtil.unlockAfterWrite(leaderboardsLock);
}
}
@Override
public CourseArea getDefaultCourseArea() {
return null;
}
@Override
protected LeaderboardType getLeaderboardType() {
return LeaderboardType.FlexibleMetaLeaderboard;
}
}
@@ -1,92 +1,98 @@
package com.sap.sailing.domain.leaderboard.meta;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.impl.TrackedRaces;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this
* meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This
* way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its
* {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the
* competitors attached to this meta leaderboard.
* <p>
*
* After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard
* group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to
* ensure that score corrections are propagated as desired. This is because score correction listeners are "transient"
* and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()}
* during <code>readObject</code> or <code>readResolve</code> is not possible because the object graph hasn't been fully
* initialized yet when those methods are called. Hence, the structures for registering the score correction listeners
* are not yet in place at that time.
*
* @author Axel Uhl (d043530)
*
*/
public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener {
private static final long serialVersionUID = 8087872002175528002L;
private final LeaderboardGroup leaderboardGroup;
public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme,
ThresholdBasedResultDiscardingRule resultDiscardingRule) {
super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule);
this.leaderboardGroup = leaderboardGroup;
leaderboardGroup.addLeaderboardGroupListener(this);
registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
}
public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() {
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
}
}
@Override
public Iterable<Leaderboard> getLeaderboards() {
return leaderboardGroup.getLeaderboards();
}
@Override
public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) {
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
if (trackedRace != null) {
getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
}
}
}
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
}
@Override
public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) {
leaderboard.removeRaceColumnListener(this);
unregisterScoreCorrectionChangeForwarder(leaderboard);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
if (trackedRace != null) {
getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
}
}
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
}
@Override
public CourseArea getDefaultCourseArea() {
return null;
}
}
package com.sap.sailing.domain.leaderboard.meta;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.impl.TrackedRaces;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this
* meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This
* way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its
* {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the
* competitors attached to this meta leaderboard.
* <p>
*
* After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard
* group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to
* ensure that score corrections are propagated as desired. This is because score correction listeners are "transient"
* and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()}
* during <code>readObject</code> or <code>readResolve</code> is not possible because the object graph hasn't been fully
* initialized yet when those methods are called. Hence, the structures for registering the score correction listeners
* are not yet in place at that time.
*
* @author Axel Uhl (d043530)
*
*/
public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener {
private static final long serialVersionUID = 8087872002175528002L;
private final LeaderboardGroup leaderboardGroup;
public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme,
ThresholdBasedResultDiscardingRule resultDiscardingRule) {
super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule);
this.leaderboardGroup = leaderboardGroup;
leaderboardGroup.addLeaderboardGroupListener(this);
registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
}
public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() {
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
}
}
@Override
public Iterable<Leaderboard> getLeaderboards() {
return leaderboardGroup.getLeaderboards();
}
@Override
public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) {
registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
if (trackedRace != null) {
getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
}
}
}
getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
}
@Override
public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) {
leaderboard.removeRaceColumnListener(this);
unregisterScoreCorrectionChangeForwarder(leaderboard);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
if (trackedRace != null) {
getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
}
}
getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
}
@Override
public CourseArea getDefaultCourseArea() {
return null;
}
@Override
protected LeaderboardType getLeaderboardType() {
return LeaderboardType.RegattaMetaLeaderboard;
}
}
@@ -13,7 +13,6 @@ import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@@ -154,6 +153,7 @@ import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.IdentityWrapper;
import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.AbstractCacheUpdater;
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
@@ -183,7 +183,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* By default, all wind sources are used, none are excluded. However, e.g., for performance reasons, particular wind
* sources such as the track-based estimation wind source, may be excluded by adding them to this set.
*/
private final Set<WindSource> windSourcesToExclude;
private final ConcurrentHashMap<WindSource, TrackedRaceImpl> windSourcesToExclude;
/**
* Keeps the oldest timestamp that is fed into this tracked race, either from a boat fix, a mark fix, a race
@@ -269,7 +269,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
private transient SmartFutureCache<Competitor, com.sap.sse.common.Util.Triple<TimePoint, TimePoint, List<Maneuver>>, EmptyUpdateInterval> maneuverCache;
private transient Map<TimePoint, Future<Wind>> directionFromStartToNextMarkCache;
private transient ConcurrentHashMap<TimePoint, Future<Wind>> directionFromStartToNextMarkCache;
protected final MarkPassingCalculator markPassingCalculator;
@@ -354,7 +354,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
private final NamedReentrantReadWriteLock loadingFromGPSFixStoreLock;
private final Map<Iterable<MarkPassing>, NamedReentrantReadWriteLock> locksForMarkPassings;
private final ConcurrentHashMap<IdentityWrapper<Iterable<MarkPassing>>, NamedReentrantReadWriteLock> locksForMarkPassings;
/**
* Caches wind requests for a few seconds to accelerate access in live mode
@@ -412,7 +412,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
rankingMetric = rankingMetricConstructor.apply(this);
raceStates = new WeakHashMap<>();
shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
locksForMarkPassings = new IdentityHashMap<>();
locksForMarkPassings = new ConcurrentHashMap<IdentityWrapper<Iterable<MarkPassing>>, NamedReentrantReadWriteLock>();
attachedRaceLogs = new ConcurrentHashMap<>();
attachedRegattaLogs = new ConcurrentHashMap<>();
attachedRaceExecutionOrderProviders = new ConcurrentHashMap<>();
@@ -424,8 +424,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
+ race.getName(), /* fair */ false);
this.cacheInvalidationTimerLock = new Object();
this.updateCount = 0;
this.windSourcesToExclude = new HashSet<WindSource>();
this.directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
this.windSourcesToExclude = new ConcurrentHashMap<>();
this.directionFromStartToNextMarkCache = new ConcurrentHashMap<TimePoint, Future<Wind>>();
this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed;
this.delayToLiveInMillis = delayToLiveInMillis;
this.startToNextMarkCacheInvalidationListeners = new ConcurrentHashMap<Mark, TrackedRaceImpl.StartToNextMarkCacheInvalidationListener>();
@@ -624,7 +624,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
gpsFixStore = EmptyGPSFixStore.INSTANCE;
competitorRankings = createCompetitorRankingsCache();
competitorRankingsLocks = createCompetitorRankingsLockMap();
directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
directionFromStartToNextMarkCache = new ConcurrentHashMap<>();
crossTrackErrorCache = new CrossTrackErrorCache(this);
crossTrackErrorCache.invalidate();
maneuverCache = createManeuverCache();
@@ -1146,11 +1146,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(startOfLeg);
if (indexOfWaypoint == -1) {
throw new IllegalArgumentException("Waypoint " + startOfLeg + " not found in " + getRace().getCourse());
} else if (indexOfWaypoint == Util.size(getRace().getCourse().getWaypoints()) - 1) {
} else if (indexOfWaypoint == getRace().getCourse().getNumberOfWaypoints() - 1) {
throw new IllegalArgumentException("Waypoint " + startOfLeg + " isn't start of any leg in "
+ getRace().getCourse());
}
return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint));
return trackedLegs.get(race.getCourse().getLeg(indexOfWaypoint));
} finally {
getRace().getCourse().unlockAfterRead();
}
@@ -1679,24 +1679,20 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
@Override
public Set<WindSource> getWindSourcesToExclude() {
synchronized (windSourcesToExclude) {
return Collections.unmodifiableSet(windSourcesToExclude);
}
return Collections.unmodifiableSet(windSourcesToExclude.keySet());
}
@Override
public void setWindSourcesToExclude(Iterable<? extends WindSource> windSourcesToExclude) {
Set<WindSource> old = new HashSet<>(this.windSourcesToExclude);
synchronized (this.windSourcesToExclude) {
LockUtil.lockForRead(getSerializationLock());
try {
this.windSourcesToExclude.clear();
for (WindSource windSourceToExclude : windSourcesToExclude) {
this.windSourcesToExclude.add(windSourceToExclude);
}
} finally {
LockUtil.unlockAfterRead(getSerializationLock());
Set<WindSource> old = new HashSet<>(getWindSourcesToExclude());
LockUtil.lockForRead(getSerializationLock());
try {
this.windSourcesToExclude.clear();
for (WindSource windSourceToExclude : windSourcesToExclude) {
this.windSourcesToExclude.put(windSourceToExclude, this);
}
} finally {
LockUtil.unlockAfterRead(getSerializationLock());
}
if (!old.equals(this.windSourcesToExclude)) {
clearAllCachesExceptManeuvers();
@@ -1742,30 +1738,33 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public Wind getDirectionFromStartToNextMark(final TimePoint at) {
Future<Wind> future;
FutureTask<Wind> newFuture = null;
synchronized (directionFromStartToNextMarkCache) {
future = directionFromStartToNextMarkCache.get(at);
if (future == null) {
newFuture = new FutureTask<Wind>(new Callable<Wind>() {
@Override
public Wind call() {
Wind result;
Leg firstLeg = getRace().getCourse().getFirstLeg();
if (firstLeg != null) {
Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at);
Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at);
if (firstLegStart != null && firstLegEnd != null) {
result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0,
firstLegEnd.getBearingGreatCircle(firstLegStart)));
future = directionFromStartToNextMarkCache.get(at);
if (future == null) {
synchronized (directionFromStartToNextMarkCache) {
future = directionFromStartToNextMarkCache.get(at);
if (future == null) {
newFuture = new FutureTask<Wind>(new Callable<Wind>() {
@Override
public Wind call() {
Wind result;
Leg firstLeg = getRace().getCourse().getFirstLeg();
if (firstLeg != null) {
Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at);
Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at);
if (firstLegStart != null && firstLegEnd != null) {
result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0,
firstLegEnd.getBearingGreatCircle(firstLegStart)));
} else {
result = null;
}
} else {
result = null;
}
} else {
result = null;
return result;
}
return result;
}
});
directionFromStartToNextMarkCache.put(at, newFuture);
});
directionFromStartToNextMarkCache.put(at, newFuture);
}
}
}
if (newFuture != null) {
@@ -2062,15 +2061,19 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
protected NamedReentrantReadWriteLock getMarkPassingsLock(Iterable<MarkPassing> markPassings) {
synchronized (locksForMarkPassings) {
NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassings);
if (lock == null) {
lock = new NamedReentrantReadWriteLock("mark passings lock for tracked race " + getRace().getName(), /* fair */
false);
locksForMarkPassings.put(markPassings, lock);
final IdentityWrapper<Iterable<MarkPassing>> markPassingsIdentity = new IdentityWrapper<>(markPassings);
NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassingsIdentity);
if (lock == null) {
synchronized (locksForMarkPassings) {
lock = locksForMarkPassings.get(markPassingsIdentity);
if (lock == null) {
lock = new NamedReentrantReadWriteLock(
"mark passings lock for tracked race " + getRace().getName(), /* fair */false);
locksForMarkPassings.put(markPassingsIdentity, lock);
}
}
return lock;
}
return lock;
}
private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(int zeroBasedIndex,
@@ -95,7 +95,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase {
+ TracTracConnectionConstants.PORT_TUNNEL_STORED : "tcp://" + TracTracConnectionConstants.HOST_NAME
+ ":" + TracTracConnectionConstants.PORT_STORED,
COURSE_DESIGN_UPDATE_URI,
false, false, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback<Void>() {
false, false, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
@@ -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);
}
@@ -1,162 +1,162 @@
package com.sap.sailing.gwt.ui.test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.ScoringSchemeType;
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.gwt.ui.server.SailingServiceImpl;
public class TestColumnSwapping {
private LeaderboardDTO lb = null;
private static final String LEADERBOARDNAME = "test";
private static final String DEFAULT_FLEET_NAME = "Default";
private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null);
private SailingServiceImpl service;
private LeaderboardDTO leaderboardOriginalDTO;
private LeaderboardDTO leaderboardDTO;
private Collection<String> leglist;
private Date leaderboardCreationDate;
private SailingServiceImpl sailingService = null;
// Test-Data
private final String TEST_LEADERBOARD_NAME = "test_board";
private final String[] races = { "1", "2", "3" };
private final boolean[] isMedalRace = { false, false, true };
@Before
public void prepareColumnSwapping() {
service = new SailingServiceImplMock();
int[] disc = { 5, 8, 9, 0, 7, 5, 43 };
service.removeLeaderboard(LEADERBOARDNAME);
service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null);
service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true);
service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true);
service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true);
leglist = new ArrayList<String>();
leglist.add("Race1");
leglist.add("Race2");
leglist.add("Race3");
leaderboardCreationDate = new Date();
try {
// get Leaderboard with name and current date
leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
@Override
public String generateRandomUUID() {
return UUID.randomUUID().toString();
}
}, /* hasOverallDetails */ false);
leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testColumnSwapping() {
testColumnSwappingFabian();
testLeaderBoardDTOMethods();
testSailingService();
}
@Test
public void testSailingService() {
sailingService = new SailingServiceImplMock();
assertNotNull("Sailingservice != NULL", sailingService);
int td[] = { 5, 8 };
sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME);
sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null);
for (int i = 0; i < races.length; i++)
sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
try {
for (int i = 0; i < races.length; i++)
sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null,
/* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null);
} catch (Exception e) {
// e.printStackTrace();
fail(e.getLocalizedMessage());
}
assertNotNull("LeaderboardDTO != NULL", lb);
// asserted data
String[] raceNames = new String[] { "3", "2", "1" };
boolean[] medalRace = { true, false, false };
for (int i = 0; i < lb.getRaceList().size(); i++) {
assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName());
assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.",
lb.raceIsMedalRace(races[i]) == medalRace[i]);
}
}
@Test
public void testLeaderBoardDTOMethods() {
lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
@Override
public String generateRandomUUID() {
return UUID.randomUUID().toString();
}
}, /* hasOverallDetails */ false);
assertNotNull("Leaderboard != NULL", lb);
lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null);
lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null);
lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
lb.moveRaceDown("1");
String[] s = new String[] { "2", "1", "3" };
for (int i = 0; i < lb.getRaceList().size(); i++) {
assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName());
}
}
@Test
public void testColumnSwappingFabian() {
service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3");
try {
leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */
true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// check if leaderboardDTO an dleaderboardOriginalDTO same
List<RaceColumnDTO> leaderboardList = leaderboardDTO.getRaceList();
List<RaceColumnDTO> leaderboardOriginalList = leaderboardOriginalDTO.getRaceList();
// ??????? assert races in list
assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray());
for (RaceColumnDTO raceDTO : leaderboardOriginalList) {
assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName());
assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName());
}
}
}
package com.sap.sailing.gwt.ui.test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.ScoringSchemeType;
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.gwt.ui.server.SailingServiceImpl;
public class TestColumnSwapping {
private LeaderboardDTO lb = null;
private static final String LEADERBOARDNAME = "test";
private static final String DEFAULT_FLEET_NAME = "Default";
private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null);
private SailingServiceImpl service;
private LeaderboardDTO leaderboardOriginalDTO;
private LeaderboardDTO leaderboardDTO;
private Collection<String> leglist;
private Date leaderboardCreationDate;
private SailingServiceImpl sailingService = null;
// Test-Data
private final String TEST_LEADERBOARD_NAME = "test_board";
private final String[] races = { "1", "2", "3" };
private final boolean[] isMedalRace = { false, false, true };
@Before
public void prepareColumnSwapping() {
service = new SailingServiceImplMock();
int[] disc = { 5, 8, 9, 0, 7, 5, 43 };
service.removeLeaderboard(LEADERBOARDNAME);
service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null);
service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true);
service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true);
service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true);
leglist = new ArrayList<String>();
leglist.add("Race1");
leglist.add("Race2");
leglist.add("Race3");
leaderboardCreationDate = new Date();
try {
// get Leaderboard with name and current date
leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
@Override
public String generateRandomUUID() {
return UUID.randomUUID().toString();
}
}, /* hasOverallDetails */ false);
leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testColumnSwapping() {
testColumnSwappingFabian();
testLeaderBoardDTOMethods();
testSailingService();
}
@Test
public void testSailingService() {
sailingService = new SailingServiceImplMock();
assertNotNull("Sailingservice != NULL", sailingService);
int td[] = { 5, 8 };
sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME);
sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null);
for (int i = 0; i < races.length; i++)
sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
try {
for (int i = 0; i < races.length; i++)
sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null,
/* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null);
} catch (Exception e) {
// e.printStackTrace();
fail(e.getLocalizedMessage());
}
assertNotNull("LeaderboardDTO != NULL", lb);
// asserted data
String[] raceNames = new String[] { "3", "2", "1" };
boolean[] medalRace = { true, false, false };
for (int i = 0; i < lb.getRaceList().size(); i++) {
assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName());
assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.",
lb.raceIsMedalRace(races[i]) == medalRace[i]);
}
}
@Test
public void testLeaderBoardDTOMethods() {
lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
@Override
public String generateRandomUUID() {
return UUID.randomUUID().toString();
}
}, /* hasOverallDetails */ false);
assertNotNull("Leaderboard != NULL", lb);
lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false);
lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false);
lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
lb.moveRaceDown("1");
String[] s = new String[] { "2", "1", "3" };
for (int i = 0; i < lb.getRaceList().size(); i++) {
assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName());
}
}
@Test
public void testColumnSwappingFabian() {
service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3");
try {
leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */
true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// check if leaderboardDTO an dleaderboardOriginalDTO same
List<RaceColumnDTO> leaderboardList = leaderboardDTO.getRaceList();
List<RaceColumnDTO> leaderboardOriginalList = leaderboardOriginalDTO.getRaceList();
// ??????? assert races in list
assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray());
for (RaceColumnDTO raceDTO : leaderboardOriginalList) {
assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName());
assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName());
}
}
}
@@ -3,7 +3,9 @@ encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.prop
encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ru.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/AbstractResultsPresenter.java=utf-8
+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;
}
@@ -17,7 +17,7 @@
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.home.Home"/>
<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
@@ -73,7 +73,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m"/>
</launchConfiguration>
@@ -16,7 +16,7 @@
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.home.Home"/>
<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
@@ -70,7 +70,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
</launchConfiguration>
@@ -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;
}
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.home.desktop.partials.old;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.ui.FlowPanel;
@@ -8,6 +9,9 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.shared.partials.fullscreen.FullscreenContainer;
import com.sap.sse.common.Color;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicatorResources;
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
public abstract class AbstractLeaderboardFullscreenViewer<T extends Widget> extends FullscreenContainer<T> implements
LeaderboardDelegate<T> {
@@ -15,14 +19,18 @@ public abstract class AbstractLeaderboardFullscreenViewer<T extends Widget> exte
private final Image autoRefreshControl = new Image("images/home/reload.svg");
private final Image settingsControl = new Image("images/home/settings.svg");
private final static BusyIndicatorResources RESOURCES = GWT.create(BusyIndicatorResources.class);
private final Label lastScoringUpdateTime = new Label();
private final Label lastScoringUpdateText = new Label();
protected final Label lastScoringComment = new Label();
protected final Label scoringScheme = new Label();
protected final BusyIndicator busyIndicator = new SimpleBusyIndicator(false, 0.9f, RESOURCES.busyIndicatorCircleInverted());
public AbstractLeaderboardFullscreenViewer() {
showLogo();
showBorder();
addToolbarBusyIndicator(busyIndicator);
addToolbarInfo(createPanel(lastScoringUpdateText, lastScoringUpdateTime));
addToolbarAction(autoRefreshControl);
addToolbarAction(settingsControl);
@@ -88,4 +96,13 @@ public abstract class AbstractLeaderboardFullscreenViewer<T extends Widget> exte
return scoringScheme.getElement();
}
@Override
public Element getBusyIndicatorElement() {
return busyIndicator.getElement();
}
@Override
public void setBusyState(boolean isBusy) {
busyIndicator.setBusy(isBusy);
}
}
@@ -106,6 +106,11 @@
background-color: #f0ab00;
}
.regattaleaderboard_meta_busyindicator {
display: inline-block;
margin: 0 0.666666666666667em;
}
.regattaleaderboard_meta_liverace {
color: red;
font-weight: 600;
@@ -30,6 +30,7 @@ public interface EventRegattaLeaderboardResources extends ClientBundle {
String regattaleaderboard_meta_update_type();
String regattaleaderboard_meta_scoring_timestamp();
String regattaleaderboard_meta_update_timestamp();
String regattaleaderboard_meta_busyindicator();
String regattaleaderboard_meta_scoring_text();
String regattaleaderboard_meta_update_text();
String regattaleaderboard_meta_reload();
@@ -10,6 +10,8 @@ public interface LeaderboardDelegate<T extends Widget> {
void setLeaderboardPanel(T leaderboardPanel);
void setBusyState(boolean isBusy);
Widget getAutoRefreshControl();
Widget getSettingsControl();
@@ -24,4 +26,5 @@ public interface LeaderboardDelegate<T extends Widget> {
Element getScoringSchemeElement();
Element getBusyIndicatorElement();
}
@@ -31,12 +31,14 @@ import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.BusyStateChangeListener;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
public class OldLeaderboard extends Composite {
public class OldLeaderboard extends Composite implements BusyStateChangeListener {
private static OldLeaderboardUiBinder uiBinder = GWT.create(OldLeaderboardUiBinder.class);
interface OldLeaderboardUiBinder extends UiBinder<Widget, OldLeaderboard> {
@@ -52,6 +54,7 @@ public class OldLeaderboard extends Composite {
@UiField DivElement lastScoringCommentDiv;
@UiField DivElement scoringSchemeDiv;
@UiField DivElement hasLiveRaceDiv;
@UiField BusyIndicator busyIndicator;
@UiField EventRegattaLeaderboardResources local_res;
private LeaderboardPanel leaderboardPanel;
@@ -166,6 +169,7 @@ public class OldLeaderboard extends Composite {
this.autoRefreshTimer = timer;
this.leaderboardPanel = leaderboardPanel;
oldLeaderboardPanel.add(leaderboardPanel);
leaderboardPanel.addBusyStateChangeListener(this);
}
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
@@ -217,8 +221,17 @@ public class OldLeaderboard extends Composite {
}
lastScoringUpdateTimeDiv.getStyle().setVisibility(Visibility.HIDDEN);
}
@Override
public void onBusyStateChange(boolean busyState) {
busyIndicator.setBusy(busyState);
if(delegate != null) {
delegate.setBusyState(busyState);
}
}
public interface OldLeaderboardDelegate extends LeaderboardDelegate<LeaderboardPanel> {
Element getHasLiveRaceElement();
}
}
@@ -1,16 +1,18 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:c="urn:import:com.sap.sse.gwt.client.controls.busyindicator">
<ui:with field="i18n" type="com.sap.sailing.gwt.common.client.i18n.TextMessages"/>
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.old.EventRegattaLeaderboardResources" />
<g:HTMLPanel styleName=" {local_res.css.marginFix}">
<div class="{res.mediaCss.grid}">
<div class="{res.mediaCss.small7} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_scoring}">
<div class="{res.mediaCss.small6} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_scoring}">
<div ui:field="lastScoringCommentDiv" class="{local_res.css.regattaleaderboard_meta_scoring_comment}"></div>
<div ui:field="hasLiveRaceDiv" class="{local_res.css.regattaleaderboard_meta_liverace}"></div>
<div ui:field="scoringSchemeDiv" class="{local_res.css.regattaleaderboard_meta_scoring_type}"></div>
</div>
<div class="{res.mediaCss.small5} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_settings}">
<div class="{res.mediaCss.small6} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_settings}">
<c:SimpleBusyIndicator addStyleNames="{local_res.css.regattaleaderboard_meta_busyindicator}" ui:field="busyIndicator" />
<div class="{local_res.css.regattaleaderboard_meta_update}">
<div ui:field="lastScoringUpdateTextDiv" class="{local_res.css.regattaleaderboard_meta_update_text}"></div>
<div ui:field="lastScoringUpdateTimeDiv" class="{local_res.css.regattaleaderboard_meta_update_timestamp}"></div>
@@ -3,7 +3,7 @@ package com.sap.sailing.gwt.home.desktop.partials.old.multileaderboard;
import java.util.Date;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.ParagraphElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
@@ -27,15 +27,19 @@ import com.sap.sailing.gwt.home.desktop.partials.old.LeaderboardDelegate;
import com.sap.sailing.gwt.ui.client.DebugIdHelper;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.leaderboard.MultiLeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.leaderboard.SelectedLeaderboardChangeListener;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.BusyStateChangeListener;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
public class OldMultiLeaderboard extends Composite {
public class OldMultiLeaderboard extends Composite implements SelectedLeaderboardChangeListener, BusyStateChangeListener {
private static OldMultiLeaderboardUiBinder uiBinder = GWT.create(OldMultiLeaderboardUiBinder.class);
interface OldMultiLeaderboardUiBinder extends UiBinder<Widget, OldMultiLeaderboard> {
@@ -46,16 +50,18 @@ public class OldMultiLeaderboard extends Composite {
@UiField Anchor settingsAnchor;
@UiField Anchor autoRefreshAnchor;
@UiField Anchor fullscreenAnchor;
@UiField ParagraphElement lastScoringUpdateTimeDiv;
@UiField ParagraphElement lastScoringUpdateTextDiv;
@UiField ParagraphElement lastScoringCommentDiv;
@UiField ParagraphElement scoringSchemeDiv;
@UiField DivElement lastScoringUpdateTimeDiv;
@UiField DivElement lastScoringUpdateTextDiv;
@UiField DivElement lastScoringCommentDiv;
@UiField DivElement scoringSchemeDiv;
@UiField BusyIndicator busyIndicator;
@UiField EventRegattaLeaderboardResources local_res;
private MultiLeaderboardPanel multiLeaderboardPanel;
private Timer autoRefreshTimer;
private final OldMultiLeaderboardDelegate delegate;
private LeaderboardPanel lastSelectedLeaderboardPanel;
public OldMultiLeaderboard() {
this(null);
}
@@ -69,6 +75,7 @@ public class OldMultiLeaderboard extends Composite {
fullscreenAnchor.setTitle(StringMessages.INSTANCE.openFullscreenView());
this.delegate = delegate;
this.setupFullscreenDelegate();
lastSelectedLeaderboardPanel = null;
}
private void setupFullscreenDelegate() {
@@ -163,6 +170,7 @@ public class OldMultiLeaderboard extends Composite {
public void setMultiLeaderboard(MultiLeaderboardPanel multiLeaderboardPanel, final Timer timer) {
this.autoRefreshTimer = timer;
this.multiLeaderboardPanel = multiLeaderboardPanel;
this.multiLeaderboardPanel.addSelectedLeaderboardChangeListener(this);
oldMultiLeaderboardPanel.add(multiLeaderboardPanel);
}
@@ -198,6 +206,23 @@ public class OldMultiLeaderboard extends Composite {
}
}
}
@Override
public void onBusyStateChange(boolean busyState) {
busyIndicator.setBusy(busyState);
if(delegate != null) {
delegate.setBusyState(busyState);
}
}
@Override
public void onSelectedLeaderboardChanged(LeaderboardPanel selectedLeaderboard) {
if(lastSelectedLeaderboardPanel != null) {
lastSelectedLeaderboardPanel.removeBusyStateChangeListener(this);
}
selectedLeaderboard.addBusyStateChangeListener(this);
lastSelectedLeaderboardPanel = selectedLeaderboard;
}
public interface OldMultiLeaderboardDelegate extends LeaderboardDelegate<MultiLeaderboardPanel>{
}
@@ -1,23 +1,25 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:c="urn:import:com.sap.sse.gwt.client.controls.busyindicator">
<ui:with field="i18n" type="com.sap.sailing.gwt.common.client.i18n.TextMessages"/>
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.desktop.partials.old.EventRegattaLeaderboardResources" />
<g:HTMLPanel styleName=" {local_res.css.marginFix}">
<div class="{res.mediaCss.grid}">
<div class="{res.mediaCss.small6} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_scoring}">
<p ui:field="lastScoringCommentDiv" class="{local_res.css.regattaleaderboard_meta_scoring_comment}"></p>
<p ui:field="scoringSchemeDiv" class="{local_res.css.regattaleaderboard_meta_scoring_type}"></p>
<div ui:field="lastScoringCommentDiv" class="{local_res.css.regattaleaderboard_meta_scoring_comment}"></div>
<div ui:field="scoringSchemeDiv" class="{local_res.css.regattaleaderboard_meta_scoring_type}"></div>
</div>
<div class="{res.mediaCss.small6} {res.mediaCss.column} {local_res.css.regattaleaderboard_meta_settings}">
<c:SimpleBusyIndicator addStyleNames="{local_res.css.regattaleaderboard_meta_busyindicator}" ui:field="busyIndicator"/>
<div class="{local_res.css.regattaleaderboard_meta_update}">
<p ui:field="lastScoringUpdateTextDiv" class="{local_res.css.regattaleaderboard_meta_update_text}"></p>
<p ui:field="lastScoringUpdateTimeDiv" class="{local_res.css.regattaleaderboard_meta_update_timestamp}"></p>
<div ui:field="lastScoringUpdateTextDiv" class="{local_res.css.regattaleaderboard_meta_update_text}"></div>
<div ui:field="lastScoringUpdateTimeDiv" class="{local_res.css.regattaleaderboard_meta_update_timestamp}"></div>
</div>
<g:Anchor ui:field="autoRefreshAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_reload}"></g:Anchor>
<g:Anchor ui:field="settingsAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_settings_button}"></g:Anchor>
<g:Anchor ui:field="fullscreenAnchor" href="javascript:;" styleName="{res.mainCss.button} {local_res.css.regattaleaderboard_meta_fullscreen_button}"></g:Anchor>
</div>
</div>
</div>
<div class="{res.mediaCss.grid}">
@@ -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
*/
@@ -8,7 +8,8 @@ public class BusyViewImpl extends Composite implements BusyView {
public BusyViewImpl() {
BusyViewResources.INSTANCE.css().ensureInjected();
initWidget(new SimpleBusyIndicator(true, 1.0f, "", BusyViewResources.INSTANCE.css().busy()));
SimpleBusyIndicator busyIndicator = new SimpleBusyIndicator(true, 1.0f);
busyIndicator.setImageStyleClass(BusyViewResources.INSTANCE.css().busy());
initWidget(busyIndicator);
}
}
@@ -37,6 +37,7 @@ public class FullscreenContainer<T extends Widget> {
String popup();
String toolbarInfo();
String toolbarAction();
String toolbarBusyIndicator();
String content();
String contentBorder();
}
@@ -88,7 +89,12 @@ public class FullscreenContainer<T extends Widget> {
widget.addStyleName(style.toolbarAction());
toolbarUi.add(widget);
}
public void addToolbarBusyIndicator(Widget widget) {
widget.addStyleName(style.toolbarBusyIndicator());
toolbarUi.add(widget);
}
public void addToolbarInfo(Widget widget) {
widget.addStyleName(style.toolbarInfo());
toolbarUi.add(widget);
@@ -50,6 +50,13 @@
cursor: pointer;
padding: 10px;
}
.toolbarBusyIndicator {
display: inline-block;
height: 50px;
width: auto;
padding: 10px;
vertical-align: top;
}
.toolbarAction:hover {
background-color: #d79900;
}
@@ -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
@@ -40,18 +40,18 @@ public class AddWindFixDialog extends DataEntryDialog<WindDTO> {
if(windDTO.trueWindSpeedInKnots == null) {
errorMessage = stringMessages.pleaseEnterAValue();
} else if(windDTO.trueWindSpeedInKnots != null && (windDTO.trueWindSpeedInKnots < 0.0 || windDTO.trueWindSpeedInKnots > 100.0)) {
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.speedInKnots(), "0", "100");
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.speedInKnots(), 0, 100);
} else if(windDTO.trueWindFromDeg == null){
errorMessage = stringMessages.pleaseEnterAValue();
} else if(windDTO.trueWindFromDeg != null && (windDTO.trueWindFromDeg < 0.0 || windDTO.trueWindFromDeg > 360.0)){
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), "0", "360");
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), 0, 360);
} else if(windDTO.measureTimepoint == null) {
errorMessage = stringMessages.pleaseEnterAValue();
} else if(windDTO.position != null) {
if(windDTO.position.getLatDeg() < -90.0 || windDTO.position.getLatDeg() > 90.0){
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), "-90", "90");
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), -90, 90);
} else if(windDTO.position.getLngDeg() < -180.0 || windDTO.position.getLngDeg() > 180.0){
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), "-180", "180");
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), -180, 180);
}
}
@@ -225,7 +225,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
declinationCheckbox.setValue(true);
trackableRacesPanel.add(declinationCheckbox);
final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateWithStartTimeNow());
final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateAsLiveRace());
simulateWithStartTimeNow.setWordWrap(false);
simulateWithStartTimeNow.setValue(false);
trackableRacesPanel.add(simulateWithStartTimeNow);
@@ -24,6 +24,7 @@ import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel;
@@ -44,6 +45,7 @@ import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
@@ -83,7 +85,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel;
private CellTable<TracTracRaceRecordDTO> racesTable;
private static final String ZERO_AS_STRING = "0";
public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, StringMessages stringMessages) {
super(sailingService, regattaRefresher, errorReporter, new RaceSelectionModel(), true, stringMessages);
@@ -229,17 +232,12 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
protected HorizontalPanel createRacesPanel() {
HorizontalPanel racesPanel = new HorizontalPanel();
CaptionPanel trackableRacesPanel = createTrackableRacesPanel();
racesPanel.add(trackableRacesPanel);
racesPanel.setCellWidth(trackableRacesPanel, "50%");
CaptionPanel trackedRacesPanel = createTrackedRacesPanel();
racesPanel.add(trackedRacesPanel);
racesPanel.setCellWidth(trackedRacesPanel, "50%");
return racesPanel;
}
@@ -276,10 +274,32 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
correctWindCheckBox.setWordWrap(false);
correctWindCheckBox.setValue(Boolean.TRUE);
final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateWithStartTimeNow());
final TextBox offsetToStartTimeOfSimulatedRaceTextBox = new TextBox();
offsetToStartTimeOfSimulatedRaceTextBox.setWidth("40px");
offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(false);
offsetToStartTimeOfSimulatedRaceTextBox.setValue(ZERO_AS_STRING);
final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateAsLiveRace());
simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox");
simulateWithStartTimeNowCheckBox.setWordWrap(false);
simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE);
simulateWithStartTimeNowCheckBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(simulateWithStartTimeNowCheckBox.getValue());
offsetToStartTimeOfSimulatedRaceTextBox.setFocus(simulateWithStartTimeNowCheckBox.getValue());
}
});
final FlowPanel simulateAsLiveRacePanel = new FlowPanel();
simulateAsLiveRacePanel.add(simulateWithStartTimeNowCheckBox);
final Label offsetToStartLabel = new Label(stringMessages.simulateWithOffset());
final HorizontalPanel simulateWithOffsetPanel = new HorizontalPanel();
simulateWithOffsetPanel.add(offsetToStartLabel);
simulateWithOffsetPanel.add(offsetToStartTimeOfSimulatedRaceTextBox);
final CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm());
ignoreTracTracMarkPassingsCheckbox.setWordWrap(false);
ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE);
@@ -287,8 +307,9 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
layoutTable.setWidget(1, 0, trackSettingsLabel);
layoutTable.setWidget(1, 1, trackWindCheckBox);
layoutTable.setWidget(2, 1, correctWindCheckBox);
layoutTable.setWidget(3, 1, simulateWithStartTimeNowCheckBox);
layoutTable.setWidget(4, 1, ignoreTracTracMarkPassingsCheckbox);
layoutTable.setWidget(3, 1, simulateAsLiveRacePanel);
layoutTable.setWidget(4, 1, simulateWithOffsetPanel);
layoutTable.setWidget(5, 1, ignoreTracTracMarkPassingsCheckbox);
// Filter
Label racesFilterLabel = new Label(stringMessages.filterRacesByName() + ":");
@@ -310,8 +331,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}
};
racesFilterablePanel.getTextBox().ensureDebugId("TrackableRacesFilterTextBox");
layoutTable.setWidget(5, 0, racesFilterLabel);
layoutTable.setWidget(5, 1, racesFilterablePanel);
layoutTable.setWidget(6, 0, racesFilterLabel);
layoutTable.setWidget(6, 1, racesFilterablePanel);
// Races
TextColumn<TracTracRaceRecordDTO> regattaNameColumn = new TextColumn<TracTracRaceRecordDTO>() {
@@ -357,7 +378,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}
};
raceVisibilityColumn.setSortable(true);
SelectionCheckboxColumn<TracTracRaceRecordDTO> selectionCheckboxColumn = new SelectionCheckboxColumn<TracTracRaceRecordDTO>(tableResources.cellTableStyle().cellTableCheckboxSelected(),
tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) {
@Override
@@ -381,26 +401,24 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
raceNameColumn, boatClassColumn, raceStartTrackingColumn, raceStatusColumn));
racesTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager());
racesTable.setWidth("100%");
raceList.addDataDisplay(racesTable);
layoutTable.setWidget(6, 0, racesTable);
cellFormatter.setColSpan(6, 0, 2);
Button startTrackingButton = new Button(stringMessages.startTracking());
startTrackingButton.ensureDebugId("StartTrackingButton");
startTrackingButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
Duration offsetToStartTimeOfSimulatedRace = null;
if(simulateWithStartTimeNowCheckBox.getValue().booleanValue()) {
offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(offsetToStartTimeOfSimulatedRaceTextBox.getValue());
}
trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(),
simulateWithStartTimeNowCheckBox.getValue(), ignoreTracTracMarkPassingsCheckbox.getValue());
offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassingsCheckbox.getValue());
}
});
layoutTable.setWidget(7, 1, startTrackingButton);
trackableRacesPanel.setContentWidget(layoutTable);
return trackableRacesPanel;
}
@@ -462,6 +480,17 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
return boatClassNames.substring(0, boatClassNames.length() - 2);
}
private Duration getMillisecondsDurationFromMinutesAsString(String minutesAsString) {
Duration result = null;
if (minutesAsString != null) {
Double minutesAsDouble = Double.parseDouble(minutesAsString);
if (minutesAsDouble != null) {
result = Duration.ONE_MINUTE.times(minutesAsDouble.longValue());
}
}
return result;
}
private void fillConfigurations() {
this.sailingService.getPreviousTracTracConfigurations(new MarkedAsyncCallback<List<TracTracConfigurationDTO>>(
@@ -551,7 +580,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}));
}
private void trackSelectedRaces(boolean trackWind, boolean correctWind, final boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings) {
private void trackSelectedRaces(boolean trackWind, boolean correctWind, final Duration offsetToStartTimeOfSimulatedRace, boolean ignoreTracTracMarkPassings) {
String liveURI = liveURITextBox.getValue();
String storedURI = storedURITextBox.getValue();
String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue();
@@ -577,7 +606,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}
if (checkBoatClassOK(selectedRegatta, selectedRaces)) {
sailingService.trackWithTracTrac(regattaIdentifier, selectedRaces, liveURI, storedURI,
courseDesignUpdateURI, trackWind, correctWind, simulateWithStartTimeNow, ignoreTracTracMarkPassings, tractracUsername,
courseDesignUpdateURI, trackWind, correctWind, offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassings, tractracUsername,
tractracPassword, new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
@@ -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;
@@ -86,6 +86,7 @@ import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
import com.sap.sailing.gwt.ui.shared.VenueDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -116,7 +117,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI,
String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception;
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception;
void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, String hostname, int port,
boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception;
@@ -78,6 +78,7 @@ import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
import com.sap.sailing.gwt.ui.shared.VenueDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
@@ -114,10 +115,10 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
* @param liveURI
* may be <code>null</code> or the empty string in which case the server will use the
* {@link TracTracRaceRecordDTO#liveURI} from the <code>rr</code> race record.
* @param simulateWithStartTimeNow
* if <code>true</code>, the connector will adjust the time stamps of all events received such that the
* @param offsetToStartTimeOfSimulatedRace
* if not <code>null</code>, the connector will adjust the time stamps of all events received such that the
* first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all
* events received such that they seem to be sent in "real-time." So, more or less the time points
* events received such that they seem to be sent in "real-time" plus the <code>offsetToStartTimeOfSimulatedRace</code>. So, more or less the time points
* attached to the events sent to the receivers will again approximate the wall time.
* @param useInternalMarkPassingAlgorithm
* whether or not to ignore the TracTrac-provided mark passings; if <code>true</code>, a separate mark
@@ -128,7 +129,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
*/
void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI,
String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination,
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername,
String tracTracPassword, AsyncCallback<Void> callback);
void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs,
@@ -361,7 +361,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String successfullyUpdatedScores();
String errorUpdatingScoresForLeaderboard(String leaderboardName, String message);
String maneuverTypesToShowWhenCompetitorIsClicked();
String simulateWithStartTimeNow();
String simulateAsLiveRace();
String simulateWithOffset();
String boatClassDoesNotMatchSelectedRegatta(String boatClass);
String regattaExistForSelectedBoatClass();
String reload();
@@ -436,7 +437,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String overallDetailsToShow();
String hhmmssUnit();
String actionAddWindData();
String valueMustBeBetweenMinMax(String name, String minValue, String maxValue);
String valueMustBeBetweenMinMax(String name, int minValue, int maxValue);
String valueMustBeGreaterThan(String name, String minValue);
String optional();
String pleaseEnterAValue();
String latitude();
@@ -503,6 +505,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String sideToWhichMarkAtLegStartWasRounded();
String raceIsLive(String raceName);
String racesAreLive(String raceNames);
String regattaIsLive(String regattaName);
String regattasAreLive(String regattaNames);
String scoringSchemeHighPointFirstGetsOne();
String scoringSchemeHighPointFirstGetsTen();
String knotsUnit();
@@ -1444,4 +1448,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String requiresValidRegatta();
String couldNotObtainRace(String regattaLikeName, String raceColumnName, String fleetName, String technicalErrorMessage);
String errorTryingToCreateEmbeddedMap(String message);
String transparentBufferLineOnHover();
String bufferLineStrokeWeight();
}
@@ -371,7 +371,8 @@ apply=Apply
successfullyUpdatedScores=Successfully updated scores
errorUpdatingScoresForLeaderboard=Error updating scores for leaderboard {0}: {1}
maneuverTypesToShowWhenCompetitorIsClicked=Maneuver types to show when competitor is clicked
simulateWithStartTimeNow=Simulate with "now" as start time
simulateAsLiveRace=Simulate as live race
simulateWithOffset=Offset before start in minutes:
boatClassDoesNotMatchSelectedRegatta=The selected races contain boat classes which are not the same as the boat class ''{0}'' of the selected regatta. No races will be loaded.
regattaExistForSelectedBoatClass=There is at least one regatta for the selected boat classes. Really create default regatta(s) for those race(s)?
reload=Reload
@@ -456,6 +457,7 @@ totalAverageSpeedOverGroundTooltip=Average speed over ground across the regatta.
hhmmssUnit=hh:mm:ss
actionAddWindData=Add wind data
valueMustBeBetweenMinMax=The value of ''{0}'' must be between {1} and {2}.
valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}.
optional=optional
pleaseEnterAValue=Please enter a value.
latitude=Latitude
@@ -527,6 +529,8 @@ sideToWhichMarkAtLegStartWasRounded=Mark rounded to
sideToWhichMarkAtLegStartWasRoundedTooltip=Side to which the mark at leg start was rounded.
raceIsLive=Race {0} is live
racesAreLive=Races {0} are live
regattaIsLive=Regatta {0} is live
regattasAreLive=Regattas {0} are live
knotsUnit=kts
knotsValue={0,number,#0.0} kts
knotsRange={0,number,#0.0} - {1,number,#0.0} kts
@@ -1410,4 +1414,6 @@ selectALeaderboardGroup=Select a leaderboard group...
pleaseSelect=Please select
requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show.
couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3}
errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
transparentBufferLineOnHover=Transparent buffer lines on hover of helplines
bufferLineStrokeWeight=Buffer line stroke weight
@@ -372,7 +372,8 @@ apply=Übernehmen
successfullyUpdatedScores=Ergebnisse erfolgreich aktualisiert
errorUpdatingScoresForLeaderboard=Fehler beim Aktualisieren der Ergebnisse für {0}: {1}
maneuverTypesToShowWhenCompetitorIsClicked=Anzuzeigende Manövertypen für angeklickte Teilnehmer
simulateWithStartTimeNow=Simulieren mit "jetzt" als Startzeit
simulateAsLiveRace=Simuliere als Live Rennen
simulateWithOffset=Zeit bis zum Start in Minuten:
boatClassDoesNotMatchSelectedRegatta=Die ausgewählten Rennen enthalten eine Bootsklasse, die nicht der Bootsklasse der ausgewählten Regatta ''{0}'' entspricht. Es werden keine Rennen geladen.
regattaExistForSelectedBoatClass=Es gibt mindestens eine Regatta zu den ausgewählten Bootsklassen. Dennoch Default-Regatta für diese(s) Rennen anlegen?
reload=Neu laden
@@ -456,7 +457,8 @@ totalAverageSpeedOverGround=\u2205 FüG
totalAverageSpeedOverGroundTooltip=Durchschnittliche Fahrt über Grund über die gesamte Regatta.
hhmmssUnit=hh:mm:ss
actionAddWindData=Winddaten hinzufügen
valueMustBeBetweenMinMax=Der Wert von ''{0}'' muß zwischen {1} und {2} sein.
valueMustBeBetweenMinMax=Der Wert von ''{0}'' muss zwischen {1} und {2} sein.
valueMustBeGreaterThan=Der Wert von ''{0}'' muss größer als {1} sein.
optional=optional
pleaseEnterAValue=Bitte geben Sie einen Wert ein.
latitude=Breite
@@ -528,6 +530,8 @@ sideToWhichMarkAtLegStartWasRounded=Tonne gerundet an
sideToWhichMarkAtLegStartWasRoundedTooltip=Seite, an der die Bahnmarke am Schenkelstart gerundet wurde
raceIsLive=Rennen {0} ist live
racesAreLive=Rennen {0} sind live
regattaIsLive=Regatta {0} ist live
regattasAreLive=Regatten {0} sind live
scoringSchemeHighPointFirstGetsOne=High Point System, Gewinner erhält 1 Punkt
scoringSchemeHighPointFirstGetsTen=High Point System, Gewinner erhält 10 Punkte
knotsUnit=kn
@@ -1401,4 +1405,6 @@ selectALeaderboardGroup=Bitte eine Ranglisten-Gruppe auswählen...
pleaseSelect=Bitte auswählen
requiresValidRegatta=Diese Seite benötigt eine gültige Regatta, Rennspalte und einen gültigen Gruppennamen um die anzuzeigende Wettfahrt zu identifizieren.
couldNotObtainRace=Wettfahrt namens {1} für Gruppe {2} in einer Regatta names {0} konnte nicht gefunden werden: {3}
errorTryingToCreateEmbeddedMap=Fehler beim Erstellen der eingebetteten Karte: {0}
errorTryingToCreateEmbeddedMap=Fehler beim Erstellen der eingebetteten Karte: {0}
transparentBufferLineOnHover=Transparente Pufferlinien bei Maus über Hilfslinien
bufferLineStrokeWeight=Linienbreite der Pufferlinien
@@ -364,7 +364,8 @@ apply=Применить
successfullyUpdatedScores=Очки обновлены успешно
errorUpdatingScoresForLeaderboard=Ошибка обновления очков для таблицы лидеров {0}: {1}
maneuverTypesToShowWhenCompetitorIsClicked=Типы маневров для отображения при нажатии на участника
simulateWithStartTimeNow=Моделирования с "сейчас" в качестве времени начала
simulateAsLiveRace=Simulate as live race
simulateWithOffset=Offset before start in minutes:
boatClassDoesNotMatchSelectedRegatta=Выбранные гонки содержат классы судов не соответствующие классу судов ''{0}'' выбранной регаты.
regattaExistForSelectedBoatClass=Есть только одна регата для выбранных классов судов.
reload=Перезагрузить
@@ -440,6 +441,7 @@ totalAverageSpeedOverGroundTooltip=Средняя скорость относи
hhmmssUnit=чч:мм:сс
actionAddWindData=Добавить данные о ветре
valueMustBeBetweenMinMax=Значение ''{0}''должно быть в пределах от {1} до {2}.
valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}.
optional=Дополнительно
pleaseEnterAValue=Введите, пожалуйста, значение.
latitude=Широта
@@ -511,6 +513,8 @@ sideToWhichMarkAtLegStartWasRounded=Отметка округленная до
sideToWhichMarkAtLegStartWasRoundedTooltip=Сторона в которую округляется отметка на старте этапа.
raceIsLive=Гонка {0} в прямом эфире
racesAreLive=Гонки {0} в прямом эфире
regattaIsLive=Регата {0} в прямом эфире
regattasAreLive=Регаты {0} в прямом эфире
knotsUnit=Узлы
knotsValue={0,number,#0.0} Узлы
knotsRange={0,number,#0.0} - {1,number,#0.0} Узлы
@@ -1340,4 +1344,6 @@ selectALeaderboardGroup=Select a leaderboard group...
pleaseSelect=Please select
requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show.
couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3}
errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
transparentBufferLineOnHover=Transparent buffer lines on hover of helplines
bufferLineStrokeWeight=Buffer line stroke weight
@@ -538,6 +538,7 @@ connectors=Connectors
dampeningInterval=Dampening Interval
trackBasedEstimationWindSourceTypeTooltip=Calculated data from the competitor tracks
valueMustBeBetweenMinMax=The value of ''{0}'' must be between {1} and {2}.
valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}.
orMultipleEmails=or multiple E-Mails separated by comma
searchEvents=Search for events
explainNoConnectionsFromReplicas=No connections from a replica found. You can connect replicas by using their AdminConsole.
@@ -844,7 +845,10 @@ explainReplicationHostname=Hostname where the replication master is running on.
igtimiAccounts=Igtimi Accounts
windChart=Wind Chart
events=活动
raceIsLive=Race {0} is live
racesAreLive=Races {0} are live
regattaIsLive=Regatta {0} is live
regattasAreLive=Regattas {0} are live
operatorNotContains=Not Contains
windwardDistanceToCompetitorFarthestAheadInMetersTooltip=迎/顺风航段时离开上风/下风第一名的平行距离或横风时的实际距离name 名字
errorAddingResultImportUrl=Error adding result import URL: {0}
@@ -1066,13 +1070,13 @@ polarSheetMinimumConfidenceMeasure=Minimum confidence measure
operator=operator
tractracUsername=TracTrac username
webWindSourceTypeName=Manual
raceIsLive=Race {0} is live
sapSailingAnalytics=航行分析
trackWind=Track Wind
rawFixes=All fixes (including outliers)
showCompetitorFullNameColumn=Show Competitor Full Name
disableRaceFilter=Disable filter
simulateWithStartTimeNow=Simulate with "now" as start time
simulateAsLiveRace=Simulate as live race
simulateWithOffset=Offset before start in minutes:
polarSheetOutlierDetectionRadiusTooltip=The radius used in the distance based outlierndetection algorithm.
noWindFixesAvailable=No Wind-Fixes available.
correctScoreFor=Correct score for competitor {0} in race {1}
@@ -1355,4 +1359,6 @@ selectALeaderboardGroup=Select a leaderboard group...
pleaseSelect=Please select
requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show.
couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3}
errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
transparentBufferLineOnHover=Transparent buffer lines on hover of helplines
bufferLineStrokeWeight=Buffer line stroke weight
@@ -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();
@@ -83,7 +83,9 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements RaceSel
timeRangeWithZoomProvider.addTimeZoomChangeListener(this);
timeRangeWithZoomProvider.addTimeRangeChangeListener(this);
chartsCss.ensureInjected();
busyIndicator = new SimpleBusyIndicator(/* busy */ true, 2.0f, chartsCss.busyIndicatorStyle(), chartsCss.busyIndicatorImageStyle());
busyIndicator = new SimpleBusyIndicator(/* busy */ true, 2.0f);
busyIndicator.setPanelStyleClass(chartsCss.busyIndicatorStyle());
busyIndicator.setImageStyleClass(chartsCss.busyIndicatorImageStyle());
settingsButton = createSettingsButton();
settingsButton.setStyleName(chartsCss.settingsButtonStyle());
settingsButton.addStyleName(chartsCss.settingsButtonBackgroundImage());
@@ -0,0 +1,57 @@
package com.sap.sailing.gwt.ui.client.shared.racemap;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.maps.client.events.click.ClickMapHandler;
import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent;
import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler;
import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent;
import com.google.gwt.maps.client.events.mouseover.MouseOverMapHandler;
import com.google.gwt.maps.client.overlays.Polyline;
import com.google.gwt.maps.client.overlays.PolylineOptions;
public class Hoverline {
private static final double TRANSPARENT = 0;
private static final double VISIBLE = 0.2d;
private final Polyline hoverline;
private final PolylineOptions options;
public Hoverline(final Polyline polyline, PolylineOptions polylineOptions, final RaceMap map) {
this.options = PolylineOptions.newInstance();
this.options.setClickable(polylineOptions.getClickable());
this.options.setGeodesic(polylineOptions.getGeodesic());
this.options.setMap(polyline.getMap());
this.options.setPath(polyline.getPath());
this.options.setStrokeColor(polylineOptions.getStrokeColor());
this.options.setZindex(polylineOptions.getZindex());
this.hoverline = Polyline.newInstance(this.options);
this.hoverline.setVisible(false);
polyline.addMouseOverHandler(new MouseOverMapHandler() {
@Override
public void onEvent(MouseOverMapEvent event) {
options.setStrokeOpacity(map.getSettings().getTransparentHoverlines() ? TRANSPARENT : VISIBLE);
options.setStrokeWeight(map.getSettings().getHoverlineStrokeWeight());
options.setVisible(true);
hoverline.setOptions(options);
}
});
hoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
@Override
public void onEvent(MouseOutMapEvent event) {
hoverline.setVisible(false);
}
});
}
public HandlerRegistration addClickHandler(ClickMapHandler handler) {
return this.hoverline.addClickHandler(handler);
}
public HandlerRegistration addMouseOutMoveHandler(MouseOutMapHandler handler) {
return this.hoverline.addMouseOutMoveHandler(handler);
}
public HandlerRegistration addMouseOverHandler(MouseOverMapHandler handler) {
return this.hoverline.addMouseOverHandler(handler);
}
}
@@ -19,6 +19,10 @@ public class RaceMapSettings extends AbstractSettings {
private RaceMapZoomSettings zoomSettings;
private RaceMapHelpLinesSettings helpLinesSettings;
private boolean transparentHoverlines = true;
private int hoverlineStrokeWeight = 10;
private long tailLengthInMilliseconds = 100000l;
@@ -55,6 +59,8 @@ public class RaceMapSettings extends AbstractSettings {
public RaceMapSettings(RaceMapSettings settings) {
this.buoyZoneRadiusInMeters = settings.buoyZoneRadiusInMeters;
this.helpLinesSettings = new RaceMapHelpLinesSettings(settings.getHelpLinesSettings().getVisibleHelpLineTypes());
this.transparentHoverlines = settings.transparentHoverlines;
this.hoverlineStrokeWeight = settings.hoverlineStrokeWeight;
this.maneuverTypesToShow = settings.maneuverTypesToShow;
this.showDouglasPeuckerPoints = settings.showDouglasPeuckerPoints;
this.showOnlySelectedCompetitors = settings.showOnlySelectedCompetitors;
@@ -153,6 +159,22 @@ public class RaceMapSettings extends AbstractSettings {
public void setHelpLinesSettings(RaceMapHelpLinesSettings helpLinesSettings) {
this.helpLinesSettings = helpLinesSettings;
}
public boolean getTransparentHoverlines() {
return this.transparentHoverlines;
}
public void setTransparentHoverlines(boolean transparentHoverlines) {
this.transparentHoverlines = transparentHoverlines;
}
public int getHoverlineStrokeWeight() {
return this.hoverlineStrokeWeight;
}
public void setHoverlineStrokeWeight(int hoverlineStrokeWeight) {
this.hoverlineStrokeWeight = hoverlineStrokeWeight;
}
public boolean isShowSelectedCompetitorsInfo() {
return showSelectedCompetitorsInfo;
@@ -24,11 +24,14 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.controls.IntegerBox;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<RaceMapSettings> {
private static final int MAX_BUOY_ZONE_RADIUS_IN_METERS = 100;
private static final int MAX_STROKE_WEIGHT = 33;
//Initializing the lists to prevent a null pointer exception in the first validation call
private List<Util.Pair<CheckBox, ManeuverType>> checkboxAndManeuverType = new ArrayList<Util.Pair<CheckBox, ManeuverType>>();
private List<Util.Pair<CheckBox, ZoomTypes>> checkboxAndZoomType = new ArrayList<Util.Pair<CheckBox,ZoomTypes>>();
@@ -43,6 +46,8 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
private CheckBox showSelectedCompetitorsInfoCheckBox;
private LongBox tailLengthBox;
private DoubleBox buoyZoneRadiusBox;
private CheckBox transparentHoverlines;
private IntegerBox hoverlineStrokeWeight;
private boolean showViewSimulation;
private final StringMessages stringMessages;
@@ -211,6 +216,17 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE));
vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.COURSEGEOMETRY));
transparentHoverlines = dialog.createCheckbox(stringMessages.transparentBufferLineOnHover());
transparentHoverlines.setValue(initialSettings.getTransparentHoverlines());
vp.add(transparentHoverlines);
HorizontalPanel hoverlineStrokeWeightPanel = new HorizontalPanel();
Label hoverlineStrokeWeightLabel = new Label(stringMessages.bufferLineStrokeWeight() + ":");
hoverlineStrokeWeightPanel.add(hoverlineStrokeWeightLabel);
hoverlineStrokeWeight = dialog.createIntegerBox(initialSettings.getHoverlineStrokeWeight(), 3);
hoverlineStrokeWeightPanel.add(hoverlineStrokeWeight);
vp.add(hoverlineStrokeWeightPanel);
return vp;
}
@@ -267,6 +283,8 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
result.setBuoyZoneRadiusInMeters(value);
}
}
result.setTransparentHoverlines(transparentHoverlines.getValue());
result.setHoverlineStrokeWeight(hoverlineStrokeWeight.getValue());
return result;
}
@@ -304,8 +322,10 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
if (valueToValidate.getHelpLinesSettings().isVisible(HelpLineTypes.BOATTAILS) && valueToValidate.getTailLengthInMilliseconds() <= 0) {
errorMessage = stringMessages.tailLengthMustBePositive();
} else if (valueToValidate.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE)
&& (valueToValidate.getBuoyZoneRadiusInMeters() < 0.0 || valueToValidate.getBuoyZoneRadiusInMeters() > 100.0)) {
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.buoyZone(), "0", "100");
&& (valueToValidate.getBuoyZoneRadiusInMeters() < 0 || valueToValidate.getBuoyZoneRadiusInMeters() > MAX_BUOY_ZONE_RADIUS_IN_METERS)) {
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.buoyZone(), 0, 100);
} else if (valueToValidate.getHoverlineStrokeWeight() < 0 || valueToValidate.getHoverlineStrokeWeight() > MAX_STROKE_WEIGHT) {
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.bufferLineStrokeWeight(), 0, MAX_STROKE_WEIGHT);
}
return errorMessage;
}
@@ -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;
@@ -149,15 +149,15 @@ public abstract class ExpandableSortableColumn<C> extends LeaderboardSortableCol
*/
public void toggleExpansion() {
if (isExpansionEnabled()) {
final boolean oldBusyState = getLeaderboardPanel().getBusyIndicator().isBusy();
getLeaderboardPanel().getBusyIndicator().setBusy(true);
final boolean oldBusyState = getLeaderboardPanel().isBusy();
getLeaderboardPanel().setBusyState(true);
setTogglingInProcess(true);
final CellTable<LeaderboardRowDTO> table = getLeaderboardPanel().getLeaderboardTable();
if (isExpanded()) {
for (AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?> column : getAllVisibleChildren()) {
getLeaderboardPanel().removeColumn(column); // removes only the children currently displayed
}
getLeaderboardPanel().getBusyIndicator().setBusy(false);
getLeaderboardPanel().setBusyState(false);
setTogglingInProcess(false);
// important: toggle expanded state after asking for all visible children
setExpanded(!isExpanded());
@@ -177,7 +177,7 @@ public abstract class ExpandableSortableColumn<C> extends LeaderboardSortableCol
}
getLeaderboardPanel().getLeaderboardTable().redraw();
}
getLeaderboardPanel().getBusyIndicator().setBusy(oldBusyState);
getLeaderboardPanel().setBusyState(oldBusyState);
setTogglingInProcess(false);
}
});
@@ -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;
}
@@ -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(),
@@ -3,8 +3,10 @@ package com.sap.sailing.gwt.ui.leaderboard;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Style.FontWeight;
@@ -35,7 +37,7 @@ import com.sap.sse.gwt.client.useragent.UserAgentDetails;
* A panel managing multiple {@link LeaderboardPanel}s (e.g. from a meta leaderboard) so that the user can switch between them.
* @author Frank
*/
public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSettings> implements TimeListener {
public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSettings> implements TimeListener, SelectedLeaderboardChangeProvider {
private LeaderboardPanel selectedLeaderboardPanel;
private FlowPanel selectedLeaderboardFlowPanel;
@@ -60,7 +62,8 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
private final String metaLeaderboardName;
private final boolean isEmbedded;
private List<LeaderboardUpdateListener> leaderboardUpdateListener;
private final Set<LeaderboardUpdateListener> leaderboardUpdateListeners;
private final Set<SelectedLeaderboardChangeListener> selectedLeaderboardChangeListeners;
public MultiLeaderboardPanel(SailingServiceAsync sailingService, String metaLeaderboardName, AsyncActionsExecutor asyncActionsExecutor,
Timer timer, boolean isEmbedded, String preselectedLeaderboardName, RaceIdentifier preselectedRace,
@@ -81,13 +84,14 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
selectedLeaderboardPanel = null;
leaderboardNamesAndDisplayNames = new ArrayList<Util.Pair<String, String>>();
leaderboardNamesAndSettings = new HashMap<String, LeaderboardSettings>();
leaderboardUpdateListener = new ArrayList<LeaderboardUpdateListener>();
leaderboardUpdateListeners = new HashSet<LeaderboardUpdateListener>();
selectedLeaderboardChangeListeners = new HashSet<SelectedLeaderboardChangeListener>();
}
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);
@@ -152,7 +156,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
}
public void addLeaderboardUpdateListener(LeaderboardUpdateListener listener) {
this.leaderboardUpdateListener.add(listener);
this.leaderboardUpdateListeners.add(listener);
}
public void setLeaderboardNames(List<Util.Pair<String, String>> newLeaderboardNamesAndDisplayNames) {
@@ -177,6 +181,10 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
});
}
// public LeaderboardPanel getSelectedLeaderboardPanel() {
// return selectedLeaderboardPanel;
// }
//
private void updateLeaderboardSelection() {
if(leaderboardsTabPanel != null) {
leaderboardsTabPanel.clear();
@@ -215,16 +223,16 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
selectedLeaderboardFlowPanel = (FlowPanel) leaderboardsTabPanel.getWidget(newTabIndex);
LeaderboardSettings newLeaderboardSettings = getOrCreateLeaderboardSettings(newSelectedLeaderboardName, leaderboardNamesAndSettings.get(selectedLeaderboardName));
selectedLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, newLeaderboardSettings, isEmbedded,
LeaderboardPanel newSelectedLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, newLeaderboardSettings, isEmbedded,
/* preselectedRace*/ null, new CompetitorSelectionModel(true), timer,
null, newSelectedLeaderboardName, errorReporter, stringMessages, userAgent,
showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null,
false, /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/ false, false);
selectedLeaderboardFlowPanel.add(selectedLeaderboardPanel);
for(LeaderboardUpdateListener listener: leaderboardUpdateListener) {
selectedLeaderboardPanel.addLeaderboardUpdateListener(listener);
selectedLeaderboardFlowPanel.add(newSelectedLeaderboardPanel);
for(LeaderboardUpdateListener listener: leaderboardUpdateListeners) {
newSelectedLeaderboardPanel.addLeaderboardUpdateListener(listener);
}
setSelectedLeaderboard(newSelectedLeaderboardPanel);
} else {
if(selectedLeaderboardPanel != null && selectedLeaderboardFlowPanel != null) {
selectedLeaderboardPanel.removeAllListeners();
@@ -254,4 +262,23 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
}
}
@Override
public void addSelectedLeaderboardChangeListener(SelectedLeaderboardChangeListener listener) {
selectedLeaderboardChangeListeners.add(listener);
}
@Override
public void removeSelectedLeaderboardChangeListener(SelectedLeaderboardChangeListener listener) {
selectedLeaderboardChangeListeners.remove(listener);
}
@Override
public void setSelectedLeaderboard(LeaderboardPanel selectedLeaderboard) {
if(this.selectedLeaderboardPanel != selectedLeaderboard) {
this.selectedLeaderboardPanel = selectedLeaderboard;
for (SelectedLeaderboardChangeListener listener : selectedLeaderboardChangeListeners) {
listener.onSelectedLeaderboardChanged(selectedLeaderboardPanel);
}
}
}
}
@@ -0,0 +1,14 @@
package com.sap.sailing.gwt.ui.leaderboard;
/**
* Allows UI components to observe a change of the selected leaderboard in a Multi-Leaderboard scenario.
*/
public interface SelectedLeaderboardChangeListener {
/**
* @param selectedLeaderboard the selected non-<code>null</code> LeaderboardPanel
*/
void onSelectedLeaderboardChanged(LeaderboardPanel selectedLeaderboard);
}
@@ -0,0 +1,11 @@
package com.sap.sailing.gwt.ui.leaderboard;
public interface SelectedLeaderboardChangeProvider {
void addSelectedLeaderboardChangeListener(SelectedLeaderboardChangeListener listener);
void removeSelectedLeaderboardChangeListener(SelectedLeaderboardChangeListener listener);
void setSelectedLeaderboard(LeaderboardPanel selectedLeaderboard);
}
@@ -319,13 +319,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
final RowUpdateWhiteboard<LeaderboardRowDTO> whiteboard = new RowUpdateWhiteboard<LeaderboardRowDTO>(
EditableLeaderboardPanel.this.getData());
getWhiteboardOwner().whiteboardProduced(whiteboard);
getBusyIndicator().setBusy(true);
setBusyState(true);
getSailingService().updateLeaderboardMaxPointsReason(getLeaderboardName(), row.competitor.getIdAsString(),
raceColumnName, value == null || value.trim().length() == 0 ? null : MaxPointsReason.valueOf(value.trim()),
getLeaderboardDisplayDate(), new AsyncCallback<Util.Triple<Double, Double, Boolean>>() {
@Override
public void onFailure(Throwable t) {
getBusyIndicator().setBusy(false);
setBusyState(false);
getErrorReporter().reportError(
"Error trying to update max points reason for competitor "
+ row.competitor.getName() + " in leaderboard " + getLeaderboardName()
@@ -334,7 +334,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
@Override
public void onSuccess(Util.Triple<Double, Double, Boolean> newNetAndTotalPointsAndIsCorrected) {
getBusyIndicator().setBusy(false);
setBusyState(false);
row.fieldsByRaceColumnName.get(raceColumnName).reasonForMaxPoints = value == null
|| value.length() == 0 ? null : MaxPointsReason.valueOf(value.trim());
row.fieldsByRaceColumnName.get(raceColumnName).netPoints = newNetAndTotalPointsAndIsCorrected.getA();
@@ -444,14 +444,14 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
final RowUpdateWhiteboard<LeaderboardRowDTO> whiteboard = new RowUpdateWhiteboard<LeaderboardRowDTO>(
EditableLeaderboardPanel.this.getData());
getWhiteboardOwner().whiteboardProduced(whiteboard);
getBusyIndicator().setBusy(true);
setBusyState(true);
getSailingService().updateLeaderboardScoreCorrection(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName,
value == null || value.trim().length() == 0 ? null : value.trim().equals("n/a") ? null
: Double.valueOf(value.trim()), getLeaderboardDisplayDate(),
new AsyncCallback<Util.Triple<Double, Double, Boolean>>() {
@Override
public void onFailure(Throwable t) {
getBusyIndicator().setBusy(false);
setBusyState(false);
getErrorReporter().reportError("Error trying to update score correction for competitor "+
row.competitor.getName()+" in leaderboard "+getLeaderboardName()+
" for race "+raceColumnName+": "+t.getMessage()+
@@ -460,7 +460,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
@Override
public void onSuccess(Util.Triple<Double, Double, Boolean> newNetAndTotalPointsAndIsCorrected) {
getBusyIndicator().setBusy(false);
setBusyState(false);
final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
leaderboardEntryDTO.netPoints = value == null || value.length() == 0 ? newNetAndTotalPointsAndIsCorrected
.getA() : Double.valueOf(value.trim());
@@ -523,13 +523,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
row.fieldsByRaceColumnName.get(raceColumnName).netPoints, new DialogCallback<Util.Pair<MaxPointsReason, Double>>() {
@Override
public void ok(final Util.Pair<MaxPointsReason, Double> editedObject) {
getBusyIndicator().setBusy(true);
setBusyState(true);
getSailingService().updateLeaderboardScoreCorrection(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName,
editedObject.getB(), getLeaderboardDisplayDate(),
new AsyncCallback<Util.Triple<Double, Double, Boolean>>() {
@Override
public void onFailure(Throwable t) {
getBusyIndicator().setBusy(false);
setBusyState(false);
getErrorReporter().reportError("Error trying to update score correction for competitor "+
row.competitor.getName()+" in leaderboard "+getLeaderboardName()+
" for race "+raceColumnName+": "+t.getMessage()+
@@ -543,7 +543,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
new AsyncCallback<Util.Triple<Double, Double, Boolean>>() {
@Override
public void onFailure(Throwable t) {
getBusyIndicator().setBusy(false);
setBusyState(false);
getErrorReporter().reportError("Error trying to update score correction for competitor "+
row.competitor.getName()+" in leaderboard "+getLeaderboardName()+
" for race "+raceColumnName+": "+t.getMessage()+
@@ -552,7 +552,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
@Override
public void onSuccess(Util.Triple<Double, Double, Boolean> newNetAndTotalPointsAndIsCorrected) {
getBusyIndicator().setBusy(false);
setBusyState(false);
final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
leaderboardEntryDTO.reasonForMaxPoints = editedObject.getA();
leaderboardEntryDTO.netPoints = newNetAndTotalPointsAndIsCorrected.getA();
@@ -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>());
@@ -409,11 +409,11 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog<BulkSco
@Override
public void ok(final BulkScoreCorrectionDTO result) {
leaderboardPanel.getBusyIndicator().setBusy(true);
leaderboardPanel.setBusyState(true);
sailingService.updateLeaderboardScoreCorrectionsAndMaxPointsReasons(result, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
leaderboardPanel.getBusyIndicator().setBusy(false);
leaderboardPanel.setBusyState(false);
errorReporter.reportError(stringMessages.errorUpdatingScoresForLeaderboard(result.getLeaderboardName(),
caught.getMessage()));
}
@@ -214,19 +214,19 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog<Util.Triple<S
final String eventName = providerNameAndEventNameBoatClassNameCapturedWhen.getB();
final String boatClassName = providerNameAndEventNameBoatClassNameCapturedWhen.getC().getA();
final Date timePointWhenResultPublished = providerNameAndEventNameBoatClassNameCapturedWhen.getC().getB();
leaderboardPanel.getBusyIndicator().setBusy(true);
leaderboardPanel.setBusyState(true);
sailingService.getScoreCorrections(scoreCorrectionProviderName, eventName, boatClassName, timePointWhenResultPublished,
new AsyncCallback<RegattaScoreCorrectionDTO>() {
@Override
public void onFailure(Throwable caught) {
leaderboardPanel.getBusyIndicator().setBusy(false);
leaderboardPanel.setBusyState(false);
errorReporter.reportError(stringMessages.errorObtainingScoreCorrections(scoreCorrectionProviderName,
eventName, boatClassName, timePointWhenResultPublished.toString(), caught.getMessage()));
}
@Override
public void onSuccess(RegattaScoreCorrectionDTO result) {
leaderboardPanel.getBusyIndicator().setBusy(false);
leaderboardPanel.setBusyState(false);
new MatchAndApplyScoreCorrectionsDialog(leaderboardPanel, stringMessages, sailingService,
errorReporter, result).show();
}
@@ -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";
@@ -233,6 +233,7 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
@@ -853,7 +854,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
final RaceColumnDTO raceColumnDTO = columnFactory.createRaceColumnDTO(raceColumn.getName(),
raceColumn.isMedalRace(), raceColumn.getExplicitFactor(),
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null);
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
raceColumn instanceof MetaLeaderboardColumn);
raceColumnDTOs.add(raceColumnDTO);
}
return raceColumnDTOs;
@@ -1023,7 +1025,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
@Override
public void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI, String storedURI,
String courseDesignUpdateURI, boolean trackWind, final boolean correctWindByDeclination,
final boolean simulateWithStartTimeNow, final boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword)
final Duration offsetToStartTimeOfSimulatedRace, final boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword)
throws Exception {
logger.info("tracWithTracTrac for regatta " + regattaToAddTo + " for race records " + rrs + " with liveURI " + liveURI
+ " and storedURI " + storedURI);
@@ -1054,7 +1056,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
new URI(courseDesignUpdateURI), new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()),
new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()),
getRaceLogStore(), getRegattaLogStore(),
RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, simulateWithStartTimeNow,
RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, offsetToStartTimeOfSimulatedRace,
useInternalMarkPassingAlgorithm, tracTracUsername, tracTracPassword, record.getRaceStatus(), record.getRaceVisibility());
if (trackWind) {
new Thread("Wind tracking starter for race " + record.getEventName() + "/" + record.getName()) {
@@ -2408,7 +2410,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO);
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO, raceColumn instanceof MetaLeaderboardColumn);
RaceLog raceLog = raceColumn.getRaceLog(fleet);
RaceLogTrackingState raceLogTrackingState = raceLog == null ? RaceLogTrackingState.NOT_A_RACELOG_TRACKED_RACE :
new RaceLogTrackingStateAnalyzer(raceLog).analyze();
@@ -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,
@@ -4,6 +4,31 @@
<div id="mainContent">
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
<div class="innerContent">
<h5 class="articleSubheadline">December 2015</h5>
<ul class="bulletList">
<li>New line hover behavior in race map. When the mouse is moved onto a line, for example the start line, a transparent,
wider line is activated behind it that acts as a buffer for the thin line, where the text displayed by the hover is still shown.
This makes it easier to hover over the lines. These wider lines can be made semi transparent instead of transparent
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>
<li>For overall / series leaderboards such as a national sailing league or the Extreme Sailing Series, the
live status of the overall leaderboard is now indicated. As long as a live race is ongoing, the respective
column in the overall leaderboard is highlighted, and a comment is displayed that indicates which regatta
is currently still live.
</li>
</ul>
<h5 class="articleSubheadline">November 2015</h5>
<ul class="bulletList">
@@ -1,25 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.server.gateway.serialization.shared.android</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.server.gateway.serialization.shared.android</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -70,7 +70,7 @@ public class TrackRaceBoatCompetitorMetadataReplicationTest extends AbstractServ
trackingParams = com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE
.createTrackingConnectivityParameters(paramURL, liveURI, storedURI, courseDesignUpdateURI,
startOfTracking, endOfTracking, /* delayToLiveInMillis */
0l, /* simulateWithStartTimeNow */false, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
0l, /* offsetToStartTimeOfSimulatedRace */null, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, tracTracUsername, tracTracPassword, "", "");
}
@@ -88,7 +88,7 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest {
trackingParams = com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE
.createTrackingConnectivityParameters(paramURL, liveURI, storedURI, courseDesignUpdateURI,
startOfTracking, endOfTracking, /* delayToLiveInMillis */
0l, /* simulateWithStartTimeNow */false, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
0l, /* offsetToStartTimeOfSimulatedRace */null, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, tracTracUsername, tracTracPassword, "", "");
}
+25 -25
View File
@@ -11,30 +11,30 @@
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+28 -28
View File
@@ -1,31 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sse.datamining.annotations</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sse.datamining.annotations</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -7,6 +7,9 @@ import com.google.gwt.text.client.IntegerParser;
import com.google.gwt.text.shared.Renderer;
import com.google.gwt.user.client.ui.ValueBox;
/**
* Renders integers in their box without any separators, non-localized, plain.
*/
public class IntegerBox extends ValueBox<Integer> {
private static final Renderer<Integer> RENDERER = new Renderer<Integer>() {
public String render(Integer object) {
@@ -7,4 +7,7 @@ public interface BusyIndicatorResources extends ClientBundle {
@Source("busy_indicator_circle.gif")
ImageResource busyIndicatorCircle();
@Source("busy_indicator_circle_inverted.gif")
ImageResource busyIndicatorCircleInverted();
}
@@ -0,0 +1,14 @@
package com.sap.sse.gwt.client.controls.busyindicator;
/**
* Allows UI components to observe the busy state of an operation.
*/
public interface BusyStateChangeListener {
/**
* @param busyState the new busy state
*/
void onBusyStateChange(boolean busyState);
}
@@ -0,0 +1,13 @@
package com.sap.sse.gwt.client.controls.busyindicator;
public interface BusyStateProvider {
void addBusyStateChangeListener(BusyStateChangeListener listener);
void removeBusyStateChangeListener(BusyStateChangeListener listener);
void setBusyState(boolean isBusy);
boolean isBusy();
}
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff. Show more