diff --git a/java/com.googlecode.java-diff-utils.test/META-INF/MANIFEST.MF b/java/com.googlecode.java-diff-utils.test/META-INF/MANIFEST.MF index 788a54e61a3..6164a87122b 100755 --- a/java/com.googlecode.java-diff-utils.test/META-INF/MANIFEST.MF +++ b/java/com.googlecode.java-diff-utils.test/META-INF/MANIFEST.MF @@ -5,7 +5,7 @@ Bundle-SymbolicName: com.googlecode.java-diff-utils.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: GOOGLECODE Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0" +Fragment-Host: com.googlecode.java-diff-utils Bundle-ClassPath: test/, . Import-Package: junit.framework;version="4.8.1" diff --git a/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF index 09f8c0245d4..016d976855e 100755 --- a/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.declination.test/META-INF/MANIFEST.MF @@ -5,9 +5,7 @@ Bundle-SymbolicName: com.sap.sailing.declination.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: com.sap.sailing.declination, - com.sap.sailing.domain, - com.sap.sailing.domain.common +Fragment-Host: com.sap.sailing.declination Import-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.impl, com.sap.sailing.domain.common.quadtree, diff --git a/java/com.sap.sailing.declination/META-INF/MANIFEST.MF b/java/com.sap.sailing.declination/META-INF/MANIFEST.MF index 115624cf7c9..88760480fec 100755 --- a/java/com.sap.sailing.declination/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.declination/META-INF/MANIFEST.MF @@ -8,8 +8,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sailing.domain, com.sap.sailing.domain.common -Export-Package: com.sap.sailing.declination, - com.sap.sailing.declination.impl;x-friends:="com.sap.sailing.declination.test" +Export-Package: com.sap.sailing.declination Import-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.impl, com.sap.sailing.domain.common.quadtree diff --git a/java/com.sap.sailing.domain.persistence.test/.classpath b/java/com.sap.sailing.domain.persistence.test/.classpath new file mode 100644 index 00000000000..e8ea977a694 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.domain.persistence.test/.project b/java/com.sap.sailing.domain.persistence.test/.project new file mode 100644 index 00000000000..8582563048a --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.persistence.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sailing.domain.persistence.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.persistence.test/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..b3876774e56 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Thu Jan 19 09:51:18 CET 2012 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/java/com.sap.sailing.domain.persistence.test/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.persistence.test/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 00000000000..79db5c270a4 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Tue Nov 08 17:32:18 CET 2011 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.domain.persistence.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.persistence.test/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..02d2ca272ae --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/META-INF/MANIFEST.MF @@ -0,0 +1,10 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Persistence +Bundle-SymbolicName: com.sap.sailing.domain.persistence.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Fragment-Host: com.sap.sailing.domain.persistence +Bundle-ActivationPolicy: lazy +Import-Package: org.junit;version="4.8.2" diff --git a/java/com.sap.sailing.domain.persistence.test/build.properties b/java/com.sap.sailing.domain.persistence.test/build.properties new file mode 100644 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.persistence.test/pom.xml b/java/com.sap.sailing.domain.persistence.test/pom.xml new file mode 100644 index 00000000000..845fb3fe795 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.persistence.test + eclipse-test-plugin + diff --git a/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/CollectionNameServiceTest.java b/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/CollectionNameServiceTest.java new file mode 100644 index 00000000000..0d8d5cd6ccb --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/CollectionNameServiceTest.java @@ -0,0 +1,31 @@ +package com.sap.sailing.domain.persistence.test; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.persistence.AlreadyRegisteredException; +import com.sap.sailing.domain.persistence.CollectionNameService; +import com.sap.sailing.domain.persistence.impl.CollectionNameServiceImpl; + +public class CollectionNameServiceTest { + private CollectionNameService service; + + @Before + public void setUp() { + service = new CollectionNameServiceImpl(); + } + + @Test + public void shouldWork() throws AlreadyRegisteredException { + service.registerExclusively(TestInterface.class, "a"); + service.registerExclusively(TestInterface.class, "b"); + service.registerExclusively(TestInterface.class, "B"); + service.registerExclusively(TestInterface.class, "B"); + } + + @Test(expected=AlreadyRegisteredException.class) + public void shouldNotWork() throws AlreadyRegisteredException { + service.registerExclusively(TestInterface.class, "b"); + service.registerExclusively(com.sap.sailing.domain.persistence.test.needadifferentpackage.TestInterface.class, "b"); + } +} diff --git a/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/TestInterface.java b/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/TestInterface.java new file mode 100644 index 00000000000..d645ceb699b --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/TestInterface.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.persistence.test; + +public interface TestInterface { + +} diff --git a/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/needadifferentpackage/TestInterface.java b/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/needadifferentpackage/TestInterface.java new file mode 100644 index 00000000000..f9b9a68b1b8 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence.test/src/com/sap/sailing/domain/persistence/test/needadifferentpackage/TestInterface.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.persistence.test.needadifferentpackage; + +public interface TestInterface { + +} diff --git a/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF index d868d5ffa5c..af9a853567f 100755 --- a/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF @@ -1,14 +1,17 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Persistence -Bundle-SymbolicName: com.sap.sailing.domain.persistence -Bundle-Version: 1.0.0.qualifier -Bundle-Vendor: SAP -Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: com.sap.sailing.mongodb, - com.sap.sailing.domain, - com.mongodb.driver;bundle-version="2.6.2", - com.sap.sailing.domain.common -Export-Package: com.sap.sailing.domain.persistence, - com.sap.sailing.domain.persistence.impl;x-friends:="com.sap.sailing.mongodb.test,com.sap.sailing.domain.swisstimingadapter.test" -Bundle-ActivationPolicy: lazy +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Persistence +Bundle-SymbolicName: com.sap.sailing.domain.persistence +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-Activator: com.sap.sailing.domain.persistence.impl.Activator +Require-Bundle: com.sap.sailing.mongodb, + com.sap.sailing.domain, + com.mongodb.driver;bundle-version="2.6.2", + com.sap.sailing.domain.common +Export-Package: com.sap.sailing.domain.persistence, + com.sap.sailing.domain.persistence.impl;x-friends:="com.sap.sailing.mongodb.test,com.sap.sailing.domain.swisstimingadapter.test,com.sap.sailing.domain.persistence.test", + com.sap.sailing.domain.persistence.media +Bundle-ActivationPolicy: lazy +Import-Package: org.osgi.framework;version="1.6.0" diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/AlreadyRegisteredException.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/AlreadyRegisteredException.java new file mode 100644 index 00000000000..52e7183508a --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/AlreadyRegisteredException.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.persistence; + +public class AlreadyRegisteredException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -9052060438723088594L; +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/CollectionNameService.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/CollectionNameService.java new file mode 100644 index 00000000000..e1e1965c059 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/CollectionNameService.java @@ -0,0 +1,41 @@ +package com.sap.sailing.domain.persistence; + +/** + * A collection name can only be registered once using this service. This + * ensures that no hidden conflicts are introduced, if somewhere two identical + * collection names were unknowingly to be used. + * + * This solves the problem of not having to hard-code all collection names + * centrally in an {@link Enum}, but still gives safety against hidden conflicts + * that might be difficult to find otherwise. + * + * {@link CollectionNameService#registerExclusively(String)} should therefore be + * called before any access to a collection. This might happen for example in + * the constructor of a bundle-specific domain factory singleton. + * + * @author Fredrik Teschke + * + */ +public interface CollectionNameService { + /** + * Should be called before accessing any collection. + * + * @param registerForInterface + * The interface to register the collection for. This might be a + * placeholder Interface that is used by multiple classes + * accessing one and the same collection, demonstrating that they + * have knowledge of each other. This is resolved to a String + * representing the fully qualified class name to avoid issues + * when bundles are restarted, in which case the identity of + * class objects changes, but the fully qualified class name + * String remains the same. + * @param collectionName + * @throws AlreadyRegisteredException + * Is thrown if the collection name has already been registered + * for another class. This shouldn't happen in a productive + * version, but should make aware of the problem while + * developing. + */ + void registerExclusively(Class registerForInterface, + String collectionName) throws AlreadyRegisteredException; +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java new file mode 100644 index 00000000000..4e32c72957c --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.persistence.impl; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import com.sap.sailing.domain.persistence.CollectionNameService; + +public class Activator implements BundleActivator { + @Override + public void start(BundleContext context) throws Exception { + CollectionNameService service = new CollectionNameServiceImpl(); + context.registerService(CollectionNameService.class, service, null); + for (CollectionNames name : CollectionNames.values()) + service.registerExclusively(CollectionNames.class, name.name()); + } + + @Override + public void stop(BundleContext context) throws Exception { + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNameServiceImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNameServiceImpl.java new file mode 100644 index 00000000000..d4713c6d5f3 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNameServiceImpl.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.persistence.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.persistence.AlreadyRegisteredException; +import com.sap.sailing.domain.persistence.CollectionNameService; + +public class CollectionNameServiceImpl implements CollectionNameService { + /** + * collection name -> fully qualified class name + */ + private Map registered = new HashMap(); + + private static final Logger logger = Logger.getLogger(CollectionNameService.class.getName()); + + @Override + public void registerExclusively(Class registerForInterface, String collectionName) + throws AlreadyRegisteredException { + String fullyQualified = registerForInterface.getName(); + if (registered.keySet().contains(collectionName) && registered.get(collectionName) != fullyQualified) { + logger.log(Level.SEVERE, "Same collection name (" + collectionName + " is required in two different places - this may lead to problems: \n" + + " - already registered for: " + registered.get(collectionName) + "\n" + + " - tried to register for: " + fullyQualified); + throw new AlreadyRegisteredException(); + } + logger.log(Level.INFO, "Registered collection name: " + collectionName); + registered.put(collectionName, fullyQualified); + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/DBMediaTrack.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/DBMediaTrack.java new file mode 100644 index 00000000000..d0b88798320 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/DBMediaTrack.java @@ -0,0 +1,23 @@ +package com.sap.sailing.domain.persistence.media; + +import java.util.Date; + +public class DBMediaTrack { + + public final String dbId; + public final String title; + public final String url; + public final Date startTime; + public final int durationInMillis; + public final String mimeType; + + public DBMediaTrack(String dbId, String title, String url, Date startTime, int duration, String mimeType) { + super(); + this.dbId = dbId; + this.title = title; + this.url = url; + this.startTime = startTime; + this.durationInMillis = duration; + this.mimeType = mimeType; + } +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaDB.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaDB.java new file mode 100644 index 00000000000..4d30c6bf801 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaDB.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.persistence.media; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * Offers CRUD methods for mongo representation of media track objects. + * + * @author Jens Rommel (d047974) + * + */ +public interface MediaDB { + + String insertMediaTrack(String title, String url, Date startTime, int durationInMillis, String mimeType); + + DBMediaTrack loadMediaTrack(String dbId); + + List loadAllMediaTracks(); + + void deleteMediaTrack(String title); + + void updateTitle(String dbId, String title); + + void updateUrl(String dbId, String url); + + void updateStartTime(String dbId, Date startTime); + + void updateDuration(String dbId, int durationInMillis); + + Collection queryOverlappingMediaTracks(Date rangeStart, Date rangeEnd); + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaDBFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaDBFactory.java new file mode 100644 index 00000000000..448690c091b --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaDBFactory.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.persistence.media; + +import com.sap.sailing.domain.persistence.media.impl.MediaDBFactoryImpl; +import com.sap.sailing.mongodb.MongoDBService; + +public interface MediaDBFactory { + MediaDBFactory INSTANCE = new MediaDBFactoryImpl(); + + MediaObjectFactory getDefaultDomainObjectFactory(); + MediaObjectFactory getDomainObjectFactory(MongoDBService mongoDBService); + MediaDB getDefaultMediaDB(); + MediaDB getMediaDB(MongoDBService mongoDBService); +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaObjectFactory.java new file mode 100644 index 00000000000..678ae3a8983 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaObjectFactory.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.persistence.media; + + +/** + * Offers methods to load media objects from a Mongo DB + * + * @author Jens Rommel (d047974) + * + */ +public interface MediaObjectFactory { +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/CollectionNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/CollectionNames.java new file mode 100644 index 00000000000..d64a6e15150 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/CollectionNames.java @@ -0,0 +1,50 @@ +package com.sap.sailing.domain.persistence.media.impl; + +/** + * Defines literals providing the names for MongoDB collections. The literal documentation described the semantics + * of the collection identified by that literal. + * + * @author Axel Uhl (D043530) + * + */ +public enum CollectionNames { + /** + * Stores the wind fixes recorded from persistent wind sources. + */ + WIND_TRACKS, + + /** + * Stores the leaderboards with their names, score corrections, competitor display name overrides, race + * columns and their fleets as well as the per-fleet tracked race assignments. + */ + LEADERBOARDS, + + /** + * Stores the leaderboard group configurations with references to the {@link #LEADERBOARDS} collection + */ + LEADERBOARD_GROUPS, + + /** + * Top-level event information about events such as Kiel Week 2011, or IDM Travem�nde 2011, including name and + * course areas. + */ + EVENTS, + + /** + * Stores regatta definitions including their series layout and fleets and race columns. Regattas can reference + * the event from the {@link #EVENTS} collection to which they belong. + */ + REGATTAS, + + /** + * Stores boat class-specific master data such as the class's hull length, logo, name, number of sailors, etc. + * To be implemented in future versions. + */ + BOAT_CLASSES, + + /** + * Stores the mapping of {@link RaceDefinition#getId race IDs} to regatta names for automatic re-association + * when tracking races again without explicitly specifying a regatta. + */ + REGATTA_FOR_RACE_ID; +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/DbNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/DbNames.java new file mode 100644 index 00000000000..54e31d0c245 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/DbNames.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.persistence.media.impl; + +public class DbNames { + + public enum Fields { + _id, MEDIA_TITLE, MEDIA_URL, STARTTIME, DURATION_IN_MILLIS, MIME_TYPE; + } + + public enum Collections { + VIDEOS; + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaDBFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaDBFactoryImpl.java new file mode 100644 index 00000000000..de6f4e2d946 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaDBFactoryImpl.java @@ -0,0 +1,38 @@ +package com.sap.sailing.domain.persistence.media.impl; + +import com.sap.sailing.domain.persistence.media.MediaDB; +import com.sap.sailing.domain.persistence.media.MediaDBFactory; +import com.sap.sailing.domain.persistence.media.MediaObjectFactory; +import com.sap.sailing.mongodb.MongoDBService; + +public class MediaDBFactoryImpl implements MediaDBFactory { + private final MediaObjectFactory defaultMediaObjectFactory; + private final MediaDB defaultMediaDB; + + public MediaDBFactoryImpl() { + super(); + this.defaultMediaObjectFactory = new MediaObjectFactoryImpl(MongoDBService.INSTANCE.getDB()); + this.defaultMediaDB = new MediaDBImpl(MongoDBService.INSTANCE.getDB()); + } + + @Override + public MediaObjectFactory getDefaultDomainObjectFactory() { + return defaultMediaObjectFactory; + } + + @Override + public MediaObjectFactory getDomainObjectFactory(MongoDBService mongoDBService) { + return new MediaObjectFactoryImpl(mongoDBService.getDB()); + } + + @Override + public MediaDB getDefaultMediaDB() { + return defaultMediaDB; + } + + @Override + public MediaDB getMediaDB(MongoDBService mongoDBService) { + return new MediaDBImpl(mongoDBService.getDB()); + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaDBImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaDBImpl.java new file mode 100644 index 00000000000..b8cfcdb62c2 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaDBImpl.java @@ -0,0 +1,180 @@ +package com.sap.sailing.domain.persistence.media.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import org.bson.types.ObjectId; + +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import com.sap.sailing.domain.persistence.media.DBMediaTrack; +import com.sap.sailing.domain.persistence.media.MediaDB; + +/** + * MongoDB Java API examples: http://blog.rasc.ch/?p=1096 + * + * @author D047974 + * + */ +public class MediaDBImpl implements MediaDB { + + private static final int SORT_ASCENDING = 1; + private static final int SORT_DESSCENDING = -1; + // private static Logger logger = Logger.getLogger(MediaDBImpl.class.getName()); + private final DB database; + private final BasicDBObject sortByStartTimeAndTitle; + + public MediaDBImpl(DB database) { + super(); + this.database = database; + sortByStartTimeAndTitle = new BasicDBObject(); + sortByStartTimeAndTitle.put(DbNames.Fields.STARTTIME.name(), SORT_DESSCENDING); + sortByStartTimeAndTitle.put(DbNames.Fields.MEDIA_TITLE.name(), SORT_ASCENDING); + } + + @Override + public String insertMediaTrack(String videoTitle, String url, Date startTime, int durationInMillis, String mimeType) { + BasicDBObject dbVideo = new BasicDBObject(); + dbVideo.put(DbNames.Fields.MEDIA_TITLE.name(), videoTitle); + dbVideo.put(DbNames.Fields.MEDIA_URL.name(), url); + dbVideo.put(DbNames.Fields.STARTTIME.name(), startTime); + dbVideo.put(DbNames.Fields.DURATION_IN_MILLIS.name(), durationInMillis); + dbVideo.put(DbNames.Fields.MIME_TYPE.name(), mimeType); + DBCollection dbVideos = getVideoCollection(); + dbVideos.insert(dbVideo); + return ((ObjectId) dbVideo.get(DbNames.Fields._id.name())).toString(); + } + + private DBCollection getVideoCollection() { + try { + DBCollection dbVideos = database.getCollection(DbNames.Collections.VIDEOS.name()); + dbVideos.ensureIndex(DbNames.Collections.VIDEOS.name()); + return dbVideos; + } catch (NullPointerException e) { + // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE + throw new RuntimeException(e); + } + } + + @Override + public DBMediaTrack loadMediaTrack(String dbId) { + ObjectId objectId = new ObjectId(dbId); + DBCursor cursor = getVideoCollection().find(new BasicDBObject(DbNames.Fields._id.name(), objectId)); + switch (cursor.count()) { + case 0: + throw new RuntimeException("Video not found with id: " + dbId); + case 1: + DBObject dbObject = cursor.next(); + DBMediaTrack dbMediaTrack = createMediaObjectFromDB(dbObject); + return dbMediaTrack; + default: + throw new RuntimeException("Ambiguous videos not found with id: " + dbId); + + } + } + + private DBMediaTrack createMediaObjectFromDB(DBObject dbObject) { + String dbId = ((ObjectId) dbObject.get(DbNames.Fields._id.name())).toString(); + String title = (String) dbObject.get(DbNames.Fields.MEDIA_TITLE.name()); + String url = (String) dbObject.get(DbNames.Fields.MEDIA_URL.name()); + Date startTime = (Date) dbObject.get(DbNames.Fields.STARTTIME.name()); + Integer durationInMillis = (Integer) dbObject.get(DbNames.Fields.DURATION_IN_MILLIS.name()); + String mimeType = (String) dbObject.get(DbNames.Fields.MIME_TYPE.name()); + DBMediaTrack dbMediaTrack = new DBMediaTrack(dbId, title, url, startTime, durationInMillis == null ? 0 : durationInMillis, mimeType); + return dbMediaTrack; + } + + @Override + public List loadAllMediaTracks() { + DBCursor cursor = getVideoCollection().find().sort(sortByStartTimeAndTitle); + List result = new ArrayList<>(cursor.count()); + while (cursor.hasNext()) { + result.add(createMediaObjectFromDB(cursor.next())); + } + return result; + } + + @Override + public void deleteMediaTrack(String dbId) { + BasicDBObject dbObject = new BasicDBObject(); + dbObject.put(DbNames.Fields._id.name(), new ObjectId(dbId)); + getVideoCollection().remove(dbObject); + } + + @Override + public void updateTitle(String dbId, String title) { + BasicDBObject updateQuery = new BasicDBObject(); + updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId)); + + BasicDBObject update = new BasicDBObject(); + update.append("$set", new BasicDBObject(DbNames.Fields.MEDIA_TITLE.name(), title)); + + getVideoCollection().update(updateQuery, update); + } + + @Override + public void updateUrl(String dbId, String url) { + BasicDBObject updateQuery = new BasicDBObject(); + updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId)); + + BasicDBObject update = new BasicDBObject(); + update.append("$set", new BasicDBObject(DbNames.Fields.MEDIA_URL.name(), url)); + + getVideoCollection().update(updateQuery, update); + } + + @Override + public void updateStartTime(String dbId, Date startTime) { + BasicDBObject updateQuery = new BasicDBObject(); + updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId)); + + BasicDBObject update = new BasicDBObject(); + update.append("$set", new BasicDBObject(DbNames.Fields.STARTTIME.name(), startTime)); + + getVideoCollection().update(updateQuery, update); + } + + @Override + public void updateDuration(String dbId, int durationInMillis) { + BasicDBObject updateQuery = new BasicDBObject(); + updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId)); + + BasicDBObject update = new BasicDBObject(); + update.append("$set", new BasicDBObject(DbNames.Fields.DURATION_IN_MILLIS.name(), durationInMillis)); + + getVideoCollection().update(updateQuery, update); + } + + @Override + public Collection queryOverlappingMediaTracks(Date rangeStart, Date rangeEnd) { + BasicDBObject startTimeCondition = new BasicDBObject(); + startTimeCondition.put(DbNames.Fields.STARTTIME.name(), new BasicDBObject("$gt", rangeStart)); + startTimeCondition.put(DbNames.Fields.STARTTIME.name(), new BasicDBObject("$lt", rangeEnd)); + BasicDBObject endTimeCondition = new BasicDBObject(); + + // Should actually be "AND greater than rangeStart - duration". + // However, using values calculated on server side turns out to be a nightmare with mongodb. + // Instead do the remaining filtering on client side... + endTimeCondition.put(DbNames.Fields.STARTTIME.name(), new BasicDBObject("$lt", rangeStart)); + + DBObject query = QueryBuilder.start().or(startTimeCondition, endTimeCondition).get(); + + DBCursor cursor = getVideoCollection().find(query).sort(sortByStartTimeAndTitle); + + List result = new ArrayList<>(cursor.count()); + while (cursor.hasNext()) { + DBMediaTrack resultCandidate = createMediaObjectFromDB(cursor.next()); + if (resultCandidate.durationInMillis > rangeStart.getTime() - resultCandidate.startTime.getTime()) { + result.add(resultCandidate); + } + } + return result; + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaObjectFactoryImpl.java new file mode 100644 index 00000000000..3eeb7c323e0 --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaObjectFactoryImpl.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.persistence.media.impl; + +import com.mongodb.DB; +import com.sap.sailing.domain.persistence.media.MediaObjectFactory; + +public class MediaObjectFactoryImpl implements MediaObjectFactory { + +// private static final Logger logger = Logger.getLogger(MediaObjectFactoryImpl.class.getName()); + +// private final DB database; + + public MediaObjectFactoryImpl(DB db) { + super(); +// this.database = db; + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MongoUtils.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MongoUtils.java new file mode 100644 index 00000000000..2ac17d56e0a --- /dev/null +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MongoUtils.java @@ -0,0 +1,39 @@ +package com.sap.sailing.domain.persistence.media.impl; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +public class MongoUtils { + /** + * Key names in MongoDB must not start with a $ sign and must not contain a "." character. This method escapes + * those. {@link #unescapeDollarAndDot} is the inverse function. + * + * @param key + * a non-null string which may have length 0 + * @return a string that can be used as a key in a MongoDB key-value pair and that can be decoded again using + * {@link #unescapeDollarAndDot(String)}. + */ + public static String escapeDollarAndDot(String key) { + String result = key.replace("%", "%25").replace("+", "%2B"); + if (result.length() > 0 && result.charAt(0) == '$') { + result = "%24"+result.substring(1); + } + return result.replace(".", "%2E"); + } + + /** + * @see #escapeDollarAndDot(String) + * @param key + * a non-null string which may have length 0 and that was returned by + * {@link #escapeDollarAndDot(String)}. + * @return the string originally passed to {@link #escapeDollarAndDot(String)} + */ + public static String unescapeDollarAndDot(String escapedKey) { + try { + return URLDecoder.decode(escapedKey, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("How come this VM doesn't know UTF-8?"); + } + } + +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF index 1e083c875a3..0f56fbee484 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/META-INF/MANIFEST.MF @@ -5,9 +5,8 @@ Bundle-SymbolicName: com.sap.sailing.domain.swisstimingadapter.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: com.sap.sailing.domain.swisstimingadapter.persistence, - com.sap.sailing.domain.swisstimingadapter, - com.sap.sailing.domain, +Fragment-Host: com.sap.sailing.domain.swisstimingadapter +Require-Bundle: com.sap.sailing.domain, com.sap.sailing.udpconnector, com.mongodb.driver;bundle-version="2.6.2", com.sap.sailing.mongodb, @@ -15,7 +14,8 @@ Require-Bundle: com.sap.sailing.domain.swisstimingadapter.persistence, com.sap.sailing.server, com.sap.sailing.domain.common, com.sap.sailing.domain.persistence, - com.sap.sailing.domain.tractracadapter + com.sap.sailing.domain.tractracadapter, + com.sap.sailing.domain.swisstimingadapter.persistence;bundle-version="1.0.0" Bundle-ClassPath: resources/, . Import-Package: com.sap.sailing.server, diff --git a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java index 8cc00d2489d..9407c4f4b3a 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/classes/services/MessageFileServiceImpl.java @@ -51,7 +51,7 @@ public class MessageFileServiceImpl implements MessageFileService { private final DateFormat timeFormat; public MessageFileServiceImpl() { - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ"); + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); timeFormat = new SimpleDateFormat("HH:mm:ss"); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF index 17af4b97bf0..18bb4067e06 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.swisstimingadapter/META-INF/MANIFEST.MF @@ -10,5 +10,4 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.udpconnector, com.googlecode.java-diff-utils;bundle-version="1.3.0", com.sap.sailing.domain.common -Export-Package: com.sap.sailing.domain.swisstimingadapter, - com.sap.sailing.domain.swisstimingadapter.impl;x-friends:="com.sap.sailing.domain.swisstimingadapter.test" +Export-Package: com.sap.sailing.domain.swisstimingadapter diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java index df03e0967d5..67cc92e6585 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java @@ -126,7 +126,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement public SailMasterConnectorImpl(String host, int port, RaceSpecificMessageLoader messageLoader, boolean canSendRequests) throws InterruptedException { super(); this.messageLoader = messageLoader; - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ"); + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); idsOfTrackedRaces = new HashSet(); this.host = host; this.port = port; diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF index 52d4f4abb60..be57064ce28 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/META-INF/MANIFEST.MF @@ -11,5 +11,4 @@ Require-Bundle: com.sap.sailing.domain.common, org.json.simple;bundle-version="1.1.0", com.sap.sailing.domain.swisstimingadapter, com.googlecode.java-diff-utils;bundle-version="1.3.0" -Export-Package: com.sap.sailing.domain.swisstimingreplayadapter, - com.sap.sailing.domain.swisstimingreplayadapter.impl;x-friends:="com.sap.sailing.domain.swisstimingreplayadapter.test" +Export-Package: com.sap.sailing.domain.swisstimingreplayadapter diff --git a/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF index 6e6f2651bb2..b4a158b7d32 100755 --- a/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.tractracadapter.test/META-INF/MANIFEST.MF @@ -5,7 +5,6 @@ Bundle-SymbolicName: com.sap.sailing.domain.tractracadapter.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Fragment-Host: com.sap.sailing.domain.tractracadapter Require-Bundle: org.junit4;bundle-version="4.8.2", - com.sap.sailing.domain.tractracadapter, - com.sap.sailing.domain.common, com.tractrac.clientmodule;bundle-version="2.0.6" diff --git a/java/com.sap.sailing.domain.tractracadapter.test/resources/clientparamsArenal49erRace2.php b/java/com.sap.sailing.domain.tractracadapter.test/resources/clientparamsArenal49erRace2.php new file mode 100755 index 00000000000..e17420b61e0 --- /dev/null +++ b/java/com.sap.sailing.domain.tractracadapter.test/resources/clientparamsArenal49erRace2.php @@ -0,0 +1,974 @@ + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +CodanIsSponsor:0 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:0 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1Length:10 +Marker1MinPixelSize:30 +Startlocation:2.726669,39.511325 +MaxDiffTime:1200 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone:1 +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:1 +Minimapgeorect:2.658863,39.538337,2.763405,39.47357 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:18 +MetersPerPixelMax:30 +MetersPerPixelMin:0.05 +isMatchRace:0 +weatherEnabled:0 +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:13 +disableselection:0 +disableleaderline:0 +DisableChangeRace:0 +PopUpRow1Name: +PopUpRow1Type:Name +PopUpRow2Name:Knots +PopUpRow2Type:BoatSpeed +PopUpRow3Name:Ranking +PopUpRow3Type:RowNumber +PopUpRow4Name:Sail +PopUpRow4Type:Ini +PopUpRow5Name:After +PopUpRow5Type:BoatAfter +ResultColumn1Name:# +ResultColumn1Size:20 +ResultColumn1Type:RowNumber +ResultColumn2Size:20 +ResultColumn2Type:ColorMarker +ResultColumn3Name:sail +ResultColumn3Size:45 +ResultColumn3Type:Ini +ResultColumn4Name:name +ResultColumn4Size:110 +ResultColumn4Type:Name +ResultColumn5Name:after +ResultColumn5Size:45 +ResultColumn5Type:BoatAfter +ResultColumn6Name:kts +ResultColumn6Size:40 +ResultColumn6Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +live-uri:tcp://germanmaster.traclive.dk:4400 +stored-uri:tcp://germanmaster.traclive.dk:4401 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 20 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:f3a86784-6dea-11e1-b933-406186cbf87c +EventName:Arenal Training Camps Regatta 2012 +EventDB:event_20120314_ArenalTrai +EventStartTime:2012-03-14 15:00:00 +EventEndTime:2012-03-19 23:00:00 + +RaceID:fbb21f7a-6ee8-11e1-b933-406186cbf87c +RaceName:49er Race2 +RaceStartTime: +RaceEndTime: +RaceTrackingStartTime:2012-03-16 11:52:50 +RaceTrackingEndTime:2012-03-16 12:39:00 +RaceDefaultRouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c +RaceHandicapSystem:NONE + +Image1UUID:01d19daa-6fa6-11e1-b933-406186cbf87c +Image1Type:Logo +Image1Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif +Image1Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CRO.gif + +Image2UUID:41ab5dd0-6fa6-11e1-b933-406186cbf87c +Image2Type:Logo +Image2Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif +Image2Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NOR.gif + +Image3UUID:4857b5d4-6fa6-11e1-b933-406186cbf87c +Image3Type:Logo +Image3Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SIN.gif +Image3Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SIN.gif + +Image4UUID:4d5fdbc4-6fa6-11e1-b933-406186cbf87c +Image4Type:Logo +Image4Description:#I/T +Image4Image:#I/T + +Image5UUID:52f8363c-6ee6-11e1-b933-406186cbf87c +Image5Type:Logo +Image5Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif +Image5Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUS.gif + +Image6UUID:536f0c3a-6ee6-11e1-b933-406186cbf87c +Image6Type:Logo +Image6Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif +Image6Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BEL.gif + +Image7UUID:54070792-6ee6-11e1-b933-406186cbf87c +Image7Type:Logo +Image7Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BER.gif +Image7Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BER.gif + +Image8UUID:54fc35b4-6ee6-11e1-b933-406186cbf87c +Image8Type:Logo +Image8Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif +Image8Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CAN.gif + +Image9UUID:5670b53c-6ee6-11e1-b933-406186cbf87c +Image9Type:Logo +Image9Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif +Image9Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CHI.gif + +Image10UUID:576d7d3a-6ee6-11e1-b933-406186cbf87c +Image10Type:Logo +Image10Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif +Image10Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/DEN.gif + +Image11UUID:5a803274-6ee6-11e1-b933-406186cbf87c +Image11Type:Logo +Image11Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ESP.gif +Image11Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ESP.gif + +Image12UUID:5bc75766-6ee6-11e1-b933-406186cbf87c +Image12Type:Logo +Image12Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FIN.gif +Image12Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FIN.gif + +Image13UUID:5d29e8a8-6ee6-11e1-b933-406186cbf87c +Image13Type:Logo +Image13Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif +Image13Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GBR.gif + +Image14UUID:618ffdd8-6ee6-11e1-b933-406186cbf87c +Image14Type:Logo +Image14Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif +Image14Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/GER.gif + +Image15UUID:69986e7a-6ee6-11e1-b933-406186cbf87c +Image15Type:Logo +Image15Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif +Image15Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/IRL.gif + +Image16UUID:6b4de60a-6ee6-11e1-b933-406186cbf87c +Image16Type:Logo +Image16Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif +Image16Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MEX.gif + +Image17UUID:6d206b1a-6ee6-11e1-b933-406186cbf87c +Image17Type:Logo +Image17Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MLT.gif +Image17Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/MLT.gif + +Image18UUID:6ee7945a-6ee6-11e1-b933-406186cbf87c +Image18Type:Logo +Image18Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif +Image18Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POL.gif + +Image19UUID:73bc6c9e-6ee6-11e1-b933-406186cbf87c +Image19Type:Logo +Image19Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POR.gif +Image19Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/POR.gif + +Image20UUID:770eeeb2-6ee6-11e1-b933-406186cbf87c +Image20Type:Logo +Image20Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif +Image20Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RUS.gif + +Image21UUID:7be2558c-6ee6-11e1-b933-406186cbf87c +Image21Type:Logo +Image21Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif +Image21Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SWE.gif + +Image22UUID:7d82a11a-6fa6-11e1-b933-406186cbf87c +Image22Type:Logo +Image22Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ESA.gif +Image22Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ESA.gif + +Image23UUID:984a13ea-6ee6-11e1-b933-406186cbf87c +Image23Type:Logo +Image23Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ARG.gif +Image23Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ARG.gif + +Image24UUID:99aec92c-6fa6-11e1-b933-406186cbf87c +Image24Type:Logo +Image24Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif +Image24Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISV.gif + +Image25UUID:9a10bc06-6ee6-11e1-b933-406186cbf87c +Image25Type:Logo +Image25Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif +Image25Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/AUT.gif + +Image26UUID:a0a42076-6ee6-11e1-b933-406186cbf87c +Image26Type:Logo +Image26Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif +Image26Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/BRA.gif + +Image27UUID:a59b73fe-6ee6-11e1-b933-406186cbf87c +Image27Type:Logo +Image27Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CZE.gif +Image27Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/CZE.gif + +Image28UUID:af2e8d46-6fa6-11e1-b933-406186cbf87c +Image28Type:Logo +Image28Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif +Image28Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/USA.gif + +Image29UUID:b3fc8280-6ee6-11e1-b933-406186cbf87c +Image29Type:Logo +Image29Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif +Image29Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/EST.gif + +Image30UUID:b5c5a970-6ee6-11e1-b933-406186cbf87c +Image30Type:Logo +Image30Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif +Image30Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/FRA.gif + +Image31UUID:d4017a72-6ee6-11e1-b933-406186cbf87c +Image31Type:Logo +Image31Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISR.gif +Image31Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ISR.gif + +Image32UUID:d5c21b1e-6ee6-11e1-b933-406186cbf87c +Image32Type:Logo +Image32Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif +Image32Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/ITA.gif + +Image33UUID:dc4823a2-6ee6-11e1-b933-406186cbf87c +Image33Type:Logo +Image33Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif +Image33Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/NED.gif + +Image34UUID:e8ed3f7a-6ee6-11e1-b933-406186cbf87c +Image34Type:Logo +Image34Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RSA.gif +Image34Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/RSA.gif + +Image35UUID:f76fa4ca-6ee6-11e1-b933-406186cbf87c +Image35Type:Logo +Image35Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif +Image35Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/SUI.gif + +Image36UUID:ff7c8372-6ee6-11e1-b933-406186cbf87c +Image36Type:Logo +Image36Description:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/VEN.gif +Image36Image:http://germanmaster.traclive.dk/events/event_20100806_SailforGol/images/VEN.gif + +Class1UUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Class1Name:49er + +Team1UUID:4d45449e-6fa6-11e1-b933-406186cbf87c +Team1Name:#I/T + +Team2UUID:983b61ce-6ee6-11e1-b933-406186cbf87c +Team2Name:ARG + +Team3UUID:52cd9274-6ee6-11e1-b933-406186cbf87c +Team3Name:AUS +Team3Comp1UUID:5313e210-6ee6-11e1-b933-406186cbf87c + +Team4UUID:99f81250-6ee6-11e1-b933-406186cbf87c +Team4Name:AUT + +Team5UUID:5356a56e-6ee6-11e1-b933-406186cbf87c +Team5Name:BEL +Team5Comp1UUID:53a32b50-6ee6-11e1-b933-406186cbf87c + +Team6UUID:53ee5c2e-6ee6-11e1-b933-406186cbf87c +Team6Name:BER +Team6Comp1UUID:54556450-6ee6-11e1-b933-406186cbf87c + +Team7UUID:a08b579e-6ee6-11e1-b933-406186cbf87c +Team7Name:BRA + +Team8UUID:54e3b700-6ee6-11e1-b933-406186cbf87c +Team8Name:CAN +Team8Comp1UUID:555c70dc-6ee6-11e1-b933-406186cbf87c +Team8Comp2UUID:56058758-6ee6-11e1-b933-406186cbf87c + +Team9UUID:5657ff56-6ee6-11e1-b933-406186cbf87c +Team9Name:CHI +Team9Comp1UUID:56f1ae62-6ee6-11e1-b933-406186cbf87c + +Team10UUID:01b899ea-6fa6-11e1-b933-406186cbf87c +Team10Name:CRO + +Team11UUID:a582b90e-6ee6-11e1-b933-406186cbf87c +Team11Name:CZE + +Team12UUID:575532f2-6ee6-11e1-b933-406186cbf87c +Team12Name:DEN +Team12Comp1UUID:5a11e940-6ee6-11e1-b933-406186cbf87c +Team12Comp2UUID:5908fa70-6ee6-11e1-b933-406186cbf87c +Team12Comp3UUID:5814d396-6ee6-11e1-b933-406186cbf87c + +Team13UUID:7d73c4ba-6fa6-11e1-b933-406186cbf87c +Team13Name:ESA + +Team14UUID:5a676708-6ee6-11e1-b933-406186cbf87c +Team14Name:ESP +Team14Comp1UUID:5b6499dc-6ee6-11e1-b933-406186cbf87c + +Team15UUID:b3e452b4-6ee6-11e1-b933-406186cbf87c +Team15Name:EST + +Team16UUID:5bae9852-6ee6-11e1-b933-406186cbf87c +Team16Name:FIN +Team16Comp1UUID:5cd0fbf8-6ee6-11e1-b933-406186cbf87c + +Team17UUID:b5ac6550-6ee6-11e1-b933-406186cbf87c +Team17Name:FRA + +Team18UUID:5d11ab9e-6ee6-11e1-b933-406186cbf87c +Team18Name:GBR +Team18Comp1UUID:6122f2ec-6ee6-11e1-b933-406186cbf87c +Team18Comp2UUID:5f99e336-6ee6-11e1-b933-406186cbf87c +Team18Comp3UUID:5e3e9626-6ee6-11e1-b933-406186cbf87c +Team18Comp4UUID:a7196fde-6f53-11e1-b933-406186cbf87c + +Team19UUID:61773ce4-6ee6-11e1-b933-406186cbf87c +Team19Name:GER +Team19Comp1UUID:62f723b8-6ee6-11e1-b933-406186cbf87c +Team19Comp2UUID:6930298c-6ee6-11e1-b933-406186cbf87c +Team19Comp3UUID:65fe4d2a-6ee6-11e1-b933-406186cbf87c +Team19Comp4UUID:67939686-6ee6-11e1-b933-406186cbf87c +Team19Comp5UUID:64718ddc-6ee6-11e1-b933-406186cbf87c + +Team20UUID:697f7014-6ee6-11e1-b933-406186cbf87c +Team20Name:IRL +Team20Comp1UUID:6ae188de-6ee6-11e1-b933-406186cbf87c + +Team21UUID:d3e8ca4a-6ee6-11e1-b933-406186cbf87c +Team21Name:ISR + +Team22UUID:998c8f38-6fa6-11e1-b933-406186cbf87c +Team22Name:ISV + +Team23UUID:d5a95ef8-6ee6-11e1-b933-406186cbf87c +Team23Name:ITA + +Team24UUID:6b34823c-6ee6-11e1-b933-406186cbf87c +Team24Name:MEX +Team24Comp1UUID:6cb33b4e-6ee6-11e1-b933-406186cbf87c + +Team25UUID:6d078a28-6ee6-11e1-b933-406186cbf87c +Team25Name:MLT +Team25Comp1UUID:6e7ed51e-6ee6-11e1-b933-406186cbf87c + +Team26UUID:dc2f4fa8-6ee6-11e1-b933-406186cbf87c +Team26Name:NED + +Team27UUID:41929688-6fa6-11e1-b933-406186cbf87c +Team27Name:NOR + +Team28UUID:6ececdc6-6ee6-11e1-b933-406186cbf87c +Team28Name:POL +Team28Comp1UUID:70402e02-6ee6-11e1-b933-406186cbf87c +Team28Comp2UUID:734b63dc-6ee6-11e1-b933-406186cbf87c +Team28Comp3UUID:71b88e32-6ee6-11e1-b933-406186cbf87c + +Team29UUID:73a18352-6ee6-11e1-b933-406186cbf87c +Team29Name:POR +Team29Comp1UUID:75197a00-6ee6-11e1-b933-406186cbf87c +Team29Comp2UUID:769e4bbc-6ee6-11e1-b933-406186cbf87c + +Team30UUID:e8de5c8a-6ee6-11e1-b933-406186cbf87c +Team30Name:RSA + +Team31UUID:76f64830-6ee6-11e1-b933-406186cbf87c +Team31Name:RUS +Team31Comp1UUID:7859678e-6ee6-11e1-b933-406186cbf87c +Team31Comp2UUID:79f07024-6ee6-11e1-b933-406186cbf87c +Team31Comp3UUID:7b88f12c-6ee6-11e1-b933-406186cbf87c + +Team32UUID:4848c7c2-6fa6-11e1-b933-406186cbf87c +Team32Name:SIN + +Team33UUID:f752f280-6ee6-11e1-b933-406186cbf87c +Team33Name:SUI + +Team34UUID:7bc98548-6ee6-11e1-b933-406186cbf87c +Team34Name:SWE +Team34Comp1UUID:821f2aec-6ee6-11e1-b933-406186cbf87c +Team34Comp2UUID:7ee431b0-6ee6-11e1-b933-406186cbf87c +Team34Comp3UUID:8072c460-6ee6-11e1-b933-406186cbf87c +Team34Comp4UUID:7d40d3a4-6ee6-11e1-b933-406186cbf87c + +Team35UUID:af160b54-6fa6-11e1-b933-406186cbf87c +Team35Name:USA + +Team36UUID:ff63e510-6ee6-11e1-b933-406186cbf87c +Team36Name:VEN + +Comp1UUID:5313e210-6ee6-11e1-b933-406186cbf87c +Comp1Name:Thomas Zajac+Thomas Czajka +Comp1NameShort:AUS 070 +Comp1Color:808000 +Comp1Nationality:AUS +Comp1Description:Burgenlaendischer YC +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1PictureUUID:52f8363c-6ee6-11e1-b933-406186cbf87c +Comp1ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp1RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp2UUID:53a32b50-6ee6-11e1-b933-406186cbf87c +Comp2Name:Lefebvre+Janssens +Comp2NameShort:BEL 1232 +Comp2Color:FFFFFF +Comp2Nationality:BEL +Comp2Description:RNSYC/RBSC +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2PictureUUID:536f0c3a-6ee6-11e1-b933-406186cbf87c +Comp2ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp2RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp3UUID:54556450-6ee6-11e1-b933-406186cbf87c +Comp3Name:Jesse Kirkland+Alexander Kirkl +Comp3NameShort:BER 1214 +Comp3Color:FFFF00 +Comp3Nationality:BER +Comp3Description:Royal Bermuda Y.C. +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3PictureUUID:54070792-6ee6-11e1-b933-406186cbf87c +Comp3ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp3RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp4UUID:555c70dc-6ee6-11e1-b933-406186cbf87c +Comp4Name:Gordon Cook+Hunter Lowden +Comp4NameShort:CAN 049 +Comp4Color:800080 +Comp4Nationality:CAN +Comp4Description:RCYC/RVYC +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4PictureUUID:54fc35b4-6ee6-11e1-b933-406186cbf87c +Comp4ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp4RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp5UUID:56058758-6ee6-11e1-b933-406186cbf87c +Comp5Name:Jonathan Ladha+Daniel Inkpen +Comp5NameShort:CAN 1168 +Comp5Color:008080 +Comp5Nationality:CAN +Comp5Description:Royal Newfoundland Y.C. +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5PictureUUID:54fc35b4-6ee6-11e1-b933-406186cbf87c +Comp5ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp5RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp6UUID:56f1ae62-6ee6-11e1-b933-406186cbf87c +Comp6Name:Pablo Herman+Luis Felipe Heman +Comp6NameShort:CHI 1216 +Comp6Color:800000 +Comp6Nationality:CHI +Comp6Description:Cofradia Nautica Pacifico +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6PictureUUID:5670b53c-6ee6-11e1-b933-406186cbf87c +Comp6ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp6RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp7UUID:5814d396-6ee6-11e1-b933-406186cbf87c +Comp7Name:Peter Krüger+Royan Danish +Comp7NameShort:DEN 1242 +Comp7Color:000080 +Comp7Nationality:DEN +Comp7Description:Yacht Club +Comp7Handicap:1.0000000000 +Comp7HandicapDistance:0.00000 +Comp7PictureUUID:576d7d3a-6ee6-11e1-b933-406186cbf87c +Comp7ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp7RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp8UUID:5908fa70-6ee6-11e1-b933-406186cbf87c +Comp8Name:Jonas Warrer+Soren Hansen +Comp8NameShort:DEN 8 +Comp8Color:00FFFF +Comp8Nationality:DEN +Comp8Description:Aarhus Sejlclub/RDY +Comp8Handicap:1.0000000000 +Comp8HandicapDistance:0.00000 +Comp8PictureUUID:576d7d3a-6ee6-11e1-b933-406186cbf87c +Comp8ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp8RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp9UUID:5a11e940-6ee6-11e1-b933-406186cbf87c +Comp9Name:Allan Norregard+Peter Lang +Comp9NameShort:DEN 9 +Comp9Color:000000 +Comp9Nationality:DEN +Comp9Description:Kolding Sejlklub +Comp9Handicap:1.0000000000 +Comp9HandicapDistance:0.00000 +Comp9PictureUUID:576d7d3a-6ee6-11e1-b933-406186cbf87c +Comp9ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp9RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp10UUID:5b6499dc-6ee6-11e1-b933-406186cbf87c +Comp10Name:Federico Alonso+Arturo Alonso +Comp10NameShort:ESP 1220 +Comp10Color:0000FF +Comp10Nationality:ESP +Comp10Description:R.C.A.R. +Comp10Handicap:1.0000000000 +Comp10HandicapDistance:0.00000 +Comp10PictureUUID:5a803274-6ee6-11e1-b933-406186cbf87c +Comp10ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp10RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp11UUID:5cd0fbf8-6ee6-11e1-b933-406186cbf87c +Comp11Name:Joonas Lindahl+Joel Tykkylaine +Comp11NameShort:FIN 350 +Comp11Color:FF00FF +Comp11Nationality:FIN +Comp11Description:EPS +Comp11Handicap:1.0000000000 +Comp11HandicapDistance:0.00000 +Comp11PictureUUID:5bc75766-6ee6-11e1-b933-406186cbf87c +Comp11ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp11RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp12UUID:5e3e9626-6ee6-11e1-b933-406186cbf87c +Comp12Name:Max Richardson+Nick Redding +Comp12NameShort:GBR 1186 +Comp12Color:008000 +Comp12Nationality:GBR +Comp12Description:Hastings +Comp12Handicap:1.0000000000 +Comp12HandicapDistance:0.00000 +Comp12PictureUUID:5d29e8a8-6ee6-11e1-b933-406186cbf87c +Comp12ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp12RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp13UUID:5f99e336-6ee6-11e1-b933-406186cbf87c +Comp13Name:James Peters+Ed Fitzgerald +Comp13NameShort:GBR 1187 +Comp13Color:00FF00 +Comp13Nationality:GBR +Comp13Description:H.I.S.C. +Comp13Handicap:1.0000000000 +Comp13HandicapDistance:0.00000 +Comp13PictureUUID:5d29e8a8-6ee6-11e1-b933-406186cbf87c +Comp13ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp13RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp14UUID:6122f2ec-6ee6-11e1-b933-406186cbf87c +Comp14Name:Dylan Fletcher+Alain Sign +Comp14NameShort:GBR 1280 +Comp14Color:FF0000 +Comp14Nationality:GBR +Comp14Description:Great Bear +Comp14Handicap:1.0000000000 +Comp14HandicapDistance:0.00000 +Comp14PictureUUID:5d29e8a8-6ee6-11e1-b933-406186cbf87c +Comp14ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp14RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp15UUID:62f723b8-6ee6-11e1-b933-406186cbf87c +Comp15Name:Erik Heil+Thomas Ploessel +Comp15NameShort:GER 100 +Comp15Color:808000 +Comp15Nationality:GER +Comp15Description:Nordedeutscher R.V. +Comp15Handicap:1.0000000000 +Comp15HandicapDistance:0.00000 +Comp15PictureUUID:618ffdd8-6ee6-11e1-b933-406186cbf87c +Comp15ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp15RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp16UUID:64718ddc-6ee6-11e1-b933-406186cbf87c +Comp16Name:Tobias Schadewaldt+Hannes Baum +Comp16NameShort:GER 1213 +Comp16Color:FFFFFF +Comp16Nationality:GER +Comp16Description:IURV +Comp16Handicap:1.0000000000 +Comp16HandicapDistance:0.00000 +Comp16PictureUUID:618ffdd8-6ee6-11e1-b933-406186cbf87c +Comp16ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp16RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp17UUID:65fe4d2a-6ee6-11e1-b933-406186cbf87c +Comp17Name:Jan Hauke Erichsen+Max Lutz +Comp17NameShort:GER 216 +Comp17Color:FFFF00 +Comp17Nationality:GER +Comp17Description:Flensburger S.C. +Comp17Handicap:1.0000000000 +Comp17HandicapDistance:0.00000 +Comp17PictureUUID:618ffdd8-6ee6-11e1-b933-406186cbf87c +Comp17ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp17RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp18UUID:67939686-6ee6-11e1-b933-406186cbf87c +Comp18Name:Justus Schmidt+Max Boehme +Comp18NameShort:GER 903 +Comp18Color:800080 +Comp18Nationality:GER +Comp18Description:KYC +Comp18Handicap:1.0000000000 +Comp18HandicapDistance:0.00000 +Comp18PictureUUID:618ffdd8-6ee6-11e1-b933-406186cbf87c +Comp18ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp18RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp19UUID:6930298c-6ee6-11e1-b933-406186cbf87c +Comp19Name:Finn Hinzpeter+Kevin Reisenaue +Comp19NameShort:GER 914 +Comp19Color:008080 +Comp19Nationality:GER +Comp19Description:DFSC +Comp19Handicap:1.0000000000 +Comp19HandicapDistance:0.00000 +Comp19PictureUUID:618ffdd8-6ee6-11e1-b933-406186cbf87c +Comp19ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp19RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp20UUID:6ae188de-6ee6-11e1-b933-406186cbf87c +Comp20Name:Ryan Seaton+Matt Mc Govern +Comp20NameShort:IRL 1 +Comp20Color:800000 +Comp20Nationality:IRL +Comp20Description:C.S.C. +Comp20Handicap:1.0000000000 +Comp20HandicapDistance:0.00000 +Comp20PictureUUID:69986e7a-6ee6-11e1-b933-406186cbf87c +Comp20ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp20RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp21UUID:6cb33b4e-6ee6-11e1-b933-406186cbf87c +Comp21Name:Jorge Xavier Murrieta+Alejandr +Comp21NameShort:MEX 1224 +Comp21Color:000080 +Comp21Nationality:MEX +Comp21Description:C.N.Avandaro +Comp21Handicap:1.0000000000 +Comp21HandicapDistance:0.00000 +Comp21PictureUUID:6b4de60a-6ee6-11e1-b933-406186cbf87c +Comp21ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp21RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp22UUID:6e7ed51e-6ee6-11e1-b933-406186cbf87c +Comp22Name:Benji Bors+Sebastian Ripard +Comp22NameShort:MLT 11 +Comp22Color:00FFFF +Comp22Nationality:MLT +Comp22Description:MYSC +Comp22Handicap:1.0000000000 +Comp22HandicapDistance:0.00000 +Comp22PictureUUID:6d206b1a-6ee6-11e1-b933-406186cbf87c +Comp22ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp22RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp23UUID:70402e02-6ee6-11e1-b933-406186cbf87c +Comp23Name:Lukasz Przybitek+Pawel Kolodzi +Comp23NameShort:POL 042 +Comp23Color:000000 +Comp23Nationality:POL +Comp23Description:AZS AWFIS GDAÑSK +Comp23Handicap:1.0000000000 +Comp23HandicapDistance:0.00000 +Comp23PictureUUID:6ee7945a-6ee6-11e1-b933-406186cbf87c +Comp23ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp23RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp24UUID:71b88e32-6ee6-11e1-b933-406186cbf87c +Comp24Name:Tomasz Janusiewski+Jacek Nowak +Comp24NameShort:POL 1 +Comp24Color:0000FF +Comp24Nationality:POL +Comp24Description:AIS UWM Olsztyn +Comp24Handicap:1.0000000000 +Comp24HandicapDistance:0.00000 +Comp24PictureUUID:6ee7945a-6ee6-11e1-b933-406186cbf87c +Comp24ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp24RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp25UUID:734b63dc-6ee6-11e1-b933-406186cbf87c +Comp25Name:Marcin Czajnowski+Jacek Piasec +Comp25NameShort:POL 117 +Comp25Color:FF00FF +Comp25Nationality:POL +Comp25Description:YKP/SKZ Hestia Sopot +Comp25Handicap:1.0000000000 +Comp25HandicapDistance:0.00000 +Comp25PictureUUID:6ee7945a-6ee6-11e1-b933-406186cbf87c +Comp25ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp25RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp26UUID:75197a00-6ee6-11e1-b933-406186cbf87c +Comp26Name:Bernardo Freitas+Francisco And +Comp26NameShort:POR 118 +Comp26Color:008000 +Comp26Nationality:POR +Comp26Description:Club Naval de Cascais +Comp26Handicap:1.0000000000 +Comp26HandicapDistance:0.00000 +Comp26PictureUUID:73bc6c9e-6ee6-11e1-b933-406186cbf87c +Comp26ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp26RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp27UUID:769e4bbc-6ee6-11e1-b933-406186cbf87c +Comp27Name:Jorge Lima+Jose Costa +Comp27NameShort:POR 1259 +Comp27Color:00FF00 +Comp27Nationality:POR +Comp27Description:A.N.Guadiana +Comp27Handicap:1.0000000000 +Comp27HandicapDistance:0.00000 +Comp27PictureUUID:73bc6c9e-6ee6-11e1-b933-406186cbf87c +Comp27ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp27RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp28UUID:7859678e-6ee6-11e1-b933-406186cbf87c +Comp28Name:Alexey Chekrygin+Vitaly Russu +Comp28NameShort:RUS 1171 +Comp28Color:FF0000 +Comp28Nationality:RUS +Comp28Description:no club +Comp28Handicap:1.0000000000 +Comp28HandicapDistance:0.00000 +Comp28PictureUUID:770eeeb2-6ee6-11e1-b933-406186cbf87c +Comp28ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp28RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp29UUID:79f07024-6ee6-11e1-b933-406186cbf87c +Comp29Name:Pavel Kalinchev+Pavel Karachov +Comp29NameShort:RUS 1227 +Comp29Color:808000 +Comp29Nationality:RUS +Comp29Description:no club +Comp29Handicap:1.0000000000 +Comp29HandicapDistance:0.00000 +Comp29PictureUUID:770eeeb2-6ee6-11e1-b933-406186cbf87c +Comp29ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp29RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp30UUID:7b88f12c-6ee6-11e1-b933-406186cbf87c +Comp30Name:Yuri Artemyev+Bubnov Viachesla +Comp30NameShort:RUS 171 +Comp30Color:FFFFFF +Comp30Nationality:RUS +Comp30Description:C.N.Drugba +Comp30Handicap:1.0000000000 +Comp30HandicapDistance:0.00000 +Comp30PictureUUID:770eeeb2-6ee6-11e1-b933-406186cbf87c +Comp30ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp30RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp31UUID:7d40d3a4-6ee6-11e1-b933-406186cbf87c +Comp31Name:Tito Ruggero+Sebastian Nulli +Comp31NameShort:SWE 1068 +Comp31Color:FFFF00 +Comp31Nationality:SWE +Comp31Description:CDV Venezia +Comp31Handicap:1.0000000000 +Comp31HandicapDistance:0.00000 +Comp31PictureUUID:7be2558c-6ee6-11e1-b933-406186cbf87c +Comp31ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp31RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp32UUID:7ee431b0-6ee6-11e1-b933-406186cbf87c +Comp32Name:Charlie Ekberg+Kalle Torlen +Comp32NameShort:SWE 1184 +Comp32Color:800080 +Comp32Nationality:SWE +Comp32Description:KSSS +Comp32Handicap:1.0000000000 +Comp32HandicapDistance:0.00000 +Comp32PictureUUID:7be2558c-6ee6-11e1-b933-406186cbf87c +Comp32ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp32RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp33UUID:8072c460-6ee6-11e1-b933-406186cbf87c +Comp33Name:Jonas Von Greijer+Niclas Dürin +Comp33NameShort:SWE 1188 +Comp33Color:008080 +Comp33Nationality:SWE +Comp33Description:GKSS +Comp33Handicap:1.0000000000 +Comp33HandicapDistance:0.00000 +Comp33PictureUUID:7be2558c-6ee6-11e1-b933-406186cbf87c +Comp33ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp33RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp34UUID:821f2aec-6ee6-11e1-b933-406186cbf87c +Comp34Name:Carl Sylvan+Otto Hamel +Comp34NameShort:SWE 1219 +Comp34Color:800000 +Comp34Nationality:SWE +Comp34Description:GKSS +Comp34Handicap:1.0000000000 +Comp34HandicapDistance:0.00000 +Comp34PictureUUID:7be2558c-6ee6-11e1-b933-406186cbf87c +Comp34ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp34RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +Comp35UUID:a7196fde-6f53-11e1-b933-406186cbf87c +Comp35Name:John Pink+Rick Peacock +Comp35NameShort:GBR 4 +Comp35Color:FFFF00 +Comp35Nationality:GBR +Comp35Description: +Comp35Handicap:1.0000000000 +Comp35HandicapDistance:0.00000 +Comp35ClassUUID:52bd518e-6ee6-11e1-b933-406186cbf87c +Comp35RouteUUID:915462a0-6ee8-11e1-b933-406186cbf87c + +ControlPoint1UUID:398a6b20-7044-11e1-b933-406186cbf87c +ControlPoint1Name:49er R4 Mark1-2 +ControlPoint1HasTwoPoints:0 +ControlPoint1ShortName:1 +ControlPoint1Mark1Lon:2.734586 +ControlPoint1Mark1Lat:39.499010 + +ControlPoint2UUID:59d18d06-6ec7-11e1-b933-406186cbf87c +ControlPoint2Name:A Start +ControlPoint2HasTwoPoints:1 +ControlPoint2ShortName:S +ControlPoint2DataSheet:P1.Color=Green###BREAKLINE###P1.Type=Conical +ControlPoint2Mark1Lon:2.750428 +ControlPoint2Mark1Lat:39.501027 +ControlPoint2Mark2Lon:2.750433 +ControlPoint2Mark2Lat:39.501030 + +ControlPoint3UUID:733cd8cc-6ec7-11e1-b933-406186cbf87c +ControlPoint3Name:A Mark1 +ControlPoint3HasTwoPoints:0 +ControlPoint3ShortName:1 +ControlPoint3Mark1Lon:2.750418 +ControlPoint3Mark1Lat:39.500985 + +ControlPoint4UUID:7c172484-6ec7-11e1-b933-406186cbf87c +ControlPoint4Name:A Mark4 +ControlPoint4HasTwoPoints:1 +ControlPoint4ShortName:4 +ControlPoint4Mark1Lon:2.750342 +ControlPoint4Mark1Lat:39.501015 +ControlPoint4Mark2Lon:2.750407 +ControlPoint4Mark2Lat:39.501030 + +ControlPoint5UUID:888923c0-6ec7-11e1-b933-406186cbf87c +ControlPoint5Name:B Start +ControlPoint5HasTwoPoints:1 +ControlPoint5ShortName:S +ControlPoint5Mark1Lon:2.749663 +ControlPoint5Mark1Lat:39.500278 +ControlPoint5Mark2Lon:2.749540 +ControlPoint5Mark2Lat:39.500453 + +ControlPoint6UUID:8f8789f0-6ec7-11e1-b933-406186cbf87c +ControlPoint6Name:B Mark1 +ControlPoint6HasTwoPoints:0 +ControlPoint6ShortName:1 +ControlPoint6Mark1Lon:2.749610 +ControlPoint6Mark1Lat:39.500335 + +ControlPoint7UUID:988a1c16-6ec7-11e1-b933-406186cbf87c +ControlPoint7Name:B Mark2 +ControlPoint7HasTwoPoints:0 +ControlPoint7ShortName:2 +ControlPoint7Mark1Lon:2.749788 +ControlPoint7Mark1Lat:39.500260 + +ControlPoint8UUID:9f1aa21c-6ec7-11e1-b933-406186cbf87c +ControlPoint8Name:B Mark3 +ControlPoint8HasTwoPoints:1 +ControlPoint8ShortName:3 +ControlPoint8Mark1Lon:2.749772 +ControlPoint8Mark1Lat:39.500267 +ControlPoint8Mark2Lon:2.750412 +ControlPoint8Mark2Lat:39.501065 + +ControlPoint9UUID:a5932fe2-6ec7-11e1-b933-406186cbf87c +ControlPoint9Name:B Mark4 +ControlPoint9HasTwoPoints:0 +ControlPoint9ShortName:4 +ControlPoint9Mark1Lon:2.748348 +ControlPoint9Mark1Lat:39.501442 +ControlPoint9Mark2Lon:2.749453 +ControlPoint9Mark2Lat:39.500157 + +ControlPoint10UUID:acc1a122-6ec7-11e1-b933-406186cbf87c +ControlPoint10Name:B Finish +ControlPoint10HasTwoPoints:1 +ControlPoint10ShortName:F +ControlPoint10Mark1Lon:2.738237 +ControlPoint10Mark1Lat:39.509987 +ControlPoint10Mark2Lon:2.750378 +ControlPoint10Mark2Lat:39.500973 + +ControlPoint11UUID:bedcb68c-7041-11e1-b933-406186cbf87c +ControlPoint11Name:49er R7 Mark1-sub +ControlPoint11HasTwoPoints:0 +ControlPoint11ShortName:1 +ControlPoint11Mark1Lon:2.737030 +ControlPoint11Mark1Lat:39.501521 + +ControlPoint12UUID:f3b7031a-7043-11e1-b933-406186cbf87c +ControlPoint12Name:49er R4 Mark1 +ControlPoint12HasTwoPoints:0 +ControlPoint12ShortName:1 +ControlPoint12Mark1Lon:2.732791 +ControlPoint12Mark1Lat:39.500188 + + +SegmentFile:segments.php +Route1UUID:915462a0-6ee8-11e1-b933-406186cbf87c +Route1Description:49er Race2 +Route1Length:0 +Route1ControlPoint1UUID:59d18d06-6ec7-11e1-b933-406186cbf87c +Route1ControlPoint2UUID:733cd8cc-6ec7-11e1-b933-406186cbf87c +Route1ControlPoint3UUID:7c172484-6ec7-11e1-b933-406186cbf87c +Route1ControlPoint4UUID:733cd8cc-6ec7-11e1-b933-406186cbf87c +Route1ControlPoint5UUID:7c172484-6ec7-11e1-b933-406186cbf87c +Route1ControlPoint6UUID:733cd8cc-6ec7-11e1-b933-406186cbf87c +Route1ControlPoint7UUID:59d18d06-6ec7-11e1-b933-406186cbf87c + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPArenal49erRace2Test.java b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPArenal49erRace2Test.java new file mode 100755 index 00000000000..fcd26364f5a --- /dev/null +++ b/java/com.sap.sailing.domain.tractracadapter.test/src/com/sap/sailing/domain/tractracadapter/test/ClientParamsPHPArenal49erRace2Test.java @@ -0,0 +1,34 @@ +package com.sap.sailing.domain.tractracadapter.test; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.junit.Before; +import org.junit.Test; + +public class ClientParamsPHPArenal49erRace2Test extends AbstractClientParamsPHPTest { + + @Before + public void setUp() throws IOException { + setUp("/clientparamsArenal49erRace2.php"); + } + + @Test + public void testEndOfTracking() { + // 2012-03-16 12:39:00 + Calendar calendar = new GregorianCalendar(); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + calendar.set(Calendar.YEAR, 2012); + calendar.set(Calendar.MONTH, 2); + calendar.set(Calendar.DAY_OF_MONTH, 16); + calendar.set(Calendar.HOUR_OF_DAY, 12); + calendar.set(Calendar.MINUTE, 39); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + assertEquals(calendar.getTime(), clientParams.getRaceTrackingEndTime().asDate()); + } +} diff --git a/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF index 7ebf8363984..3069546ff6c 100755 --- a/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.tractracadapter/META-INF/MANIFEST.MF @@ -10,7 +10,7 @@ Require-Bundle: com.sap.sailing.domain, com.googlecode.java-diff-utils;bundle-version="1.3.0", com.sap.sailing.domain.common Export-Package: com.sap.sailing.domain.tractracadapter, - com.sap.sailing.domain.tractracadapter.impl;x-friends:="com.sap.sailing.domain.test,com.sap.sailing.domain.swisstimingadapter.test,com.sap.sailing.domain.tractracadapter.test" + com.sap.sailing.domain.tractracadapter.impl;x-friends:="com.sap.sailing.domain.test,com.sap.sailing.domain.swisstimingadapter.test" Bundle-ActivationPolicy: lazy Import-Package: org.json.simple, org.json.simple.parser diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java index db308df8ef2..31b692edbdf 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/ClientParamsPHP.java @@ -64,7 +64,7 @@ public class ClientParamsPHP { */ private final Map propertiesByID; - private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss Z"); + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); public static enum HandicapSystem { NONE, diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java index 90e7be752be..6b5f38fb3b9 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java @@ -247,11 +247,12 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements return task; } + private void pollAndParseClientParamsPHP(final URL paramURL, final Simulator simulator) { Set raceDefinitions = getRaces(); if (raceDefinitions != null && !raceDefinitions.isEmpty()) { logger.info("fetching paramURL "+paramURL+" to check for updates for race(s) "+getRaces()); - ClientParamsPHP clientParams; + final ClientParamsPHP clientParams; try { clientParams = new ClientParamsPHP(new InputStreamReader(paramURL.openStream())); List newCourseControlPoints = new ArrayList<>(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java index 12c56dea19c..9c4898214ed 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindFixesAsNavigableSet.java @@ -77,11 +77,7 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl protected TimePoint lowerToResolution(TimePoint timePoint) { TimePoint result; final TimePoint timePointOfLastEvent = getTrackedRace().getTimePointOfNewestEvent(); - if (timePointOfLastEvent == null) { - // nothing received yet; "lowering" to end of time - result = new MillisecondsTimePoint((Long.MAX_VALUE - 1) / getResolutionInMilliseconds() - * getResolutionInMilliseconds()); - } else if (timePoint.compareTo(timePointOfLastEvent) > 0) { + if (timePointOfLastEvent != null && timePoint.compareTo(timePointOfLastEvent) > 0) { result = lowerToResolution(timePointOfLastEvent); } else { result = new MillisecondsTimePoint((timePoint.asMillis() - 1) / getResolutionInMilliseconds() @@ -93,11 +89,7 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl protected TimePoint floorToResolution(TimePoint timePoint) { TimePoint result; final TimePoint timePointOfLastEvent = getTrackedRace().getTimePointOfNewestEvent(); - if (timePointOfLastEvent == null) { - // nothing received yet; "lowering" to end of time - result = new MillisecondsTimePoint((Long.MAX_VALUE - 1) / getResolutionInMilliseconds() - * getResolutionInMilliseconds()); - } else if (timePoint.compareTo(timePointOfLastEvent) > 0) { + if (timePointOfLastEvent != null && timePoint.compareTo(timePointOfLastEvent) > 0) { result = floorToResolution(timePointOfLastEvent); } else { result = new MillisecondsTimePoint(timePoint.asMillis() / getResolutionInMilliseconds() @@ -109,10 +101,7 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl protected TimePoint ceilingToResolution(TimePoint timePoint) { TimePoint result; final TimePoint startOfTracking = getTrackedRace().getStartOfTracking(); - if (startOfTracking == null) { - // no start of tracking yet; "ceiling" to beginning of time - result = new MillisecondsTimePoint(0); - } else if (timePoint.compareTo(startOfTracking) < 0) { + if (startOfTracking != null && timePoint.compareTo(startOfTracking) < 0) { result = ceilingToResolution(startOfTracking); } else { result = new MillisecondsTimePoint(((timePoint.asMillis() - 1) / getResolutionInMilliseconds() + 1) @@ -124,10 +113,7 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl protected TimePoint higherToResolution(TimePoint timePoint) { TimePoint result; final TimePoint startOfTracking = getTrackedRace().getStartOfTracking(); - if (startOfTracking == null) { - // no start of tracking yet; "ceiling" to beginning of time - result = new MillisecondsTimePoint(0); - } else if (timePoint.compareTo(startOfTracking) < 0) { + if (startOfTracking == null && timePoint.compareTo(startOfTracking) < 0) { result = higherToResolution(startOfTracking); } else { result = new MillisecondsTimePoint((timePoint.asMillis() / getResolutionInMilliseconds() + 1) diff --git a/java/com.sap.sailing.ess40.resultimport.test/.gitignore b/java/com.sap.sailing.ess40.resultimport.test/.gitignore new file mode 100644 index 00000000000..ba077a4031a --- /dev/null +++ b/java/com.sap.sailing.ess40.resultimport.test/.gitignore @@ -0,0 +1 @@ +bin diff --git a/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF index d3147584d1e..1a0c4d3f49f 100755 --- a/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.ess40.resultimport.test/META-INF/MANIFEST.MF @@ -5,8 +5,7 @@ Bundle-SymbolicName: com.sap.sailing.ess40.resultimport.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Fragment-Host: com.sap.sailing.ess40.resultimport Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy -Require-Bundle: com.sap.sailing.ess40.resultimport, - org.junit4;bundle-version="4.8.2", - com.sap.sailing.domain.common +Require-Bundle: org.junit4;bundle-version="4.8.2" diff --git a/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF b/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF index 891823af4ff..67878ffb16c 100755 --- a/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.ess40.resultimport/META-INF/MANIFEST.MF @@ -10,5 +10,4 @@ Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sailing.domain, com.sap.sailing.domain.common -Export-Package: com.sap.sailing.ess40.resultimport, - com.sap.sailing.ess40.resultimport.impl;x-friends:="com.sap.sailing.ess40.resultimport.test" +Export-Package: com.sap.sailing.ess40.resultimport diff --git a/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF index 1a13ebdfb03..edccf1dca88 100755 --- a/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.expeditionconnector.test/META-INF/MANIFEST.MF @@ -5,13 +5,10 @@ Bundle-SymbolicName: com.sap.sailing.expeditionconnector.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Fragment-Host: com.sap.sailing.expeditionconnector Require-Bundle: com.sap.sailing.server, - com.sap.sailing.expeditionconnector, - com.sap.sailing.domain, - com.sap.sailing.declination, com.sap.sailing.domain.test, - com.sap.sailing.udpconnector, - com.sap.sailing.domain.common + com.sap.sailing.udpconnector Import-Package: junit.framework;version="4.8.1", org.junit;version="4.8.1", org.osgi.framework;version="1.6.0" diff --git a/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF index 6fa46a10163..5b55c4bf8d1 100755 --- a/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.freg.resultimport.test/META-INF/MANIFEST.MF @@ -5,8 +5,7 @@ Bundle-SymbolicName: com.sap.sailing.freg.resultimport.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Fragment-Host: com.sap.sailing.freg.resultimport Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy -Require-Bundle: org.junit4;bundle-version="4.8.2", - com.sap.sailing.freg.resultimport, - com.sap.sailing.domain.common +Require-Bundle: org.junit4;bundle-version="4.8.2" diff --git a/java/com.sap.sailing.freg.resultimport.test/bin/freg_html_export_sample.html b/java/com.sap.sailing.freg.resultimport.test/bin/freg_html_export_sample.html new file mode 100644 index 00000000000..ec635ea5d62 --- /dev/null +++ b/java/com.sap.sailing.freg.resultimport.test/bin/freg_html_export_sample.html @@ -0,0 +1,23 @@ + + +Coupe Nationale 5O5 + + + + + + +

Coupe Nationale 5O5

Societe des Regates Rochelaises
08/07/2011 au 11/07/2011

Résultat du Lundi 11 Juillet 2011 à 13.30 h.

Groupe COUPE NATIONALE 5O5 après 8 courses (7 retenues) (75 inscrits) +
+ +
+
(Cliquez sur les noms soulignés pour accéder à la fiche du coureur)

RgsIdentConcurrentsP RetP totc.1c.2c.3c.4c.5c.6c.7c.8Club / Pays
1

GER 9070

BOGACKI Morten
 JESS Holger 

9.00 12.003
3.00
1
1.00
1
1.00
1
1.00
1
1.00
1
1.00
3
3.00
1
1.00

KYC/DYC

2

AUS 9036

QUIRK Michael
 REFFOLD Simon 

20.00 44.002
2.00
3
3.00
3
3.00
24
24.00
4
4.00
2
2.00
2
2.00
4
4.00

RPEYC

3

FRA 9067

LOYAL Bertrand
 NEVO Laurent 

27.00 41.004
4.00
2
2.00
14
14.00
2
2.00
5
5.00
4
4.00
7
7.00
3
3.00

C N MESCHERS
S R ROCHELAISES

4

GBR 9032

PINNELL Ian
 PUINELL Ian 

44.00 58.001
1.00
4
4.00
7
7.00
8
8.00
3
3.00
14
14.00
14
14.00
7
7.00

HISC

5

FRA 8902

BOITE Philippe
 THOMAS Benjamin 

52.00 67.0012
12.00
5
5.00
6
6.00
9
9.00
9
9.00
5
5.00
6
6.00
15
15.00

C V ST QUENTIN
S R HAVRE

6

SUI 8543

BART Cedric
 GROB Laurent 

55.00 74.0010
10.00
6
6.00
2
2.00
19
19.00
10
10.00
12
12.00
5
5.00
10
10.00

CNB

7

GBR 9012

SMITH Andy
 NEEDHAM Tim 

60.00 80.005
5.00
8
8.00
20
20.00
18
18.00
8
8.00
3
3.00
1
1.00
17
17.00

NOTTS COUNTY SC

8

GER 8975

HOLZAPFEL Alexander
 WORM Stefan 

60.00 74.0011
11.00
7
7.00
9
9.00
5
5.00
13
13.00
7
7.00
8
8.00
14
14.00

ALLEMAGNE

9

GBR 9057

LEWNS Chris
 PARSONS Oan 

64.00 140.009
9.00
9
9.00
10
10.00
4
4.00
14
14.00
9
9.00
9
9.00
DNF
76.00

WHITSTABLE

10

DEN 8958

HOFFMANNBUHL Nikolas
 BUHL Henrik 

72.00 102.007
7.00
DPI
30.00
5
5.00
13
13.00
7
7.00
8
8.00
24
24.00
8
8.00

STETTEN BACHKLUB

11

SUI 8569

DETSCHEN Rene
 HOLLINGER Lionel 

74.00 94.006
6.00
16
16.00
19
19.00
20
20.00
6
6.00
6
6.00
10
10.00
11
11.00

CNB

12

FRA 8900

GUBRI Serge
 DI PEDE Thibault 

96.00 125.0018
18.00
13
13.00
4
4.00
7
7.00
26
26.00
29
29.00
19
19.00
9
9.00

C V SEINE-PORT
C V SEINE-PORT

13

GBR 8935

RUS 
 LAFORTUNE Geraud 

97.00 116.0015
15.00
12
12.00
16
16.00
14
14.00
12
12.00
15
15.00
13
13.00
19
19.00

GRANDE-BRETAGNE
C V SEINE-PORT

14

FRA 8752

MARTIN Nicolas
 LAFORTUNE Julien 

98.00 132.0034
34.00
17
17.00
8
8.00
3
3.00
23
23.00
24
24.00
21
21.00
2
2.00

C V SEINE-PORT
C V SEINE-PORT

15

FRA 8515

CROCE Nicolas
 FAUCHEUX Florian 

98.00 124.0014
14.00
11
11.00
22
22.00
17
17.00
11
11.00
11
11.00
12
12.00
26
26.00

C N CANET PERPIGNAN
C N CANET PERPIGNAN

16

SUI 8954

STAUFFER Florian
 JACOT Philippe 

118.00 194.00DNF
76.00
DNF
76.00
11
11.00
10
10.00
2
2.00
10
10.00
4
4.00
5
5.00

CLUB DE VOILE DE NEUCHATEL

17

FRA 8948

GUILLOU Nicolas
 FEREC Alain 

144.00 220.008
8.00
14
14.00
32
32.00
40
40.00
21
21.00
18
18.00
11
11.00
DNC
76.00

S N P H
S N P H

18

FRA 8870

SILVESTRE Christian
 AUBERGER Nicolas 

144.00 193.0019
19.00
34
34.00
12
12.00
22
22.00
17
17.00
19
19.00
49
49.00
21
21.00

Y C ILE DE FRANCE
Y C Grenoble Chara

19

FRA 9044

DEVAUX Mathieu
 DUBUC Philippe 

146.00 180.0024
24.00
22
22.00
25
25.00
6
6.00
22
22.00
34
34.00
29
29.00
18
18.00

S R FECAMP
S R FECAMP

20

FRA 8869

BOYER Etienne
 BOYER Emmanuel 

147.00 182.0020
20.00
27
27.00
29
29.00
33
33.00
16
16.00
16
16.00
35
35.00
6
6.00

C V BORDEAUX
C V BORDEAUX

21

GBR 8881

DEANE Roger
 TURNER Dave 

147.00 188.0017
17.00
41
41.00
13
13.00
25
25.00
15
15.00
37
37.00
15
15.00
25
25.00

ROCK SC

22

FRA 8716

MARTIN Aline
 TOUPET Fabrice 

150.00 226.00DNF
76.00
10
10.00
15
15.00
11
11.00
19
19.00
28
28.00
51
51.00
16
16.00

C V SEINE-PORT
C V ST QUENTIN

23

GBR 8905

TURNBULL Stuart
 LUNN Jason 

151.00 196.0035
35.00
21
21.00
17
17.00
45
45.00
20
20.00
17
17.00
17
17.00
24
24.00

BURTON SAILING CLUB

24

FRA 8961

VIBERT Marc
 LAPEYRE Bertrand 

163.00 199.0021
21.00
19
19.00
28
28.00
15
15.00
36
36.00
30
30.00
20
20.00
30
30.00

C.V.M.L
S N P H

25

GER 9026

GUY Hubert
 DETAPPE Xavier 

165.00 241.0013
13.00
DPI
38.00
RDG
26.00
RDG
26.00
18
18.00
13
13.00
DNF
76.00
31
31.00

ABYC
A S CHEMINOTS ROCH

26

FRA 8909

DOERENBECHER Francis
 WOLFF Philippe 

170.00 207.0022
22.00
25
25.00
35
35.00
37
37.00
28
28.00
20
20.00
27
27.00
13
13.00

C N DE SCIEZ
AC ALSACE LORRAINE

27

SUI 9060

HOURIET Catherine
 DONZE Gil 

181.00 238.0032
32.00
35
35.00
23
23.00
12
12.00
35
35.00
57
57.00
32
32.00
12
12.00

YCG

28

FRA 8908

FOURCADET Amaury
 FERTIN Vincent 

182.00 215.0030
30.00
28
28.00
18
18.00
32
32.00
33
33.00
25
25.00
22
22.00
27
27.00

BLAYE NAUTIQUE
Y C C

29

FRA 9024

GACHET Claude
 VALERY Kleber 

185.00 221.0036
36.00
20
20.00
21
21.00
16
16.00
29
29.00
35
35.00
31
31.00
33
33.00

Y C Grenoble Chara
Y C Grenoble Chara

30

FRA 9010

LAFORTUNE Baptiste
 CARVALLO Gilles 

188.00 246.0028
28.00
18
18.00
31
31.00
58
58.00
24
24.00
26
26.00
26
26.00
35
35.00

C V SEINE-PORT
C N HAUTE SEINE

31

FRA 9068

DE LISLE Francois
 BRILLAUD Herve 

189.00 234.0045
45.00
26
26.00
44
44.00
29
29.00
27
27.00
22
22.00
18
18.00
23
23.00

C N FERRET
C N FERRET

32

BEL 9066

DE PAUW Geert
 JOURQUIN Koen 

195.00 246.0016
16.00
31
31.00
41
41.00
51
51.00
25
25.00
31
31.00
23
23.00
28
28.00

BELGIQUE

33

GBR 8925

WEDGE Martin
 ALLEN Scott 

209.00 285.0023
23.00
24
24.00
30
30.00
36
36.00
32
32.00
23
23.00
DNF
76.00
41
41.00

RTYC

34

GBR 8749

MILTON Alastair
 MILTON Laurence 

213.00 265.0031
31.00
42
42.00
39
39.00
21
21.00
43
43.00
21
21.00
16
16.00
52
52.00

HELENSBURGH SC

35

FRA 7941

BRENET Veronique
 PETER Jean-pierre 

217.00 293.0025
25.00
30
30.00
DSQ
76.00
23
23.00
37
37.00
40
40.00
42
42.00
20
20.00

AC ALSACE LORRAINE
AC ALSACE LORRAINE

36

FRA 8729

TOSELLO Alain
 NIDDAM Yves 

224.00 269.0029
29.00
29
29.00
38
38.00
41
41.00
30
30.00
45
45.00
28
28.00
29
29.00

LES VOILES D ANTIBES
Y C BEAULIEU

37

FRA 8885

DUPONT Jean baptiste
 ANCELOT Jean claude 

248.00 302.0039
39.00
39
39.00
26
26.00
31
31.00
41
41.00
54
54.00
33
33.00
39
39.00

C .V. de PARIS
S N O NANTES

38

SUI 8614

JACOT Valéry
 BATTAGLIA Corsin 

250.00 326.0050
50.00
DNS
76.00
24
24.00
35
35.00
31
31.00
32
32.00
56
56.00
22
22.00

CERCLE DE VOILE DE NEUCHATEL

39

FRA 8486

VIAUD Pascal
 MICHEL Nicolas 

252.00 308.0026
26.00
33
33.00
46
46.00
56
56.00
40
40.00
33
33.00
36
36.00
38
38.00

S R ROCHELAISES
C V A N NANTES

40

FRA 8427

MONNIER Frederic
 PLATHIER Yann 

255.00 298.0037
37.00
37
37.00
40
40.00
30
30.00
39
39.00
36
36.00
43
43.00
36
36.00

Y C Grenoble Chara
S A LYONNAIS

41

ITA 8671

PIACQUADIO Giacinto
 MESSINA Anrea 

280.00 339.0042
42.00
32
32.00
43
43.00
34
34.00
59
59.00
53
53.00
34
34.00
42
42.00

YACHTING CLUB CERN

42

BEL 9

VANDERSTEN Philippe
 GENNART Genevieve 

295.00 371.0040
40.00
47
47.00
36
36.00
39
39.00
44
44.00
48
48.00
41
41.00
DNC
76.00

SNEH

43

FRA 8567

COULMIN Pierre-jerome
 PETER Vincent 

298.00 374.0047
47.00
45
45.00
58
58.00
62
62.00
34
34.00
27
27.00
25
25.00
DNF
76.00

WIND 14
WIND 14

44

FRA 8565

DUMAS Bruno
 DAVID Michel 

301.00 377.0048
48.00
36
36.00
27
27.00
27
27.00
48
48.00
39
39.00
DNF
76.00
DNC
76.00

C V ARCACHON
C V ARCACHON

45

FRA 8800

LAFORTUNE Claude
 LAFORTUNE Adrien 

301.00 350.0049
49.00
46
46.00
33
33.00
43
43.00
47
47.00
49
49.00
39
39.00
44
44.00

C V SEINE-PORT
C V SEINE-PORT

46

FRA 8899

KARADJIAN Alain
 LAFORTUNE Jordane 

308.00 384.0041
41.00
38
38.00
45
45.00
38
38.00
64
64.00
50
50.00
DNF
76.00
32
32.00

C V ST QUENTIN
C V SEINE-PORT

47

FRA 8695

PUISSANT Jean-guy
 LE GUEN Bernard 

308.00 384.0061
61.00
44
44.00
34
34.00
42
42.00
42
42.00
47
47.00
38
38.00
DNC
76.00

CV LAC 3 PROVINCES
DAUPHINS WIND JOCON.

48

FRA 8

DAGUZAN Thierry
 POIRIER Arnaud 

317.00 382.0033
33.00
54
54.00
63
63.00
65
65.00
38
38.00
41
41.00
40
40.00
48
48.00

S R ROCHELAISES
EV ROCHELAISE

49

FRA 8699

DUCROS Jean philippe
 VICTOR Antoine 

333.00 394.0038
38.00
49
49.00
61
61.00
48
48.00
51
51.00
51
51.00
50
50.00
46
46.00

C.V.M.L
C.V.M.L

50

FRA 9050

VASSOR Eric
 CHENE Pierre yves 

342.00 418.0054
54.00
51
51.00
62
62.00
55
55.00
46
46.00
44
44.00
30
30.00
DNF
76.00

USAM VOILE
USAM VOILE

51

FRA 8711

MAUPAY Jerome
 CAMINADE Francois 

342.00 396.0046
46.00
43
43.00
52
52.00
50
50.00
52
52.00
52
52.00
54
54.00
47
47.00

Y C Grenoble Chara
Y C Grenoble Chara

52

FRA 8980

BOIRY Michel
 HUBERT Jean-baptiste 

347.00 409.0062
62.00
48
48.00
42
42.00
47
47.00
62
62.00
55
55.00
53
53.00
40
40.00

C .V. de PARIS
C V SEINE-PORT

53

FRA 8187

GRACIES Bruno
 THIBAUD Cyril 

352.00 428.00DNF
76.00
40
40.00
54
54.00
44
44.00
49
49.00
46
46.00
DNF
76.00
43
43.00

EV ROCHELAISE
G S LA ROCHELLE

54

FRA 8773

CONVERSY Gael
 FERROIS Melissa 

362.00 418.0055
55.00
55
55.00
48
48.00
54
54.00
55
55.00
56
56.00
46
46.00
49
49.00

Y C BEAULIEU
YC POINTE ROUGE

55

GBR 8907

BERRY Jim
 BARNES David 

375.00 451.0027
27.00
DNF
76.00
47
47.00
57
57.00
54
54.00
38
38.00
DNF
76.00
DNF
76.00

GRANDE-BRETAGNE

56

FRA 8949

JEAN Jeremy
 DUQUESNE Philippe 

380.00 456.00DNF
76.00
DNS
76.00
37
37.00
28
28.00
68
68.00
58
58.00
DNS
76.00
37
37.00

S N O NANTES
S N O NANTES

57

FRA 8704

NEIDHART Elisabeth
 GOUGEON Matthieu 

381.00 457.0044
44.00
DNF
76.00
49
49.00
26
26.00
DNF
76.00
DNS
76.00
DNF
76.00
34
34.00

C.V.M.L
C.V.M.L

58

FRA 8623

DAUGEY Gerard
 ROUANNE Dominique 

396.00 472.00DNF
76.00
DNS
76.00
56
56.00
52
52.00
DNF
76.00
43
43.00
48
48.00
45
45.00

C.V.M.L
Y C CAVALAIRE

59

FRA 8826

DEBRY Tristan
 DEBRY Alain 

407.00 483.0052
52.00
DNF
76.00
51
51.00
61
61.00
60
60.00
60
60.00
47
47.00
DNF
76.00

Y.C.DINARD
Y.C.DINARD

60

FRA 8431

DECHAUFFOUR Jacques
 DECHAUFFOUR Sabine 

417.00 493.0058
58.00
53
53.00
66
66.00
63
63.00
65
65.00
68
68.00
44
44.00
DNF
76.00

S N O NANTES
S N O NANTES

61

FRA 8366

PICOU Dominique
 BOUTON Fabrice 

421.00 497.00DNF
76.00
DNS
76.00
68
68.00
69
69.00
53
53.00
42
42.00
37
37.00
DNC
76.00

S R ROCHELAISES
S R ROCHELAISES

62

FRA 9033

HUE Patrice
 HUE Pierre francois 

426.00 502.0059
59.00
DNF
76.00
50
50.00
53
53.00
50
50.00
62
62.00
DNS
76.00
DNF
76.00

C V ANGERS
S N P H

63

FRA 8880

MOUCLIER Emmanuel
 THIBERGE Olivier 

428.00 504.0057
57.00
50
50.00
69
69.00
70
70.00
61
61.00
DNF
76.00
45
45.00
DNF
76.00

C.V.M.L
C .V. de PARIS

64

GER 8052

FEVERHAKE Jurgen
 NEHRENBERG Anne 

435.00 511.0056
56.00
52
52.00
55
55.00
66
66.00
66
66.00
64
64.00
DNF
76.00
DNF
76.00

YCSB

65

FRA 8748

CARVALLO Samuel
 RAULY Sebastien 

441.00 517.0051
51.00
DPI
71.00
70
70.00
73
73.00
58
58.00
66
66.00
52
52.00
DNF
76.00

C N HAUTE SEINE
C N HAUTE SEINE

66

FRA 8564

MENDES Jose
 LOZOUET Jean-paul 

453.00 529.00DNF
76.00
DNS
76.00
65
65.00
49
49.00
70
70.00
67
67.00
DNF
76.00
50
50.00

C.V.M.L
C .V. de PARIS

67

FRA 8362

MAGRET Olivier
 GLADE Mathieu 

460.00 536.0053
53.00
DNF
76.00
57
57.00
46
46.00
DNF
76.00
DNF
76.00
DNS
76.00
DNC
76.00

C N T LACYDON
S N DE SANARY

68

FRA 8705

VOCANSON Luc
 MUNIGLIA Thierry 

461.00 537.0043
43.00
DNF
76.00
53
53.00
68
68.00
69
69.00
DNF
76.00
DNS
76.00
DNF
76.00

C.V.M.L
C.V.M.L

69

FRA 9019

LEHMANN Robert
 EDEL Bernard 

469.00 545.00DNF
76.00
DNS
76.00
59
59.00
60
60.00
57
57.00
65
65.00
DNS
76.00
DNF
76.00

AC ALSACE LORRAINE
AC ALSACE LORRAINE

70

FRA 8676

DECHAUFFOUR Valentin
 GENDRY Pierre 

469.00 545.00DNC
76.00
DNF
76.00
60
60.00
59
59.00
63
63.00
59
59.00
DNF
76.00
DNF
76.00

S N O NANTES
S N O NANTES

71

FRA 898

NOURAUD Daniel
 FINOT Thibault 

475.00 551.0060
60.00
DNS
76.00
64
64.00
67
67.00
56
56.00
DNF
76.00
DNS
76.00
DNF
76.00

S R ROCHELAISES
S N O NANTES

72

FRA 8401

RENOUX Alain
 LAMARQUE Frederic 

479.00 555.00DNF
76.00
DNF
76.00
71
71.00
71
71.00
71
71.00
63
63.00
DNS
76.00
51
51.00

C V ANGOULEME
V LACANAU GUYENNE

73

FRA 8541

BARON Jean
 DUBERNET Louis 

492.00 568.00DNF
76.00
DNF
76.00
67
67.00
DPI
76.00
45
45.00
DNF
76.00
DNS
76.00
DNF
76.00

U N P CAP BRETON
U N P CAP BRETON

74

FRA 8717

PUGET Mathieu
 LACOTTE Nicolas 

500.00 576.00DNF
76.00
DNC
76.00
DNF
76.00
DNF
76.00
72
72.00
69
69.00
55
55.00
DNF
76.00

USAM VOILE
USAM VOILE

75

NED 8287

KALF Mark
 KALF Mak 

500.00 576.00DNF
76.00
DNS
76.00
72
72.00
72
72.00
67
67.00
61
61.00
DNF
76.00
DNC
76.00

HOLLANDE

Pénalités et réparations : 92 (9 DNC; 59 DNF; 17 DNS; 4 DPI; 1 DSQ; 2 RDG; )
[Retour début de page] +

LORBER Yves
Président du Comité de Course

DESFORGES Roland
Président du Jury


Produit le 11/07/2011 par DESFORGES Roland et logiciel FReg2011 (vers. 15/06/2011) FFVoile © 2001/2010
\ No newline at end of file diff --git a/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF b/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF index 911dfabc37d..cd8665159d0 100755 --- a/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.freg.resultimport/META-INF/MANIFEST.MF @@ -10,5 +10,4 @@ Import-Package: org.osgi.framework;version="1.3.0" Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sailing.domain.common, com.sap.sailing.domain -Export-Package: com.sap.sailing.freg.resultimport, - com.sap.sailing.freg.resultimport.impl;x-friends:="com.sap.sailing.freg.resultimport.test" +Export-Package: com.sap.sailing.freg.resultimport diff --git a/java/com.sap.sailing.geocoding.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.geocoding.test/META-INF/MANIFEST.MF index c98b8dfcc50..8b9b5341f2c 100644 --- a/java/com.sap.sailing.geocoding.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.geocoding.test/META-INF/MANIFEST.MF @@ -5,8 +5,7 @@ Bundle-SymbolicName: com.sap.sailing.geocoding.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: com.sap.sailing.geocoding, - com.sap.sailing.domain.common +Fragment-Host: com.sap.sailing.geocoding Import-Package: junit.framework;version="4.8.2", org.json.simple.parser, org.junit;version="4.8.2" diff --git a/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF b/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF index 734cee6ded4..c7096696050 100644 --- a/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.geocoding/META-INF/MANIFEST.MF @@ -6,7 +6,6 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ActivationPolicy: lazy -Export-Package: com.sap.sailing.geocoding, - com.sap.sailing.geocoding.impl;x-friends:="com.sap.sailing.geocoding.test" +Export-Package: com.sap.sailing.geocoding Require-Bundle: org.json.simple;bundle-version="1.1.0", com.sap.sailing.domain.common diff --git a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF index 899f7bf1041..17c05205321 100755 --- a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF @@ -5,12 +5,7 @@ Bundle-SymbolicName: com.sap.sailing.gwt.ui.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.6 -Require-Bundle: com.sap.sailing.gwt.ui, - com.sap.sailing.domain, - com.sap.sailing.server, - com.google.gwt.osgi;bundle-version="2.4.0", - com.sap.sailing.domain.common, - com.sap.sailing.domain.tractracadapter +Fragment-Host: com.sap.sailing.gwt.ui Bundle-ClassPath: . Import-Package: javax.jms;version="1.1.0", javax.servlet;version="2.5.0", diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index c76ceeb0e91..b00398787b4 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -1,45 +1,40 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Sailing App GWT UI -Bundle-SymbolicName: com.sap.sailing.gwt.ui -Bundle-Version: 1.0.0.qualifier -Bundle-Vendor: SAP -Bundle-RequiredExecutionEnvironment: JavaSE-1.6 -Require-Bundle: com.sap.sailing.domain, - com.sap.sailing.server, - com.sap.sailing.server.gateway, - com.sap.sailing.domain.tractracadapter, - com.sap.sailing.mongodb, - com.mongodb.driver;bundle-version="2.6.2", - com.sap.sailing.domain.swisstimingadapter, - com.sap.sailing.domain.persistence, - com.sap.sailing.domain.swisstimingadapter.persistence, - com.sap.sailing.domain.tractracadapter.persistence, - com.google.gwt.osgi;bundle-version="2.4.0", - com.sap.sailing.domain.common, - org.moxieapps.gwt.highcharts;bundle-version="1.1.4", - com.sap.sailing.geocoding, - com.sap.sailing.server.replication, - com.googlecode.java-diff-utils;bundle-version="1.3.0", - com.sap.sailing.freg.resultimport, - com.sap.sailing.domain.swisstimingreplayadapter -Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator -Bundle-ActivationPolicy: lazy -Export-Package: com.sap.sailing.gwt.ui.client;x-friends:="com.sap.sailing.gwt.ui.test", - com.sap.sailing.gwt.ui.adminconsole;x-friends:="com.sap.sailing.gwt.ui.test", - com.sap.sailing.gwt.ui.leaderboard;x-friends:="com.sap.sailing.gwt.ui.test", - com.sap.sailing.gwt.ui.leaderboardedit;x-friends:="com.sap.sailing.gwt.ui.test", - com.sap.sailing.gwt.ui.server;x-friends:="com.sap.sailing.gwt.ui.test", - com.sap.sailing.gwt.ui.shared;x-friends:="com.sap.sailing.gwt.ui.test" -Import-Package: javax.jms;version="1.1.0", - javax.servlet;version="2.5.0", - javax.servlet.http;version="2.5.0", - org.json.simple, - org.json.simple.parser, - org.osgi.framework, - org.osgi.util.tracker -Web-ContextPath: /gwt -Bundle-ClassPath: WEB-INF/classes/, - WEB-INF/lib/gwt-maps.jar, - WEB-INF/lib/gwt-servlet.jar -TrackLifecycle: true +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Sailing App GWT UI +Bundle-SymbolicName: com.sap.sailing.gwt.ui +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Require-Bundle: com.sap.sailing.domain, + com.sap.sailing.server, + com.sap.sailing.server.gateway, + com.sap.sailing.domain.tractracadapter, + com.sap.sailing.mongodb, + com.mongodb.driver;bundle-version="2.6.2", + com.sap.sailing.domain.swisstimingadapter, + com.sap.sailing.domain.persistence, + com.sap.sailing.domain.swisstimingadapter.persistence, + com.sap.sailing.domain.tractracadapter.persistence, + com.google.gwt.osgi;bundle-version="2.4.0", + com.sap.sailing.domain.common, + org.moxieapps.gwt.highcharts;bundle-version="1.1.4", + com.sap.sailing.geocoding, + com.sap.sailing.server.replication, + com.googlecode.java-diff-utils;bundle-version="1.3.0", + com.sap.sailing.freg.resultimport, + com.sap.sailing.domain.swisstimingreplayadapter +Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator +Bundle-ActivationPolicy: lazy +Export-Package: com.sap.sailing.gwt.ui.shared.media +Import-Package: javax.jms;version="1.1.0", + javax.servlet;version="2.5.0", + javax.servlet.http;version="2.5.0", + org.json.simple, + org.json.simple.parser, + org.osgi.framework, + org.osgi.util.tracker +Web-ContextPath: /gwt +Bundle-ClassPath: WEB-INF/classes/, + WEB-INF/lib/gwt-maps.jar, + WEB-INF/lib/gwt-servlet.jar +TrackLifecycle: true diff --git a/java/com.sap.sailing.gwt.ui/RaceBoard.css b/java/com.sap.sailing.gwt.ui/RaceBoard.css index 4fdd429a1d6..10e45c64ea0 100644 --- a/java/com.sap.sailing.gwt.ui/RaceBoard.css +++ b/java/com.sap.sailing.gwt.ui/RaceBoard.css @@ -219,6 +219,41 @@ img.openColumn { cursor: pointer; } +.raceBoardNavigation-standaloneElement { + background: url("images/btn-cta-orangeleft.png") repeat-x scroll center 0 transparent; + color: #FFFFFF; + cursor: pointer; + font-family: UbuntuBold,Verdana,Arial,sans-serif; + font-size: 14px; + margin: 0 0 0 0; + padding: 2px 7px 2px 3px; + height: 23px; + display: inline-block; + text-decoration: none; + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); + border-bottom: 1px solid rgba(0, 0, 0, 0.25); + border-radius: 5px 5px 5px 5px; + -moz-border-radius: 5px 0 0 5px; + -webkit-border-radius: 5px 5px 5px 5px; +} + +.raceBoardNavigation-standaloneElement:active { + top: 1px; + position: relative; +} + +.raceBoardNavigation-standaloneElement:hover { + background: url("images/btn-cta-orangeleft.png") repeat-x scroll center -30px transparent; + cursor: pointer; +} + +.raceBoardNavigation-standaloneElement label { + cursor: pointer; +} + .timePanelButton { float: left; margin: 0 0 0 5px; diff --git a/java/com.sap.sailing.gwt.ui/VideoPopup.css b/java/com.sap.sailing.gwt.ui/VideoPopup.css new file mode 100644 index 00000000000..75d508b5a28 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/VideoPopup.css @@ -0,0 +1,18 @@ +html { + overflow: auto; +} + +html,body,div,iframe { + margin: 0px; + padding: 0px; + height: 100%; + border: none; +} + +video { + display: block; + width: 100%; + border: none; + overflow-y: auto; + overflow-x: hidden; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/VideoPopup.html b/java/com.sap.sailing.gwt.ui/VideoPopup.html new file mode 100644 index 00000000000..1d8d8e00859 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/VideoPopup.html @@ -0,0 +1,18 @@ + + + + + SAP Sailing Video + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml index 3c83016e1ea..5886c2c2fdb 100755 --- a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml +++ b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml @@ -1,108 +1,118 @@ - - - - - GWTCacheControlServletFilter - com.sap.sailing.server.gateway.GWTCacheControlServletFilter - - - GWTCacheControlServletFilter - /* - - - - Sailing Service - SailingService - com.sap.sailing.gwt.ui.server.SailingServiceImpl - - - SailingService - /sailing - - - - User Management Service - UserManagementService - com.sap.sailing.gwt.ui.server.UserManagementServiceImpl - - - UserManagementService - /usermanagement - - - - - remoteLoggingServlet - com.google.gwt.logging.server.RemoteLoggingServiceImpl - - - - remoteLoggingServlet - /com.sap.sailing.gwt.ui.AdminConsole/remote_logging - - - - - Administration pages - /AdminConsole.html - - - administrator - - - - - - - - Leaderboard editing pages - /LeaderboardEditing.html - - - administrator - moderator - - - - - - user management pages - /UserManagement.html - - - administrator - - - - - BASIC - SailingServerRealm - - + + + + + GWTCacheControlServletFilter + com.sap.sailing.server.gateway.GWTCacheControlServletFilter + + + GWTCacheControlServletFilter + /* + + + + Sailing Service + SailingService + com.sap.sailing.gwt.ui.server.SailingServiceImpl + + + SailingService + /sailing + + + + Media Service + MediaService + com.sap.sailing.gwt.ui.server.MediaServiceImpl + + + MediaService + /media + + + + User Management Service + UserManagementService + com.sap.sailing.gwt.ui.server.UserManagementServiceImpl + + + UserManagementService + /usermanagement + + + + + remoteLoggingServlet + com.google.gwt.logging.server.RemoteLoggingServiceImpl + + + + remoteLoggingServlet + /com.sap.sailing.gwt.ui.AdminConsole/remote_logging + + + + + Administration pages + /AdminConsole.html + + + administrator + + + + + + + + Leaderboard editing pages + /LeaderboardEditing.html + + + administrator + moderator + + + + + + user management pages + /UserManagement.html + + + administrator + + + + + BASIC + SailingServerRealm + + diff --git a/java/com.sap.sailing.gwt.ui/YoutubePopup.html b/java/com.sap.sailing.gwt.ui/YoutubePopup.html new file mode 100644 index 00000000000..c95d34a4a85 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/YoutubePopup.html @@ -0,0 +1,18 @@ + + + + + SAP Sailing Video + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/build.properties b/java/com.sap.sailing.gwt.ui/build.properties index eda39c337bd..4b75ba3b7b9 100755 --- a/java/com.sap.sailing.gwt.ui/build.properties +++ b/java/com.sap.sailing.gwt.ui/build.properties @@ -27,10 +27,16 @@ bin.includes = META-INF/,\ com.sap.sailing.gwt.ui.RaceBoard/,\ highcharts/,\ Spectator.css,\ - fontface.css,\ - webfonts/,\ - RegattaOverview.css,\ + fontface.css,\ + webfonts/,\ + RegattaOverview.css,\ RegattaOverview.html,\ + com.sap.sailing.gwt.ui.VideoPopup/,\ + VideoPopup.html,\ + VideoPopup.css,\ + com.sap.sailing.gwt.ui.YoutubePopup/,\ + YoutubePopup.html,\ TvView.html,\ - com.sap.sailing.gwt.ui.TvView/ + com.sap.sailing.gwt.ui.TvView/ +>>>>>>> master output.. = WEB-INF/classes/ diff --git a/java/com.sap.sailing.gwt.ui/pom.xml b/java/com.sap.sailing.gwt.ui/pom.xml index 2c542529b6b..e2611aef7fa 100644 --- a/java/com.sap.sailing.gwt.ui/pom.xml +++ b/java/com.sap.sailing.gwt.ui/pom.xml @@ -44,13 +44,15 @@ -Dgwt.persistentunitcache=false - com.sap.sailing.gwt.ui.RaceBoard - com.sap.sailing.gwt.ui.Spectator - com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.ui.Leaderboard - com.sap.sailing.gwt.ui.UserManagement + com.sap.sailing.gwt.ui.LeaderboardEditing + com.sap.sailing.gwt.ui.RaceBoard + com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.TvView + com.sap.sailing.gwt.ui.UserManagement + com.sap.sailing.gwt.ui.VideoPopup + com.sap.sailing.gwt.ui.YoutubePopup ${basedir}/.generated 3 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 63ba63ef719..ee60baaec86 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -36,11 +36,10 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt tabPanel.setSize("95%", "95%"); rootPanel.add(tabPanel); //, 10, 10); - regattaDisplayers = new HashSet(); - + regattaDisplayers = new HashSet(); + // TODO for managing events and venues, the following two lines are preparing the addition of the respective panel -// SailingEventManagementPanel sailingEventManagementPanel = new SailingEventManagementPanel(sailingService, this, stringMessages); -// tabPanel.add(sailingEventManagementPanel, stringMessages.events()); +// SailingEventManagementPanel sailingEventManagementPanel = new SailingEventManagementPanel(sailingService, this, stringMessages); RegattaStructureManagementPanel eventStructureManagementPanel = new RegattaStructureManagementPanel(sailingService, this, stringMessages, this); //eventStructureManagementPanel.ensureDebugId("RegattaStructureManagement"); @@ -108,7 +107,9 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, stringMessages); //replicationPanel.ensureDebugId("ReplicationManagement"); - tabPanel.add(replicationPanel, stringMessages.replication(), /* asHTML */ false); + tabPanel.add(replicationPanel, stringMessages.replication(), /* asHTML */ false); + final MediaPanel mediaPanel = new MediaPanel(mediaService, this, stringMessages); + tabPanel.add(mediaPanel, stringMessages.mediaPanel(), /* asHTML */ false); tabPanel.selectTab(0); tabPanel.addSelectionHandler(new SelectionHandler() { @@ -116,7 +117,10 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt public void onSelection(SelectionEvent event) { if (leaderboardConfigPanel.isVisible()) { leaderboardConfigPanel.loadAndRefreshLeaderboards(); - } + } + if (mediaPanel.isVisible()) { + mediaPanel.onShow(); + } } }); fillRegattas(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java new file mode 100644 index 00000000000..f467f6d147a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; + +import com.google.gwt.core.client.GWT; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class MediaActionBarCell extends ImagesBarCell { + + public static final String ACTION_REMOVE = "ACTION_REMOVE"; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private final StringMessages stringMessages; + + public MediaActionBarCell(StringMessages stringMessages) { + super(); + this.stringMessages = stringMessages; + } + + @Override + protected Iterable getImageSpecs() { + return Arrays.asList(new ImageSpec(ACTION_REMOVE, + stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java new file mode 100644 index 00000000000..923c2a97761 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java @@ -0,0 +1,372 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Collection; +import java.util.Comparator; + +import com.google.gwt.cell.client.EditTextCell; +import com.google.gwt.cell.client.FieldUpdater; +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.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.view.client.DefaultSelectionEventManager; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.SelectionModel; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.MediaServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +/** + * Table inspired by http://gwt.google.com/samples/Showcase/Showcase.html#!CwCellTable + * + * @author D047974 + * + */ +public class MediaPanel extends FlowPanel { + + private final MediaServiceAsync mediaService; + private final ErrorReporter errorReporter; + private final StringMessages stringMessages; + private final Grid mediaTracks; + private CellTable mediaTracksTable; + private ListDataProvider mediaTrackListDataProvider = new ListDataProvider(); + + public MediaPanel(MediaServiceAsync mediaService, ErrorReporter errorReporter, StringMessages stringMessages) { + this.mediaService = mediaService; + this.stringMessages = stringMessages; + this.errorReporter = errorReporter; + mediaTracks = new Grid(); + mediaTracks.resizeColumns(3); + add(mediaTracks); + Button refreshButton = new Button(stringMessages.refresh()); + refreshButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + loadMediaTracks(); + } + + }); + add(refreshButton); + Button addUrlButton = new Button(stringMessages.addMediaTrack()); + addUrlButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + addUrlMediaTrack(); + } + }); + add(addUrlButton); + + Button addYoutubeButton = new Button(stringMessages.addYoutubeTrack()); + addYoutubeButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + addYoutubeMediaTrack(); + } + }); + add(addYoutubeButton); + createMediaTracksTable(); + } + + protected void loadMediaTracks() { + mediaTrackListDataProvider.getList().clear(); + mediaService.getAllMediaTracks(new AsyncCallback>() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Collection allMediaTracks) { + mediaTrackListDataProvider.getList().addAll(allMediaTracks); + mediaTrackListDataProvider.refresh(); + } + }); + + } + + private void createMediaTracksTable() { + AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); + // Create a CellTable. + + // Set a key provider that provides a unique key for each contact. If key is + // used to identify contacts when fields (such as the name and address) + // change. + mediaTracksTable = new CellTable(1000, tableResources); + mediaTracksTable.setWidth("100%"); + + // Attach a column sort handler to the ListDataProvider to sort the list. + ListHandler sortHandler = new ListHandler(mediaTrackListDataProvider.getList()); + mediaTracksTable.addColumnSortHandler(sortHandler); + + // Add a selection model so we can select cells. + final SelectionModel selectionModel = new SingleSelectionModel(); + mediaTracksTable.setSelectionModel(selectionModel, + DefaultSelectionEventManager. createDefaultManager()); + + // Initialize the columns. + initTableColumns(selectionModel, sortHandler); + + mediaTrackListDataProvider.addDataDisplay(mediaTracksTable); + add(mediaTracksTable); + } + + /** + * Add the columns to the table. + */ + private void initTableColumns(final SelectionModel selectionModel, + ListHandler sortHandler) { + // // Checkbox column. This table will uses a checkbox column for selection. + // // Alternatively, you can call cellTable.setSelectionEnabled(true) to enable + // // mouse selection. + // Column checkColumn = new Column(new CheckboxCell(true, false)) + // { + // @Override + // public Boolean getValue(ContactInfo object) { + // // Get the value from the selection model. + // return selectionModel.isSelected(object); + // } + // }; + // cellTable.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("
")); + // cellTable.setColumnWidth(checkColumn, 40, Unit.PX); + + // media title + Column titleColumn = new Column(new EditTextCell()) { + @Override + public String getValue(MediaTrack mediaTrack) { + return mediaTrack.title; + } + }; + titleColumn.setSortable(true); + sortHandler.setComparator(titleColumn, new Comparator() { + public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) { + return mediaTrack1.title.compareTo(mediaTrack2.title); + } + }); + mediaTracksTable.addColumn(titleColumn, stringMessages.title()); + titleColumn.setFieldUpdater(new FieldUpdater() { + public void update(int index, MediaTrack mediaTrack, String newTitle) { + // Called when the user changes the value. + mediaTrack.title = newTitle; + mediaService.updateTitle(mediaTrack, new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Void allMediaTracks) { + mediaTrackListDataProvider.refresh(); + } + }); + } + }); + mediaTracksTable.setColumnWidth(titleColumn, 20, Unit.PCT); + + // url + Column urlColumn = new Column(new EditTextCell()) { + @Override + public String getValue(MediaTrack mediaTrack) { + return mediaTrack.url; + } + }; + urlColumn.setSortable(true); + sortHandler.setComparator(urlColumn, new Comparator() { + public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) { + return mediaTrack1.url.compareTo(mediaTrack2.url); + } + }); + mediaTracksTable.addColumn(urlColumn, stringMessages.url()); + urlColumn.setFieldUpdater(new FieldUpdater() { + public void update(int index, MediaTrack mediaTrack, String newUrl) { + // Called when the user changes the value. + mediaTrack.url = newUrl; + mediaService.updateUrl(mediaTrack, new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Void allMediaTracks) { + mediaTrackListDataProvider.refresh(); + } + }); + } + }); + mediaTracksTable.setColumnWidth(urlColumn, 100, Unit.PCT); + + // start time + Column startTimeColumn = new Column(new EditTextCell()) { + @Override + public String getValue(MediaTrack mediaTrack) { + return mediaTrack.startTime == null ? "" : TimeFormatUtil.DATETIME_FORMAT.format(mediaTrack.startTime); + } + }; + startTimeColumn.setSortable(true); + sortHandler.setComparator(startTimeColumn, new Comparator() { + public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) { + return mediaTrack1.startTime.compareTo(mediaTrack2.startTime); + } + }); + startTimeColumn.setFieldUpdater(new FieldUpdater() { + public void update(int index, MediaTrack mediaTrack, String newStartTime) { + // Called when the user changes the value. + mediaTrack.startTime = TimeFormatUtil.DATETIME_FORMAT.parse(newStartTime); + mediaService.updateStartTime(mediaTrack, new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Void allMediaTracks) { + mediaTrackListDataProvider.refresh(); + } + }); + } + }); + mediaTracksTable.addColumn(startTimeColumn, stringMessages.startTime()); + mediaTracksTable.setColumnWidth(startTimeColumn, 100, Unit.PCT); + + // duration + Column durationColumn = new Column(new EditTextCell()) { + @Override + public String getValue(MediaTrack mediaTrack) { + return TimeFormatUtil.milliSecondsToHrsMinSec(mediaTrack.durationInMillis); + } + }; + durationColumn.setSortable(true); + sortHandler.setComparator(durationColumn, new Comparator() { + public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) { + return Integer.valueOf(mediaTrack1.durationInMillis).compareTo(Integer.valueOf(mediaTrack2.durationInMillis)); + } + }); + durationColumn.setFieldUpdater(new FieldUpdater() { + public void update(int index, MediaTrack mediaTrack, String newDuration) { + // Called when the user changes the value. + mediaTrack.durationInMillis = TimeFormatUtil.hrsMinSecToMilliSeconds(newDuration); + mediaService.updateDuration(mediaTrack, new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Void allMediaTracks) { + mediaTrackListDataProvider.refresh(); + } + }); + } + }); + mediaTracksTable.addColumn(durationColumn, stringMessages.duration()); + mediaTracksTable.setColumnWidth(durationColumn, 100, Unit.PCT); + + // delete action + ImagesBarColumn mediaActionColumn = new ImagesBarColumn( + new MediaActionBarCell(stringMessages)); + mediaActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, MediaTrack mediaTrack, String value) { + if (MediaActionBarCell.ACTION_REMOVE.equals(value)) { + if (Window.confirm(stringMessages.reallyRemoveMediaTrack(mediaTrack.title))) { + removeMediaTrack(mediaTrack); + } + } + } + }); + mediaTracksTable.addColumn(mediaActionColumn, stringMessages.delete()); + mediaTracksTable.setColumnWidth(mediaActionColumn, 5, Unit.PCT); + + } + + protected void removeMediaTrack(MediaTrack mediaTrack) { + mediaService.deleteMediaTrack(mediaTrack, new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Void allMediaTracks) { + loadMediaTracks(); + } + }); + } + + private void addUrlMediaTrack() { + MediaTrackDialog dialog = new MediaTrackDialog(stringMessages, new DialogCallback() { + + @Override + public void cancel() { + // no op + } + + @Override + public void ok(MediaTrack mediaTrack) { + mediaService.addMediaTrack(mediaTrack, new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Void allMediaTracks) { + loadMediaTracks(); + } + }); + + } + }); + dialog.show(); + } + + protected void addYoutubeMediaTrack() { + MediaTrackYoutubeDialog dialog = new MediaTrackYoutubeDialog(stringMessages, new DialogCallback() { + + @Override + public void cancel() { + // no op + } + + @Override + public void ok(MediaTrack mediaTrack) { + mediaService.addMediaTrack(mediaTrack, new AsyncCallback() { + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError(t.toString()); + } + + @Override + public void onSuccess(Void allMediaTracks) { + loadMediaTracks(); + } + }); + + } + }); + dialog.show(); + } + + public void onShow() { + loadMediaTracks(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaTrackDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaTrackDialog.java new file mode 100644 index 00000000000..ac3b2ad1d01 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaTrackDialog.java @@ -0,0 +1,159 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Date; + +import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.media.client.Audio; +import com.google.gwt.media.client.MediaBase; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack.MimeType; + +public class MediaTrackDialog extends DataEntryDialog { + + private static final boolean DONT_FIRE_EVENTS = false; + + private static final Validator MEDIA_TRACK_VALIDATOR = new Validator() { + + @Override + public String getErrorMessage(MediaTrack valueToValidate) { + return null; + } + + }; + + private final StringMessages stringMessages; + + private MediaTrack mediaTrack = new MediaTrack(); + + private TextBox urlBox; + + private TextBox titleBox; + + private Label startTimeLabel; + + private Label mimeTypeLabel; + + private Label durationLabel; + + public MediaTrackDialog(StringMessages stringMessages, DialogCallback dialogCallback) { + super(stringMessages.addMediaTrack(), "", stringMessages.ok(), stringMessages.cancel(), MEDIA_TRACK_VALIDATOR, dialogCallback); + this.stringMessages = stringMessages; + } + + @Override + protected MediaTrack getResult() { + mediaTrack.url = urlBox.getValue(); + mediaTrack.title = titleBox.getValue(); + return mediaTrack; + } + + + private void loadMediaDuration() { + MediaBase mediaBase = Audio.createIfSupported(); + if (mediaBase != null) { + addLoadMetadataHandler(mediaBase.getMediaElement()); + mediaBase.setPreload(MediaElement.PRELOAD_METADATA); + mediaBase.setSrc(mediaTrack.url); + mediaBase.load(); + } + } + + native void addLoadMetadataHandler(MediaElement mediaElement) /*-{ + var that = this; + mediaElement.addEventListener('loadedmetadata', function() { + that.@com.sap.sailing.gwt.ui.adminconsole.MediaTrackDialog::loadedmetadata(Lcom/google/gwt/dom/client/MediaElement;)(mediaElement); + }); + }-*/; + + public void loadedmetadata(MediaElement mediaElement) { + double startOffsetTime = mediaElement.getStartOffsetTime(); + mediaTrack.startTime = Double.isNaN(startOffsetTime) ? null : new Date(Math.round(startOffsetTime * 1000)); + mediaTrack.durationInMillis = (int) Math.round(mediaElement.getDuration() * 1000); + refreshUI(); + } + + + @Override + protected Widget getAdditionalWidget() { + VerticalPanel mainPanel = new VerticalPanel(); + Grid formGrid = new Grid(5, 2); + formGrid.setCellSpacing(3); + formGrid.setWidget(0, 0, new Label(stringMessages.url() + ":")); + urlBox = createUrlBox(); + formGrid.setWidget(0, 1, urlBox); + formGrid.setWidget(1, 0, new Label(stringMessages.name() + ":")); + titleBox = createTextBox(null); + formGrid.setWidget(1, 1, titleBox); + formGrid.setWidget(2, 0, new Label(stringMessages.mimeType() + ":")); + mimeTypeLabel = new Label(); + formGrid.setWidget(2, 1, mimeTypeLabel); + formGrid.setWidget(3, 0, new Label(stringMessages.startTime() + ":")); + startTimeLabel = new Label(); + formGrid.setWidget(3, 1, startTimeLabel); + formGrid.setWidget(4, 0, new Label(stringMessages.duration() + ":")); + durationLabel = new Label(); + formGrid.setWidget(4, 1, durationLabel); + mainPanel.add(formGrid); + return mainPanel; + } + + private TextBox createUrlBox() { + TextBox result = createTextBox(null); + result.addChangeHandler(new ChangeHandler() { + + @Override + public void onChange(ChangeEvent event) { + updateFromUrl(); + } + + }); + return result; + } + + protected void updateFromUrl() { + mediaTrack.url = urlBox.getValue(); + loadMediaDuration(); + + String lastPathSegment = mediaTrack.url.substring(mediaTrack.url.lastIndexOf('/') + 1); + int dotPos = lastPathSegment.lastIndexOf('.'); + if (dotPos >= 0) { + mediaTrack.title = lastPathSegment.substring(0, dotPos); + String fileEnding = lastPathSegment.substring(dotPos + 1).toLowerCase(); + + try { + mediaTrack.mimeType = MimeType.valueOf(fileEnding); + } catch (IllegalArgumentException e) { + // ignore. TODO: Somehow put it into the error message. + // throw new IllegalArgumentException("Unsupported media type '" + mimeType + "'.", e); + mediaTrack.mimeType = null; + } + } else { + mediaTrack.title = mediaTrack.url; + mediaTrack.mimeType = null; + } + refreshUI(); + } + + private void refreshUI() { + titleBox.setValue(mediaTrack.title, DONT_FIRE_EVENTS); + mimeTypeLabel.setText(mediaTrack.typeToString()); + String startTimeText = mediaTrack.startTime == null ? "undefined" : TimeFormatUtil.DATETIME_FORMAT.format(mediaTrack.startTime); + startTimeLabel.setText(startTimeText); + durationLabel.setText(TimeFormatUtil.milliSecondsToHrsMinSec(mediaTrack.durationInMillis)); + } + + @Override + public void show() { + super.show(); + urlBox.setFocus(true); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaTrackYoutubeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaTrackYoutubeDialog.java new file mode 100644 index 00000000000..f974657f622 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaTrackYoutubeDialog.java @@ -0,0 +1,162 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.dom.client.Style; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.TextArea; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack.MimeType; + +public class MediaTrackYoutubeDialog extends DataEntryDialog { + + private static final boolean DONT_FIRE_EVENTS = false; + + private static final Validator MEDIA_TRACK_VALIDATOR = new Validator() { + + @Override + public String getErrorMessage(MediaTrack valueToValidate) { + return null; + } + + }; + + private final StringMessages stringMessages; + + private MediaTrack mediaTrack = new MediaTrack(); + + private TextBox youtubeIdBox; + + private Label titleLabel; + + private Label durationLabel; + + private TextArea descriptionText; + + private SafeHtmlBuilder safeHtmlBuilder = new SafeHtmlBuilder(); + + public MediaTrackYoutubeDialog(StringMessages stringMessages, DialogCallback dialogCallback) { + super(stringMessages.addYoutubeTrack(), "", stringMessages.ok(), stringMessages.cancel(), MEDIA_TRACK_VALIDATOR, dialogCallback); + this.stringMessages = stringMessages; + registerNativeMethods(); + } + + @Override + protected MediaTrack getResult() { + mediaTrack.url = youtubeIdBox.getValue(); + mediaTrack.mimeType = MimeType.youtube; + return mediaTrack; + } + + + @Override + protected Widget getAdditionalWidget() { + VerticalPanel mainPanel = new VerticalPanel(); + Grid formGrid = new Grid(5, 2); + formGrid.setCellSpacing(3); + formGrid.setWidget(0, 0, new Label(stringMessages.youtubeId() + ":")); + youtubeIdBox = createYoutubeIdBox(); + formGrid.setWidget(0, 1, youtubeIdBox); + formGrid.setWidget(1, 0, new Label(stringMessages.name() + ":")); + titleLabel = new Label(); + formGrid.setWidget(1, 1, titleLabel); + formGrid.setWidget(2, 0, new Label(stringMessages.duration() + ":")); + durationLabel = new Label(); + formGrid.setWidget(2, 1, durationLabel); + mainPanel.add(formGrid); + descriptionText = new TextArea(); + mainPanel.add(descriptionText); + return mainPanel; + } + + private TextBox createYoutubeIdBox() { + TextBox result = createTextBox(null); + result.addChangeHandler(new ChangeHandler() { + + @Override + public void onChange(ChangeEvent event) { + startUpdateMetadata(); + setUiEnabled(false); + } + + }); + return result; + } + + protected void setUiEnabled(boolean isEnabled) { + youtubeIdBox.setEnabled(isEnabled); + getOkButton().setEnabled(isEnabled); + if (isEnabled) { + setCursor(Style.Cursor.AUTO); + } else { + setCursor(Style.Cursor.WAIT); + } + } + + protected void startUpdateMetadata() { + mediaTrack.url = youtubeIdBox.getValue(); + loadYoutubeMetadata(mediaTrack.url); + } + + private native void registerNativeMethods() /*-{ + var that = this; + window.youtubeMetadataCallback = function(metadata) { + var title = metadata.entry.media$group.media$title.$t; + var duration = metadata.entry.media$group.yt$duration.seconds; + var description = metadata.entry.media$group.media$description.$t; + that.@com.sap.sailing.gwt.ui.adminconsole.MediaTrackYoutubeDialog::youtubeMetadataCallback(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(title, duration, description); + } + }-*/; + + /** Inspired by https://developers.google.com/web-toolkit/doc/latest/tutorial/Xsite + * + * @param youtubeId + */ + public native void loadYoutubeMetadata(String youtubeId) /*-{ + var that = this; + + //Create temporary script element. + window.youtubeMetadataCallbackScript = document.createElement("script"); + window.youtubeMetadataCallbackScript.src = "http://gdata.youtube.com/feeds/api/videos/" + youtubeId + "?alt=json&orderby=published&format=6&callback=youtubeMetadataCallback"; + document.body.appendChild(window.youtubeMetadataCallbackScript); + + // Cancel meta data capturing after has 2-seconds timeout. + setTimeout(function() { + //Remove temporary script element. + document.body.removeChild(window.youtubeMetadataCallbackScript); + delete window.youtubeMetadataCallbackScript; + that.@com.sap.sailing.gwt.ui.adminconsole.MediaTrackYoutubeDialog::setUiEnabled(Z)(true); + }, 2000); + + }-*/; + + public void youtubeMetadataCallback(String title, String durationInSeconds, String description) { + setUiEnabled(true); + mediaTrack.title = title; + try { + mediaTrack.durationInMillis = (int) (1000 * Double.valueOf(durationInSeconds)); + } catch (NumberFormatException ex) { + mediaTrack.durationInMillis = 0; + } + descriptionText.setText(description); + refreshUI(); + } + + private void refreshUI() { + titleLabel.setText(mediaTrack.title); + durationLabel.setText(TimeFormatUtil.milliSecondsToHrsMinSec(mediaTrack.durationInMillis)); + } + + @Override + public void show() { + super.show(); + youtubeIdBox.setFocus(true); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TimeFormatUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TimeFormatUtil.java new file mode 100644 index 00000000000..7494b473cc1 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TimeFormatUtil.java @@ -0,0 +1,42 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; +import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; + +public class TimeFormatUtil { + + public static final DateTimeFormat DATETIME_FORMAT = DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_MEDIUM); + + private static NumberFormat zeroPaddingNumberFormat_Min = NumberFormatterFactory.getDecimalFormat(2, 0); + private static NumberFormat zeroPaddingNumberFormat_Sec = NumberFormatterFactory.getDecimalFormat(2, 3); + + public static int hrsMinSecToMilliSeconds(String hrsMinSec) { + String[] segements = hrsMinSec.split(":"); + int milliseconds = 0; + switch (segements.length) { + case 3: + String hours = segements[0].trim(); + milliseconds = Integer.valueOf(hours) * 60 * 60 * 1000; + case 2: + String minutes = segements[segements.length - 2].trim(); + milliseconds = milliseconds + Integer.valueOf(minutes) * 60 * 1000; + case 1: + String seconds = segements[segements.length - 1].trim(); + milliseconds = milliseconds + Math.round(Float.valueOf(seconds) * 1000); + } + return milliseconds; + } + + public static String milliSecondsToHrsMinSec(long milliseconds) { + long hours = (milliseconds / (60 * 60 * 1000)); + long rest = (milliseconds % (60 * 60 * 1000)); + long minutes = (rest / (60 * 1000)); + rest = (rest % (60 * 1000)); + double seconds = (double) rest / 1000; + String result = String.valueOf(hours) + ':' + zeroPaddingNumberFormat_Min.format(minutes) + ':' + zeroPaddingNumberFormat_Sec.format(seconds); + return result; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java index 6decbc11bfb..9335b9cce50 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractEntryPoint.java @@ -1,176 +1,190 @@ -package com.sap.sailing.gwt.ui.client; - -import com.google.gwt.core.client.EntryPoint; -import com.google.gwt.core.client.GWT; -import com.google.gwt.debug.client.DebugInfo; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.resources.client.TextResource; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.ServiceDefTarget; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.DialogBox; -import com.google.gwt.user.client.ui.FocusWidget; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.VerticalPanel; - -public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, WindowSizeDetector { - public static final String DEBUG_ID_ATTRIBUTE = "selenium-id"; - public static final String DEBUG_ID_PREFIX = ""; - - private DialogBox errorDialogBox; - private HTML serverResponseLabel; - private Button dialogCloseButton; - protected StringMessages stringMessages; - protected UserAgentDetails userAgent; - - /** - * Create a remote service proxy to talk to the server-side sailing service. - */ - protected final SailingServiceAsync sailingService = GWT.create(SailingService.class); - - /** - * Create a remote service proxy to talk to the server-side user management service. - */ - protected final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class); - - /** - * The message displayed to the user when the server cannot be reached or - * returns an error. - */ - private static final String SERVER_ERROR = "An error occurred while " //$NON-NLS-1$ - + "attempting to contact the server. Please check your network " + "connection and try again."; //$NON-NLS-1$ //$NON-NLS-2$ - - @Override - public final void onModuleLoad() { - if(DebugInfo.isDebugIdEnabled()) { - PendingAjaxCallBundle bundle = GWT.create(PendingAjaxCallBundle.class); - TextResource script = bundle.ajaxSemaphoreJS(); - JavaScriptInjector.inject(script.getText()); - - DebugInfo.setDebugIdAttribute(DEBUG_ID_ATTRIBUTE, false); - DebugInfo.setDebugIdPrefix(DEBUG_ID_PREFIX); - } - - doOnModuleLoad(); - - if(DebugInfo.isDebugIdEnabled()) { - PendingAjaxCallMarker.decrementPendingAjaxCalls(); - } - } - - protected void doOnModuleLoad() { - stringMessages = GWT.create(StringMessages.class); - errorDialogBox = createErrorDialog(); /* TODO: Make this more generic (e.g. make it support all kinds of messages) */ - userAgent = new UserAgentDetails(Window.Navigator.getUserAgent()); - - ServiceDefTarget sailingServiceDef = (ServiceDefTarget) sailingService; - ServiceDefTarget userManagementServiceDef = (ServiceDefTarget) userManagementService; - String moduleBaseURL = GWT.getModuleBaseURL(); - String baseURL = moduleBaseURL.substring(0, moduleBaseURL.lastIndexOf('/', moduleBaseURL.length()-2)+1); - sailingServiceDef.setServiceEntryPoint(baseURL + "sailing"); - userManagementServiceDef.setServiceEntryPoint(baseURL + "usermanagement"); - } - - @Override - public void reportError(String message) { - errorDialogBox.setText(message); - serverResponseLabel.addStyleName("serverResponseLabelError"); //$NON-NLS-1$ - serverResponseLabel.setHTML(SERVER_ERROR); - errorDialogBox.center(); - dialogCloseButton.setFocus(true); - } - - @Override - public void reportError(String message, boolean silentMode) { - if (silentMode) { - Window.setStatus(message); - } else { - reportError(message); - } - } - - @Override - public boolean isSmallWidth() { - int width = Window.getClientWidth(); - return width <= 720; - } - - public void createErrorPage(String message) { - LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(stringMessages, this); - logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); - RootPanel.get().add(logoAndTitlePanel); - RootPanel.get().add(new Label(message)); - } - - private DialogBox createErrorDialog() { - // Create the popup dialog box - final DialogBox myErrorDialogBox = new DialogBox(); - myErrorDialogBox.setText("Remote Procedure Call"); //$NON-NLS-1$ - myErrorDialogBox.setAnimationEnabled(true); - dialogCloseButton = new Button("Close"); //$NON-NLS-1$ - // We can set the id of a widget by accessing its Element - dialogCloseButton.getElement().setId("closeButton"); //$NON-NLS-1$ - final Label textToServerLabel = new Label(); - serverResponseLabel = new HTML(); - VerticalPanel dialogVPanel = new VerticalPanel(); - dialogVPanel.add(new HTML("Error communicating with server")); //$NON-NLS-1$ - dialogVPanel.add(textToServerLabel); - dialogVPanel.add(new HTML("
Server replies:")); //$NON-NLS-1$ - dialogVPanel.add(serverResponseLabel); - dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT); - dialogVPanel.add(dialogCloseButton); - myErrorDialogBox.setWidget(dialogVPanel); - // Add a handler to close the DialogBox - dialogCloseButton.addClickHandler(new ClickHandler() { - public void onClick(ClickEvent event) { - myErrorDialogBox.hide(); - } - }); - return myErrorDialogBox; - } - - public static void linkEnterToButton(final Button button, FocusWidget... widgets) { - for (FocusWidget widget : widgets) { - widget.addKeyPressHandler(new KeyPressHandler() { - @Override - public void onKeyPress(KeyPressEvent event) { - if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) { - button.click(); - } - } - }); - } - } - - public static void linkEscapeToButton(final Button button, FocusWidget... widgets) { - for (FocusWidget widget : widgets) { - widget.addKeyPressHandler(new KeyPressHandler() { - @Override - public void onKeyPress(KeyPressEvent event) { - if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) { - button.click(); - } - } - }); - } - } - - public static void addFocusUponKeyUpToggler(final FocusWidget focusable) { - focusable.addKeyUpHandler(new KeyUpHandler() { - @Override - public void onKeyUp(KeyUpEvent event) { - focusable.setFocus(false); - // this ensures that the value is copied into the TextBox.getValue() result and a ChangeEvent is fired - focusable.setFocus(true); - } - }); - } -} +package com.sap.sailing.gwt.ui.client; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; +import com.google.gwt.debug.client.DebugInfo; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.resources.client.TextResource; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.FocusWidget; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.VerticalPanel; + +public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, WindowSizeDetector { + /** + *

The attribute which is used for the debug id.

+ */ + public static final String DEBUG_ID_ATTRIBUTE = "selenium-id"; //$NON-NLS-1$ + + /** + *

The prefix which is used for the debug id.

+ */ + public static final String DEBUG_ID_PREFIX = ""; //$NON-NLS-1$ + + private DialogBox errorDialogBox; + private HTML serverResponseLabel; + private Button dialogCloseButton; + protected StringMessages stringMessages; + protected UserAgentDetails userAgent; + + /** + * Create a remote service proxy to talk to the server-side sailing service. + */ + protected final SailingServiceAsync sailingService = GWT.create(SailingService.class); + + /** + * Create a remote service proxy to talk to the server-side media service. + */ + protected final MediaServiceAsync mediaService = GWT.create(MediaService.class); + + /** + * Create a remote service proxy to talk to the server-side user management service. + */ + protected final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class); + + /** + * The message displayed to the user when the server cannot be reached or + * returns an error. + */ + private static final String SERVER_ERROR = "An error occurred while " //$NON-NLS-1$ + + "attempting to contact the server. Please check your network " + "connection and try again."; //$NON-NLS-1$ //$NON-NLS-2$ + + @Override + public final void onModuleLoad() { + if(DebugInfo.isDebugIdEnabled()) { + PendingAjaxCallBundle bundle = GWT.create(PendingAjaxCallBundle.class); + TextResource script = bundle.ajaxSemaphoreJS(); + JavaScriptInjector.inject(script.getText()); + + DebugInfo.setDebugIdAttribute(DEBUG_ID_ATTRIBUTE, false); + DebugInfo.setDebugIdPrefix(DEBUG_ID_PREFIX); + } + + doOnModuleLoad(); + + if(DebugInfo.isDebugIdEnabled()) { + PendingAjaxCallMarker.decrementPendingAjaxCalls(MarkedAsyncCallback.CATEGORY_GLOBAL); + } + } + + protected void doOnModuleLoad() { + stringMessages = GWT.create(StringMessages.class); + errorDialogBox = createErrorDialog(); /* TODO: Make this more generic (e.g. make it support all kinds of messages) */ + userAgent = new UserAgentDetails(Window.Navigator.getUserAgent()); + + ServiceDefTarget sailingServiceDef = (ServiceDefTarget) sailingService; + ServiceDefTarget mediaServiceDef = (ServiceDefTarget) mediaService; + ServiceDefTarget userManagementServiceDef = (ServiceDefTarget) userManagementService; + String moduleBaseURL = GWT.getModuleBaseURL(); + String baseURL = moduleBaseURL.substring(0, moduleBaseURL.lastIndexOf('/', moduleBaseURL.length()-2)+1); + sailingServiceDef.setServiceEntryPoint(baseURL + "sailing"); + mediaServiceDef.setServiceEntryPoint(baseURL + "media"); + userManagementServiceDef.setServiceEntryPoint(baseURL + "usermanagement"); + } + + @Override + public void reportError(String message) { + errorDialogBox.setText(message); + serverResponseLabel.addStyleName("serverResponseLabelError"); //$NON-NLS-1$ + serverResponseLabel.setHTML(SERVER_ERROR); + errorDialogBox.center(); + dialogCloseButton.setFocus(true); + } + + @Override + public void reportError(String message, boolean silentMode) { + if (silentMode) { + Window.setStatus(message); + } else { + reportError(message); + } + } + + @Override + public boolean isSmallWidth() { + int width = Window.getClientWidth(); + return width <= 720; + } + + public void createErrorPage(String message) { + LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(stringMessages, this); + logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); + RootPanel.get().add(logoAndTitlePanel); + RootPanel.get().add(new Label(message)); + } + + private DialogBox createErrorDialog() { + // Create the popup dialog box + final DialogBox myErrorDialogBox = new DialogBox(); + myErrorDialogBox.setText("Remote Procedure Call"); //$NON-NLS-1$ + myErrorDialogBox.setAnimationEnabled(true); + dialogCloseButton = new Button("Close"); //$NON-NLS-1$ + // We can set the id of a widget by accessing its Element + dialogCloseButton.getElement().setId("closeButton"); //$NON-NLS-1$ + final Label textToServerLabel = new Label(); + serverResponseLabel = new HTML(); + VerticalPanel dialogVPanel = new VerticalPanel(); + dialogVPanel.add(new HTML("Error communicating with server")); //$NON-NLS-1$ + dialogVPanel.add(textToServerLabel); + dialogVPanel.add(new HTML("
Server replies:")); //$NON-NLS-1$ + dialogVPanel.add(serverResponseLabel); + dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT); + dialogVPanel.add(dialogCloseButton); + myErrorDialogBox.setWidget(dialogVPanel); + // Add a handler to close the DialogBox + dialogCloseButton.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + myErrorDialogBox.hide(); + } + }); + return myErrorDialogBox; + } + + public static void linkEnterToButton(final Button button, FocusWidget... widgets) { + for (FocusWidget widget : widgets) { + widget.addKeyPressHandler(new KeyPressHandler() { + @Override + public void onKeyPress(KeyPressEvent event) { + if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) { + button.click(); + } + } + }); + } + } + + public static void linkEscapeToButton(final Button button, FocusWidget... widgets) { + for (FocusWidget widget : widgets) { + widget.addKeyPressHandler(new KeyPressHandler() { + @Override + public void onKeyPress(KeyPressEvent event) { + if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) { + button.click(); + } + } + }); + } + } + + public static void addFocusUponKeyUpToggler(final FocusWidget focusable) { + focusable.addKeyUpHandler(new KeyUpHandler() { + @Override + public void onKeyUp(KeyUpEvent event) { + focusable.setFocus(false); + // this ensures that the value is copied into the TextBox.getValue() result and a ChangeEvent is fired + focusable.setFocus(true); + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java index 7d32fa4b426..65caa378028 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DataEntryDialog.java @@ -1,389 +1,394 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.Date; - -import com.google.gwt.dom.client.Style.FontWeight; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.DialogBox; -import com.google.gwt.user.client.ui.DoubleBox; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HasVerticalAlignment; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.IntegerBox; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.LongBox; -import com.google.gwt.user.client.ui.RadioButton; -import com.google.gwt.user.client.ui.TextArea; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.user.datepicker.client.DateBox; - -/** - * An abstract data entry dialog class, capturing data of type T, with generic OK/Cancel buttons, title and - * message. Subclasses may override the {@link #show()} method to set the focus on their favorable initial entry field. - * Subclasses can specify a widget to show in the dialog to capture properties specific to the result type T by - * overriding the {@link #getAdditionalWidget()} method. - * - * @author Axel Uhl (d043530) - */ -public abstract class DataEntryDialog { - private final DialogBox dateEntryDialog; - private final Validator validator; - private final Button okButton; - private final Button cancelButton; - private final Label statusLabel; - private final FlowPanel panelForAdditionalWidget; - - public static interface Validator { - /** - * @return null in case the valueToValidate is valid; a user-readable error message otherwise - */ - String getErrorMessage(T valueToValidate); - } - - public static interface DialogCallback { - void ok(T editedObject); - void cancel(); - } - - /** - * @param validator - * an optional validator; if null, no validation of data entered is performed; otherwise, - * data validation is triggered upon any noticeable change in any of the elements constructed by - * {@link #createCheckbox(String)}, {@link #createTextBox(String)}, etc. - * @param callback - * will be called when the dialog if {@link AsyncCallback#onFailure(Throwable) cancelled} or - * {@link AsyncCallback#onSuccess(Object) confirmed} - */ - public DataEntryDialog(String title, String message, String okButtonName, String cancelButtonName, - Validator validator, final DialogCallback callback) { - this(title, message, okButtonName, cancelButtonName, validator, /* animationEnabled */ true, callback); - } - - /** - * @param validator - * an optional validator; if null, no validation of data entered is performed; otherwise, - * data validation is triggered upon any noticeable change in any of the elements constructed by - * {@link #createCheckbox(String)}, {@link #createTextBox(String)}, etc. - * @param callback - * will be called when the dialog if {@link AsyncCallback#onFailure(Throwable) cancelled} or - * {@link AsyncCallback#onSuccess(Object) confirmed} - */ - public DataEntryDialog(String title, String message, String okButtonName, String cancelButtonName, - Validator validator, boolean animationEnabled, final DialogCallback callback) { - dateEntryDialog = new DialogBox(); - dateEntryDialog.setText(title); - dateEntryDialog.setAnimationEnabled(animationEnabled); - this.validator = validator; - okButton = new Button(okButtonName); - okButton.getElement().getStyle().setMargin(3, Unit.PX); - FlowPanel dialogFPanel = new FlowPanel(); - statusLabel = new Label(); - dialogFPanel.add(statusLabel); - if (message != null) { - Label messageLabel = new Label(message); - messageLabel.addStyleName("dialogMessageLabel"); - dialogFPanel.add(messageLabel); - } - - panelForAdditionalWidget = new FlowPanel(); - dialogFPanel.add(panelForAdditionalWidget); - FlowPanel buttonPanel = new FlowPanel(); - dialogFPanel.add(buttonPanel); - buttonPanel.setStyleName("additionalWidgets"); - buttonPanel.add(okButton); - cancelButton = new Button(cancelButtonName); - cancelButton.getElement().getStyle().setMargin(3, Unit.PX); - buttonPanel.add(cancelButton); - cancelButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - dateEntryDialog.hide(); - callback.cancel(); - } - }); - dateEntryDialog.setWidget(dialogFPanel); - okButton.addClickHandler(new ClickHandler() { - public void onClick(ClickEvent event) { - dateEntryDialog.hide(); - callback.ok(getResult()); - } - }); - } - - protected boolean validate() { - String errorMessage = null; - if (validator != null) { - errorMessage = validator.getErrorMessage(getResult()); - } - if (errorMessage == null) { - getStatusLabel().setText(""); - getOkButton().setEnabled(true); - } else { - getStatusLabel().setText(errorMessage); - getStatusLabel().setStyleName("errorLabel"); - getOkButton().setEnabled(false); - } - return errorMessage == null; - } - - protected abstract T getResult(); - - /** - * Creates a text box with a key-up listener attached which ensures the value is updated after each - * key-up event and the entire dialog is {@link #validate() validated} in this case. - * - * @param initialValue initial value to show in text box; null is permissible - */ - public TextBox createTextBox(String initialValue) { - TextBox textBox = new TextBox(); - textBox.setText(initialValue == null ? "" : initialValue); - AbstractEntryPoint.addFocusUponKeyUpToggler(textBox); - textBox.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), textBox); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textBox); - return textBox; - } - - /** - * Creates a text area with a key-up listener attached which ensures the value is updated after each - * key-up event and the entire dialog is {@link #validate() validated} in this case. - * - * @param initialValue Initial value to show in text area; null is permissible - */ - public TextArea createTextArea(String initialValue) { - TextArea textArea = new TextArea(); - textArea.setText(initialValue == null ? "" : initialValue); - AbstractEntryPoint.addFocusUponKeyUpToggler(textArea); - textArea.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent arg0) { - validate(); - } - }); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textArea); - return textArea; - } - - /** - * Creates a box for a long value with a key-up listener attached which ensures the value is updated after each - * key-up event and the entire dialog is {@link #validate() validated} in this case. - * - * @param initialValue initial value to show in the long box; null is permissible - */ - public LongBox createLongBox(long initialValue, int visibleLength) { - LongBox longBox = new LongBox(); - longBox.setVisibleLength(visibleLength); - longBox.setValue(initialValue); - AbstractEntryPoint.addFocusUponKeyUpToggler(longBox); - longBox.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), longBox); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), longBox); - return longBox; - } - - public DoubleBox createDoubleBox(double initialValue, int visibleLength) { - return createDoubleBoxInternal(initialValue, visibleLength); - } - - public DoubleBox createDoubleBox(int visibleLength) { - return createDoubleBoxInternal(null, visibleLength); - } - - private DoubleBox createDoubleBoxInternal(Double initialValue, int visibleLength) { - DoubleBox doubleBox = new DoubleBox(); - doubleBox.setVisibleLength(visibleLength); - doubleBox.setValue(initialValue); - AbstractEntryPoint.addFocusUponKeyUpToggler(doubleBox); - doubleBox.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), doubleBox); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), doubleBox); - return doubleBox; - } - - public DateBox createDateBox(long initialTimeInMs, int visibleLength) { - return createDateBoxInternal(new Date(initialTimeInMs), visibleLength); - } - - public DateBox createDateBox(int visibleLength) { - return createDateBoxInternal(null, visibleLength); - } - - private DateBox createDateBoxInternal(Date initialDate, int visibleLength) { - DateBox textBox = new DateBox(); - textBox.getTextBox().setVisibleLength(visibleLength); - textBox.setValue(initialDate); - AbstractEntryPoint.addFocusUponKeyUpToggler(textBox.getTextBox()); - textBox.getTextBox().addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), textBox.getTextBox()); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textBox.getTextBox()); - return textBox; - } - - /** - * Creates a box for a integer value with a key-up listener attached which ensures the value is updated after each - * key-up event and the entire dialog is {@link #validate() validated} in this case. - * - * @param initialValue initial value to show in the integer box; null is permissible - */ - public IntegerBox createIntegerBox(int initialValue, int visibleLength) { - IntegerBox intBox = new IntegerBox(); - intBox.setVisibleLength(visibleLength); - intBox.setValue(initialValue); - AbstractEntryPoint.addFocusUponKeyUpToggler(intBox); - intBox.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), intBox); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), intBox); - return intBox; - } - - public LongBox createLongBoxWithOptionalValue(Long initialValue, int visibleLength) { - LongBox longBox = new LongBox(); - longBox.setVisibleLength(visibleLength); - longBox.setValue(initialValue); - AbstractEntryPoint.addFocusUponKeyUpToggler(longBox); - longBox.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), longBox); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), longBox); - return longBox; - } - - public Label createHeadlineLabel(String headlineText) { - Label headlineLabel = new Label(headlineText); - headlineLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - headlineLabel.getElement().getStyle().setPaddingTop(1, Unit.EM); - return headlineLabel; - } - - public FlowPanel createHeadline(String headlineText, boolean regularHeadline) { - FlowPanel headlinePanel = new FlowPanel(); - Label headlineLabel = new Label(headlineText); - if (regularHeadline) { - headlinePanel.addStyleName("dialogInnerHeadline"); - } else { - headlinePanel.addStyleName("dialogInnerHeadlineOther"); - } - headlinePanel.add(headlineLabel); - return headlinePanel; - } - - public CheckBox createCheckbox(String checkboxLabel) { - CheckBox result = new CheckBox(checkboxLabel); - result.setWordWrap(false); - result.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), result); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), result); - return result; - } - - public RadioButton createRadioButton(String radioButtonGroupName, String radioButtonLabel) { - RadioButton result = new RadioButton(radioButtonGroupName, radioButtonLabel); - result.setWordWrap(false); - result.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), result); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), result); - return result; - } - - public ListBox createListBox(boolean isMultipleSelect) { - ListBox result = new ListBox(isMultipleSelect); - result.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - validate(); - } - }); - AbstractEntryPoint.linkEnterToButton(getOkButton(), result); - AbstractEntryPoint.linkEscapeToButton(getCancelButton(), result); - return result; - } - - public void alignAllPanelWidgetsVertically(HorizontalPanel panel, HasVerticalAlignment.VerticalAlignmentConstant alignment) { - for(int i = 0; i < panel.getWidgetCount(); i++) { - panel.setCellVerticalAlignment(panel.getWidget(i), alignment); - } - } - - /** - * Can contribute an additional widget to be displayed underneath the text entry field. If null is - * returned, no additional widget will be displayed. This is the default behavior of this default implementation. - */ - protected Widget getAdditionalWidget() { - return null; - } - - protected Button getOkButton() { - return okButton; - } - - protected Button getCancelButton() { - return cancelButton; - } - - /** - * status label can be used to display status information within the dialog to the user - */ - protected Label getStatusLabel() { - return statusLabel; - } - - public void show() { - Widget additionalWidget = getAdditionalWidget(); - if (additionalWidget != null) { - panelForAdditionalWidget.add(additionalWidget); - } - validate(); - dateEntryDialog.center(); - } - -} +package com.sap.sailing.gwt.ui.client; + +import java.util.Date; + +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.FontWeight; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.DoubleBox; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.IntegerBox; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.LongBox; +import com.google.gwt.user.client.ui.RadioButton; +import com.google.gwt.user.client.ui.TextArea; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.datepicker.client.DateBox; + +/** + * An abstract data entry dialog class, capturing data of type T, with generic OK/Cancel buttons, title and + * message. Subclasses may override the {@link #show()} method to set the focus on their favorable initial entry field. + * Subclasses can specify a widget to show in the dialog to capture properties specific to the result type T by + * overriding the {@link #getAdditionalWidget()} method. + * + * @author Axel Uhl (d043530) + */ +public abstract class DataEntryDialog { + private final DialogBox dateEntryDialog; + private final Validator validator; + private final Button okButton; + private final Button cancelButton; + private final Label statusLabel; + private final FlowPanel panelForAdditionalWidget; + + public static interface Validator { + /** + * @return null in case the valueToValidate is valid; a user-readable error message otherwise + */ + String getErrorMessage(T valueToValidate); + } + + public static interface DialogCallback { + void ok(T editedObject); + void cancel(); + } + + /** + * @param validator + * an optional validator; if null, no validation of data entered is performed; otherwise, + * data validation is triggered upon any noticeable change in any of the elements constructed by + * {@link #createCheckbox(String)}, {@link #createTextBox(String)}, etc. + * @param callback + * will be called when the dialog if {@link AsyncCallback#onFailure(Throwable) cancelled} or + * {@link AsyncCallback#onSuccess(Object) confirmed} + */ + public DataEntryDialog(String title, String message, String okButtonName, String cancelButtonName, + Validator validator, final DialogCallback callback) { + this(title, message, okButtonName, cancelButtonName, validator, /* animationEnabled */ true, callback); + } + + /** + * @param validator + * an optional validator; if null, no validation of data entered is performed; otherwise, + * data validation is triggered upon any noticeable change in any of the elements constructed by + * {@link #createCheckbox(String)}, {@link #createTextBox(String)}, etc. + * @param callback + * will be called when the dialog if {@link AsyncCallback#onFailure(Throwable) cancelled} or + * {@link AsyncCallback#onSuccess(Object) confirmed} + */ + public DataEntryDialog(String title, String message, String okButtonName, String cancelButtonName, + Validator validator, boolean animationEnabled, final DialogCallback callback) { + dateEntryDialog = new DialogBox(); + dateEntryDialog.setText(title); + dateEntryDialog.setAnimationEnabled(animationEnabled); + this.validator = validator; + okButton = new Button(okButtonName); + okButton.getElement().getStyle().setMargin(3, Unit.PX); + FlowPanel dialogFPanel = new FlowPanel(); + statusLabel = new Label(); + dialogFPanel.add(statusLabel); + if (message != null) { + Label messageLabel = new Label(message); + messageLabel.addStyleName("dialogMessageLabel"); + dialogFPanel.add(messageLabel); + } + + panelForAdditionalWidget = new FlowPanel(); + dialogFPanel.add(panelForAdditionalWidget); + FlowPanel buttonPanel = new FlowPanel(); + dialogFPanel.add(buttonPanel); + buttonPanel.setStyleName("additionalWidgets"); + buttonPanel.add(okButton); + cancelButton = new Button(cancelButtonName); + cancelButton.getElement().getStyle().setMargin(3, Unit.PX); + buttonPanel.add(cancelButton); + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dateEntryDialog.hide(); + callback.cancel(); + } + }); + dateEntryDialog.setWidget(dialogFPanel); + okButton.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + dateEntryDialog.hide(); + callback.ok(getResult()); + } + }); + } + + protected boolean validate() { + String errorMessage = null; + if (validator != null) { + errorMessage = validator.getErrorMessage(getResult()); + } + if (errorMessage == null) { + getStatusLabel().setText(""); + getOkButton().setEnabled(true); + } else { + getStatusLabel().setText(errorMessage); + getStatusLabel().setStyleName("errorLabel"); + getOkButton().setEnabled(false); + } + return errorMessage == null; + } + + protected abstract T getResult(); + + /** + * Creates a text box with a key-up listener attached which ensures the value is updated after each + * key-up event and the entire dialog is {@link #validate() validated} in this case. + * + * @param initialValue initial value to show in text box; null is permissible + */ + public TextBox createTextBox(String initialValue) { + TextBox textBox = new TextBox(); + textBox.setText(initialValue == null ? "" : initialValue); + AbstractEntryPoint.addFocusUponKeyUpToggler(textBox); + textBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), textBox); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textBox); + return textBox; + } + + /** + * Creates a text area with a key-up listener attached which ensures the value is updated after each + * key-up event and the entire dialog is {@link #validate() validated} in this case. + * + * @param initialValue Initial value to show in text area; null is permissible + */ + public TextArea createTextArea(String initialValue) { + TextArea textArea = new TextArea(); + textArea.setText(initialValue == null ? "" : initialValue); + AbstractEntryPoint.addFocusUponKeyUpToggler(textArea); + textArea.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent arg0) { + validate(); + } + }); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textArea); + return textArea; + } + + /** + * Creates a box for a long value with a key-up listener attached which ensures the value is updated after each + * key-up event and the entire dialog is {@link #validate() validated} in this case. + * + * @param initialValue initial value to show in the long box; null is permissible + */ + public LongBox createLongBox(long initialValue, int visibleLength) { + LongBox longBox = new LongBox(); + longBox.setVisibleLength(visibleLength); + longBox.setValue(initialValue); + AbstractEntryPoint.addFocusUponKeyUpToggler(longBox); + longBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), longBox); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), longBox); + return longBox; + } + + public DoubleBox createDoubleBox(double initialValue, int visibleLength) { + return createDoubleBoxInternal(initialValue, visibleLength); + } + + public DoubleBox createDoubleBox(int visibleLength) { + return createDoubleBoxInternal(null, visibleLength); + } + + private DoubleBox createDoubleBoxInternal(Double initialValue, int visibleLength) { + DoubleBox doubleBox = new DoubleBox(); + doubleBox.setVisibleLength(visibleLength); + doubleBox.setValue(initialValue); + AbstractEntryPoint.addFocusUponKeyUpToggler(doubleBox); + doubleBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), doubleBox); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), doubleBox); + return doubleBox; + } + + public DateBox createDateBox(long initialTimeInMs, int visibleLength) { + return createDateBoxInternal(new Date(initialTimeInMs), visibleLength); + } + + public DateBox createDateBox(int visibleLength) { + return createDateBoxInternal(null, visibleLength); + } + + private DateBox createDateBoxInternal(Date initialDate, int visibleLength) { + DateBox textBox = new DateBox(); + textBox.getTextBox().setVisibleLength(visibleLength); + textBox.setValue(initialDate); + AbstractEntryPoint.addFocusUponKeyUpToggler(textBox.getTextBox()); + textBox.getTextBox().addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), textBox.getTextBox()); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textBox.getTextBox()); + return textBox; + } + + /** + * Creates a box for a integer value with a key-up listener attached which ensures the value is updated after each + * key-up event and the entire dialog is {@link #validate() validated} in this case. + * + * @param initialValue initial value to show in the integer box; null is permissible + */ + public IntegerBox createIntegerBox(int initialValue, int visibleLength) { + IntegerBox intBox = new IntegerBox(); + intBox.setVisibleLength(visibleLength); + intBox.setValue(initialValue); + AbstractEntryPoint.addFocusUponKeyUpToggler(intBox); + intBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), intBox); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), intBox); + return intBox; + } + + public LongBox createLongBoxWithOptionalValue(Long initialValue, int visibleLength) { + LongBox longBox = new LongBox(); + longBox.setVisibleLength(visibleLength); + longBox.setValue(initialValue); + AbstractEntryPoint.addFocusUponKeyUpToggler(longBox); + longBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), longBox); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), longBox); + return longBox; + } + + public Label createHeadlineLabel(String headlineText) { + Label headlineLabel = new Label(headlineText); + headlineLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); + headlineLabel.getElement().getStyle().setPaddingTop(1, Unit.EM); + return headlineLabel; + } + + public FlowPanel createHeadline(String headlineText, boolean regularHeadline) { + FlowPanel headlinePanel = new FlowPanel(); + Label headlineLabel = new Label(headlineText); + if (regularHeadline) { + headlinePanel.addStyleName("dialogInnerHeadline"); + } else { + headlinePanel.addStyleName("dialogInnerHeadlineOther"); + } + headlinePanel.add(headlineLabel); + return headlinePanel; + } + + public CheckBox createCheckbox(String checkboxLabel) { + CheckBox result = new CheckBox(checkboxLabel); + result.setWordWrap(false); + result.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), result); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), result); + return result; + } + + public RadioButton createRadioButton(String radioButtonGroupName, String radioButtonLabel) { + RadioButton result = new RadioButton(radioButtonGroupName, radioButtonLabel); + result.setWordWrap(false); + result.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), result); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), result); + return result; + } + + public ListBox createListBox(boolean isMultipleSelect) { + ListBox result = new ListBox(isMultipleSelect); + result.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + AbstractEntryPoint.linkEnterToButton(getOkButton(), result); + AbstractEntryPoint.linkEscapeToButton(getCancelButton(), result); + return result; + } + + public void alignAllPanelWidgetsVertically(HorizontalPanel panel, HasVerticalAlignment.VerticalAlignmentConstant alignment) { + for(int i = 0; i < panel.getWidgetCount(); i++) { + panel.setCellVerticalAlignment(panel.getWidget(i), alignment); + } + } + + /** + * Can contribute an additional widget to be displayed underneath the text entry field. If null is + * returned, no additional widget will be displayed. This is the default behavior of this default implementation. + */ + protected Widget getAdditionalWidget() { + return null; + } + + protected Button getOkButton() { + return okButton; + } + + protected Button getCancelButton() { + return cancelButton; + } + + /** + * status label can be used to display status information within the dialog to the user + */ + protected Label getStatusLabel() { + return statusLabel; + } + + protected void setCursor(Style.Cursor cursor) { + dateEntryDialog.getElement().getStyle().setCursor(cursor); + } + + public void show() { + Widget additionalWidget = getAdditionalWidget(); + if (additionalWidget != null) { + panelForAdditionalWidget.add(additionalWidget); + } + validate(); + dateEntryDialog.center(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java index 263f92bb5f1..7fd61f58c36 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java @@ -1,10 +1,14 @@ package com.sap.sailing.gwt.ui.client; +import com.google.gwt.core.shared.GWT; import com.google.gwt.i18n.client.NumberFormat; import com.sap.sailing.domain.common.DetailType; public class DetailTypeFormatter { - public static String format(DetailType detailType, StringMessages stringMessages) { + + private static final StringMessages stringMessages = GWT.create(StringMessages.class); + + public static String format(DetailType detailType) { switch (detailType) { case DISTANCE_TRAVELED: return stringMessages.distanceInMeters(); @@ -82,6 +86,104 @@ public class DetailTypeFormatter { } return null; } + + /** + * Returns the unit of the given {@link DetailType}, like 'm', 'kts' or an empty string, if the detail type has no + * unit.
+ * Throws an UnsupportedOperationException if the given detail type isn't supported.
+ * Currently supported are:
+ *
    + *
  • CURRENT_SPEED_OVER_GROUND_IN_KNOTS
  • + *
  • MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS
  • + *
  • DISTANCE_TRAVELED
  • + *
  • GAP_TO_LEADER_IN_SECONDS
  • + *
  • VELOCITY_MADE_GOOD_IN_KNOTS
  • + *
  • WINDWARD_DISTANCE_TO_OVERALL_LEADER
  • + *
  • AVERAGE_SPEED_OVER_GROUND_IN_KNOTS
  • + *
  • ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS
  • + *
  • GAP_CHANGE_SINCE_LEG_START_IN_SECONDS
  • + *
  • AVERAGE_CROSS_TRACK_ERROR_IN_METERS
  • + *
  • RACE_DISTANCE_TRAVELED
  • + *
  • RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS
  • + *
  • RACE_GAP_TO_LEADER_IN_SECONDS
  • + *
  • RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS
  • + *
  • RACE_DISTANCE_TO_LEADER_IN_METERS
  • + *
  • RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS
  • + *
  • AVERAGE_TACK_LOSS_IN_METERS
  • + *
  • AVERAGE_JIBE_LOSS_IN_METERS
  • + *
  • TOTAL_TIME_SAILED_IN_SECONDS
  • + * + *
  • RACE_RANK (no unit)
  • + *
  • CURRENT_LEG (no unit)
  • + *
  • TACK (no unit)
  • + *
  • JIBE (no unit)
  • + *
  • PENALTY_CIRCLE (no unit)
  • + *
+ * + * @param detailType + * @return The unit of the detail type as string. + */ + public static String getUnit(DetailType detailType) { + switch (detailType) { + case CURRENT_SPEED_OVER_GROUND_IN_KNOTS: + case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS: + case MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS: + return stringMessages.currentSpeedOverGroundInKnotsUnit(); + + case DISTANCE_TRAVELED: + case RACE_DISTANCE_TO_LEADER_IN_METERS: + case RACE_DISTANCE_TRAVELED: + case AVERAGE_TACK_LOSS_IN_METERS: + case AVERAGE_JIBE_LOSS_IN_METERS: + case AVERAGE_MANEUVER_LOSS_IN_METERS: + return stringMessages.distanceInMetersUnit(); + + case GAP_TO_LEADER_IN_SECONDS: + case GAP_CHANGE_SINCE_LEG_START_IN_SECONDS: + case RACE_GAP_TO_LEADER_IN_SECONDS: + return stringMessages.gapToLeaderInSecondsUnit(); + + case VELOCITY_MADE_GOOD_IN_KNOTS: + return stringMessages.velocityMadeGoodInKnotsUnit(); + + case WINDWARD_DISTANCE_TO_OVERALL_LEADER: + case WINDWARD_DISTANCE_TO_GO_IN_METERS: + return stringMessages.windwardDistanceToGoInMetersUnit(); + + case AVERAGE_SPEED_OVER_GROUND_IN_KNOTS: + return stringMessages.averageSpeedInKnotsUnit(); + + case ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS: + return stringMessages.estimatedTimeToNextWaypointInSecondsUnit(); + + case AVERAGE_CROSS_TRACK_ERROR_IN_METERS: + case RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS: + return stringMessages.metersUnit(); + + case TIME_TRAVELED: + return stringMessages.secondsUnit(); + + case TOTAL_TIME_SAILED_IN_SECONDS: + return stringMessages.hhmmssUnit(); + + case RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS: + return stringMessages.averageSpeedInKnotsUnit(); + + // Case for detail types without unit, so that an empty string is returned. + case RACE_RANK: + case CURRENT_LEG: + case TACK: + case JIBE: + case PENALTY_CIRCLE: + return ""; + + default: + // Throwing an exception to get notificated if an implementation of + // an detail type is missing. + throw new UnsupportedOperationException("Theres currently no support for the enum value '" + detailType + + "' in this method."); + } + } public static NumberFormat getNumberFormat(DetailType detailType) { String decimalPlaces = ""; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/JavaScriptInjector.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/JavaScriptInjector.java index 94b9dbc05e8..e65c8d56ce2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/JavaScriptInjector.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/JavaScriptInjector.java @@ -6,9 +6,22 @@ import com.google.gwt.dom.client.HeadElement; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.ScriptElement; +/** + *

Utility class to inject JavaScript in the default document.

+ * + * @author + * D049941 + */ public class JavaScriptInjector { private static HeadElement head; + /** + *

Injects the given JavaScript code in the head of the default document. To inject the code a script element is + * created and appended to the head element of the document.

+ * + * @param javascript + * The JavaScript code to inject. + */ public static void inject(String javascript) { HeadElement head = getHead(); ScriptElement element = createScriptElement(); @@ -19,17 +32,17 @@ public class JavaScriptInjector { private static ScriptElement createScriptElement() { Document document = Document.get(); ScriptElement script = document.createScriptElement(); - script.setAttribute("type", "text/javascript"); + script.setAttribute("type", "text/javascript"); //$NON-NLS-1$ //$NON-NLS-2$ return script; } private static HeadElement getHead() { - if (head == null) { + if (JavaScriptInjector.head == null) { Document document = Document.get(); - NodeList nodes = document.getElementsByTagName("head"); + NodeList nodes = document.getElementsByTagName("head"); //$NON-NLS-1$ Element element = nodes.getItem(0); - assert element != null : "HTML Head element required"; + assert element != null : "HTML Head element required"; //$NON-NLS-1$ HeadElement head = HeadElement.as(element); JavaScriptInjector.head = head; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MarkedAsyncCallback.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MarkedAsyncCallback.java index 3d0c47fe1bc..39c40aa791e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MarkedAsyncCallback.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MarkedAsyncCallback.java @@ -1,34 +1,104 @@ package com.sap.sailing.gwt.ui.client; +import com.google.gwt.debug.client.DebugInfo; import com.google.gwt.user.client.rpc.AsyncCallback; +/** + *

Abstract base class for asynchronous remote procedure calls which should be marked as pending until they complete. + * Such calls will be marked using a counter, which is necessary in UI tests to be able able to tell exactly when an + * asynchronous request has finished. With this counter a test which triggers a request (which can cause additional + * request) can wait until the requests have finished. It's guaranteed that the counter is decremented no matter if + * the call was successful or not.

+ * + *

Note: Since the counter for pending Ajax requests is incremented as soon as an instance of this class is created + * you must not create instances which are not used or assigned to an field!

+ * + * @param + * The type of the return value for the asynchronous remote procedure call. + * @author + * D049941 + */ public abstract class MarkedAsyncCallback implements AsyncCallback { + /** + *

The key for the category of global requests. The key for the global category is just an empty string.

+ */ + public static final String CATEGORY_GLOBAL = ""; //$NON-NLS-1$ + private String category; + + /** + *

Creates a marked asynchronous remote procedure call for the global category.

+ */ public MarkedAsyncCallback() { - PendingAjaxCallMarker.incrementPendingAjaxCalls(); + this(CATEGORY_GLOBAL); } + /** + *

Creates a marked asynchronous remote procedure call for the given category. As soon as the asynchronous remote + * procedure call is created, the counter for pending Ajax calls for the given category is incremented.

+ * + * @param category + * The category of the asynchronous remote procedure call. + */ + public MarkedAsyncCallback(String category) { + this.category = category; + + if(DebugInfo.isDebugIdEnabled()) { + PendingAjaxCallMarker.incrementPendingAjaxCalls(this.category); + } + } + + /** + *

Called when the asynchronous call fails to complete normally. The concrete handling of the failure has to be + * implemented in {@link #handleFailure(Throwable)}. It's guaranteed that the counter for the pending requests is + * decremented.

+ * + * @param cause + * The failure encountered while executing the remote procedure call. + */ @Override public final void onFailure(Throwable cause) { try { handleFailure(cause); } finally { - //if(DebugInfo.isDebugIdEnabled()) - PendingAjaxCallMarker.decrementPendingAjaxCalls(); + if(DebugInfo.isDebugIdEnabled()) { + PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category); + } } } + /** + *

Called when the asynchronous call completes successfully. The concrete handling of the result has to be + * implemented in {@link #handleSuccess(Object)}. It's guaranteed that the counter for the pending requests is + * decremented.

+ * + * @param result + * The return value of the remote produced call. + */ @Override public final void onSuccess(T result) { try { handleSuccess(result); } finally { - //if(DebugInfo.isDebugIdEnabled()) - PendingAjaxCallMarker.decrementPendingAjaxCalls(); + if(DebugInfo.isDebugIdEnabled()) { + PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category); + } } } + /** + *

Called when the asynchronous call fails to complete normally.

+ * + * @param cause + * The failure encountered while executing the remote procedure call. + */ protected abstract void handleFailure(Throwable cause); + /** + *

Called when the asynchronous call completes successfully.

+ * + * @param result + * The return value of the remote produced call. + */ protected abstract void handleSuccess(T result); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaPlayer.java new file mode 100644 index 00000000000..6886390d0c9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaPlayer.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.ui.client; + +import java.util.Date; + +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public interface MediaPlayer { + + boolean isPaused(); + + void pause(); + + void play(); + + void alignTime(Date raceTime); + + MediaTrack getMediaTrack(); + + double getDuration(); + + void setTime(double mediaTime); + + double getTime(); + + void setPlaybackSpeed(double newPlaySpeedFactor); + + void setMuted(boolean isToBeMuted); + + void destroy(); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaService.java new file mode 100644 index 00000000000..0fbf01bccb7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaService.java @@ -0,0 +1,29 @@ +package com.sap.sailing.gwt.ui.client; + +import java.util.Collection; + +import com.google.gwt.user.client.rpc.RemoteService; +import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +@RemoteServiceRelativePath("media") +public interface MediaService extends RemoteService { + + Collection getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier); + + Collection getAllMediaTracks(); + + void addMediaTrack(MediaTrack mediaTrack); + + void deleteMediaTrack(MediaTrack mediaTrack); + + void updateTitle(MediaTrack mediaTrack); + + void updateUrl(MediaTrack mediaTrack); + + void updateStartTime(MediaTrack mediaTrack); + + void updateDuration(MediaTrack mediaTrack); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaServiceAsync.java new file mode 100644 index 00000000000..72a94d15a2b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaServiceAsync.java @@ -0,0 +1,28 @@ +package com.sap.sailing.gwt.ui.client; + +import java.util.Collection; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public interface MediaServiceAsync { + + void getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier, + AsyncCallback> callback); + + void getAllMediaTracks(AsyncCallback> asyncCallback); + + void addMediaTrack(MediaTrack mediaTrack, AsyncCallback asyncCallback); + + void deleteMediaTrack(MediaTrack mediaTrack, AsyncCallback asyncCallback); + + void updateTitle(MediaTrack mediaTrack, AsyncCallback asyncCallback); + + void updateUrl(MediaTrack mediaTrack, AsyncCallback asyncCallback); + + void updateStartTime(MediaTrack mediaTrack, AsyncCallback asyncCallback); + + void updateDuration(MediaTrack mediaTrack, AsyncCallback asyncCallback); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallBundle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallBundle.java index 91074468faa..0aa3238565d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallBundle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallBundle.java @@ -3,7 +3,20 @@ package com.sap.sailing.gwt.ui.client; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.TextResource; +/** + *

The client bundle for the resource "ajax-semaphore.js" which is needed for the execution of UI tests.

+ * + * @author + * D049941 + */ public interface PendingAjaxCallBundle extends ClientBundle { + /** + *

Returns the JavaScript for the counter of pending Ajax requests. The counter is used in UI tests to be able + * to tell exactly when an asynchronous request has finished.

+ * + * @return + * The JavaScript for the counter of pending Ajax requests. + */ @Source("ajax-semaphore.js") TextResource ajaxSemaphoreJS(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallMarker.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallMarker.java index 06299b3c430..cfbab574a9e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallMarker.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PendingAjaxCallMarker.java @@ -1,20 +1,38 @@ package com.sap.sailing.gwt.ui.client; +/** + *

Utility class to mark pending Ajax requests.

+ * + * @author + * D049941 + */ public class PendingAjaxCallMarker { - public static native void decrementPendingAjaxCalls() /*-{ + /** + *

Decrements the counter for pending Ajax request of the given category.

+ * + * @param category + * The category of the Ajax request. + */ + public static native void decrementPendingAjaxCalls(String category) /*-{ try { if (($wnd.PENDING_AJAX_CALLS) && (typeof $wnd.PENDING_AJAX_CALLS.decrementPendingCalls === "function")) { - $wnd.PENDING_AJAX_CALLS.decrementPendingCalls(); + $wnd.PENDING_AJAX_CALLS.decrementPendingCalls(category); } } catch (exception) { // Do error handling } }-*/; - - public static native void incrementPendingAjaxCalls() /*-{ + + /** + *

Increments the counter for pending Ajax request of the given category.

+ * + * @param category + * The category of the Ajax request. + */ + public static native void incrementPendingAjaxCalls(String category) /*-{ try { if (($wnd.PENDING_AJAX_CALLS) && (typeof $wnd.PENDING_AJAX_CALLS.incrementPendingCalls === "function")) { - $wnd.PENDING_AJAX_CALLS.incrementPendingCalls(); + $wnd.PENDING_AJAX_CALLS.incrementPendingCalls(category); } } catch (exception) { // Do error handling @@ -22,6 +40,6 @@ public class PendingAjaxCallMarker { }-*/; private PendingAjaxCallMarker() { - super(); + // Utility class } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PlayStateListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PlayStateListener.java index 8479ada2bfc..1d5e2cbc5c0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PlayStateListener.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/PlayStateListener.java @@ -1,14 +1,16 @@ -package com.sap.sailing.gwt.ui.client; - -import com.sap.sailing.gwt.ui.client.Timer.PlayModes; -import com.sap.sailing.gwt.ui.client.Timer.PlayStates; - -public interface PlayStateListener { - /** - * Called by a timer where this listener is registered when a timer's play state has changed. - * - * @param isPlaying - * true if the timer is now automatically updating its time - */ - void playStateChanged(PlayStates playState, PlayModes playMode); -} +package com.sap.sailing.gwt.ui.client; + +import com.sap.sailing.gwt.ui.client.Timer.PlayModes; +import com.sap.sailing.gwt.ui.client.Timer.PlayStates; + +public interface PlayStateListener { + /** + * Called by a timer where this listener is registered when a timer's play state has changed. + * + * @param isPlaying + * true if the timer is now automatically updating its time + */ + void playStateChanged(PlayStates playState, PlayModes playMode); + + void playSpeedFactorChanged(double newPlaySpeedFactor); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 28ebe3ee06a..bec7e3e6aa1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1,452 +1,461 @@ -package com.sap.sailing.gwt.ui.client; - -import com.google.gwt.i18n.client.Messages; - -public interface StringMessages extends Messages { - String helloWorld(); - String kielWeel2011(); - String stgAccount(); - String tracTracEvents(); - String swissTimingEvents(); - String trackedBefore(); - String listRaces(); - String wind(); - String windPanelLabel(); - String refresh(); - String remove(); - String windSource(); - String dampeningInterval(); - String map(); - String race(); - String races(); - String tracked(); - String time(); - String playSpeed(); - String playSpeedHelp(); - String playModeLive(); - String playModeReplay(); - String timeDelay(); - String timeDelayHelp(); - String speedInKnots(); - String fromDeg(); - String noRacesYet(); - String total(); - String carry(); - String competitor(); - String competitors(); - String defaultLeaderboard(); - String noSuchLeaderboard(); - String leaderboardConfiguration(); - String newDotDotDot(); - String editScores(); - String columnNamesInSelectedLeaderboard(); - String addDotDotDot(); - String renameDotDotDot(); - String trackedRaceConnectedToSelectedRaceName(); - String linkToColumn(); - String unlink(); - String leaderboardName(); - String cancel(); - String pleaseEnterAName(); - String discardRacesFromHowManyStartedRacesOn(); - String leaderboardWithThisNameAlreadyExists(); - String discardThresholdsMustBeNumeric(); - String discardThresholdsMustBeAscending(); - String discarding(); - String startingFromNumberOfRaces(); - String renameLeaderboard(); - String addColumnToLeaderboard(); - String pleaseEnterNameForNewRaceColumn(); - String ok(); - String medalRace(); - String renameRace(); - String openSelectedLeaderboard(); - String rank(); - String totalRank(); - String collapse(); - String expand(); - String leg(); - String distanceInMeters(); - String distanceInMetersUnit(); - String averageSpeedInKnots(); - String averageSpeedInKnotsUnit(); - String rankGain(); - String sapSailingAnalytics(); - String leaderboard(); - String leaderboards(); - String leaderboardSettings(); - String settings(); - String selectAtLeastOneLegDetail(); - String currentSpeedOverGroundInKnots(); - String currentSpeedOverGroundInKnotsUnit(); - String estimatedTimeToNextWaypointInSeconds(); - String estimatedTimeToNextWaypointInSecondsUnit(); - String gapToLeaderInSeconds(); - String gapToLeaderInSecondsUnit(); - String velocityMadeGoodInKnots(); - String velocityMadeGoodInKnotsUnit(); - String windwardDistanceToGoInMeters(); - String windwardDistanceToLeaderInMeters(); - String windwardDistanceToGoInMetersUnit(); - String name(); - String leaveEmptyForDefault(); - String trackNewEvent(); - String trackableRaces(); - String trackWind(); - String regattaName(); - String eventName(); - String event(); - String startTime(); - String regatta(); - String regattas(); - String startTracking(); - String stopTracking(); - String connections(); - String historyOfConnections(); - String defineNewConnection(); - String trackedRaces(); - String canSendRequests(); - String connectAndReadRaces(); - String trackSettings(); - String ports(); - String liveData(); - String storedData(); - String uris(); - String jsonUrl(); - String livePort(); - String storedPort(); - String liveUri(); - String storedUri(); - String eventsConnectedTo(); - String hostname(); - String declinationCheckbox(); - String raceStartTrackingColumn(); - String pauseAutomaticRefresh(); - String autoRefresh(); - String delayInSeconds(); - String delayBetweenAutoAdvances(); - String delayMustBeNonNegative(); - String chooseUpdateIntervalOfAtLeastOneSecond(); - String errorSettingWindForRace(); - String errorFetchingWindInformationForRace(); - String errorWhileTryingToSetWindSourceForRace(); - String inEvent(); - String to(); - String showEstimatedWind(); - String timing(); - String raceDetailsToShow(); - String legDetailsToShow(); - String columnMoveUp(); - String columnMoveDown(); - String port(); - String raceStartTimeColumn(); - String showOnlySelectedCompetitors(); - String showSelectedCompetitorsInfo(); - String lengthInSeconds(); - String selectedRaces(); - String stressTest(); - String numberOfManeuvers(); - String tacks(); - String jibes(); - String penaltyCircles(); - String medalRaceIsNull(); - String maneuverTypes(); - String configuration(); - String chooseChart(); - String distanceTraveled(); - String velocityMadeGoodLong(); - String speedOverGroundLong(); - String gapToLeaderLong(); - String pointsToLoad(); - String speed(); - String distance(); - String in(); - String secondsUnit(); - String metersUnit(); - String failedToLoadRaceData(); - String close(); - String compareCompetitors(); - String description(); - String sailNumber(); - String country(); - String no3LetterCodes(); - String add(); - String delete(); - String showCharts(); - String raceWithThisNameAlreadyExists(); - String raceWithThisNameAlreadyExistsInRegatta(); - String headUp(); - String bearAway(); - String tack(); - String jibe(); - String penaltyCircle(); - String markPassing(); - String mark(); - String otherManeuver(); - String douglasPeuckerPoints(); - String filterRacesByName(); - String actionEdit(); - String actionEditScores(); - String actionOpenBrowser(); - String actionRemove(); - String actionEditRaces(); - String actionRaceEdit(); - String actionRaceUnlink(); - String actionRaceRemove(); - String filterLeaderboardsByName(); - String windwardDistanceToLeader(); - String actions(); - String islinked(); - String yes(); - String no(); - String hoverOverAPoint(); - String noSelection(); - String raceIsKnownToStartUpwind(); - String events(); - String settingsForComponent(String localizedComponentName); - String noEventsFound(); - String noEventSelected(); - String noLeaderboardsFound(); - String searchEvents(); - String location(); - String from(); - String until(); - String onlyLiveEvents(); - String locationNotAvailable(); - String startDateNotAvailable(); - String startDate(); - String unknownManeuver(); - String tailLengthMustBePositive(); - String failedToLoadRaceInformation(String message); - String competitorPassedMarkAtDate(String competitorName, String markName, String dateTime); - String valueForCompetitorAt(String competitorName, String dateTime, String valueWithUnit); - String reallyRemoveRace(String string); - String showDetails(); - String hideLeaderboard(); - String hideLeaderboards(); - String showLeaderboard(); - String hideRaces(); - String showMap(); - String hideMap(); - String noLeaderboardSelected(); - String leaderboardGroups(); - String filterLeaderboardGroupsByName(); - String createNewLeaderboardGroup(); - String leaderboardGroupConfiguration(); - String legs(); - String currentLeg(); - String leaderboardGroup(); - String pleaseEnterNonEmptyDescription(); - String groupWithThisNameAlreadyExists(); - String detailsOfLeaderboardGroup(); - String edit(); - String save(); - String abort(); - String noLeaderboardGroupWithNameFound(String groupName); - String overview(); - String welcomeToSailingAnalytics(); - String welcomeToSailingAnalyticsBody(); - String home(); - String clickAndDragToZoomIn(); - String windSpeed(); - String allTimesInUTC(); - String autoZoomTo(); - String autoZoomToBoats(); - String autoZoomToBuoys(); - String autoZoomOff(); - String autoZoomToTails(); - String autoZoomToWindSensors(); - String autoZoomSelectedCompetitors(); - String bearing(); - String zoom(); - String stepSizeMustBeGreaterThanNull(); - String stepSizeInSeconds(); - String refreshInterval(); - String refreshIntervalMustBeGreaterThanXSeconds(String seconds); - String competitorCharts(); - String selectAtLeastOneCompetitor(); - String directionChange(); - String speedChange(); - String degreesShort(); - String untracked(); - String delayForLiveMode(); - String notAvailable(); - String details(); - String noGroupSelected(); - String combinedWindSourceTypeName(); - String courseBasedWindSourceTypeName(); - String trackBasedEstimationWindSourceTypeName(); - String expeditionWindSourceTypeName(); - String webWindSourceTypeName(); - String clickChartToSetTime(); - String position(); - String windSourcesUsed(); - String errorTryingToUpdateWindSourcesToExclude(String raceName, String message); - String feedback(); - String startStopPlaying(); - String backToLive(); - String slowPlaySpeedDown(); - String speedPlaySpeedUp(); - String showHideComponent(String localizedShortName); - String degreesBoatToTheWind(); - String racesInLeaderboard(); - String leaderboardsInGroup(); - String replication(); - String errorFetchingReplicaData(String message); - String averageCrossTrackErrorInMeters(); - String enterMaster(); - String errorStartingReplication(String hostname, String exchangeName, String message); - String helpLines(); - String startLine(); - String finishLine(); - String advantageLine(); - String courseMiddleLine(); - String messagingPortNumber(); - String servletPortNumber(); - String registeredAt(String string); - String replicatingFromMaster(String hostname, int jmsPort, int servletPort); - String showWindSpeedSeries(); - String showWindDirectionSeries(); - String fleet(); - String boatClass(); - String setDelayToLive(); - String pleaseEnterNonEmptyVenue(); - String eventWithThisNameAlreadyExists(); - String venue(); - String pleaseSelectABoatClass(); - String regattaWithThisNameAlreadyExists(); - String seriesWithThisNameAlreadyExists(); - String fleetWithThisNameAlreadyExists(); - String fleets(); - String series(); - String splitFleet(); - String color(); - String medalSeries(); - String noColor(); - String pleaseSelectARegatta(); - String addFleet(); - String doYouReallyWantToRemoveRegatta(String name); - String errorLoadingScoreCorrectionProviders(String message); - String selectResultListToImportFrom(); - String of(); - String errorObtainingScoreCorrections(String scoreCorrectionProviderName, String eventName, String boatClassName, - String string, String message); - String assignRaceNumbersToRaceColumns(); - String importOfficialResults(); - String selectAll(); - String apply(); - String successfullyUpdatedScores(); - String errorUpdatingScoresForLeaderboard(String leaderboardName, String message); - String maneuverTypesToShowWhenCompetitorIsClicked(); - String simulateWithStartTimeNow(); - String boatClassDoesNotMatchSelectedRegatta(String boatClass, String regattaName); - String regattaExistForSelectedBoatClass(); - String reload(); - String addRegatta(); - String exchangeName(); - String course(); - String courseLayout(); - String buoy(); - String gate(); - String buoys(); - String waypoint(); - String showAllCompetitors(); - String boatTails(); - String maximalCount(); - String maxVisibleCompetitorsCountMustBePositive(); - String fregResultImportUrls(); - String errorRefreshingFregUrlList(String message); - String successfullyUpdatedFregUrls(); - String errorRemovingFregUrls(String message); - String pleaseEnterNonEmptyUrl(); - String addFragUrl(); - String errorAddingFragUrl(String message); - String errorTryingToObtainLeaderboardContents(String message); - String lastScoreUpdate(); - String warningBrowserUnsupported(); - String newMark(); - String controlPoint(); - String errorUpdatingRaceCourse(String message); - String successfullyUpdatedCourse(); - String selectOneMarkOrTwoMarksForGate(); - String errorTryingToObtainTheMarksOfTheRace(String message); - String errorTryingToObtainRaceCourse(String message); - String insertWaypointBeforeSelected(); - String insertWaypointAfterSelected(); - String scoringSchemeLowPointSystem(); - String scoringSchemeHighPointSystem(); - String scoringSystem(); - String createFlexibleLeaderboard(); - String createRegattaLeaderboard(); - String editFlexibleLeaderboard(); - String editRegattaLeaderboard(); - String noRegatta(); - String useOverallLeaderboard(); - String overallStandings(); - String scoringSchemeHighPointEssOverall(); - String scoringSchemeHighPointLastBreaksTie(); - String type(); - String actionAddRaces(); - String racesWithNamePrefix(); - String maximumSpeedOverGroundInKnots(); - String totalTimeSailedDownwindInSeconds(); - String totalTimeSailedUpwindInSeconds(); - String totalTimeSailedReachingInSeconds(); - String totalTimeSailedInSeconds(); - String overallDetailsToShow(); - String hhmmssUnit(); - String actionAddWindData(); - String valueMustBeBetweenMinMax(String name, String minValue, String maxValue); - String optional(); - String pleaseEnterAValue(); - String latitude(); - String longitude(); - String actionAddEvent(); - String publicationUrl(); - String suppress(); - String isPublic(); - String unsuppress(); - String suppressedCompetitors(); - String pleaseSelectASeries(); - String pleaseSelectARace(); - String displayGroupsInReverseOrder(); - String actionConfigureUrl(); - String configureLeaderboardUrlDescription(); - String embedded(); - String additionalUrlSettings(); - String restrictToSingleSelectedRace(); - String expandSelectedRace(); - String showRaceDetails(); - String numberOfRaces(); - String numberOfRacesMustBeNonNegativeNumber(); - String chooseTheWayYouSelectRaces(); - String selectFromAllRaces(); - String selectANumberOfRaces(); - String numberOfLastNRaces(); - String expandLastRace(); - String factor(); - String errorUpdatingIsMedalRace(String message); - String maneuverLoss(); - String averageManeuverLossInMeters(); - String averageTackLossInMeters(); - String averageJibeLossInMeters(); - String shape(); - String pattern(); - String legend(); - String live(); - String swissTimingArchiveConnector(); - String errorFetchingRankingChartData(String message); - String loadingCompetitorData(); - String rankNAfterRace(long l, String string); - String afterRace(); - String showRankChart(); - String overallLeaderboardSelection(); - String showOverallLeaderboardOnSamePage(); - String errorTryingToObtainOverallLeaderboards(String message); - String finished(); - String passedTo(String name); - String gapChangeSinceLegStartInSeconds(); - String sideToWhichMarkAtLegStartWasRounded(); - String raceIsLive(String raceName); - String racesAreLive(String raceNames); -} +package com.sap.sailing.gwt.ui.client; + +import com.google.gwt.i18n.client.Messages; + +public interface StringMessages extends Messages { + String helloWorld(); + String kielWeel2011(); + String stgAccount(); + String tracTracEvents(); + String swissTimingEvents(); + String trackedBefore(); + String listRaces(); + String wind(); + String windPanelLabel(); + String refresh(); + String remove(); + String windSource(); + String dampeningInterval(); + String map(); + String race(); + String races(); + String tracked(); + String time(); + String playSpeed(); + String playSpeedHelp(); + String playModeLive(); + String playModeReplay(); + String timeDelay(); + String timeDelayHelp(); + String speedInKnots(); + String fromDeg(); + String noRacesYet(); + String total(); + String carry(); + String competitor(); + String competitors(); + String defaultLeaderboard(); + String noSuchLeaderboard(); + String leaderboardConfiguration(); + String newDotDotDot(); + String editScores(); + String columnNamesInSelectedLeaderboard(); + String addDotDotDot(); + String renameDotDotDot(); + String trackedRaceConnectedToSelectedRaceName(); + String linkToColumn(); + String unlink(); + String leaderboardName(); + String cancel(); + String pleaseEnterAName(); + String discardRacesFromHowManyStartedRacesOn(); + String leaderboardWithThisNameAlreadyExists(); + String discardThresholdsMustBeNumeric(); + String discardThresholdsMustBeAscending(); + String discarding(); + String startingFromNumberOfRaces(); + String renameLeaderboard(); + String addColumnToLeaderboard(); + String pleaseEnterNameForNewRaceColumn(); + String ok(); + String medalRace(); + String renameRace(); + String openSelectedLeaderboard(); + String rank(); + String totalRank(); + String collapse(); + String expand(); + String leg(); + String distanceInMeters(); + String distanceInMetersUnit(); + String averageSpeedInKnots(); + String averageSpeedInKnotsUnit(); + String rankGain(); + String sapSailingAnalytics(); + String leaderboard(); + String leaderboards(); + String leaderboardSettings(); + String settings(); + String selectAtLeastOneLegDetail(); + String currentSpeedOverGroundInKnots(); + String currentSpeedOverGroundInKnotsUnit(); + String estimatedTimeToNextWaypointInSeconds(); + String estimatedTimeToNextWaypointInSecondsUnit(); + String gapToLeaderInSeconds(); + String gapToLeaderInSecondsUnit(); + String velocityMadeGoodInKnots(); + String velocityMadeGoodInKnotsUnit(); + String windwardDistanceToGoInMeters(); + String windwardDistanceToLeaderInMeters(); + String windwardDistanceToGoInMetersUnit(); + String name(); + String leaveEmptyForDefault(); + String trackNewEvent(); + String trackableRaces(); + String trackWind(); + String regattaName(); + String eventName(); + String event(); + String startTime(); + String regatta(); + String regattas(); + String startTracking(); + String stopTracking(); + String connections(); + String historyOfConnections(); + String defineNewConnection(); + String trackedRaces(); + String canSendRequests(); + String connectAndReadRaces(); + String trackSettings(); + String ports(); + String liveData(); + String storedData(); + String uris(); + String jsonUrl(); + String livePort(); + String storedPort(); + String liveUri(); + String storedUri(); + String eventsConnectedTo(); + String hostname(); + String declinationCheckbox(); + String raceStartTrackingColumn(); + String pauseAutomaticRefresh(); + String autoRefresh(); + String delayInSeconds(); + String delayBetweenAutoAdvances(); + String delayMustBeNonNegative(); + String chooseUpdateIntervalOfAtLeastOneSecond(); + String errorSettingWindForRace(); + String errorFetchingWindInformationForRace(); + String errorWhileTryingToSetWindSourceForRace(); + String inEvent(); + String to(); + String showEstimatedWind(); + String timing(); + String raceDetailsToShow(); + String legDetailsToShow(); + String columnMoveUp(); + String columnMoveDown(); + String port(); + String raceStartTimeColumn(); + String showOnlySelectedCompetitors(); + String showSelectedCompetitorsInfo(); + String lengthInSeconds(); + String selectedRaces(); + String stressTest(); + String numberOfManeuvers(); + String tacks(); + String jibes(); + String penaltyCircles(); + String medalRaceIsNull(); + String maneuverTypes(); + String configuration(); + String chooseChart(); + String distanceTraveled(); + String velocityMadeGoodLong(); + String speedOverGroundLong(); + String gapToLeaderLong(); + String pointsToLoad(); + String speed(); + String distance(); + String in(); + String secondsUnit(); + String metersUnit(); + String failedToLoadRaceData(); + String close(); + String compareCompetitors(); + String description(); + String sailNumber(); + String country(); + String no3LetterCodes(); + String add(); + String delete(); + String showCharts(); + String raceWithThisNameAlreadyExists(); + String raceWithThisNameAlreadyExistsInRegatta(); + String headUp(); + String bearAway(); + String tack(); + String jibe(); + String penaltyCircle(); + String markPassing(); + String mark(); + String otherManeuver(); + String douglasPeuckerPoints(); + String filterRacesByName(); + String actionEdit(); + String actionEditScores(); + String actionOpenBrowser(); + String actionRemove(); + String actionEditRaces(); + String actionRaceEdit(); + String actionRaceUnlink(); + String actionRaceRemove(); + String filterLeaderboardsByName(); + String windwardDistanceToLeader(); + String actions(); + String islinked(); + String yes(); + String no(); + String hoverOverAPoint(); + String noSelection(); + String raceIsKnownToStartUpwind(); + String events(); + String settingsForComponent(String localizedComponentName); + String noEventsFound(); + String noEventSelected(); + String noLeaderboardsFound(); + String searchEvents(); + String location(); + String from(); + String until(); + String onlyLiveEvents(); + String locationNotAvailable(); + String startDateNotAvailable(); + String startDate(); + String unknownManeuver(); + String tailLengthMustBePositive(); + String failedToLoadRaceInformation(String message); + String competitorPassedMarkAtDate(String competitorName, String markName, String dateTime); + String valueForCompetitorAt(String competitorName, String dateTime, String valueWithUnit); + String reallyRemoveRace(String string); + String showDetails(); + String hideLeaderboard(); + String hideLeaderboards(); + String showLeaderboard(); + String hideRaces(); + String showMap(); + String hideMap(); + String noLeaderboardSelected(); + String leaderboardGroups(); + String filterLeaderboardGroupsByName(); + String createNewLeaderboardGroup(); + String leaderboardGroupConfiguration(); + String legs(); + String currentLeg(); + String leaderboardGroup(); + String pleaseEnterNonEmptyDescription(); + String groupWithThisNameAlreadyExists(); + String detailsOfLeaderboardGroup(); + String edit(); + String save(); + String abort(); + String noLeaderboardGroupWithNameFound(String groupName); + String overview(); + String welcomeToSailingAnalytics(); + String welcomeToSailingAnalyticsBody(); + String home(); + String clickAndDragToZoomIn(); + String windSpeed(); + String allTimesInUTC(); + String autoZoomTo(); + String autoZoomToBoats(); + String autoZoomToBuoys(); + String autoZoomOff(); + String autoZoomToTails(); + String autoZoomToWindSensors(); + String autoZoomSelectedCompetitors(); + String bearing(); + String zoom(); + String stepSizeMustBeGreaterThanNull(); + String stepSizeInSeconds(); + String refreshInterval(); + String refreshIntervalMustBeGreaterThanXSeconds(String seconds); + String competitorCharts(); + String selectAtLeastOneCompetitor(); + String directionChange(); + String speedChange(); + String degreesShort(); + String untracked(); + String delayForLiveMode(); + String notAvailable(); + String details(); + String noGroupSelected(); + String combinedWindSourceTypeName(); + String courseBasedWindSourceTypeName(); + String trackBasedEstimationWindSourceTypeName(); + String expeditionWindSourceTypeName(); + String webWindSourceTypeName(); + String clickChartToSetTime(); + String position(); + String windSourcesUsed(); + String errorTryingToUpdateWindSourcesToExclude(String raceName, String message); + String feedback(); + String startStopPlaying(); + String backToLive(); + String slowPlaySpeedDown(); + String speedPlaySpeedUp(); + String showHideComponent(String localizedShortName); + String degreesBoatToTheWind(); + String racesInLeaderboard(); + String leaderboardsInGroup(); + String replication(); + String errorFetchingReplicaData(String message); + String averageCrossTrackErrorInMeters(); + String enterMaster(); + String errorStartingReplication(String hostname, String exchangeName, String message); + String helpLines(); + String startLine(); + String finishLine(); + String advantageLine(); + String courseMiddleLine(); + String messagingPortNumber(); + String servletPortNumber(); + String registeredAt(String string); + String replicatingFromMaster(String hostname, int jmsPort, int servletPort); + String showWindSpeedSeries(); + String showWindDirectionSeries(); + String fleet(); + String boatClass(); + String setDelayToLive(); + String pleaseEnterNonEmptyVenue(); + String eventWithThisNameAlreadyExists(); + String venue(); + String pleaseSelectABoatClass(); + String regattaWithThisNameAlreadyExists(); + String seriesWithThisNameAlreadyExists(); + String fleetWithThisNameAlreadyExists(); + String fleets(); + String series(); + String splitFleet(); + String color(); + String medalSeries(); + String noColor(); + String pleaseSelectARegatta(); + String addFleet(); + String doYouReallyWantToRemoveRegatta(String name); + String errorLoadingScoreCorrectionProviders(String message); + String selectResultListToImportFrom(); + String of(); + String errorObtainingScoreCorrections(String scoreCorrectionProviderName, String eventName, String boatClassName, + String string, String message); + String assignRaceNumbersToRaceColumns(); + String importOfficialResults(); + String selectAll(); + String apply(); + String successfullyUpdatedScores(); + String errorUpdatingScoresForLeaderboard(String leaderboardName, String message); + String maneuverTypesToShowWhenCompetitorIsClicked(); + String simulateWithStartTimeNow(); + String boatClassDoesNotMatchSelectedRegatta(String boatClass, String regattaName); + String regattaExistForSelectedBoatClass(); + String reload(); + String addRegatta(); + String exchangeName(); + String course(); + String courseLayout(); + String buoy(); + String gate(); + String buoys(); + String waypoint(); + String showAllCompetitors(); + String boatTails(); + String maximalCount(); + String maxVisibleCompetitorsCountMustBePositive(); + String fregResultImportUrls(); + String errorRefreshingFregUrlList(String message); + String successfullyUpdatedFregUrls(); + String errorRemovingFregUrls(String message); + String pleaseEnterNonEmptyUrl(); + String addFragUrl(); + String errorAddingFragUrl(String message); + String errorTryingToObtainLeaderboardContents(String message); + String lastScoreUpdate(); + String warningBrowserUnsupported(); + String newMark(); + String controlPoint(); + String errorUpdatingRaceCourse(String message); + String successfullyUpdatedCourse(); + String selectOneMarkOrTwoMarksForGate(); + String errorTryingToObtainTheMarksOfTheRace(String message); + String errorTryingToObtainRaceCourse(String message); + String insertWaypointBeforeSelected(); + String insertWaypointAfterSelected(); + String scoringSchemeLowPointSystem(); + String scoringSchemeHighPointSystem(); + String scoringSystem(); + String createFlexibleLeaderboard(); + String createRegattaLeaderboard(); + String editFlexibleLeaderboard(); + String editRegattaLeaderboard(); + String noRegatta(); + String useOverallLeaderboard(); + String overallStandings(); + String scoringSchemeHighPointEssOverall(); + String scoringSchemeHighPointLastBreaksTie(); + String type(); + String actionAddRaces(); + String racesWithNamePrefix(); + String maximumSpeedOverGroundInKnots(); + String totalTimeSailedDownwindInSeconds(); + String totalTimeSailedUpwindInSeconds(); + String totalTimeSailedReachingInSeconds(); + String totalTimeSailedInSeconds(); + String overallDetailsToShow(); + String hhmmssUnit(); + String actionAddWindData(); + String valueMustBeBetweenMinMax(String name, String minValue, String maxValue); + String optional(); + String pleaseEnterAValue(); + String latitude(); + String longitude(); + String actionAddEvent(); + String publicationUrl(); + String suppress(); + String isPublic(); + String unsuppress(); + String suppressedCompetitors(); + String pleaseSelectASeries(); + String pleaseSelectARace(); + String displayGroupsInReverseOrder(); + String actionConfigureUrl(); + String configureLeaderboardUrlDescription(); + String embedded(); + String mediaPanel(); + String url(); + String mimeType(); + String title(); + String reallyRemoveMediaTrack(String mediaTrackTitle); + String duration(); + String addMediaTrack(); + String addYoutubeTrack(); + String youtubeId(); + String additionalUrlSettings(); + String restrictToSingleSelectedRace(); + String expandSelectedRace(); + String showRaceDetails(); + String numberOfRaces(); + String numberOfRacesMustBeNonNegativeNumber(); + String chooseTheWayYouSelectRaces(); + String selectFromAllRaces(); + String selectANumberOfRaces(); + String numberOfLastNRaces(); + String expandLastRace(); + String factor(); + String errorUpdatingIsMedalRace(String message); + String maneuverLoss(); + String averageManeuverLossInMeters(); + String averageTackLossInMeters(); + String averageJibeLossInMeters(); + String shape(); + String pattern(); + String legend(); + String live(); + String swissTimingArchiveConnector(); + String errorFetchingRankingChartData(String message); + String loadingCompetitorData(); + String rankNAfterRace(long l, String string); + String afterRace(); + String showRankChart(); + String overallLeaderboardSelection(); + String showOverallLeaderboardOnSamePage(); + String errorTryingToObtainOverallLeaderboards(String message); + String finished(); + String passedTo(String name); + String gapChangeSinceLegStartInSeconds(); + String sideToWhichMarkAtLegStartWasRounded(); + String raceIsLive(String raceName); + String racesAreLive(String raceNames); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 6ab441629d0..3bd92fd18fa 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -408,6 +408,15 @@ isPublic=Is public suppress=Suppress unsuppress=Unsuppress suppressedCompetitors=Suppressed Competitors +mediaPanel=Audio & Video +url=URL +mimeType=MIME Type +title=Title +duration=Duration (h:mm:ss.ms) +addMediaTrack=Add Audio/Video +addYoutubeTrack=Add Youtube +youtubeId=Youtube Id +reallyRemoveMediaTrack=Do you really want to remove media track ''{0}''? pleaseSelectASeries=Please select a series pleaseSelectARace=Please select a race displayGroupsInReverseOrder=Display groups in reverse order @@ -449,4 +458,4 @@ passedTo=passed to {0} gapChangeSinceLegStartInSeconds=Gap Change sideToWhichMarkAtLegStartWasRounded=Mark rounded to raceIsLive=Race {0} is live -racesAreLive=Races {0} are live +racesAreLive=Races {0} are live diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index a694852f740..160baa7424e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -409,6 +409,15 @@ isPublic=Öffentlich zugänglich suppress=Entfernen unsuppress=Wieder hinzufügen suppressedCompetitors=Unterdrückte Wettkampfteilnehmer +mediaPanel=Audio & Video +url=URL +mimeType=MIME Type +title=Titel +duration=Dauer (h:mm:ss.ms) +addMediaTrack=Audio/Video hinzufügen +addYoutubeTrack=Youtube hinzufügen +youtubeId=Youtube Id +reallyRemoveMediaTrack=Möchten Sie ''{0}'' wirklich entfernen? pleaseSelectASeries=Bitte wählen Sie eine Serie aus pleaseSelectARace=Bitte wählen Sie ein Rennen aus displayGroupsInReverseOrder=Zeige die Gruppen in umgekehrter Reihenfolge @@ -445,9 +454,9 @@ showRankChart=Rang-Diagramm anzeigen overallLeaderboardSelection=Gesamtergebnisse showOverallLeaderboardOnSamePage=Gesamtergebnisse auf derselben Seite anzeigen errorTryingToObtainOverallLeaderboards=Fehler beim Laden der Gesamtergebnisse: {0} -finished=beendet +finished=beendet passedTo=an {0} gerundet gapChangeSinceLegStartInSeconds=Gap Änderung -sideToWhichMarkAtLegStartWasRounded=Tonne gerundet an +sideToWhichMarkAtLegStartWasRounded=Tonne gerundet an raceIsLive=Rennen {0} ist live racesAreLive=Rennen {0} sind live diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java index 16337004786..4d962fc9260 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java @@ -1,497 +1,502 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.Date; - -import com.google.gwt.core.client.GWT; -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.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.ui.AbstractImagePrototype; -import com.google.gwt.user.client.ui.Anchor; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.Image; -import com.google.gwt.user.client.ui.IntegerBox; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.RequiresResize; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.client.Timer.PlayModes; -import com.sap.sailing.gwt.ui.client.Timer.PlayStates; -import com.sap.sailing.gwt.ui.shared.components.Component; -import com.sap.sailing.gwt.ui.shared.components.SettingsDialog; -import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.shared.controls.slider.SliderBar; -import com.sap.sailing.gwt.ui.shared.controls.slider.TimeSlider; - -public class TimePanel extends FormPanel implements Component, TimeListener, TimeZoomChangeListener, - TimeRangeChangeListener, PlayStateListener, RequiresResize { - protected final Timer timer; - protected final TimeRangeWithZoomProvider timeRangeProvider; - - private final IntegerBox playSpeedBox; - private final Label timeDelayLabel; - private final Label timeLabel; - private final Label dateLabel; - private final Label playModeLabel; - protected final TimeSlider timeSlider; - private final Button backToLivePlayButton; - protected final StringMessages stringMessages; - protected final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_FULL); - protected final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss"); - private final ImageResource playSpeedImg; - private final ImageResource playModeLiveActiveImg; - private final ImageResource playModeReplayActiveImg; - private final ImageResource playModeInactiveImg; - private final Button playPauseButton; - private final Image playModeImage; - protected Date lastReceivedDataTimepoint; - private final Button slowDownButton; - private final Button speedUpButton; - - /** - * The live delay may be adjusted automatically if the server decides so. However, if the user explicitly sets a live delay, - * server-side updates to the delay should be suppressed. This flag records whether the user has performed a manual delay - * override. - */ - private boolean userExplicitlyChangedLivePlayDelay; - - /** - * the minimum time the slider extends it's time when the end of the slider is reached - */ - private long MINIMUM_AUTO_ADVANCE_TIME_IN_MS = 5 * 60 * 1000; // 5 minutes - - private static ClientResources resources = GWT.create(ClientResources.class); - - private class SettingsClickHandler implements ClickHandler { - private final StringMessages stringConstants; - - private SettingsClickHandler(StringMessages stringConstants) { - this.stringConstants = stringConstants; - } - - @Override - public void onClick(ClickEvent event) { - new SettingsDialog(TimePanel.this, stringConstants).show(); - } - } - - public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages) { - this.timer = timer; - this.timeRangeProvider = timeRangeProvider; - this.stringMessages = stringMessages; - timer.addTimeListener(this); - timer.addPlayStateListener(this); - timeRangeProvider.addTimeRangeChangeListener(this); - userExplicitlyChangedLivePlayDelay = false; - FlowPanel timePanelInnerWrapper = new FlowPanel(); - timePanelInnerWrapper.setStyleName("timePanelInnerWrapper"); - timePanelInnerWrapper.setSize("100%", "100%"); - - SimplePanel timePanelSlider = new SimplePanel(); - timePanelSlider.setStyleName("timePanelSlider"); - timePanelSlider.getElement().getStyle().setPaddingLeft(66, Unit.PX); - timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX); - - playSpeedImg = resources.timesliderPlaySpeedIcon(); - playPauseButton = new Button(""); - - playModeLiveActiveImg = resources.timesliderPlayStateLiveActiveIcon(); - playModeReplayActiveImg = resources.timesliderPlayStateReplayActiveIcon(); - playModeInactiveImg = resources.timesliderPlayStateLiveInactiveIcon(); - playModeImage = new Image(playModeInactiveImg); - - timeSlider = new TimeSlider(); - timeSlider.setEnabled(true); - timeSlider.setLabelFormatter(new SliderBar.LabelFormatter() { - final DateTimeFormat timeWithMinutesFormatter = DateTimeFormat.getFormat("HH:mm"); - @Override - public String formatLabel(SliderBar slider, Double value, Double previousValue) { - Date date = new Date(value.longValue()); - return timeWithMinutesFormatter.format(date); - } - }); - - timeSlider.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent newValue) { - if(timeSlider.getCurrentValue() != null) { - if (TimePanel.this.timer.getPlayMode() == PlayModes.Live) { - // put timer into replay mode when user explicitly adjusts time; avoids having to press pause first - TimePanel.this.timer.setPlayMode(PlayModes.Replay); - } - TimePanel.this.timer.setTime(timeSlider.getCurrentValue().longValue()); - } - } - }); - - timePanelInnerWrapper.add(timePanelSlider); - timePanelSlider.add(timeSlider); - - FlowPanel controlsPanel = new FlowPanel(); - - controlsPanel.setStyleName("timePanel-controls"); - timePanelInnerWrapper.add(controlsPanel); - - // play button control - FlowPanel playControlPanel = new FlowPanel(); - playControlPanel.setStyleName("timePanel-controls-play"); - controlsPanel.add(playControlPanel); - - playPauseButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - switch(TimePanel.this.timer.getPlayState()) { - case Stopped: - TimePanel.this.timer.play(); - break; - case Playing: - TimePanel.this.timer.pause(); - break; - case Paused: - TimePanel.this.timer.play(); - break; - } - } - }); - playPauseButton.setTitle(stringMessages.startStopPlaying()); - playPauseButton.getElement().addClassName("playPauseButton"); - playControlPanel.add(playPauseButton); - - backToLivePlayButton = new Button(stringMessages.playModeLive()); - backToLivePlayButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - TimePanel.this.timer.setPlayMode(PlayModes.Live); - TimePanel.this.timer.play(); - } - }); - backToLivePlayButton.addStyleName("backToLivePlayButton"); - backToLivePlayButton.setTitle(stringMessages.backToLive()); - playControlPanel.add(backToLivePlayButton); - - // current date and time control - FlowPanel timeControlPanel = new FlowPanel(); - timeControlPanel.setStyleName("timePanel-controls-time"); - dateLabel = new Label(); - timeLabel = new Label(); - timeControlPanel.add(dateLabel); - timeControlPanel.add(timeLabel); - - dateLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - dateLabel.getElement().setClassName("dateLabel"); - timeLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - timeLabel.getElement().setClassName("timeLabel"); - - FlowPanel playModeControlPanel = new FlowPanel(); - playModeControlPanel.setStyleName("timePanel-controls-playmode"); - playModeControlPanel.add(playModeImage); - playModeImage.getElement().getStyle().setFloat(Style.Float.LEFT); - - playModeLabel = new Label(); - playModeControlPanel .add(playModeLabel); - - playModeLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - playModeLabel.getElement().setClassName("playModeLabel"); - - // play speed controls - FlowPanel playSpeedControlPanel = new FlowPanel(); - playSpeedControlPanel.setStyleName("timePanel-controls-playSpeed"); - - - playSpeedBox = new IntegerBox(); - playSpeedBox.setVisibleLength(3); - playSpeedBox.setWidth("25px"); - playSpeedBox.setHeight("14px"); - playSpeedBox.setValue(1); - playSpeedBox.setTitle(stringMessages.playSpeedHelp()); - playSpeedBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - Integer newPlaySpeedFactor = playSpeedBox.getValue() == null ? 0 : playSpeedBox.getValue(); - TimePanel.this.timer.setPlaySpeedFactor(newPlaySpeedFactor); - } - }); - - Image playSpeedImage = new Image(playSpeedImg); - playSpeedControlPanel.add(playSpeedImage); - playSpeedControlPanel.add(playSpeedBox); - - slowDownButton = new Button("-1"); - slowDownButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - playSpeedBox.setValue(playSpeedBox.getValue() == null ? 0 : playSpeedBox.getValue() - 1); - TimePanel.this.timer.setPlaySpeedFactor(playSpeedBox.getValue()); - } - }); - slowDownButton.setTitle(stringMessages.slowPlaySpeedDown()); - playSpeedControlPanel.add(slowDownButton); - - speedUpButton = new Button("+1"); - speedUpButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - playSpeedBox.setValue(playSpeedBox.getValue() == null ? 0 : playSpeedBox.getValue() + 1); - TimePanel.this.timer.setPlaySpeedFactor(playSpeedBox.getValue()); - } - }); - speedUpButton.setTitle(stringMessages.speedPlaySpeedUp()); - playSpeedControlPanel.add(speedUpButton); - - playSpeedImage.getElement().getStyle().setFloat(Style.Float.LEFT); - playSpeedImage.getElement().getStyle().setPadding(3, Style.Unit.PX); - - playSpeedBox.getElement().getStyle().setFloat(Style.Float.LEFT); - playSpeedBox.getElement().getStyle().setPadding(2, Style.Unit.PX); - - speedUpButton.addStyleName("timePanelButton"); - slowDownButton.addStyleName("timePanelButton"); - - // time delay - FlowPanel timeDelayPanel = new FlowPanel(); - timeDelayPanel.setStyleName("timePanel-controls-timeDelay"); - - timeDelayLabel = new Label(); - timeDelayPanel.add(timeDelayLabel); - - timeDelayLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - timeDelayLabel.getElement().getStyle().setPadding(3, Style.Unit.PX); - - // settings - ImageResource settingsIcon = resources.settingsIcon(); - Anchor settingsAnchor = new Anchor(AbstractImagePrototype.create(settingsIcon).getSafeHtml()); - settingsAnchor.setTitle(stringMessages.settings()); - settingsAnchor.setStyleName("timePanelSettings"); - settingsAnchor.addClickHandler(new SettingsClickHandler(stringMessages)); - controlsPanel.add(settingsAnchor); - setWidget(timePanelInnerWrapper); - playStateChanged(timer.getPlayState(), timer.getPlayMode()); - - - controlsPanel.add(playSpeedControlPanel); - controlsPanel.add(playModeControlPanel ); - controlsPanel.add(timeDelayPanel); - controlsPanel.add(timeControlPanel); - } - - @Override - public void timeChanged(Date time) { - if(timeRangeProvider.isZoomed()) { - } else { - if (getFromTime() != null && getToTime() != null) { - // handle the case where time advances beyond slider's end. - if (time.after(getToTime())) { - switch (timer.getPlayMode()) { - case Live: - Date newMaxTime = new Date(time.getTime()); - if (newMaxTime.getTime() - getToTime().getTime() < MINIMUM_AUTO_ADVANCE_TIME_IN_MS) { - newMaxTime.setTime(getToTime().getTime() + MINIMUM_AUTO_ADVANCE_TIME_IN_MS); - } - setMinMax(getFromTime(), newMaxTime, /* fireEvent */ false); // no event because we guarantee that time is between min/max - break; - case Replay: - timer.stop(); - break; - } - } - timeSlider.setCurrentValue(new Double(time.getTime()), false); - } - } - dateLabel.setText(dateFormatter.format(time)); - if (lastReceivedDataTimepoint == null) { - timeLabel.setText(timeFormatter.format(time)); - } else { - timeLabel.setText(timeFormatter.format(time) + " (" + timeFormatter.format(lastReceivedDataTimepoint) + ")"); - } - } - - protected Date getFromTime() { - return timeRangeProvider.getFromTime(); - } - - protected Date getToTime() { - return timeRangeProvider.getToTime(); - } - - /** - * @param min must not be null - * @param max must not be null - */ - public void setMinMax(Date min, Date max, boolean fireEvent) { - assert min != null && max != null; - - boolean changed = false; - if (!max.equals(timeRangeProvider.getToTime())) { - changed = true; - timeSlider.setMaxValue(new Double(max.getTime()), fireEvent); - } - if (!min.equals(timeRangeProvider.getFromTime())) { - changed = true; - timeSlider.setMinValue(new Double(min.getTime()), fireEvent); - if (timeSlider.getCurrentValue() == null) { - timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent); - } - } - if (changed) { - if(!timeRangeProvider.isZoomed()) { - timeRangeProvider.setTimeRange(min, max, this); - } - - int numSteps = timeSlider.getElement().getClientWidth(); - if (numSteps > 0) { - timeSlider.setStepSize(numSteps, fireEvent); - } else { - timeSlider.setStepSize(1000, fireEvent); - } - } - } - - public void resetTimeSlider() { - timeSlider.clearMarkers(); - timeSlider.redraw(); - } - - @Override - public void playStateChanged(PlayStates playState, PlayModes playMode) { - boolean liveModeToBeMadePossible = isLiveModeToBeMadePossible(); - setLiveGenerallyPossible(liveModeToBeMadePossible); - setJumpToLiveEnablement(liveModeToBeMadePossible && playMode != PlayModes.Live); - switch (playState) { - case Playing: - playPauseButton.getElement().addClassName("playPauseButtonPause"); - if (playMode == PlayModes.Live) { - playModeImage.setResource(playModeLiveActiveImg); - } else { - playModeImage.setResource(playModeReplayActiveImg); - } - break; - case Paused: - playPauseButton.getElement().removeClassName("playPauseButtonPause"); - case Stopped: - playPauseButton.getElement().removeClassName("playPauseButtonPause"); - playModeImage.setResource(playModeInactiveImg); - break; - } - - switch (playMode) { - case Live: - playModeLabel.setText(stringMessages.playModeLive()); - timeDelayLabel.setText(stringMessages.timeDelay() + ": " + timer.getCurrentDelayInMillis() / 1000 + " s"); - timeDelayLabel.setVisible(true); - timeSlider.setEnabled(true); - playSpeedBox.setEnabled(false); - slowDownButton.setEnabled(false); - speedUpButton.setEnabled(false); - break; - case Replay: - timeDelayLabel.setVisible(false); - timeDelayLabel.setText(""); - playModeLabel.setText(stringMessages.playModeReplay()); - timeSlider.setEnabled(true); - playSpeedBox.setEnabled(true); - slowDownButton.setEnabled(true); - speedUpButton.setEnabled(true); - break; - } - } - - @Override - public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) { - } - - @Override - public void onTimeZoomReset() { - } - - @Override - public void onTimeRangeChanged(Date fromTime, Date toTime) { - } - - protected boolean isLiveModeToBeMadePossible() { - return false; - } - - /** - * Enables the {@link #backToLivePlayButton} if and only if enabled is true. - */ - protected void setJumpToLiveEnablement(boolean enabled) { - backToLivePlayButton.setEnabled(enabled); - } - - /** - * Iff possible, makes the {@link #backToLivePlayButton} button visible. - */ - protected void setLiveGenerallyPossible(boolean possible) { - backToLivePlayButton.setVisible(possible); - } - - @SuppressWarnings("unchecked") - public T getSettings() { - TimePanelSettings result = new TimePanelSettings(); - result.setDelayToLivePlayInSeconds(timer.getLivePlayDelayInMillis()/1000); - result.setRefreshInterval(timer.getRefreshInterval()); - return (T) result; - } - - @Override - public Widget getEntryWidget() { - return this; - } - - @Override - public boolean hasSettings() { - return true; - } - - @Override - public SettingsDialogComponent getSettingsDialogComponent() { - return new TimePanelSettingsDialogComponent(getSettings(), stringMessages); - } - - protected boolean isUserExplicitlyChangedLivePlayDelay() { - return userExplicitlyChangedLivePlayDelay; - } - - @Override - public void updateSettings(T newSettings) { - boolean delayChanged = newSettings.getDelayToLivePlayInSeconds() != getSettings().getDelayToLivePlayInSeconds(); - if (delayChanged) { - userExplicitlyChangedLivePlayDelay = true; - timer.setLivePlayDelayInMillis(1000l * newSettings.getDelayToLivePlayInSeconds()); - if (timer.getPlayMode() == PlayModes.Live) { - timeDelayLabel.setText(String.valueOf(newSettings.getDelayToLivePlayInSeconds()) + " s"); - } - } - timer.setRefreshInterval(newSettings.getRefreshInterval()); - } - - @Override - public String getLocalizedShortName() { - return "Time control"; - } - - @Override - public void onResize() { - // handle what is required by @link{ProvidesResize} - Widget child = getWidget(); - if (child instanceof RequiresResize) { - ((RequiresResize) child).onResize(); - } - timeSlider.onResize(); - } - - public Date getLastReceivedDataTimepoint() { - return lastReceivedDataTimepoint; - } - - public void setLastReceivedDataTimepoint(Date lastReceivedDataTimepoint) { - this.lastReceivedDataTimepoint = lastReceivedDataTimepoint; - } -} +package com.sap.sailing.gwt.ui.client; + +import java.util.Date; + +import com.google.gwt.core.client.GWT; +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.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.ui.AbstractImagePrototype; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.IntegerBox; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.Timer.PlayModes; +import com.sap.sailing.gwt.ui.client.Timer.PlayStates; +import com.sap.sailing.gwt.ui.shared.components.Component; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.shared.controls.slider.SliderBar; +import com.sap.sailing.gwt.ui.shared.controls.slider.TimeSlider; + +public class TimePanel extends FormPanel implements Component, TimeListener, TimeZoomChangeListener, + TimeRangeChangeListener, PlayStateListener, RequiresResize { + protected final Timer timer; + protected final TimeRangeWithZoomProvider timeRangeProvider; + + private final IntegerBox playSpeedBox; + private final Label timeDelayLabel; + private final Label timeLabel; + private final Label dateLabel; + private final Label playModeLabel; + protected final TimeSlider timeSlider; + private final Button backToLivePlayButton; + protected final StringMessages stringMessages; + protected final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_FULL); + protected final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss"); + private final ImageResource playSpeedImg; + private final ImageResource playModeLiveActiveImg; + private final ImageResource playModeReplayActiveImg; + private final ImageResource playModeInactiveImg; + private final Button playPauseButton; + private final Image playModeImage; + protected Date lastReceivedDataTimepoint; + private final Button slowDownButton; + private final Button speedUpButton; + + /** + * The live delay may be adjusted automatically if the server decides so. However, if the user explicitly sets a live delay, + * server-side updates to the delay should be suppressed. This flag records whether the user has performed a manual delay + * override. + */ + private boolean userExplicitlyChangedLivePlayDelay; + + /** + * the minimum time the slider extends it's time when the end of the slider is reached + */ + private long MINIMUM_AUTO_ADVANCE_TIME_IN_MS = 5 * 60 * 1000; // 5 minutes + + private static ClientResources resources = GWT.create(ClientResources.class); + + private class SettingsClickHandler implements ClickHandler { + private final StringMessages stringConstants; + + private SettingsClickHandler(StringMessages stringConstants) { + this.stringConstants = stringConstants; + } + + @Override + public void onClick(ClickEvent event) { + new SettingsDialog(TimePanel.this, stringConstants).show(); + } + } + + public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages) { + this.timer = timer; + this.timeRangeProvider = timeRangeProvider; + this.stringMessages = stringMessages; + timer.addTimeListener(this); + timer.addPlayStateListener(this); + timeRangeProvider.addTimeRangeChangeListener(this); + userExplicitlyChangedLivePlayDelay = false; + FlowPanel timePanelInnerWrapper = new FlowPanel(); + timePanelInnerWrapper.setStyleName("timePanelInnerWrapper"); + timePanelInnerWrapper.setSize("100%", "100%"); + + SimplePanel timePanelSlider = new SimplePanel(); + timePanelSlider.setStyleName("timePanelSlider"); + timePanelSlider.getElement().getStyle().setPaddingLeft(66, Unit.PX); + timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX); + + playSpeedImg = resources.timesliderPlaySpeedIcon(); + playPauseButton = new Button(""); + + playModeLiveActiveImg = resources.timesliderPlayStateLiveActiveIcon(); + playModeReplayActiveImg = resources.timesliderPlayStateReplayActiveIcon(); + playModeInactiveImg = resources.timesliderPlayStateLiveInactiveIcon(); + playModeImage = new Image(playModeInactiveImg); + + timeSlider = new TimeSlider(); + timeSlider.setEnabled(true); + timeSlider.setLabelFormatter(new SliderBar.LabelFormatter() { + final DateTimeFormat timeWithMinutesFormatter = DateTimeFormat.getFormat("HH:mm"); + @Override + public String formatLabel(SliderBar slider, Double value, Double previousValue) { + Date date = new Date(value.longValue()); + return timeWithMinutesFormatter.format(date); + } + }); + + timeSlider.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent newValue) { + if(timeSlider.getCurrentValue() != null) { + if (TimePanel.this.timer.getPlayMode() == PlayModes.Live) { + // put timer into replay mode when user explicitly adjusts time; avoids having to press pause first + TimePanel.this.timer.setPlayMode(PlayModes.Replay); + } + TimePanel.this.timer.setTime(timeSlider.getCurrentValue().longValue()); + } + } + }); + + timePanelInnerWrapper.add(timePanelSlider); + timePanelSlider.add(timeSlider); + + FlowPanel controlsPanel = new FlowPanel(); + + controlsPanel.setStyleName("timePanel-controls"); + timePanelInnerWrapper.add(controlsPanel); + + // play button control + FlowPanel playControlPanel = new FlowPanel(); + playControlPanel.setStyleName("timePanel-controls-play"); + controlsPanel.add(playControlPanel); + + playPauseButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + switch(TimePanel.this.timer.getPlayState()) { + case Stopped: + TimePanel.this.timer.play(); + break; + case Playing: + TimePanel.this.timer.pause(); + break; + case Paused: + TimePanel.this.timer.play(); + break; + } + } + }); + playPauseButton.setTitle(stringMessages.startStopPlaying()); + playPauseButton.getElement().addClassName("playPauseButton"); + playControlPanel.add(playPauseButton); + + backToLivePlayButton = new Button(stringMessages.playModeLive()); + backToLivePlayButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + TimePanel.this.timer.setPlayMode(PlayModes.Live); + TimePanel.this.timer.play(); + } + }); + backToLivePlayButton.addStyleName("backToLivePlayButton"); + backToLivePlayButton.setTitle(stringMessages.backToLive()); + playControlPanel.add(backToLivePlayButton); + + // current date and time control + FlowPanel timeControlPanel = new FlowPanel(); + timeControlPanel.setStyleName("timePanel-controls-time"); + dateLabel = new Label(); + timeLabel = new Label(); + timeControlPanel.add(dateLabel); + timeControlPanel.add(timeLabel); + + dateLabel.getElement().getStyle().setFloat(Style.Float.LEFT); + dateLabel.getElement().setClassName("dateLabel"); + timeLabel.getElement().getStyle().setFloat(Style.Float.LEFT); + timeLabel.getElement().setClassName("timeLabel"); + + FlowPanel playModeControlPanel = new FlowPanel(); + playModeControlPanel.setStyleName("timePanel-controls-playmode"); + playModeControlPanel.add(playModeImage); + playModeImage.getElement().getStyle().setFloat(Style.Float.LEFT); + + playModeLabel = new Label(); + playModeControlPanel .add(playModeLabel); + + playModeLabel.getElement().getStyle().setFloat(Style.Float.LEFT); + playModeLabel.getElement().setClassName("playModeLabel"); + + // play speed controls + FlowPanel playSpeedControlPanel = new FlowPanel(); + playSpeedControlPanel.setStyleName("timePanel-controls-playSpeed"); + + + playSpeedBox = new IntegerBox(); + playSpeedBox.setVisibleLength(3); + playSpeedBox.setWidth("25px"); + playSpeedBox.setHeight("14px"); + playSpeedBox.setValue(1); + playSpeedBox.setTitle(stringMessages.playSpeedHelp()); + playSpeedBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + Integer newPlaySpeedFactor = playSpeedBox.getValue() == null ? 0 : playSpeedBox.getValue(); + TimePanel.this.timer.setPlaySpeedFactor(newPlaySpeedFactor); + } + }); + + Image playSpeedImage = new Image(playSpeedImg); + playSpeedControlPanel.add(playSpeedImage); + playSpeedControlPanel.add(playSpeedBox); + + slowDownButton = new Button("-1"); + slowDownButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + playSpeedBox.setValue(playSpeedBox.getValue() == null ? 0 : playSpeedBox.getValue() - 1); + TimePanel.this.timer.setPlaySpeedFactor(playSpeedBox.getValue()); + } + }); + slowDownButton.setTitle(stringMessages.slowPlaySpeedDown()); + playSpeedControlPanel.add(slowDownButton); + + speedUpButton = new Button("+1"); + speedUpButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + playSpeedBox.setValue(playSpeedBox.getValue() == null ? 0 : playSpeedBox.getValue() + 1); + TimePanel.this.timer.setPlaySpeedFactor(playSpeedBox.getValue()); + } + }); + speedUpButton.setTitle(stringMessages.speedPlaySpeedUp()); + playSpeedControlPanel.add(speedUpButton); + + playSpeedImage.getElement().getStyle().setFloat(Style.Float.LEFT); + playSpeedImage.getElement().getStyle().setPadding(3, Style.Unit.PX); + + playSpeedBox.getElement().getStyle().setFloat(Style.Float.LEFT); + playSpeedBox.getElement().getStyle().setPadding(2, Style.Unit.PX); + + speedUpButton.addStyleName("timePanelButton"); + slowDownButton.addStyleName("timePanelButton"); + + // time delay + FlowPanel timeDelayPanel = new FlowPanel(); + timeDelayPanel.setStyleName("timePanel-controls-timeDelay"); + + timeDelayLabel = new Label(); + timeDelayPanel.add(timeDelayLabel); + + timeDelayLabel.getElement().getStyle().setFloat(Style.Float.LEFT); + timeDelayLabel.getElement().getStyle().setPadding(3, Style.Unit.PX); + + // settings + ImageResource settingsIcon = resources.settingsIcon(); + Anchor settingsAnchor = new Anchor(AbstractImagePrototype.create(settingsIcon).getSafeHtml()); + settingsAnchor.setTitle(stringMessages.settings()); + settingsAnchor.setStyleName("timePanelSettings"); + settingsAnchor.addClickHandler(new SettingsClickHandler(stringMessages)); + controlsPanel.add(settingsAnchor); + setWidget(timePanelInnerWrapper); + playStateChanged(timer.getPlayState(), timer.getPlayMode()); + + + controlsPanel.add(playSpeedControlPanel); + controlsPanel.add(playModeControlPanel ); + controlsPanel.add(timeDelayPanel); + controlsPanel.add(timeControlPanel); + } + + @Override + public void timeChanged(Date time) { + if(timeRangeProvider.isZoomed()) { + } else { + if (getFromTime() != null && getToTime() != null) { + // handle the case where time advances beyond slider's end. + if (time.after(getToTime())) { + switch (timer.getPlayMode()) { + case Live: + Date newMaxTime = new Date(time.getTime()); + if (newMaxTime.getTime() - getToTime().getTime() < MINIMUM_AUTO_ADVANCE_TIME_IN_MS) { + newMaxTime.setTime(getToTime().getTime() + MINIMUM_AUTO_ADVANCE_TIME_IN_MS); + } + setMinMax(getFromTime(), newMaxTime, /* fireEvent */ false); // no event because we guarantee that time is between min/max + break; + case Replay: + timer.stop(); + break; + } + } + timeSlider.setCurrentValue(new Double(time.getTime()), false); + } + } + dateLabel.setText(dateFormatter.format(time)); + if (lastReceivedDataTimepoint == null) { + timeLabel.setText(timeFormatter.format(time)); + } else { + timeLabel.setText(timeFormatter.format(time) + " (" + timeFormatter.format(lastReceivedDataTimepoint) + ")"); + } + } + + protected Date getFromTime() { + return timeRangeProvider.getFromTime(); + } + + protected Date getToTime() { + return timeRangeProvider.getToTime(); + } + + /** + * @param min must not be null + * @param max must not be null + */ + public void setMinMax(Date min, Date max, boolean fireEvent) { + assert min != null && max != null; + + boolean changed = false; + if (!max.equals(timeRangeProvider.getToTime())) { + changed = true; + timeSlider.setMaxValue(new Double(max.getTime()), fireEvent); + } + if (!min.equals(timeRangeProvider.getFromTime())) { + changed = true; + timeSlider.setMinValue(new Double(min.getTime()), fireEvent); + if (timeSlider.getCurrentValue() == null) { + timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent); + } + } + if (changed) { + if(!timeRangeProvider.isZoomed()) { + timeRangeProvider.setTimeRange(min, max, this); + } + + int numSteps = timeSlider.getElement().getClientWidth(); + if (numSteps > 0) { + timeSlider.setStepSize(numSteps, fireEvent); + } else { + timeSlider.setStepSize(1000, fireEvent); + } + } + } + + public void resetTimeSlider() { + timeSlider.clearMarkers(); + timeSlider.redraw(); + } + + @Override + public void playStateChanged(PlayStates playState, PlayModes playMode) { + boolean liveModeToBeMadePossible = isLiveModeToBeMadePossible(); + setLiveGenerallyPossible(liveModeToBeMadePossible); + setJumpToLiveEnablement(liveModeToBeMadePossible && playMode != PlayModes.Live); + switch (playState) { + case Playing: + playPauseButton.getElement().addClassName("playPauseButtonPause"); + if (playMode == PlayModes.Live) { + playModeImage.setResource(playModeLiveActiveImg); + } else { + playModeImage.setResource(playModeReplayActiveImg); + } + break; + case Paused: + playPauseButton.getElement().removeClassName("playPauseButtonPause"); + case Stopped: + playPauseButton.getElement().removeClassName("playPauseButtonPause"); + playModeImage.setResource(playModeInactiveImg); + break; + } + + switch (playMode) { + case Live: + playModeLabel.setText(stringMessages.playModeLive()); + timeDelayLabel.setText(stringMessages.timeDelay() + ": " + timer.getCurrentDelayInMillis() / 1000 + " s"); + timeDelayLabel.setVisible(true); + timeSlider.setEnabled(true); + playSpeedBox.setEnabled(false); + slowDownButton.setEnabled(false); + speedUpButton.setEnabled(false); + break; + case Replay: + timeDelayLabel.setVisible(false); + timeDelayLabel.setText(""); + playModeLabel.setText(stringMessages.playModeReplay()); + timeSlider.setEnabled(true); + playSpeedBox.setEnabled(true); + slowDownButton.setEnabled(true); + speedUpButton.setEnabled(true); + break; + } + } + + @Override + public void playSpeedFactorChanged(double newPlaySpeedFactor) { + // nothing to do + } + + @Override + public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) { + } + + @Override + public void onTimeZoomReset() { + } + + @Override + public void onTimeRangeChanged(Date fromTime, Date toTime) { + } + + protected boolean isLiveModeToBeMadePossible() { + return false; + } + + /** + * Enables the {@link #backToLivePlayButton} if and only if enabled is true. + */ + protected void setJumpToLiveEnablement(boolean enabled) { + backToLivePlayButton.setEnabled(enabled); + } + + /** + * Iff possible, makes the {@link #backToLivePlayButton} button visible. + */ + protected void setLiveGenerallyPossible(boolean possible) { + backToLivePlayButton.setVisible(possible); + } + + @SuppressWarnings("unchecked") + public T getSettings() { + TimePanelSettings result = new TimePanelSettings(); + result.setDelayToLivePlayInSeconds(timer.getLivePlayDelayInMillis()/1000); + result.setRefreshInterval(timer.getRefreshInterval()); + return (T) result; + } + + @Override + public Widget getEntryWidget() { + return this; + } + + @Override + public boolean hasSettings() { + return true; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent() { + return new TimePanelSettingsDialogComponent(getSettings(), stringMessages); + } + + protected boolean isUserExplicitlyChangedLivePlayDelay() { + return userExplicitlyChangedLivePlayDelay; + } + + @Override + public void updateSettings(T newSettings) { + boolean delayChanged = newSettings.getDelayToLivePlayInSeconds() != getSettings().getDelayToLivePlayInSeconds(); + if (delayChanged) { + userExplicitlyChangedLivePlayDelay = true; + timer.setLivePlayDelayInMillis(1000l * newSettings.getDelayToLivePlayInSeconds()); + if (timer.getPlayMode() == PlayModes.Live) { + timeDelayLabel.setText(String.valueOf(newSettings.getDelayToLivePlayInSeconds()) + " s"); + } + } + timer.setRefreshInterval(newSettings.getRefreshInterval()); + } + + @Override + public String getLocalizedShortName() { + return "Time control"; + } + + @Override + public void onResize() { + // handle what is required by @link{ProvidesResize} + Widget child = getWidget(); + if (child instanceof RequiresResize) { + ((RequiresResize) child).onResize(); + } + timeSlider.onResize(); + } + + public Date getLastReceivedDataTimepoint() { + return lastReceivedDataTimepoint; + } + + public void setLastReceivedDataTimepoint(Date lastReceivedDataTimepoint) { + this.lastReceivedDataTimepoint = lastReceivedDataTimepoint; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/Timer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/Timer.java index 4187e6eb186..8ff63014ee3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/Timer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/Timer.java @@ -1,297 +1,300 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.Date; -import java.util.HashSet; -import java.util.Set; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.RepeatingCommand; - -/** - * Manages a timer and can auto-advance it at a given acceleration/deceleration rate, with a given delay compared to - * real time. It can be {@link #pause() paused} and {@link #resume() resumed}. It can be in one of two play modes: - * {@link PlayModes#Live} and {@link PlayModes#Replay}. In live play mode the timer is always in state {@link PlayStates#Playing} - * and adjusts the time to now-{@link #livePlayDelayInMillis}. As soon as it's paused or stopped it enters - * {@link PlayModes#Replay}. By entering {@link PlayModes#Live} with {@link #setPlayMode(PlayModes)}, the timer starts - * playing right away. - * - * @author Axel Uhl (d043530) - * - */ -public class Timer { - /** - * Used by {@link #quantizeTimeStamp(long)}; currently set to 1s (1000ms) - */ - private static final long LIVE_CLOCK_QUANTUM = 1000; - - /** - * The time point represented by this timer - */ - private Date time; - - /** - * The time delay to the current point in time in millisseconds which - * will only be used in {@link PlayModes#Live} play mode. - */ - private long livePlayDelayInMillis; - - /** - * Listeners interested in changes of {@link #time} - */ - private final Set timeListeners; - - /** - * Listeners interested in changes of {@link #playing} - */ - private final Set playStateListeners; - - /** - * If Playing, an auto-refreshing command is running with {@link #refreshInterval} - * as its refresh interval. - */ - private PlayStates playState; - - /** - * The current play mode of the timer: live or replay. In live mode the timer will adjust to now-delay each time it - * refreshes. In replay mode it will advance time by the refresh interval times the play speed factor. - */ - private PlayModes playMode; - - /** - * The refresh interval in milliseconds for auto-updating this timer - */ - private long refreshInterval; - - /** - * Set to true if the refreshInterval changed while {@link #playing}. This forces the auto-refreshing command - * to re-schedule itself with the new interval, then terminate. - */ - private boolean refreshIntervalChanged; - - /** - * Factor by which the timer runs faster than real time. 1.0 means real-time, 2.0 means twice as fast as real time, and so on. - * Applies only if the timer is in replay mode. - */ - private double playSpeedFactor; - - /** - * Set to true to enable the timer to advance the max time automatically if the current time gets greater than the max time - */ - private boolean autoAdvance; - - /** - * The timer can run in two different modes: Live and Replay - * 'Live' means the timer is used for a real time event - * 'Replay' means the timer is used for an already finished event in the past - */ - public enum PlayModes { Live, Replay }; - - public enum PlayStates { Stopped, Playing, Paused }; - - /** - * The timer is created in stopped state, using "now" as its current time, 1.0 as its {@link #playSpeedFactor play speed factor} and - * 1 second (1000ms) as the {@link #refreshInterval delay between automatic updates} should the timer be - * {@link #resume() started}. - */ - public Timer(PlayModes playMode) { - this(playMode, 1000); - } - - /** - * The timer is created in stopped state, using "now" as its current time, 1.0 as its {@link #playSpeedFactor - * acceleration factor} and delayBetweenAutoAdvancesInMilliseconds as the - * {@link #refreshInterval delay between automatic updates} should the timer be - * {@link #resume() started}. The {@link #livePlayDelayInMillis} is set to zero seconds. - */ - public Timer(PlayModes playMode, long refreshInterval) { - this.refreshInterval = refreshInterval; - time = new Date(); - timeListeners = new HashSet(); - playStateListeners = new HashSet(); - playState = PlayStates.Stopped; - playSpeedFactor = 1.0; - livePlayDelayInMillis = 0l; - autoAdvance = true; - setPlayMode(playMode); - } - - public void addTimeListener(TimeListener listener) { - timeListeners.add(listener); - } - - public void removeTimeListener(TimeListener listener) { - timeListeners.remove(listener); - } - - public void addPlayStateListener(PlayStateListener listener) { - playStateListeners.add(listener); - } - - public void removePlayStateListener(PlayStateListener listener) { - playStateListeners.remove(listener); - } - - public void setTime(long timePointAsMillis) { - Date oldTime = time; - time = new Date(timePointAsMillis); - if ((oldTime == null) != (time == null) || (oldTime != null && !oldTime.equals(time))) { - for (TimeListener listener : timeListeners) { - listener.timeChanged(time); - } - } - } - - public void setPlaySpeedFactor(double playSpeedFactor) { - this.playSpeedFactor = playSpeedFactor; - } - - public void setRefreshInterval(long refreshInterval) { - this.refreshInterval = refreshInterval; - if (playState == PlayStates.Playing) { - refreshIntervalChanged = true; - } - } - - public long getRefreshInterval() { - return refreshInterval; - } - - public Date getTime() { - return time; - } - - /** - * When setting the play mode to live, the timer will automatically be put into play state playing. - */ - public void setPlayMode(PlayModes newPlayMode) { - if (this.playMode != newPlayMode) { - this.playMode = newPlayMode; - for (PlayStateListener playStateListener : playStateListeners) { - playStateListener.playStateChanged(playState, playMode); - } - if (newPlayMode == PlayModes.Live) { - play(); - } - } - } - - /** - * Pauses this timer after the next time advance. {@link #playing} is set to false if not already - * paused, and registered {@link PlayStateListener}s will be notified. If the play mode was live, it'll be set to - * replay. - */ - public void pause() { - if (playState == PlayStates.Playing) { - playState = PlayStates.Paused; // this will cause the repeating command to stop executing - for (PlayStateListener playStateListener : playStateListeners) { - playStateListener.playStateChanged(playState, playMode); - } - setPlayMode(PlayModes.Replay); - } - } - - /** - * Puts the timer into the stopped state. If it was in live mode, puts it into replay mode. - */ - public void stop() { - if (playState == PlayStates.Playing) { - playState = PlayStates.Stopped; - for (PlayStateListener playStateListener : playStateListeners) { - playStateListener.playStateChanged(playState, playMode); - } - setPlayMode(PlayModes.Replay); - } - } - - public void play() { - if (playState != PlayStates.Playing) { - playState = PlayStates.Playing; - if (playMode == PlayModes.Live) { - setTime(System.currentTimeMillis()-livePlayDelayInMillis); - } - if (autoAdvance) { - startAutoAdvance(); - } - for (PlayStateListener playStateListener : playStateListeners) { - playStateListener.playStateChanged(playState, playMode); - } - } - } - - private void startAutoAdvance() { - RepeatingCommand command = new RepeatingCommand() { - @Override - public boolean execute() { - if (time != null && playState == PlayStates.Playing) { - long newTime = time.getTime(); - if (playMode == PlayModes.Replay) { - newTime += (long) playSpeedFactor * refreshInterval; - } else { - // play mode is Live; quantize to make cache hits more likely - newTime = quantizeTimeStamp(System.currentTimeMillis() - getLivePlayDelayInMillis()); - } - setTime(newTime); - } - if (refreshIntervalChanged) { - refreshIntervalChanged = false; - scheduleAdvancerCommand(this); - return false; // stop this command; use the newly-scheduled one instead that uses the new frequency - } else { - return playState == PlayStates.Playing ? true: false; - } - } - - }; - scheduleAdvancerCommand(command); - } - - /** - * In {@link PlayModes#Live live mode}, time stamps are quantized to make it more likely for the back-end to achieve - * a cache hit. Quantization is controlled by the {@link #LIVE_CLOCK_QUANTUM} constant. - */ - private long quantizeTimeStamp(long millis) { - return millis - (millis % LIVE_CLOCK_QUANTUM); - } - - private void scheduleAdvancerCommand(RepeatingCommand command) { - Scheduler.get().scheduleFixedPeriod(command, (int) refreshInterval); - } - - public void setLivePlayDelayInMillis(long delayInMilliseconds) { - if (this.livePlayDelayInMillis != delayInMilliseconds) { - this.livePlayDelayInMillis = delayInMilliseconds; - if (getPlayMode() == PlayModes.Live) { - setTime(new Date().getTime() - delayInMilliseconds); - } - } - } - - /** - * Tells how much the timer is currently behind "now." Note that this is not the same as asking - * {@link #getLivePlayDelayInMillis()} which tells the delay that will be established if the timer is put - * into live mode. - */ - public long getCurrentDelayInMillis() { - return System.currentTimeMillis() - getTime().getTime(); - } - - public long getLivePlayDelayInMillis() { - return livePlayDelayInMillis; - } - - public PlayStates getPlayState() { - return playState; - } - - public PlayModes getPlayMode() { - return playMode; - } - - public boolean isAutoAdvance() { - return autoAdvance; - } - - public void setAutoAdvance(boolean autoAdvance) { - this.autoAdvance = autoAdvance; - } -} +package com.sap.sailing.gwt.ui.client; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; + +/** + * Manages a timer and can auto-advance it at a given acceleration/deceleration rate, with a given delay compared to + * real time. It can be {@link #pause() paused} and {@link #resume() resumed}. It can be in one of two play modes: + * {@link PlayModes#Live} and {@link PlayModes#Replay}. In live play mode the timer is always in state {@link PlayStates#Playing} + * and adjusts the time to now-{@link #livePlayDelayInMillis}. As soon as it's paused or stopped it enters + * {@link PlayModes#Replay}. By entering {@link PlayModes#Live} with {@link #setPlayMode(PlayModes)}, the timer starts + * playing right away. + * + * @author Axel Uhl (d043530) + * + */ +public class Timer { + /** + * Used by {@link #quantizeTimeStamp(long)}; currently set to 1s (1000ms) + */ + private static final long LIVE_CLOCK_QUANTUM = 1000; + + /** + * The time point represented by this timer + */ + private Date time; + + /** + * The time delay to the current point in time in millisseconds which + * will only be used in {@link PlayModes#Live} play mode. + */ + private long livePlayDelayInMillis; + + /** + * Listeners interested in changes of {@link #time} + */ + private final Set timeListeners; + + /** + * Listeners interested in changes of {@link #playing} + */ + private final Set playStateListeners; + + /** + * If Playing, an auto-refreshing command is running with {@link #refreshInterval} + * as its refresh interval. + */ + private PlayStates playState; + + /** + * The current play mode of the timer: live or replay. In live mode the timer will adjust to now-delay each time it + * refreshes. In replay mode it will advance time by the refresh interval times the play speed factor. + */ + private PlayModes playMode; + + /** + * The refresh interval in milliseconds for auto-updating this timer + */ + private long refreshInterval; + + /** + * Set to true if the refreshInterval changed while {@link #playing}. This forces the auto-refreshing command + * to re-schedule itself with the new interval, then terminate. + */ + private boolean refreshIntervalChanged; + + /** + * Factor by which the timer runs faster than real time. 1.0 means real-time, 2.0 means twice as fast as real time, and so on. + * Applies only if the timer is in replay mode. + */ + private double playSpeedFactor; + + /** + * Set to true to enable the timer to advance the max time automatically if the current time gets greater than the max time + */ + private boolean autoAdvance; + + /** + * The timer can run in two different modes: Live and Replay + * 'Live' means the timer is used for a real time event + * 'Replay' means the timer is used for an already finished event in the past + */ + public enum PlayModes { Live, Replay }; + + public enum PlayStates { Stopped, Playing, Paused }; + + /** + * The timer is created in stopped state, using "now" as its current time, 1.0 as its {@link #playSpeedFactor play speed factor} and + * 1 second (1000ms) as the {@link #refreshInterval delay between automatic updates} should the timer be + * {@link #resume() started}. + */ + public Timer(PlayModes playMode) { + this(playMode, 1000); + } + + /** + * The timer is created in stopped state, using "now" as its current time, 1.0 as its {@link #playSpeedFactor + * acceleration factor} and delayBetweenAutoAdvancesInMilliseconds as the + * {@link #refreshInterval delay between automatic updates} should the timer be + * {@link #resume() started}. The {@link #livePlayDelayInMillis} is set to zero seconds. + */ + public Timer(PlayModes playMode, long refreshInterval) { + this.refreshInterval = refreshInterval; + time = new Date(); + timeListeners = new HashSet(); + playStateListeners = new HashSet(); + playState = PlayStates.Stopped; + setPlaySpeedFactor(1.0); + livePlayDelayInMillis = 0l; + autoAdvance = true; + setPlayMode(playMode); + } + + public void addTimeListener(TimeListener listener) { + timeListeners.add(listener); + } + + public void removeTimeListener(TimeListener listener) { + timeListeners.remove(listener); + } + + public void addPlayStateListener(PlayStateListener listener) { + playStateListeners.add(listener); + } + + public void removePlayStateListener(PlayStateListener listener) { + playStateListeners.remove(listener); + } + + public void setTime(long timePointAsMillis) { + Date oldTime = time; + time = new Date(timePointAsMillis); + if ((oldTime == null) != (time == null) || (oldTime != null && !oldTime.equals(time))) { + for (TimeListener listener : timeListeners) { + listener.timeChanged(time); + } + } + } + + public void setPlaySpeedFactor(double playSpeedFactor) { + this.playSpeedFactor = playSpeedFactor; + for (PlayStateListener playStateListener : playStateListeners) { + playStateListener.playSpeedFactorChanged(this.playSpeedFactor); + } + } + + public void setRefreshInterval(long refreshInterval) { + this.refreshInterval = refreshInterval; + if (playState == PlayStates.Playing) { + refreshIntervalChanged = true; + } + } + + public long getRefreshInterval() { + return refreshInterval; + } + + public Date getTime() { + return time; + } + + /** + * When setting the play mode to live, the timer will automatically be put into play state playing. + */ + public void setPlayMode(PlayModes newPlayMode) { + if (this.playMode != newPlayMode) { + this.playMode = newPlayMode; + for (PlayStateListener playStateListener : playStateListeners) { + playStateListener.playStateChanged(playState, playMode); + } + if (newPlayMode == PlayModes.Live) { + play(); + } + } + } + + /** + * Pauses this timer after the next time advance. {@link #playing} is set to false if not already + * paused, and registered {@link PlayStateListener}s will be notified. If the play mode was live, it'll be set to + * replay. + */ + public void pause() { + if (playState == PlayStates.Playing) { + playState = PlayStates.Paused; // this will cause the repeating command to stop executing + for (PlayStateListener playStateListener : playStateListeners) { + playStateListener.playStateChanged(playState, playMode); + } + setPlayMode(PlayModes.Replay); + } + } + + /** + * Puts the timer into the stopped state. If it was in live mode, puts it into replay mode. + */ + public void stop() { + if (playState == PlayStates.Playing) { + playState = PlayStates.Stopped; + for (PlayStateListener playStateListener : playStateListeners) { + playStateListener.playStateChanged(playState, playMode); + } + setPlayMode(PlayModes.Replay); + } + } + + public void play() { + if (playState != PlayStates.Playing) { + playState = PlayStates.Playing; + if (playMode == PlayModes.Live) { + setTime(System.currentTimeMillis()-livePlayDelayInMillis); + } + if (autoAdvance) { + startAutoAdvance(); + } + for (PlayStateListener playStateListener : playStateListeners) { + playStateListener.playStateChanged(playState, playMode); + } + } + } + + private void startAutoAdvance() { + RepeatingCommand command = new RepeatingCommand() { + @Override + public boolean execute() { + if (time != null && playState == PlayStates.Playing) { + long newTime = time.getTime(); + if (playMode == PlayModes.Replay) { + newTime += (long) playSpeedFactor * refreshInterval; + } else { + // play mode is Live; quantize to make cache hits more likely + newTime = quantizeTimeStamp(System.currentTimeMillis() - getLivePlayDelayInMillis()); + } + setTime(newTime); + } + if (refreshIntervalChanged) { + refreshIntervalChanged = false; + scheduleAdvancerCommand(this); + return false; // stop this command; use the newly-scheduled one instead that uses the new frequency + } else { + return playState == PlayStates.Playing ? true: false; + } + } + + }; + scheduleAdvancerCommand(command); + } + + /** + * In {@link PlayModes#Live live mode}, time stamps are quantized to make it more likely for the back-end to achieve + * a cache hit. Quantization is controlled by the {@link #LIVE_CLOCK_QUANTUM} constant. + */ + private long quantizeTimeStamp(long millis) { + return millis - (millis % LIVE_CLOCK_QUANTUM); + } + + private void scheduleAdvancerCommand(RepeatingCommand command) { + Scheduler.get().scheduleFixedPeriod(command, (int) refreshInterval); + } + + public void setLivePlayDelayInMillis(long delayInMilliseconds) { + if (this.livePlayDelayInMillis != delayInMilliseconds) { + this.livePlayDelayInMillis = delayInMilliseconds; + if (getPlayMode() == PlayModes.Live) { + setTime(new Date().getTime() - delayInMilliseconds); + } + } + } + + /** + * Tells how much the timer is currently behind "now." Note that this is not the same as asking + * {@link #getLivePlayDelayInMillis()} which tells the delay that will be established if the timer is put + * into live mode. + */ + public long getCurrentDelayInMillis() { + return System.currentTimeMillis() - getTime().getTime(); + } + + public long getLivePlayDelayInMillis() { + return livePlayDelayInMillis; + } + + public PlayStates getPlayState() { + return playState; + } + + public PlayModes getPlayMode() { + return playMode; + } + + public boolean isAutoAdvance() { + return autoAdvance; + } + + public void setAutoAdvance(boolean autoAdvance) { + this.autoAdvance = autoAdvance; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleLegDetailColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleLegDetailColumn.java index ff307c25481..17bd506d5c9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleLegDetailColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/FormattedDoubleLegDetailColumn.java @@ -4,7 +4,9 @@ import com.google.gwt.cell.client.Cell.Context; import com.google.gwt.cell.client.TextCell; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.SortingOrder; +import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO; @@ -12,6 +14,20 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO; public class FormattedDoubleLegDetailColumn extends LegDetailColumn implements HasStringAndDoubleValue { private final NumberFormat formatter; private final MinMaxRenderer minMaxRenderer; + + /** + * Creates a new column for the given {@link DetailType}. Have a look at + * {@link DetailTypeFormatter#getUnit(DetailType)}, to see if the given type is supported. + * + * @param detailType + * @param field + * @param headerStyle + * @param columnStyle + */ + public FormattedDoubleLegDetailColumn(DetailType detailType, LegDetailField field, String headerStyle, String columnStyle) { + this(DetailTypeFormatter.format(detailType), DetailTypeFormatter.getUnit(detailType).isEmpty() ? "" : "[" + DetailTypeFormatter.getUnit(detailType) + "]", + field, detailType.getPrecision(), detailType.getDefaultSortingOrder(), headerStyle, columnStyle); + } public FormattedDoubleLegDetailColumn(String title, String unit, com.sap.sailing.gwt.ui.leaderboard.LegDetailColumn.LegDetailField field, int decimals, SortingOrder preferredSortingOrder, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java old mode 100755 new mode 100644 index 64f8721f35c..5cc909db366 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -1,328 +1,328 @@ -package com.sap.sailing.gwt.ui.leaderboard; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; - -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.DockLayoutPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.RootLayoutPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.ScrollPanel; -import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; -import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; -import com.sap.sailing.gwt.ui.client.DataEntryDialog; -import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.Timer; -import com.sap.sailing.gwt.ui.client.Timer.PlayModes; -import com.sap.sailing.gwt.ui.client.URLFactory; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; -import com.sap.sailing.gwt.ui.raceboard.GlobalNavigationPanel; -import com.sap.sailing.gwt.ui.shared.AbstractLeaderboardDTO; - - -public class LeaderboardEntryPoint extends AbstractEntryPoint { - private static final Logger logger = Logger.getLogger(LeaderboardEntryPoint.class.getName()); - private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName"; - private static final String PARAM_EMBEDDED = "embedded"; - private static final String PARAM_SHOW_RACE_DETAILS = "showRaceDetails"; - private static final String PARAM_RACE_NAME = "raceName"; - private static final String PARAM_RACE_DETAIL = "raceDetail"; - private static final String PARAM_OVERALL_DETAIL = "overallDetail"; - private static final String PARAM_LEG_DETAIL = "legDetail"; - private static final String PARAM_MANEUVER_DETAIL = "maneuverDetail"; - private static final String PARAM_AUTO_EXPAND_PRESELECTED_RACE = "autoExpandPreselectedRace"; - private static final String PARAM_AUTO_EXPAND_LAST_RACE_COLUMN = "autoExpandLastRaceColumn"; - private static final String PARAM_REGATTA_NAME = "regattaName"; - private static final String PARAM_REFRESH_INTERVAL_MILLIS = "refreshIntervalMillis"; - private static final String PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE = "showOverallLeaderboardsOnSamePage"; - private static final String PARAM_DELAY_TO_LIVE_MILLIS = "delayToLiveMillis"; - - /** - * Lets the client choose a different race column selection which displays only up to the last N races with N being the integer - * number specified by the parameter. - */ - private static final String PARAM_NAME_LAST_N = "lastN"; - private String leaderboardName; - private String leaderboardGroupName; - private GlobalNavigationPanel globalNavigationPanel; - - @Override - protected void doOnModuleLoad() { - super.doOnModuleLoad(); - final boolean showRaceDetails = Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS) != null - && Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS).equalsIgnoreCase("true"); - final boolean embedded = Window.Location.getParameter(PARAM_EMBEDDED) != null - && Window.Location.getParameter(PARAM_EMBEDDED).equalsIgnoreCase("true"); - final long delayToLiveMillis = Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS) != null ? - Long.valueOf(Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS)) : 5000l; // default 5s - sailingService.getLeaderboardNames(new AsyncCallback>() { - @Override - public void onSuccess(List leaderboardNames) { - leaderboardName = Window.Location.getParameter("name"); - leaderboardGroupName = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME); - if (leaderboardNames.contains(leaderboardName)) { - createUI(showRaceDetails, embedded, delayToLiveMillis); - } else { - RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); - } - } - - @Override - public void onFailure(Throwable t) { - reportError("Error trying to obtain list of leaderboard names: " + t.getMessage()); - } - }); - } - - private void createUI(boolean showRaceDetails, boolean embedded, long delayToLiveMillis) { - DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); - RootLayoutPanel.get().add(mainPanel); - LogoAndTitlePanel logoAndTitlePanel = null; - if (!embedded) { - // Hack to shorten the leaderboardName in case of overall leaderboards - String leaderboardDisplayName = Window.Location.getParameter("displayName"); - if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) { - leaderboardDisplayName = leaderboardName; - } - globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, null, leaderboardGroupName); - logoAndTitlePanel = new LogoAndTitlePanel(leaderboardGroupName, leaderboardDisplayName, stringMessages, this) { - @Override - public void onResize() { - super.onResize(); - if (isSmallWidth()) { - remove(globalNavigationPanel); - } else { - add(globalNavigationPanel); - } - } - }; - logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); - if (!isSmallWidth()) { - logoAndTitlePanel.add(globalNavigationPanel); - } - mainPanel.addNorth(logoAndTitlePanel, 68); - } - ScrollPanel contentScrollPanel = new ScrollPanel(); - long delayBetweenAutoAdvancesInMilliseconds = 3000l; - final RaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); - Timer timer = new Timer(PlayModes.Replay, delayBetweenAutoAdvancesInMilliseconds); - timer.setLivePlayDelayInMillis(delayToLiveMillis); - final LeaderboardSettings leaderboardSettings = createLeaderboardSettingsFromURLParameters(Window.Location.getParameterMap()); - if (leaderboardSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { - timer.setPlayMode(PlayModes.Live); // the leaderboard, viewed via the entry point, always goes "live" - } - LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, new AsyncActionsExecutor(), - leaderboardSettings, - preselectedRace, new CompetitorSelectionModel( - /* hasMultiSelection */true), timer, leaderboardName, LeaderboardEntryPoint.this, - stringMessages, userAgent, showRaceDetails, /* raceTimesInfoProvider */ null, Window.Location.getParameterMap().containsKey(PARAM_AUTO_EXPAND_LAST_RACE_COLUMN) ? - Boolean.valueOf(Window.Location.getParameterMap().get(PARAM_AUTO_EXPAND_LAST_RACE_COLUMN).get(0)) : false); - leaderboardPanel.addStyleName(LeaderboardPanel.LEADERBOARD_MARGIN_STYLE); - contentScrollPanel.setWidget(leaderboardPanel); - - mainPanel.add(contentScrollPanel); - } - - private RaceIdentifier getPreselectedRace(Map> parameterMap) { - RaceIdentifier result; - if (parameterMap.containsKey(PARAM_RACE_NAME) && parameterMap.get(PARAM_RACE_NAME).size() == 1 && - parameterMap.containsKey(PARAM_REGATTA_NAME) && parameterMap.get(PARAM_REGATTA_NAME).size() == 1) { - result = new RegattaNameAndRaceName(parameterMap.get(PARAM_REGATTA_NAME).get(0), parameterMap.get(PARAM_RACE_NAME).get(0)); - } else { - result = null; - } - return result; - } - - /** - * Constructs {@link LeaderboardSettings} from the URL parameters found - */ - private LeaderboardSettings createLeaderboardSettingsFromURLParameters(Map> parameterMap) { - LeaderboardSettings result; - Long refreshIntervalMillis = parameterMap.containsKey(PARAM_REFRESH_INTERVAL_MILLIS) ? Long - .valueOf(parameterMap.get(PARAM_REFRESH_INTERVAL_MILLIS).get(0)) : null; - RaceColumnSelectionStrategies raceColumnSelectionStrategy; - if (parameterMap.containsKey(PARAM_NAME_LAST_N)) { - raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; - } else { - raceColumnSelectionStrategy = RaceColumnSelectionStrategies.EXPLICIT; - } - final Integer numberOfLastRacesToShow; - if (parameterMap.containsKey(PARAM_NAME_LAST_N)) { - numberOfLastRacesToShow = Integer.valueOf(parameterMap.get(PARAM_NAME_LAST_N).get(0)); - } else { - numberOfLastRacesToShow = null; - } - boolean showOverallLeaderboardsOnSamePage = parameterMap.containsKey(PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE) ? - Boolean.valueOf(parameterMap.get(PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE).get(0)) : - false; - if (parameterMap.containsKey(PARAM_RACE_NAME) || parameterMap.containsKey(PARAM_RACE_DETAIL) || - parameterMap.containsKey(PARAM_LEG_DETAIL) || parameterMap.containsKey(PARAM_MANEUVER_DETAIL) || - parameterMap.containsKey(PARAM_OVERALL_DETAIL)) { - List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_MANEUVER_DETAIL)); - List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_RACE_DETAIL)); - List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_OVERALL_DETAIL)); - List legDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_LEG_DETAIL)); - List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(PARAM_RACE_NAME)); - boolean autoExpandPreSelectedRace = parameterMap.containsKey(PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? - Boolean.valueOf(parameterMap.get(PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : - (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); - result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, - /* namesOfRaceColumnsToShow */ null, - namesOfRacesToShow, numberOfLastRacesToShow, - autoExpandPreSelectedRace, refreshIntervalMillis, /* delay to live */ null, - /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? - namesOfRacesToShow.get(0) : null, /* ascending */ true, - /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), - raceColumnSelectionStrategy, showOverallLeaderboardsOnSamePage); - } else { - final List overallDetails = Collections.emptyList(); - result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, - /* overallDetails */ overallDetails, null, - /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, - raceColumnSelectionStrategy, showOverallLeaderboardsOnSamePage); - } - return result; - } - - private List getDetailTypeListFromParamValue(List list) { - List result = new ArrayList(); - if (list != null) { - for (String entry : list) { - try { - result.add(DetailType.valueOf(entry)); - } catch (IllegalArgumentException e) { - logger.info("Can't find detail type "+entry+". Ignoring."); - } - } - } - return result; - } - - private List getStringListFromParamValue(List list) { - List result = new ArrayList(); - if (list != null) { - result.addAll(list); - } - return result; - } - - public static class LeaderboardUrlSettings { - private final LeaderboardSettings leaderboardSettings; - private final boolean embedded; - private final boolean showRaceDetails; - private final boolean autoExpandLastRaceColumn; - private final boolean autoRefresh; - - public LeaderboardUrlSettings(LeaderboardSettings leaderboardSettings, boolean embedded, - boolean showRaceDetails, boolean autoRefresh, boolean autoExpandLastRaceColumn) { - super(); - this.leaderboardSettings = leaderboardSettings; - this.embedded = embedded; - this.showRaceDetails = showRaceDetails; - this.autoRefresh = autoRefresh; - this.autoExpandLastRaceColumn = autoExpandLastRaceColumn; - } - - public LeaderboardSettings getLeaderboardSettings() { - return leaderboardSettings; - } - - public boolean isEmbedded() { - return embedded; - } - - public boolean isShowRaceDetails() { - return showRaceDetails; - } - - public boolean isAutoRefresh() { - return autoRefresh; - } - - public boolean isAutoExpandLastRaceColumn() { - return autoExpandLastRaceColumn; - } - } - - /** - * Assembles a dialog that other parts of the application can use to let the user parameterize a leaderboard and - * obtain the according URL for it. This keeps the "secrets" of which URL parameters have which meaning encapsulated - * within this class.

- * - * The implementation by and large uses the {@link LeaderboardSettingsDialogComponent}'s widget and adds to it a checkbox - * for driving the {@link #PARAM_EMBEDDED} field. - * - * @see LeaderboardEntryPoint#getUrl(String, LeaderboardSettings, boolean) - */ - public static DataEntryDialog getUrlConfigurationDialog(final AbstractLeaderboardDTO leaderboard, - final StringMessages stringMessages) { - return new LeaderboardUrlConfigurationDialog(stringMessages, leaderboard); - } - - /** - * Assembles a URL for a leaderboard that displays with the settings and embedded mode - * as specified by the parameters. - */ - public static String getUrl(String leaderboardName, LeaderboardUrlSettings settings) { - StringBuilder overallDetails = new StringBuilder(); - for (DetailType overallDetail : settings.getLeaderboardSettings().getOverallDetailsToShow()) { - overallDetails.append('&'); - overallDetails.append(PARAM_OVERALL_DETAIL); - overallDetails.append('='); - overallDetails.append(overallDetail.name()); - } - StringBuilder legDetails = new StringBuilder(); - for (DetailType legDetail : settings.getLeaderboardSettings().getLegDetailsToShow()) { - legDetails.append('&'); - legDetails.append(PARAM_LEG_DETAIL); - legDetails.append('='); - legDetails.append(legDetail.name()); - } - StringBuilder raceDetails = new StringBuilder(); - for (DetailType raceDetail : settings.getLeaderboardSettings().getRaceDetailsToShow()) { - raceDetails.append('&'); - raceDetails.append(PARAM_RACE_DETAIL); - raceDetails.append('='); - raceDetails.append(raceDetail.name()); - } - StringBuilder maneuverDetails = new StringBuilder(); - for (DetailType maneuverDetail : settings.getLeaderboardSettings().getManeuverDetailsToShow()) { - maneuverDetails.append('&'); - maneuverDetails.append(PARAM_MANEUVER_DETAIL); - maneuverDetails.append('='); - maneuverDetails.append(maneuverDetail.name()); - } - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = URLFactory.INSTANCE.encode("/gwt/Leaderboard.html?name=" + leaderboardName - + (settings.isShowRaceDetails() ? "&"+PARAM_SHOW_RACE_DETAILS+"=true" : "") - + (settings.isEmbedded() ? "&"+PARAM_EMBEDDED+"=true" : "") - + (settings.getLeaderboardSettings().isShowOverallLeaderboardsOnSamePage() ? "&"+PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE+"=true" : "") - + (settings.getLeaderboardSettings().getDelayInMilliseconds() == null && - settings.getLeaderboardSettings().getDelayInMilliseconds() != 0 ? "" : - "&"+PARAM_DELAY_TO_LIVE_MILLIS+"="+settings.getLeaderboardSettings().getDelayInMilliseconds()) - + (!settings.isAutoRefresh() || (settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() == null && - settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() != 0) ? "" : - "&"+PARAM_REFRESH_INTERVAL_MILLIS+"="+settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds()) - + legDetails.toString() - + raceDetails.toString() - + overallDetails.toString() - + maneuverDetails.toString() - + (settings.isAutoExpandLastRaceColumn() ? "&"+PARAM_AUTO_EXPAND_LAST_RACE_COLUMN+"=true" : "") - + (settings.getLeaderboardSettings().getNumberOfLastRacesToShow() == null ? "" : - "&"+PARAM_NAME_LAST_N+"="+settings.getLeaderboardSettings().getNumberOfLastRacesToShow()) - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); - return link; - } -} +package com.sap.sailing.gwt.ui.leaderboard; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; +import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.Timer; +import com.sap.sailing.gwt.ui.client.Timer.PlayModes; +import com.sap.sailing.gwt.ui.client.URLFactory; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; +import com.sap.sailing.gwt.ui.raceboard.GlobalNavigationPanel; +import com.sap.sailing.gwt.ui.shared.AbstractLeaderboardDTO; + + +public class LeaderboardEntryPoint extends AbstractEntryPoint { + private static final Logger logger = Logger.getLogger(LeaderboardEntryPoint.class.getName()); + private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName"; + private static final String PARAM_EMBEDDED = "embedded"; + private static final String PARAM_SHOW_RACE_DETAILS = "showRaceDetails"; + private static final String PARAM_RACE_NAME = "raceName"; + private static final String PARAM_RACE_DETAIL = "raceDetail"; + private static final String PARAM_OVERALL_DETAIL = "overallDetail"; + private static final String PARAM_LEG_DETAIL = "legDetail"; + private static final String PARAM_MANEUVER_DETAIL = "maneuverDetail"; + private static final String PARAM_AUTO_EXPAND_PRESELECTED_RACE = "autoExpandPreselectedRace"; + private static final String PARAM_AUTO_EXPAND_LAST_RACE_COLUMN = "autoExpandLastRaceColumn"; + private static final String PARAM_REGATTA_NAME = "regattaName"; + private static final String PARAM_REFRESH_INTERVAL_MILLIS = "refreshIntervalMillis"; + private static final String PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE = "showOverallLeaderboardsOnSamePage"; + private static final String PARAM_DELAY_TO_LIVE_MILLIS = "delayToLiveMillis"; + + /** + * Lets the client choose a different race column selection which displays only up to the last N races with N being the integer + * number specified by the parameter. + */ + private static final String PARAM_NAME_LAST_N = "lastN"; + private String leaderboardName; + private String leaderboardGroupName; + private GlobalNavigationPanel globalNavigationPanel; + + @Override + protected void doOnModuleLoad() { + super.doOnModuleLoad(); + final boolean showRaceDetails = Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS) != null + && Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS).equalsIgnoreCase("true"); + final boolean embedded = Window.Location.getParameter(PARAM_EMBEDDED) != null + && Window.Location.getParameter(PARAM_EMBEDDED).equalsIgnoreCase("true"); + final long delayToLiveMillis = Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS) != null ? + Long.valueOf(Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS)) : 5000l; // default 5s + sailingService.getLeaderboardNames(new AsyncCallback>() { + @Override + public void onSuccess(List leaderboardNames) { + leaderboardName = Window.Location.getParameter("name"); + leaderboardGroupName = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME); + if (leaderboardNames.contains(leaderboardName)) { + createUI(showRaceDetails, embedded, delayToLiveMillis); + } else { + RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard())); + } + } + + @Override + public void onFailure(Throwable t) { + reportError("Error trying to obtain list of leaderboard names: " + t.getMessage()); + } + }); + } + + private void createUI(boolean showRaceDetails, boolean embedded, long delayToLiveMillis) { + DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); + RootLayoutPanel.get().add(mainPanel); + LogoAndTitlePanel logoAndTitlePanel = null; + if (!embedded) { + // Hack to shorten the leaderboardName in case of overall leaderboards + String leaderboardDisplayName = Window.Location.getParameter("displayName"); + if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) { + leaderboardDisplayName = leaderboardName; + } + globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, null, leaderboardGroupName); + logoAndTitlePanel = new LogoAndTitlePanel(leaderboardGroupName, leaderboardDisplayName, stringMessages, this) { + @Override + public void onResize() { + super.onResize(); + if (isSmallWidth()) { + remove(globalNavigationPanel); + } else { + add(globalNavigationPanel); + } + } + }; + logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); + if (!isSmallWidth()) { + logoAndTitlePanel.add(globalNavigationPanel); + } + mainPanel.addNorth(logoAndTitlePanel, 68); + } + ScrollPanel contentScrollPanel = new ScrollPanel(); + long delayBetweenAutoAdvancesInMilliseconds = 3000l; + final RaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); + Timer timer = new Timer(PlayModes.Replay, delayBetweenAutoAdvancesInMilliseconds); + timer.setLivePlayDelayInMillis(delayToLiveMillis); + final LeaderboardSettings leaderboardSettings = createLeaderboardSettingsFromURLParameters(Window.Location.getParameterMap()); + if (leaderboardSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { + timer.setPlayMode(PlayModes.Live); // the leaderboard, viewed via the entry point, always goes "live" + } + LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, new AsyncActionsExecutor(), + leaderboardSettings, + preselectedRace, new CompetitorSelectionModel( + /* hasMultiSelection */true), timer, leaderboardName, LeaderboardEntryPoint.this, + stringMessages, userAgent, showRaceDetails, /* raceTimesInfoProvider */ null, Window.Location.getParameterMap().containsKey(PARAM_AUTO_EXPAND_LAST_RACE_COLUMN) ? + Boolean.valueOf(Window.Location.getParameterMap().get(PARAM_AUTO_EXPAND_LAST_RACE_COLUMN).get(0)) : false); + leaderboardPanel.addStyleName(LeaderboardPanel.LEADERBOARD_MARGIN_STYLE); + contentScrollPanel.setWidget(leaderboardPanel); + + mainPanel.add(contentScrollPanel); + } + + private RaceIdentifier getPreselectedRace(Map> parameterMap) { + RaceIdentifier result; + if (parameterMap.containsKey(PARAM_RACE_NAME) && parameterMap.get(PARAM_RACE_NAME).size() == 1 && + parameterMap.containsKey(PARAM_REGATTA_NAME) && parameterMap.get(PARAM_REGATTA_NAME).size() == 1) { + result = new RegattaNameAndRaceName(parameterMap.get(PARAM_REGATTA_NAME).get(0), parameterMap.get(PARAM_RACE_NAME).get(0)); + } else { + result = null; + } + return result; + } + + /** + * Constructs {@link LeaderboardSettings} from the URL parameters found + */ + private LeaderboardSettings createLeaderboardSettingsFromURLParameters(Map> parameterMap) { + LeaderboardSettings result; + Long refreshIntervalMillis = parameterMap.containsKey(PARAM_REFRESH_INTERVAL_MILLIS) ? Long + .valueOf(parameterMap.get(PARAM_REFRESH_INTERVAL_MILLIS).get(0)) : null; + RaceColumnSelectionStrategies raceColumnSelectionStrategy; + if (parameterMap.containsKey(PARAM_NAME_LAST_N)) { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.LAST_N; + } else { + raceColumnSelectionStrategy = RaceColumnSelectionStrategies.EXPLICIT; + } + final Integer numberOfLastRacesToShow; + if (parameterMap.containsKey(PARAM_NAME_LAST_N)) { + numberOfLastRacesToShow = Integer.valueOf(parameterMap.get(PARAM_NAME_LAST_N).get(0)); + } else { + numberOfLastRacesToShow = null; + } + boolean showOverallLeaderboardsOnSamePage = parameterMap.containsKey(PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE) ? + Boolean.valueOf(parameterMap.get(PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE).get(0)) : + false; + if (parameterMap.containsKey(PARAM_RACE_NAME) || parameterMap.containsKey(PARAM_RACE_DETAIL) || + parameterMap.containsKey(PARAM_LEG_DETAIL) || parameterMap.containsKey(PARAM_MANEUVER_DETAIL) || + parameterMap.containsKey(PARAM_OVERALL_DETAIL)) { + List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_MANEUVER_DETAIL)); + List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_RACE_DETAIL)); + List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_OVERALL_DETAIL)); + List legDetails = getDetailTypeListFromParamValue(parameterMap.get(PARAM_LEG_DETAIL)); + List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(PARAM_RACE_NAME)); + boolean autoExpandPreSelectedRace = parameterMap.containsKey(PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? + Boolean.valueOf(parameterMap.get(PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : + (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, + /* namesOfRaceColumnsToShow */ null, + namesOfRacesToShow, numberOfLastRacesToShow, + autoExpandPreSelectedRace, refreshIntervalMillis, /* delay to live */ null, + /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? + namesOfRacesToShow.get(0) : null, /* ascending */ true, + /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), + raceColumnSelectionStrategy, showOverallLeaderboardsOnSamePage); + } else { + final List overallDetails = Collections.emptyList(); + result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, + /* overallDetails */ overallDetails, null, + /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, + raceColumnSelectionStrategy, showOverallLeaderboardsOnSamePage); + } + return result; + } + + private List getDetailTypeListFromParamValue(List list) { + List result = new ArrayList(); + if (list != null) { + for (String entry : list) { + try { + result.add(DetailType.valueOf(entry)); + } catch (IllegalArgumentException e) { + logger.info("Can't find detail type "+entry+". Ignoring."); + } + } + } + return result; + } + + private List getStringListFromParamValue(List list) { + List result = new ArrayList(); + if (list != null) { + result.addAll(list); + } + return result; + } + + public static class LeaderboardUrlSettings { + private final LeaderboardSettings leaderboardSettings; + private final boolean embedded; + private final boolean showRaceDetails; + private final boolean autoExpandLastRaceColumn; + private final boolean autoRefresh; + + public LeaderboardUrlSettings(LeaderboardSettings leaderboardSettings, boolean embedded, + boolean showRaceDetails, boolean autoRefresh, boolean autoExpandLastRaceColumn) { + super(); + this.leaderboardSettings = leaderboardSettings; + this.embedded = embedded; + this.showRaceDetails = showRaceDetails; + this.autoRefresh = autoRefresh; + this.autoExpandLastRaceColumn = autoExpandLastRaceColumn; + } + + public LeaderboardSettings getLeaderboardSettings() { + return leaderboardSettings; + } + + public boolean isEmbedded() { + return embedded; + } + + public boolean isShowRaceDetails() { + return showRaceDetails; + } + + public boolean isAutoRefresh() { + return autoRefresh; + } + + public boolean isAutoExpandLastRaceColumn() { + return autoExpandLastRaceColumn; + } + } + + /** + * Assembles a dialog that other parts of the application can use to let the user parameterize a leaderboard and + * obtain the according URL for it. This keeps the "secrets" of which URL parameters have which meaning encapsulated + * within this class.

+ * + * The implementation by and large uses the {@link LeaderboardSettingsDialogComponent}'s widget and adds to it a checkbox + * for driving the {@link #PARAM_EMBEDDED} field. + * + * @see LeaderboardEntryPoint#getUrl(String, LeaderboardSettings, boolean) + */ + public static DataEntryDialog getUrlConfigurationDialog(final AbstractLeaderboardDTO leaderboard, + final StringMessages stringMessages) { + return new LeaderboardUrlConfigurationDialog(stringMessages, leaderboard); + } + + /** + * Assembles a URL for a leaderboard that displays with the settings and embedded mode + * as specified by the parameters. + */ + public static String getUrl(String leaderboardName, LeaderboardUrlSettings settings) { + StringBuilder overallDetails = new StringBuilder(); + for (DetailType overallDetail : settings.getLeaderboardSettings().getOverallDetailsToShow()) { + overallDetails.append('&'); + overallDetails.append(PARAM_OVERALL_DETAIL); + overallDetails.append('='); + overallDetails.append(overallDetail.name()); + } + StringBuilder legDetails = new StringBuilder(); + for (DetailType legDetail : settings.getLeaderboardSettings().getLegDetailsToShow()) { + legDetails.append('&'); + legDetails.append(PARAM_LEG_DETAIL); + legDetails.append('='); + legDetails.append(legDetail.name()); + } + StringBuilder raceDetails = new StringBuilder(); + for (DetailType raceDetail : settings.getLeaderboardSettings().getRaceDetailsToShow()) { + raceDetails.append('&'); + raceDetails.append(PARAM_RACE_DETAIL); + raceDetails.append('='); + raceDetails.append(raceDetail.name()); + } + StringBuilder maneuverDetails = new StringBuilder(); + for (DetailType maneuverDetail : settings.getLeaderboardSettings().getManeuverDetailsToShow()) { + maneuverDetails.append('&'); + maneuverDetails.append(PARAM_MANEUVER_DETAIL); + maneuverDetails.append('='); + maneuverDetails.append(maneuverDetail.name()); + } + String debugParam = Window.Location.getParameter("gwt.codesvr"); + String link = URLFactory.INSTANCE.encode("/gwt/Leaderboard.html?name=" + leaderboardName + + (settings.isShowRaceDetails() ? "&"+PARAM_SHOW_RACE_DETAILS+"=true" : "") + + (settings.isEmbedded() ? "&"+PARAM_EMBEDDED+"=true" : "") + + (settings.getLeaderboardSettings().isShowOverallLeaderboardsOnSamePage() ? "&"+PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE+"=true" : "") + + (settings.getLeaderboardSettings().getDelayInMilliseconds() == null && + settings.getLeaderboardSettings().getDelayInMilliseconds() != 0 ? "" : + "&"+PARAM_DELAY_TO_LIVE_MILLIS+"="+settings.getLeaderboardSettings().getDelayInMilliseconds()) + + (!settings.isAutoRefresh() || (settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() == null && + settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds() != 0) ? "" : + "&"+PARAM_REFRESH_INTERVAL_MILLIS+"="+settings.getLeaderboardSettings().getDelayBetweenAutoAdvancesInMilliseconds()) + + legDetails.toString() + + raceDetails.toString() + + overallDetails.toString() + + maneuverDetails.toString() + + (settings.isAutoExpandLastRaceColumn() ? "&"+PARAM_AUTO_EXPAND_LAST_RACE_COLUMN+"=true" : "") + + (settings.getLeaderboardSettings().getNumberOfLastRacesToShow() == null ? "" : + "&"+PARAM_NAME_LAST_N+"="+settings.getLeaderboardSettings().getNumberOfLastRacesToShow()) + + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); + return link; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java old mode 100644 new mode 100755 index b7d117d0cc0..0345bea5bb8 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -1,2365 +1,2356 @@ -package com.sap.sailing.gwt.ui.leaderboard; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -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.cell.client.Cell; -import com.google.gwt.cell.client.Cell.Context; -import com.google.gwt.cell.client.CompositeCell; -import com.google.gwt.cell.client.EditTextCell; -import com.google.gwt.cell.client.TextCell; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style.FontWeight; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.i18n.client.NumberFormat; -import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.safehtml.client.SafeHtmlTemplates; -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.cellview.client.Header; -import com.google.gwt.user.cellview.client.TextHeader; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.AbstractImagePrototype; -import com.google.gwt.user.client.ui.Anchor; -import com.google.gwt.user.client.ui.DockPanel; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.HasHorizontalAlignment; -import com.google.gwt.user.client.ui.HasVerticalAlignment; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.ImageResourceRenderer; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.VerticalPanel; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; -import com.google.gwt.view.client.SelectionChangeEvent; -import com.google.gwt.view.client.SelectionChangeEvent.Handler; -import com.google.gwt.view.client.SelectionModel; -import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.InvertibleComparator; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.SortingOrder; -import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.impl.Util.Pair; -import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; -import com.sap.sailing.gwt.ui.actions.GetLeaderboardByNameAction; -import com.sap.sailing.gwt.ui.client.Collator; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.FlagImageResolver; -import com.sap.sailing.gwt.ui.client.PlayStateListener; -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.TimeListener; -import com.sap.sailing.gwt.ui.client.Timer; -import com.sap.sailing.gwt.ui.client.Timer.PlayModes; -import com.sap.sailing.gwt.ui.client.Timer.PlayStates; -import com.sap.sailing.gwt.ui.client.UserAgentDetails; -import com.sap.sailing.gwt.ui.leaderboard.LegDetailColumn.LegDetailField; -import com.sap.sailing.gwt.ui.shared.AbstractLeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.CompetitorDTO; -import com.sap.sailing.gwt.ui.shared.FleetDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO; -import com.sap.sailing.gwt.ui.shared.LegEntryDTO; -import com.sap.sailing.gwt.ui.shared.RaceColumnDTO; -import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; -import com.sap.sailing.gwt.ui.shared.components.Component; -import com.sap.sailing.gwt.ui.shared.components.IsEmbeddableComponent; -import com.sap.sailing.gwt.ui.shared.components.SettingsDialog; -import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.shared.panels.BusyIndicator; -import com.sap.sailing.gwt.ui.shared.panels.SimpleBusyIndicator; - -/** - * A leaderboard essentially consists of a table widget that in its columns displays the entries. - * - * @author Axel Uhl (D043530) - * - */ -public class LeaderboardPanel extends FormPanel implements TimeListener, PlayStateListener, - Component, IsEmbeddableComponent, CompetitorSelectionChangeListener, LeaderboardFetcher { - private static final int RANK_COLUMN_INDEX = 0; - - private static final int SAIL_ID_COLUMN_INDEX = 1; - - private static final int CARRY_COLUMN_INDEX = 3; - - protected static final NumberFormat scoreFormat = NumberFormat.getFormat("0.00"); - - private final SailingServiceAsync sailingService; - - private static String IS_LIVE_TEXT_COLOR = "#1876B3"; - private static String DEFAULT_TEXT_COLOR = "#000000"; - - private static final String STYLE_LEADERBOARD_CONTENT = "leaderboardContent"; - private static final String STYLE_LEADERBOARD_INFO = "leaderboardInfo"; - private static final String STYLE_LEADERBOARD_TOOLBAR = "leaderboardContent-toolbar"; - private static final String STYLE_LEADERBOARD_LIVE_RACE = "leaderboardContent-liverace"; - - interface RaceColumnTemplates extends SafeHtmlTemplates { - @SafeHtmlTemplates.Template("

") - SafeHtml cellFrameWithTextColorAndFleetBorder(String textColor, String borderStyle); - - @SafeHtmlTemplates.Template("
") - SafeHtml cellFrameWithTextColor(String textColor); - } - - private static RaceColumnTemplates raceColumnTemplate = GWT.create(RaceColumnTemplates.class); - - /** - * The leaderboard name is used to - * {@link SailingServiceAsync#getLeaderboardByName(String, java.util.Date, String[], com.google.gwt.user.client.rpc.AsyncCallback) - * obtain the leaderboard contents} from the server. It may change in case the leaderboard is renamed. - */ - private String leaderboardName; - - private final ErrorReporter errorReporter; - - private final StringMessages stringMessages; - - private final SortedCellTable leaderboardTable; - - private final SelectionModel leaderboardSelectionModel; - - private LeaderboardDTO leaderboard; - - private final RankColumn rankColumn; - - /** - * Passed to the {@link ManeuverCountRaceColumn}. Modifications to this list will modify the column's children list - * when updated the next time. - */ - private final List selectedManeuverDetails; - - /** - * Passed to the {@link LegColumn}. Modifications to this list will modify the column's children list when updated - * the next time. - */ - private final List selectedLegDetails; - - /** - * Passed to the {@link TextRaceColumn}. Modifications to this list will modify the column's children list when - * updated the next time. - */ - private final List selectedRaceDetails; - - private final List selectedOverallDetailColumns; - - private final Map> overallDetailColumnMap; - - private RaceColumnSelection raceColumnSelection; - - protected final String RACE_COLUMN_HEADER_STYLE; - - protected final String LEG_DETAIL_COLUMN_HEADER_STYLE; - - protected final String LEG_DETAIL_COLUMN_STYLE; - - protected final String LEG_COLUMN_HEADER_STYLE; - - protected final String RACE_COLUMN_STYLE; - - protected final String LEG_COLUMN_STYLE; - - protected final String TOTAL_COLUMN_STYLE; - - public static final String LEADERBOARD_MARGIN_STYLE = "leaderboardMargin"; - - private final Timer timer; - - private boolean autoExpandPreSelectedRace; - - private boolean autoExpandLastRaceColumn; - - private boolean showOverallLeaderboardsOnSamePage; - - /** - * Remembers whether the auto-expand of the pre-selected race (see {@link #autoExpandPreSelectedRace}) or last - * selected race {@link #autoExpandLastRaceColumn} has been performed once. It must not be performed another time. - */ - private boolean autoExpandPerformedOnce; - - /** - * This anchor's HTML holds the image tag for the play/pause button that needs to be updated when the {@link #timer} - * changes its playing state - */ - private final Anchor playPause; - - private final CompetitorSelectionProvider competitorSelectionProvider; - - /** - * If this is null, all leaderboard columns added by updating the leaderboard from the server are - * automatically added to the table. Otherwise, only the column whose - * {@link RaceColumnDTO#getRaceIdentifier(String) race identifier} matches the value of this attribute will be - * added. - */ - private final RaceIdentifier preSelectedRace; - - private final VerticalPanel contentPanel; - - /** - * Used to display one or more overall leaderboards if selected. See also - * {@link LeaderboardSettings#isShowOverallLeaderboardsOnSamePage()}. Always contained by the {@link #contentPanel} at the - * right position, but may be empty. - */ - private final VerticalPanel overallLeaderboardsPanel; - - /** - * The leaderboard panels displayed in {@link #overallLeaderboardsPanel}. - */ - private final List overallLeaderboardPanels; - - private final HorizontalPanel refreshAndSettingsPanel; - - private final FlowPanel informationPanel; - private final Label scoreCorrectionLastUpdateTimeLabel; - private final Label scoreCorrectionCommentLabel; - private final Label liveRaceLabel; - - private final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_LONG); - private final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss"); - - private boolean isEmbedded = false; - - private static LeaderboardResources resources = GWT.create(LeaderboardResources.class); - private static LeaderboardTableResources tableResources = GWT.create(LeaderboardTableResources.class); - - private final ImageResource pauseIcon; - private final ImageResource playIcon; - - private final BusyIndicator busyIndicator; - - /** - * Tells whether the leaderboard settings were explicitly changed by an external call to - * {@link #updateSettings(LeaderboardSettings)}. If so, a {@link #playStateChanged(PlayStates, PlayModes) play state - * change} will not automatically lead to a settings change. - */ - private boolean settingsUpdatedExplicitly = false; - - /** - * Tells if the leaderboard is currently handling a {@link #playStateChanged(PlayStates, PlayModes) play state - * change}. If this is the case, a call to {@link #updateSettings(LeaderboardSettings)} won't set the - * {@link #settingsUpdatedExplicitly} flag. - */ - private boolean currentlyHandlingPlayStateChange; - - private PlayModes oldPlayMode; - - private final AsyncActionsExecutor asyncActionsExecutor; - - /** - * See also {@link #getDefaultSortColumn()}. If no other column is explicitly selected for sorting and this - * attribute holds a non-null string identifying a valid race by name that is represented in this - * leaderboard panel then sort by it. Otherwise, default sorting will default to the overall rank column. - */ - private String raceNameForDefaultSorting; - - /** - * Can be used to disallow users to drill into the race details. - */ - private final boolean showRaceDetails; - - /** - * The {@link LastNRacesColumnSelection} column selection strategy requires a {@link RaceTimesInfoProvider}. This can either be injected - * by passing a non-null object of that type to the constructor, or such an object is created and remembered in this - * attribute when required the first time. - */ - private RaceTimesInfoProvider raceTimesInfoProvider; - - private final UserAgentDetails userAgent; - - private class SettingsClickHandler implements ClickHandler { - private final StringMessages stringMessages; - - private SettingsClickHandler(StringMessages stringMessages) { - this.stringMessages = stringMessages; - } - - @Override - public void onClick(ClickEvent event) { - new SettingsDialog(LeaderboardPanel.this, stringMessages).show(); - } - } - - protected StringMessages getStringMessages() { - return stringMessages; - } - - @Override - public Widget getEntryWidget() { - return this; - } - - @Override - public Widget getHeaderWidget() { - return null; - } - - @Override - public Widget getContentWidget() { - return contentPanel; - } - - @Override - public Widget getToolbarWidget() { - return null; - } - - @Override - public Widget getLegendWidget() { - return null; - } - - @Override - public boolean isEmbedded() { - return isEmbedded; - } - - protected VerticalPanel getContentPanel() { - return contentPanel; - } - - public void updateSettings(LeaderboardSettings newSettings) { - if (!newSettings.updateUponPlayStateChange() || !currentlyHandlingPlayStateChange) { - settingsUpdatedExplicitly = true; - } - List> columnsToExpandAgain = new ArrayList>(); - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column c = getLeaderboardTable().getColumn(i); - if (c instanceof ExpandableSortableColumn) { - ExpandableSortableColumn expandableSortableColumn = (ExpandableSortableColumn) c; - if (expandableSortableColumn.isExpanded()) { - // now toggle expansion back and forth, - // enforcing a re-build of the visible - // child columns - expandableSortableColumn.toggleExpansion(); - columnsToExpandAgain.add(expandableSortableColumn); - } - } - } - if (newSettings.getManeuverDetailsToShow() != null) { - selectedManeuverDetails.clear(); - selectedManeuverDetails.addAll(newSettings.getManeuverDetailsToShow()); - } - if (newSettings.getLegDetailsToShow() != null) { - selectedLegDetails.clear(); - selectedLegDetails.addAll(newSettings.getLegDetailsToShow()); - } - if (newSettings.getRaceDetailsToShow() != null) { - selectedRaceDetails.clear(); - selectedRaceDetails.addAll(newSettings.getRaceDetailsToShow()); - } - if (newSettings.getOverallDetailsToShow() != null) { - selectedOverallDetailColumns.clear(); - selectedOverallDetailColumns.addAll(newSettings.getOverallDetailsToShow()); - } - // update strategy for determining the race columns to show; if settings' race columns to show is null, use the - // previously selected race columns / number of race columns for the new configuration - Iterable oldNamesOfRaceColumnsToShow = null; - if (newSettings.getNamesOfRaceColumnsToShow() == null) { - oldNamesOfRaceColumnsToShow = raceColumnSelection.getSelectedRaceColumnNames(); - } - switch (newSettings.getActiveRaceColumnSelectionStrategy()) { - case EXPLICIT: - if (preSelectedRace == null) { - raceColumnSelection = new ExplicitRaceColumnSelection(); - } else { - raceColumnSelection = new ExplicitRaceColumnSelectionWithPreselectedRace(preSelectedRace); - } - if (newSettings.getNamesOfRaceColumnsToShow() != null) { - raceColumnSelection.requestClear(); - for (String nameOfRaceColumnToShow : newSettings.getNamesOfRaceColumnsToShow()) { - RaceColumnDTO raceColumnToShow = getRaceByColumnName(nameOfRaceColumnToShow); - if (raceColumnToShow != null) { - raceColumnSelection.requestRaceColumnSelection(raceColumnToShow.name, raceColumnToShow); - } - } - } else { - // apply the old column selections again - for (String oldNameOfRaceColumnToShow : oldNamesOfRaceColumnsToShow) { - raceColumnSelection.requestRaceColumnSelection(oldNameOfRaceColumnToShow, - getLeaderboard().getRaceColumnByName(oldNameOfRaceColumnToShow)); - } - if (newSettings.getNamesOfRacesToShow() != null) { - raceColumnSelection.requestClear(); - for (String nameOfRaceToShow : newSettings.getNamesOfRacesToShow()) { - RaceColumnDTO raceColumnToShow = getRaceByName(nameOfRaceToShow); - if (raceColumnToShow != null) { - raceColumnSelection.requestRaceColumnSelection(raceColumnToShow.name, raceColumnToShow); - } - } - } - } - break; - case LAST_N: - setRaceColumnSelectionToLastNStrategy(newSettings.getNumberOfLastRacesToShow()); - break; - } - setAutoExpandPreSelectedRace(false); // avoid expansion during updateLeaderboard(...); will expand later if it - // was expanded before - // update leaderboard after settings panel column selection change - updateLeaderboard(leaderboard); - setAutoExpandPreSelectedRace(newSettings.isAutoExpandPreSelectedRace()); - - if (newSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { - timer.setRefreshInterval(newSettings.getDelayBetweenAutoAdvancesInMilliseconds()); - } - if (newSettings.getDelayInMilliseconds() != null) { - setDelayInMilliseconds(newSettings.getDelayInMilliseconds()); - } - for (ExpandableSortableColumn expandableSortableColumn : columnsToExpandAgain) { - expandableSortableColumn.toggleExpansion(); - } - if (newSettings.getNameOfRaceToSort() != null) { - final RaceColumn raceColumnByRaceName = getRaceColumnByRaceName(newSettings.getNameOfRaceToSort()); - if (raceColumnByRaceName != null) { - getLeaderboardTable().sortColumn(raceColumnByRaceName, /* ascending */true); - } - } - if (newSettings.isShowOverallLeaderboardsOnSamePage() != showOverallLeaderboardsOnSamePage) { - showOverallLeaderboardsOnSamePage = newSettings.isShowOverallLeaderboardsOnSamePage(); - if (showOverallLeaderboardsOnSamePage) { - showOverallLeaderboards(); - } else { - hideOverallLeaderboards(); - } - } - } - - private void showOverallLeaderboards() { - overallLeaderboardsPanel.clear(); - final Label overallStandingsLabel = new Label(stringMessages.overallStandings()); - overallStandingsLabel.setStyleName("leaderboardHeading"); - overallLeaderboardsPanel.add(overallStandingsLabel); - updateOverallLeaderboardsAndAddToPanel(); - } - - /** - * Fetches from the server the list of overall ("meta") leaderboard descriptions in which this main leaderboard occurs and - * creates one {@link LeaderboardPanel} for each such overall leaderboard. Those panels are then added to - * {@link #overallLeaderboardPanels}, registered with the {@link #timer} for common timing with the main leaderboard panel, - * and the panels are made visible by adding them to the {@link #overallLeaderboardsPanel} panel. - */ - private void updateOverallLeaderboardsAndAddToPanel() { - getSailingService().getOverallLeaderboardNamesContaining(leaderboardName, new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - getErrorReporter().reportError( - stringMessages.errorTryingToObtainOverallLeaderboards(caught.getMessage())); - } - - @Override - public void onSuccess(List overallLeaderboardNames) { - for (String overallLeaderboardName : overallLeaderboardNames) { - LeaderboardPanel overallLeaderboardPanel = new LeaderboardPanel( - getSailingService(), asyncActionsExecutor, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( - /* namesOfRaceColumnsToShow */ null, /* namesOfRacesToShow */ null, /* nameOfRaceToSort */ null, - /* autoExpandPreSelectedRace */ false, /* showOverallLeaderboardsOnSamePage */ false), - /* preSelectedRace */ null, new CompetitorSelectionModel(/* hasMultiSelection */ true), - timer, overallLeaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails, - /* optionalRaceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false); - overallLeaderboardPanels.add(overallLeaderboardPanel); - overallLeaderboardsPanel.add(overallLeaderboardPanel); - } - } - }); - - } - - /** - * Clears the panel used to display the overall leaderboards, removes all overall leaderboard panels from their listener - * relationship with the {@link #timer} and then clears the {@link #overallLeaderboardPanels} list. - */ - private void hideOverallLeaderboards() { - overallLeaderboardsPanel.clear(); - for (LeaderboardPanel overallLeaderboardPanel : overallLeaderboardPanels) { - timer.removeTimeListener(overallLeaderboardPanel); - timer.removePlayStateListener(overallLeaderboardPanel); - } - overallLeaderboardPanels.clear(); - } - - private void setRaceColumnSelectionToLastNStrategy(final Integer numberOfLastRacesToShow) { - raceColumnSelection = new LastNRacesColumnSelection(numberOfLastRacesToShow, getRaceTimesInfoProvider()); - if (timer.getPlayState() != Timer.PlayStates.Playing) { - // wait for the first update and adjust leaderboard once the race times have been received - final RaceTimesInfoProviderListener raceTimesInfoProviderListener = new RaceTimesInfoProviderListener() { - @Override - public void raceTimesInfosReceived(Map raceTimesInfo) { - updateLeaderboard(getLeaderboard()); - getRaceTimesInfoProvider().removeRaceTimesInfoProviderListener(this); - } - }; - getRaceTimesInfoProvider().addRaceTimesInfoProviderListener(raceTimesInfoProviderListener); - } - } - - /** - * A leaderboard panel may have been provided with a valid {@link RaceTimesInfoProvider} upon creation; in this case, that object - * will be returned. If none was provided to the constructor, one is created and remembered if no previously created/remembered - * object exists.

- * - * Precondition: {@link #timer} is not null - */ - private RaceTimesInfoProvider getRaceTimesInfoProvider() { - if (raceTimesInfoProvider == null) { - final List trackedRacesIdentifiers; - if (leaderboard != null && getTrackedRacesIdentifiers() != null) { - trackedRacesIdentifiers = getTrackedRacesIdentifiers(); - } else { - trackedRacesIdentifiers = Collections.emptyList(); - } - raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), errorReporter, - trackedRacesIdentifiers, timer.getRefreshInterval()); - } - return raceTimesInfoProvider; - } - - protected class CompetitorColumn extends SortableColumn { - private final CompetitorColumnBase base; - - protected CompetitorColumn(CompetitorColumnBase base) { - super(base.getCell(getLeaderboard()), SortingOrder.ASCENDING); - this.base = base; - } - - public CompetitorColumn(CompositeCell compositeCell, CompetitorColumnBase base) { - super(compositeCell, SortingOrder.ASCENDING); - this.base = base; - } - - @Override - public InvertibleComparator getComparator() { - return new InvertibleComparatorAdapter() { - @Override - public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { - return Collator.getInstance().compare(getLeaderboard().getDisplayName(o1.competitor), - getLeaderboard().getDisplayName(o2.competitor)); - } - }; - } - - @Override - public Header getHeader() { - return base.getHeader(); - } - - @Override - public LeaderboardRowDTO getValue(LeaderboardRowDTO object) { - return object; - } - - protected void defaultRender(Context context, LeaderboardRowDTO object, SafeHtmlBuilder sb) { - super.render(context, object, sb); - } - - @Override - public void render(Context context, LeaderboardRowDTO object, SafeHtmlBuilder sb) { - String competitorColor = LeaderboardPanel.this.competitorSelectionProvider.getColor(object.competitor); - String competitorColorBarStyle; - if (LeaderboardPanel.this.isEmbedded) { - competitorColorBarStyle = "style=\"border-bottom: 2px solid " + competitorColor + ";\""; - } else { - competitorColorBarStyle = "style=\"border: none;\""; - } - base.render(object, competitorColorBarStyle, sb); - } - } - - /** - * Shows the country flag and sail ID, if present - * - * @author Axel Uhl (d043530) - * - */ - private class SailIDColumn extends SortableColumn { - private final CompetitorFetcher competitorFetcher; - - protected SailIDColumn(CompetitorFetcher competitorFetcher) { - super(new TextCell(), SortingOrder.ASCENDING); - this.competitorFetcher = competitorFetcher; - } - - @Override - public InvertibleComparator getComparator() { - return new InvertibleComparatorAdapter() { - @Override - public int compare(T o1, T o2) { - return competitorFetcher.getCompetitor(o1).sailID == null ? competitorFetcher.getCompetitor(o2).sailID == null ? 0 : -1 - : competitorFetcher.getCompetitor(o2).sailID == null ? 1 : Collator.getInstance().compare( - competitorFetcher.getCompetitor(o1).sailID, competitorFetcher.getCompetitor(o2).sailID); - } - }; - } - - @Override - public Header getHeader() { - return new TextHeader(stringMessages.competitor()); - } - - @Override - public void render(Context context, T object, SafeHtmlBuilder sb) { - ImageResourceRenderer renderer = new ImageResourceRenderer(); - ImageResource flagImageResource = FlagImageResolver - .getFlagImageResource(competitorFetcher.getCompetitor(object).twoLetterIsoCountryCode); - if (flagImageResource != null) { - sb.append(renderer.render(flagImageResource)); - sb.appendHtmlConstant(" "); - } - sb.appendEscaped(competitorFetcher.getCompetitor(object).sailID); - } - - @Override - public String getValue(T object) { - return competitorFetcher.getCompetitor(object).sailID; - } - } - - /** - * Displays net/total points and possible max-points reasons based on a {@link LeaderboardRowDTO} and a race name - * and makes the column sortable by the total points. - * - * @author Axel Uhl (D043530) - * - */ - protected abstract class RaceColumn extends ExpandableSortableColumn { - private RaceColumnDTO race; - - private final String headerStyle; - private final String columnStyle; - - public RaceColumn(RaceColumnDTO race, boolean enableExpansion, Cell cell, - SortingOrder preferredSortingOrder, String headerStyle, String columnStyle) { - super(LeaderboardPanel.this, enableExpansion, cell, preferredSortingOrder, stringMessages, - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, selectedRaceDetails); - setHorizontalAlignment(ALIGN_CENTER); - this.race = race; - this.headerStyle = headerStyle; - this.columnStyle = columnStyle; - } - - public RaceColumnDTO getRace() { - return race; - } - - public void setRace(RaceColumnDTO race) { - this.race = race; - } - - public String getRaceColumnName() { - return race.getRaceColumnName(); - } - - public boolean isMedalRace() { - return race.isMedalRace(); - } - - public boolean isLive(FleetDTO fleetDTO) { - return race.isLive(fleetDTO); - } - - @Override - public String getColumnStyle() { - return columnStyle; - } - - /** - * Displays a combination of total points and maxPointsReason in bold, transparent, strike-through, depending on - * various criteria. Here's how: - * - *

-         *                                  total points                |    maxPointsReason
-         * -------------------------------+-----------------------------+-----------------------
-         *  not discarded, no maxPoints   | bold                        | none
-         *  not discarded, maxPoints      | bold                        | transparent
-         *  discarded, no maxPoints       | transparent, strike-through | none
-         *  discarded, maxPoints          | transparent, strike-through | transparent, strike-through
-         * 
- */ - @Override - public void render(Context context, LeaderboardRowDTO object, SafeHtmlBuilder html) { - LeaderboardEntryDTO entry = object.fieldsByRaceColumnName.get(getRaceColumnName()); - if (entry != null) { - boolean isLive = isLive(entry.fleet); - - String textColor = isLive ? IS_LIVE_TEXT_COLOR : DEFAULT_TEXT_COLOR; - String totalPointsAsText = entry.totalPoints == null ? "" : scoreFormat.format(entry.totalPoints); - String netPointsAsText = entry.netPoints == null ? "" : scoreFormat.format(entry.netPoints); - - if (entry.fleet != null && entry.fleet.getColor() != null) { - html.append(raceColumnTemplate.cellFrameWithTextColorAndFleetBorder(textColor, entry.fleet.getColor().getAsHtml())); - } else { - html.append(raceColumnTemplate.cellFrameWithTextColor(textColor)); - } - - // don't show points if max points / penalty - if (entry.reasonForMaxPoints == null || entry.reasonForMaxPoints == MaxPointsReason.NONE) { - if (!entry.discarded) { - html.appendHtmlConstant(""); - html.appendHtmlConstant(totalPointsAsText); - html.appendHtmlConstant(""); - } else { - html.appendHtmlConstant(" "); - html.appendHtmlConstant(netPointsAsText); - html.appendHtmlConstant(""); - } - } else { - html.appendHtmlConstant(" "); - if (entry.discarded) { - html.appendHtmlConstant(""); - } - html.appendEscaped(entry.reasonForMaxPoints == MaxPointsReason.NONE ? "" : entry.reasonForMaxPoints.name()); - if (entry.discarded) { - html.appendHtmlConstant(""); - } - html.appendHtmlConstant(""); - } - html.appendHtmlConstant("
"); - } - } - - @Override - public InvertibleComparator getComparator() { - return new InvertibleComparatorAdapter() { - @Override - public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { - List competitorsFromBestToWorst = getLeaderboard().getCompetitorsFromBestToWorst( - race); - int o1Rank = competitorsFromBestToWorst.indexOf(o1.competitor) + 1; - int o2Rank = competitorsFromBestToWorst.indexOf(o2.competitor) + 1; - return o1Rank == 0 ? o2Rank == 0 ? 0 : isAscending() ? 1 : -1 : o2Rank == 0 ? isAscending() ? -1 - : 1 : o1Rank - o2Rank; - } - }; - } - - @Override - public String getHeaderStyle() { - return headerStyle; - } - - @Override - public Header getHeader() { - SortableExpandableColumnHeader header = new SortableExpandableColumnHeader( - /* title */race.getRaceColumnName(), - /* iconURL */race.isMedalRace() ? "/gwt/images/medal_small.png" : null, LeaderboardPanel.this, this, - stringMessages); - return header; - } - } - - public static DetailType[] getAvailableRaceDetailColumnTypes() { - return new DetailType[] { DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, - DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, - DetailType.RACE_DISTANCE_TRAVELED, - DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, - DetailType.NUMBER_OF_MANEUVERS, DetailType.DISPLAY_LEGS, DetailType.CURRENT_LEG, - DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS }; - } - - public static DetailType[] getAvailableOverallDetailColumnTypes() { - return new DetailType[] { DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, - DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, - DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS, - DetailType.TOTAL_TIME_SAILED_IN_SECONDS, DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS }; - } - - private class TextRaceColumn extends RaceColumn implements RaceNameProvider { - /** - * Remembers the leg columns; null-padded, if {@link #getLegColumn(int)} asks for a column index - * not yet existing. It is important to remember the columns because column removal happens based on identity. - */ - private final List legColumns; - - public TextRaceColumn(RaceColumnDTO race, boolean expandable, SortingOrder preferredSortingOrder, - String headerStyle, String columnStyle) { - super(race, expandable, new TextCell(), preferredSortingOrder, headerStyle, columnStyle); - legColumns = new ArrayList(); - } - - public String getValue(LeaderboardRowDTO object) { - // The following code exists only for robustness. This method should never be called because - // RaceColumn implements its own render(...) method which doesn't make use of getValue(...) - final Double totalPoints = object.fieldsByRaceColumnName.get(getRaceColumnName()).totalPoints; - return "" + (totalPoints == null ? "" : scoreFormat.format(totalPoints)); - } - - @Override - protected void ensureExpansionDataIsLoaded(final Runnable callWhenExpansionDataIsLoaded) { - if (getLeaderboard().getLegCount(getRaceColumnName()) != -1) { - callWhenExpansionDataIsLoaded.run(); - } else { - getSailingService().getLeaderboardByName(getLeaderboardName(), - timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(), - /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), - new AsyncCallback() { - @Override - public void onSuccess(LeaderboardDTO result) { - updateLeaderboard(result); - callWhenExpansionDataIsLoaded.run(); - } - - @Override - public void onFailure(Throwable caught) { - getErrorReporter().reportError( - stringMessages.errorTryingToObtainLeaderboardContents(caught.getMessage()), - true /* silentMode */); - } - }); - } - } - - @Override - protected Map> getDetailColumnMap( - LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, - String detailColumnStyle) { - Map> result = new HashMap>(); - result.put( - DetailType.RACE_DISTANCE_TRAVELED, - new FormattedDoubleLegDetailColumn(stringMessages.distanceInMeters(), "[" - + stringMessages.distanceInMetersUnit() + "]", new RaceDistanceTraveledInMeters(), - DetailType.RACE_DISTANCE_TRAVELED.getPrecision(), DetailType.RACE_DISTANCE_TRAVELED - .getDefaultSortingOrder(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); - result.put(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new FormattedDoubleLegDetailColumn( - stringMessages.averageSpeedInKnots(), "[" + stringMessages.averageSpeedInKnotsUnit() + "]", - new RaceAverageSpeedInKnots(), DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS.getPrecision(), - DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS.getDefaultSortingOrder(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); - result.put( - DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, - new FormattedDoubleLegDetailColumn(stringMessages.gapToLeaderInSeconds(), "[" - + stringMessages.gapToLeaderInSecondsUnit() + "]", new RaceGapToLeaderInSeconds(), - DetailType.RACE_GAP_TO_LEADER_IN_SECONDS.getPrecision(), - DetailType.RACE_GAP_TO_LEADER_IN_SECONDS.getDefaultSortingOrder(), LEG_COLUMN_HEADER_STYLE, - LEG_COLUMN_STYLE)); - result.put( - DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, - new FormattedDoubleLegDetailColumn(stringMessages.currentSpeedOverGroundInKnots(), "[" - + stringMessages.currentSpeedOverGroundInKnotsUnit() + "]", new CurrentSpeedOverGroundInKnots(), - DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS.getPrecision(), - DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS.getDefaultSortingOrder(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); - result.put( - DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, - new FormattedDoubleLegDetailColumn(stringMessages.windwardDistanceToLeader(), "[" - + stringMessages.distanceInMetersUnit() + "]", new RaceDistanceToLeaderInMeters(), - DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS.getPrecision(), - DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS.getDefaultSortingOrder(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); - result.put(DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS, - new FormattedDoubleLegDetailColumn(stringMessages.averageCrossTrackErrorInMeters(), "[" - + stringMessages.metersUnit() + "]", new RaceAverageCrossTrackErrorInMeters(), - DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS.getPrecision(), - DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS.getDefaultSortingOrder(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); - result.put(DetailType.NUMBER_OF_MANEUVERS, getManeuverCountRaceColumn()); - result.put(DetailType.CURRENT_LEG, - new FormattedDoubleLegDetailColumn(stringMessages.currentLeg(), "", new CurrentLeg(), - DetailType.CURRENT_LEG.getPrecision(), DetailType.CURRENT_LEG.getDefaultSortingOrder(), - LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); - - return result; - } - - private ManeuverCountRaceColumn getManeuverCountRaceColumn() { - return new ManeuverCountRaceColumn(getLeaderboardPanel(), this, stringMessages, - LeaderboardPanel.this.selectedManeuverDetails, LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, - LEG_DETAIL_COLUMN_HEADER_STYLE, LEG_DETAIL_COLUMN_STYLE); - } - - @Override - protected Iterable> getDirectChildren() { - List> result = new ArrayList>(); - for (SortableColumn column : super.getDirectChildren()) { - result.add(column); - } - if (isExpanded() && selectedRaceDetails.contains(DetailType.DISPLAY_LEGS)) { - // it is important to re-use existing LegColumn objects because - // removing the columns from the table - // is based on column identity - int legCount = getLeaderboard().getLegCount(getRaceColumnName()); - if (legCount != -1) { - for (int i = 0; i < legCount; i++) { - LegColumn legColumn = getLegColumn(i); - result.add(legColumn); - } - } else { - // the race is no longer part of the LeaderboardDTO; consider the non-null legs in legColumns: - for (LegColumn legColumn : legColumns) { - if (legColumn != null) { - result.add(legColumn); - } - } - } - } - return result; - } - - private LegColumn getLegColumn(int legNumber) { - LegColumn result; - if (legColumns.size() > legNumber && legColumns.get(legNumber) != null) { - result = legColumns.get(legNumber); - } else { - result = new LegColumn(LeaderboardPanel.this, getRaceColumnName(), legNumber, SortingOrder.ASCENDING, - stringMessages, Collections.unmodifiableList(selectedLegDetails), LEG_COLUMN_HEADER_STYLE, - LEG_COLUMN_STYLE, LEG_DETAIL_COLUMN_HEADER_STYLE, LEG_DETAIL_COLUMN_STYLE); - while (legColumns.size() <= legNumber) { - legColumns.add(null); - } - legColumns.set(legNumber, result); - } - return result; - } - - /** - * Accumulates the average speed over all legs of a race - * - * @author Axel Uhl (D043530) - */ - private class RaceAverageSpeedInKnots implements LegDetailField { - @Override - public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.legDetails != null) { - double distanceTraveledInMeters = 0; - long timeInMilliseconds = 0; - for (LegEntryDTO legDetail : fieldsForRace.legDetails) { - if (legDetail != null) { - distanceTraveledInMeters += legDetail.distanceTraveledInMeters; - timeInMilliseconds += legDetail.timeInMilliseconds; - } - } - if (timeInMilliseconds != 0) { - result = distanceTraveledInMeters / (double) timeInMilliseconds * 1000 * 3600 / 1852; - } - } - return result; - } - } - - /** - * Fetches the average cross-track error for the race - * - * @author Axel Uhl (D043530) - */ - private class RaceAverageCrossTrackErrorInMeters implements LegDetailField { - @Override - public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null) { - result = fieldsForRace.averageCrossTrackErrorInMeters; - } - return result; - } - } - - /** - * Accumulates the distance traveled over all legs of a race - * - * @author Axel Uhl (D043530) - */ - private class RaceDistanceTraveledInMeters implements LegDetailField { - @Override - public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.legDetails != null) { - for (LegEntryDTO legDetail : fieldsForRace.legDetails) { - if (legDetail != null) { - if (legDetail.distanceTraveledInMeters != null) { - if (result == null) { - result = 0.0; - } - result += legDetail.distanceTraveledInMeters; - } - } - } - } - return result; - } - } - - private class CurrentLeg implements LegDetailField { - @Override - public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.legDetails != null && !fieldsForRace.legDetails.isEmpty()) { - for (LegEntryDTO legDetail : fieldsForRace.legDetails) { - if (legDetail != null) { - if (legDetail.started) { - if (result == null) { - result = 0.0; - } - result++; - } else { - if (result != null) { - result++; - } - break; - } - } - } - } - return result; - } - } - - /** - * Computes the gap to leader exploiting the ordering of the leg detail columns - * - * @author Axel Uhl (D043530) - */ - private class RaceGapToLeaderInSeconds implements LegDetailField { - @Override - public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.legDetails != null) { - int lastLegIndex = fieldsForRace.legDetails.size() - 1; - LegEntryDTO lastLegDetail = fieldsForRace.legDetails.get(lastLegIndex); - // competitor may be in leg prior to the one the leader is in; find competitors current leg - while (lastLegDetail == null && lastLegIndex > 0) { - lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex); - } - if (lastLegDetail != null) { - result = lastLegDetail.gapToLeaderInSeconds; - } - } - return result; - } - } - - /** - * Computes the windward distance to the overall leader in meters - * - * @author Axel Uhl (D043530) - */ - private class RaceDistanceToLeaderInMeters implements LegDetailField { - @Override - public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.windwardDistanceToOverallLeaderInMeters != null) { - result = fieldsForRace.windwardDistanceToOverallLeaderInMeters; - } - return result; - } - } - - private class CurrentSpeedOverGroundInKnots implements LegDetailField { - @Override - public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.legDetails != null) { - int lastLegIndex = fieldsForRace.legDetails.size() - 1; - LegEntryDTO lastLegDetail = fieldsForRace.legDetails.get(lastLegIndex); - // competitor may be in leg prior to the one the leader is in; find competitors current leg - while (lastLegDetail == null && lastLegIndex > 0) { - lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex); - } - if (lastLegDetail != null) { - result = lastLegDetail.currentSpeedOverGroundInKnots; - } - } - return result; - } - } - } - - /** - * Displays the totals for a competitor for the entire leaderboard. - * - * @author Axel Uhl (D043530) - * - */ - private class TotalsColumn extends SortableColumn { - private final String columnStyle; - - protected TotalsColumn(String columnStyle) { - super(new TextCell(), SortingOrder.ASCENDING); - this.columnStyle = columnStyle; - setHorizontalAlignment(ALIGN_CENTER); - } - - @Override - public String getValue(LeaderboardRowDTO object) { - Double totalPoints = getLeaderboard().getTotalPoints(object); - return "" + (totalPoints == null ? "" : scoreFormat.format(totalPoints)); - } - - @Override - public void render(Context context, LeaderboardRowDTO object, SafeHtmlBuilder sb) { - String textColor = getLeaderboard().hasLiveRace() ? IS_LIVE_TEXT_COLOR : DEFAULT_TEXT_COLOR; - - sb.appendHtmlConstant(""); - sb.appendEscaped(getValue(object)); - sb.appendHtmlConstant(""); - } - - @Override - public InvertibleComparator getComparator() { - return new InvertibleComparatorAdapter() { - @Override - public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { - return getLeaderboard().competitors.indexOf(o1.competitor) - - getLeaderboard().competitors.indexOf(o2.competitor); - } - }; - } - - @Override - public String getColumnStyle() { - return columnStyle; - } - - @Override - public Header getHeader() { - return new TextHeader(stringMessages.total()); - } - } - - protected class CarryColumn extends SortableColumn { - public CarryColumn() { - super(new TextCell(), SortingOrder.ASCENDING); - setSortable(true); - } - - protected CarryColumn(EditTextCell editTextCell) { - super(editTextCell, SortingOrder.ASCENDING); - setSortable(true); - } - - @Override - public String getValue(LeaderboardRowDTO object) { - return object.carriedPoints == null ? "" : scoreFormat.format(object.carriedPoints); - } - - @Override - public InvertibleComparator getComparator() { - return new InvertibleComparatorAdapter() { - @Override - public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { - Double o1CarriedPoints = o1.carriedPoints; - if (o1CarriedPoints == null) { - o1CarriedPoints = 0.0; - } - Double o2CarriedPoints = o2.carriedPoints; - if (o2CarriedPoints == null) { - o2CarriedPoints = 0.0; - } - return o1CarriedPoints.compareTo(o2CarriedPoints); - } - }; - } - - @Override - public Header getHeader() { - return new TextHeader(stringMessages.carry()); - } - } - - private class RankColumn extends SortableColumn { - public RankColumn() { - super(new TextCell(), SortingOrder.ASCENDING); - setHorizontalAlignment(ALIGN_CENTER); - setSortable(true); - } - - @Override - public String getValue(LeaderboardRowDTO object) { - final int rank = getLeaderboard().getRank(object.competitor); - return "" + (rank == 0 ? "" : rank); - } - - @Override - public InvertibleComparator getComparator() { - return new InvertibleComparatorAdapter() { - @Override - public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { - final int rank1 = getLeaderboard().getRank(o1.competitor); - final int rank2 = getLeaderboard().getRank(o2.competitor); - return rank1 == 0 ? rank2 == 0 ? 0 : 1 : rank2 == 0 ? -1 : rank1 - rank2; - } - }; - } - - @Override - public Header getHeader() { - return new TextHeader(stringMessages.totalRank()); - } - } - - public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - LeaderboardSettings settings, CompetitorSelectionProvider competitorSelectionProvider, - String leaderboardName, ErrorReporter errorReporter, final StringMessages stringMessages, - final UserAgentDetails userAgent, boolean showRaceDetails) { - this(sailingService, asyncActionsExecutor, settings, /* preSelectedRace */null, competitorSelectionProvider, - leaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails); - } - - public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - LeaderboardSettings settings, RaceIdentifier preSelectedRace, - CompetitorSelectionProvider competitorSelectionProvider, String leaderboardName, - ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, - boolean showRaceDetails) { - this(sailingService, asyncActionsExecutor, settings, preSelectedRace, competitorSelectionProvider, new Timer( - PlayModes.Replay, /* delayBetweenAutoAdvancesInMilliseconds */3000l), leaderboardName, - errorReporter, stringMessages, userAgent, showRaceDetails, /* optionalRaceTimesInfoProvider */ null, - /* autoExpandLastRaceColumn */ false); - } - - public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - LeaderboardSettings settings, RaceIdentifier preSelectedRace, - CompetitorSelectionProvider competitorSelectionProvider, Timer timer, String leaderboardName, - ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, - boolean showRaceDetails, RaceTimesInfoProvider optionalRaceTimesInfoProvider, boolean autoExpandLastRaceColumn) { - this.showRaceDetails = showRaceDetails; - this.sailingService = sailingService; - this.asyncActionsExecutor = asyncActionsExecutor; - this.preSelectedRace = preSelectedRace; - this.competitorSelectionProvider = competitorSelectionProvider; - competitorSelectionProvider.addCompetitorSelectionChangeListener(this); - this.setLeaderboardName(leaderboardName); - this.errorReporter = errorReporter; - this.stringMessages = stringMessages; - this.selectedLegDetails = new ArrayList(); - this.selectedRaceDetails = new ArrayList(); - this.selectedOverallDetailColumns = new ArrayList(); - this.raceTimesInfoProvider = optionalRaceTimesInfoProvider; - this.selectedManeuverDetails = new ArrayList(); - this.showOverallLeaderboardsOnSamePage = settings.isShowOverallLeaderboardsOnSamePage(); - overallDetailColumnMap = createOverallDetailColumnMap(); - settingsUpdatedExplicitly = !settings.updateUponPlayStateChange(); - if (settings.getLegDetailsToShow() != null) { - selectedLegDetails.addAll(settings.getLegDetailsToShow()); - } - if (settings.getManeuverDetailsToShow() != null) { - selectedManeuverDetails.addAll(settings.getManeuverDetailsToShow()); - } - if (settings.getRaceDetailsToShow() != null) { - selectedRaceDetails.addAll(settings.getRaceDetailsToShow()); - } - if (settings.getOverallDetailsToShow() != null) { - selectedOverallDetailColumns.addAll(settings.getOverallDetailsToShow()); - } - setAutoExpandPreSelectedRace(settings.isAutoExpandPreSelectedRace()); - this.autoExpandLastRaceColumn = autoExpandLastRaceColumn; - if (settings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { - timer.setRefreshInterval(settings.getDelayBetweenAutoAdvancesInMilliseconds()); - } - this.timer = timer; - timer.addPlayStateListener(this); - timer.addTimeListener(this); - switch (settings.getActiveRaceColumnSelectionStrategy()) { - case EXPLICIT: - if (preSelectedRace == null) { - raceColumnSelection = new ExplicitRaceColumnSelection(); - } else { - raceColumnSelection = new ExplicitRaceColumnSelectionWithPreselectedRace(preSelectedRace); - } - break; - case LAST_N: - setRaceColumnSelectionToLastNStrategy(settings.getNumberOfLastRacesToShow()); - break; - } - rankColumn = new RankColumn(); - RACE_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableRaceColumnHeader(); - LEG_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegColumnHeader(); - LEG_DETAIL_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegDetailColumnHeader(); - RACE_COLUMN_STYLE = tableResources.cellTableStyle().cellTableRaceColumn(); - LEG_COLUMN_STYLE = tableResources.cellTableStyle().cellTableLegColumn(); - LEG_DETAIL_COLUMN_STYLE = tableResources.cellTableStyle().cellTableLegDetailColumn(); - TOTAL_COLUMN_STYLE = tableResources.cellTableStyle().cellTableTotalColumn(); - leaderboardTable = new SortedCellTableWithStylableHeaders( - /* pageSize */10000, tableResources); - getLeaderboardTable().setWidth("100%"); - this.userAgent = userAgent; - if (userAgent.isMobile() == UserAgentDetails.PlatformTypes.MOBILE) { - leaderboardSelectionModel = new ToggleSelectionModel(); - } else { - leaderboardSelectionModel = new MultiSelectionModel(); - } - leaderboardSelectionModel.addSelectionChangeHandler(new Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - List selection = new ArrayList(); - for (LeaderboardRowDTO row : getSelectedRows()) { - selection.add(row.competitor); - } - LeaderboardPanel.this.competitorSelectionProvider.setSelection(selection, - /* listenersNotToNotify */LeaderboardPanel.this); - } - }); - leaderboardTable.setSelectionModel(leaderboardSelectionModel); - loadCompleteLeaderboard(getLeaderboardDisplayDate()); - - if (this.preSelectedRace == null) { - isEmbedded = false; - } else { - isEmbedded = true; - } - contentPanel = new VerticalPanel(); - contentPanel.setStyleName(STYLE_LEADERBOARD_CONTENT); - overallLeaderboardsPanel = new VerticalPanel(); - overallLeaderboardPanels = new ArrayList(); - - // the information panel - informationPanel = new FlowPanel(); - informationPanel.setStyleName(STYLE_LEADERBOARD_INFO); - scoreCorrectionLastUpdateTimeLabel = new Label(""); - scoreCorrectionCommentLabel = new Label(""); - informationPanel.add(scoreCorrectionCommentLabel); - informationPanel.add(scoreCorrectionLastUpdateTimeLabel); - - liveRaceLabel = new Label(stringMessages.live()); - liveRaceLabel.setStyleName(STYLE_LEADERBOARD_LIVE_RACE); - liveRaceLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - liveRaceLabel.getElement().getStyle().setColor(IS_LIVE_TEXT_COLOR); - liveRaceLabel.setVisible(false); - informationPanel.add(liveRaceLabel); - - // the toolbar panel - DockPanel toolbarPanel = new DockPanel(); - toolbarPanel.setStyleName(STYLE_LEADERBOARD_TOOLBAR); - busyIndicator = new SimpleBusyIndicator(false, 0.8f); - if (!isEmbedded) { - toolbarPanel.add(informationPanel, DockPanel.WEST); - toolbarPanel.add(busyIndicator, DockPanel.WEST); - } - toolbarPanel.setWidth("100%"); - toolbarPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); - ClickHandler playPauseHandler = new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (LeaderboardPanel.this.timer.getPlayState() == PlayStates.Playing) { - LeaderboardPanel.this.timer.pause(); - } else { - // playing the standalone leaderboard means putting it into live mode - LeaderboardPanel.this.timer.setPlayMode(PlayModes.Live); - } - } - }; - ImageResource chartIcon = resources.chartIcon(); - ImageResource rankChartIcon = resources.rankChartIcon(); - ImageResource leaderboardSettingsIcon = resources.leaderboardSettingsIcon(); - pauseIcon = resources.pauseIcon(); - playIcon = resources.playIcon(); - refreshAndSettingsPanel = new HorizontalPanel(); - refreshAndSettingsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); - HorizontalPanel refreshPanel = new HorizontalPanel(); - refreshPanel.setSpacing(5); - refreshPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); - refreshPanel.addStyleName("refreshPanel"); - toolbarPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT); - toolbarPanel.addStyleName("refreshAndSettings"); - playPause = new Anchor(getPlayPauseImgHtml(timer.getPlayState())); - playPause.addClickHandler(playPauseHandler); - playStateChanged(timer.getPlayState(), timer.getPlayMode()); - refreshPanel.add(playPause); - Anchor rankChartsAnchor = new Anchor(AbstractImagePrototype.create(rankChartIcon).getSafeHtml()); - rankChartsAnchor.setTitle(stringMessages.showRankChart()); - rankChartsAnchor.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - showRankChartDialog(); - } - }); - Anchor chartsAnchor = new Anchor(AbstractImagePrototype.create(chartIcon).getSafeHtml()); - chartsAnchor.setTitle(stringMessages.showCharts()); - chartsAnchor.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - showCompareCompetitorsDialog(); - } - }); - Anchor settingsAnchor = new Anchor(AbstractImagePrototype.create(leaderboardSettingsIcon).getSafeHtml()); - settingsAnchor.setTitle(stringMessages.settings()); - settingsAnchor.addClickHandler(new SettingsClickHandler(stringMessages)); - refreshAndSettingsPanel.add(rankChartsAnchor); - refreshAndSettingsPanel.add(chartsAnchor); - refreshAndSettingsPanel.add(refreshPanel); - refreshAndSettingsPanel.add(settingsAnchor); - toolbarPanel.add(refreshAndSettingsPanel, DockPanel.EAST); - if (!isEmbedded) { - contentPanel.add(toolbarPanel); - } - contentPanel.add(getLeaderboardTable()); - contentPanel.add(overallLeaderboardsPanel); - setWidget(contentPanel); - raceNameForDefaultSorting = settings.getNameOfRaceToSort(); - } - - private Map> createOverallDetailColumnMap() { - Map> result = new HashMap>(); - result.put(DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, new MaxSpeedOverallColumn(stringMessages, - RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); - result.put(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, new KingOfTheUpwindColumn(stringMessages, - RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); - result.put(DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, new KingOfTheDownwindColumn(stringMessages, - RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); - result.put(DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS, new KingOfTheReachingColumn(stringMessages, - RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); - result.put(DetailType.TOTAL_TIME_SAILED_IN_SECONDS, new TotalTimeSailedColumn(stringMessages, - RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); - return result; - } - - /** - * Largely for subclasses to add more stuff to this panel, such as more toolbar buttons. - */ - protected HorizontalPanel getRefreshAndSettingsPanel() { - return refreshAndSettingsPanel; - } - - private RaceColumnDTO getRaceByName(String raceName) { - if (getLeaderboard() != null) { - for (RaceColumnDTO race : getLeaderboard().getRaceList()) { - for (FleetDTO fleet : race.getFleets()) { - if (race.getRaceIdentifier(fleet) != null - && raceName.equals(race.getRaceIdentifier(fleet).getRaceName())) { - return race; - } - } - } - } - return null; - } - - private RaceColumnDTO getRaceByColumnName(String columnName) { - if (getLeaderboard() != null) { - for (RaceColumnDTO race : getLeaderboard().getRaceList()) { - if (columnName.equals(race.getRaceColumnName())) { - return race; - } - } - } - return null; - } - - private RaceColumn getRaceColumnByRaceName(String raceName) { - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column column = getLeaderboardTable().getColumn(i); - if (column instanceof RaceColumn) { - RaceColumnDTO raceInLeaderboard = ((RaceColumn) column).getRace(); - for (FleetDTO fleet : raceInLeaderboard.getFleets()) { - final RegattaAndRaceIdentifier raceIdentifier = raceInLeaderboard.getRaceIdentifier(fleet); - if (raceIdentifier != null && raceIdentifier.getRaceName().equals(raceName)) { - return (RaceColumn) column; - } - } - } - } - return null; - } - - private RaceColumn getRaceColumnByRaceColumnName(String raceColumnName) { - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column column = getLeaderboardTable().getColumn(i); - if (column instanceof RaceColumn) { - if (((RaceColumn) column).getRaceColumnName().equals(raceColumnName)) { - return (RaceColumn) column; - } - } - } - return null; - } - - private SafeHtml getPlayPauseImgHtml(PlayStates playState) { - if (playState == PlayStates.Playing) { - return AbstractImagePrototype.create(pauseIcon).getSafeHtml(); - } else { - return AbstractImagePrototype.create(playIcon).getSafeHtml(); - } - } - - private void setDelayInMilliseconds(long delayInMilliseconds) { - timer.setLivePlayDelayInMillis(delayInMilliseconds); - } - - private boolean isAutoExpandPreSelectedRace() { - return autoExpandPreSelectedRace; - } - - private boolean isAutoExpandLastRaceColumn() { - return autoExpandLastRaceColumn; - } - - private void setAutoExpandPreSelectedRace(boolean autoExpandPreSelectedRace) { - this.autoExpandPreSelectedRace = autoExpandPreSelectedRace; - if (autoExpandPreSelectedRace) { - autoExpandPerformedOnce = false; - } - } - - /** - * The time point for which the leaderboard currently shows results. In {@link PlayModes#Replay replay mode} this is - * the {@link #timer}'s time point. In {@link PlayModes#Live live mode} the {@link #timer}'s time is quantizes to - * the closest full second to increase the likelihood of cache hits in the back end. - */ - protected Date getLeaderboardDisplayDate() { - return timer.getTime(); - } - - /** - * adds the column to the right end of the {@link #getLeaderboardTable() leaderboard table} and sets - * the column style according to the {@link SortableColumn#getColumnStyle() column's style definition}. - */ - protected void addColumn(SortableColumn column) { - leaderboardTable.addColumn(column, column.getHeader(), column.getComparator(), column - .getPreferredSortingOrder().isAscending()); - String columnStyle = column.getColumnStyle(); - if (columnStyle != null) { - getLeaderboardTable().addColumnStyleName(getLeaderboardTable().getColumnCount() - 1, columnStyle); - } - } - - protected void insertColumn(int beforeIndex, SortableColumn column) { - // remove column styles of those columns whose index will shift right by - // one: - removeColumnStyles(beforeIndex); - getLeaderboardTable().insertColumn(beforeIndex, column, column.getHeader(), column.getComparator(), - column.getPreferredSortingOrder().isAscending()); - addColumnStyles(beforeIndex); - } - - private void addColumnStyles(int startColumn) { - for (int i = startColumn; i < getLeaderboardTable().getColumnCount(); i++) { - SortableColumn columnToRemoveStyleFor = (SortableColumn) getLeaderboardTable() - .getColumn(i); - String columnStyle = columnToRemoveStyleFor.getColumnStyle(); - if (columnStyle != null) { - getLeaderboardTable().addColumnStyleName(i, columnStyle); - } - } - } - - private void removeColumnStyles(int startColumn) { - for (int i = startColumn; i < getLeaderboardTable().getColumnCount(); i++) { - SortableColumn columnToRemoveStyleFor = (SortableColumn) getLeaderboardTable() - .getColumn(i); - String columnStyle = columnToRemoveStyleFor.getColumnStyle(); - if (columnStyle != null) { - getLeaderboardTable().removeColumnStyleName(i, columnStyle); - } - } - } - - /** - * removes the column specified by columnIndex from the {@link #getLeaderboardTable() leaderboard - * table} and fixes the column styles again (see {@link #addColumnStyles(int)}). - */ - protected void removeColumn(int columnIndex) { - Column c = getLeaderboardTable().getColumn(columnIndex); - if (c instanceof ExpandableSortableColumn) { - ExpandableSortableColumn expandableColumn = (ExpandableSortableColumn) c; - if (expandableColumn.isExpanded()) { - // remove expanded child columns from the leaderboard... - expandableColumn.toggleExpansion(); - // them remember that column c was expanded: - expandableColumn.setExpanded(true); - } - } - removeColumnStyles(/* startColumn */columnIndex); - getLeaderboardTable().removeColumn(columnIndex); - addColumnStyles(/* startColumn */columnIndex); - } - - protected void removeColumn(Column c) { - int columnIndex = getLeaderboardTable().getColumnIndex(c); - if (columnIndex != -1) { - removeColumn(columnIndex); - } - } - - private void loadCompleteLeaderboard(Date date) { - if (needsDataLoading()) { - GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(sailingService, - getLeaderboardName(), timer.getPlayMode() == PlayModes.Live ? null : date, - /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), - new AsyncCallback() { - @Override - public void onSuccess(LeaderboardDTO result) { - updateLeaderboard(result); - getBusyIndicator().setBusy(false); - } - - @Override - public void onFailure(Throwable caught) { - getBusyIndicator().setBusy(false); - getErrorReporter() - .reportError("Error trying to obtain leaderboard contents: " + caught.getMessage(), - true /* silentMode */); - } - }); - asyncActionsExecutor.execute(getLeaderboardByNameAction); - } else { - getBusyIndicator().setBusy(false); - } - } - - private boolean needsDataLoading() { - return isVisible(); - } - - /** - * Determine from column expansion state which races need their leg details - */ - private Collection getNamesOfExpandedRaces() { - Collection namesOfExpandedRaces = new ArrayList(); - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column column = getLeaderboardTable().getColumn(i); - if (column instanceof RaceColumn) { - RaceColumn raceColumn = (RaceColumn) column; - if (raceColumn.isExpanded()) { - namesOfExpandedRaces.add(raceColumn.getRaceColumnName()); - } - } - } - return namesOfExpandedRaces; - } - - /** - * Assigns leaderboard to {@link #leaderboard} and updates the UI accordingly. Also updates the min/max - * values on the columns. - */ - protected void updateLeaderboard(LeaderboardDTO leaderboard) { - if (leaderboard != null) { - Collection> columnsToCollapseAndExpandAgain = getExpandedRaceColumnsWhoseDisplayedLegCountChanged(leaderboard); - for (RaceColumn columnToCollapseAndExpandAgain : columnsToCollapseAndExpandAgain) { - columnToCollapseAndExpandAgain.toggleExpansion(); - } - competitorSelectionProvider.setCompetitors(leaderboard.competitors, /* listenersNotToNotify */this); - raceColumnSelection.autoUpdateRaceColumnSelectionForUpdatedLeaderboard(getLeaderboard(), leaderboard); - setLeaderboard(leaderboard); - adjustColumnLayout(leaderboard); - updateRaceColumnDTOsToRaceColumns(leaderboard); - for (RaceColumn columnToCollapseAndExpandAgain : columnsToCollapseAndExpandAgain) { - columnToCollapseAndExpandAgain.toggleExpansion(); - } - adjustDelayToLive(); - getData().getList().clear(); - getData().getList().addAll(getRowsToDisplay(leaderboard)); - RaceColumn lastRaceColumn = null; - for (int i=getLeaderboardTable().getColumnCount()-1; i>=0; i--) { - if (getLeaderboardTable().getColumn(i) instanceof RaceColumn) { - lastRaceColumn = (RaceColumn) getLeaderboardTable().getColumn(i); - break; - } - } - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - SortableColumn c = (SortableColumn) getLeaderboardTable().getColumn(i); - c.updateMinMax(leaderboard); - // Toggle pre-selected race, if the setting is set and it isn't open yet, or the last race column if that was requested - if ((!autoExpandPerformedOnce && isAutoExpandPreSelectedRace() && c instanceof RaceColumn - && ((RaceColumn) c).getRace().hasTrackedRace(preSelectedRace)) || - (isAutoExpandLastRaceColumn() && c == lastRaceColumn)) { - ExpandableSortableColumn expandableSortableColumn = (ExpandableSortableColumn) c; - if (!expandableSortableColumn.isExpanded()) { - expandableSortableColumn.toggleExpansion(); - autoExpandPerformedOnce = true; - } - } - } - if (leaderboardTable.getCurrentlySortedColumn() != null) { - leaderboardTable.sort(); - } else { - SortableColumn columnToSortFor = getDefaultSortColumn(); - leaderboardTable.sortColumn(columnToSortFor, columnToSortFor.getPreferredSortingOrder().isAscending()); - } - // Reselect the selected rows - clearSelection(); - for (LeaderboardRowDTO row : getLeaderboardTable().getDataProvider().getList()) { - if (competitorSelectionProvider.isSelected(row.competitor)) { - leaderboardSelectionModel.setSelected(row, true); - } - } - - scoreCorrectionCommentLabel.setText(leaderboard.getComment() != null ? leaderboard.getComment() : ""); - if (leaderboard.getTimePointOfLastCorrectionsValidity() != null) { - Date lastCorrectionDate = leaderboard.getTimePointOfLastCorrectionsValidity(); - String lastUpdate = dateFormatter.format(lastCorrectionDate) + ", " - + timeFormatter.format(lastCorrectionDate); - scoreCorrectionLastUpdateTimeLabel.setText(stringMessages.lastScoreUpdate() + ": " + lastUpdate); - } else { - scoreCorrectionLastUpdateTimeLabel.setText(""); - } - - List> liveRaces = leaderboard.getLiveRaces(); - boolean hasLiveRace = !liveRaces.isEmpty(); - if (hasLiveRace) { - String liveRaceText = ""; - if(liveRaces.size() == 1) { - Pair liveRace = liveRaces.get(0); - liveRaceText = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'"); - } else { - String raceNames = ""; - for(Pair liveRace: liveRaces) { - raceNames += "'" + liveRace.getA().getRaceColumnName() + "', "; - } - // remove last ", " - raceNames = raceNames.substring(0, raceNames.length()-2); - liveRaceText = stringMessages.racesAreLive(raceNames); - } - liveRaceLabel.setText(liveRaceText); - } else { - liveRaceLabel.setText(""); - } - scoreCorrectionLastUpdateTimeLabel.setVisible(!hasLiveRace); - liveRaceLabel.setVisible(hasLiveRace); - } - } - - /** - * The race columns hold a now outdated copy of a {@link RaceColumnDTO} which needs to be updated from the {@link LeaderboardDTO} just received - */ - private void updateRaceColumnDTOsToRaceColumns(LeaderboardDTO leaderboard) { - for (RaceColumnDTO newRace : leaderboard.getRaceList()) { - RaceColumn raceColumn = getRaceColumnByRaceColumnName(newRace.name); - if (raceColumn != null) { - raceColumn.setRace(newRace); - } - } - } - - /** - * Due to a course change, a race may change its number of legs. All expanded race columns that show leg columns and - * whose leg count changed need to be collapsed before the leaderboard is replaced, and expanded afterwards again. - * Race columns whose toggling is {@link ExpandableSortableColumn#isTogglingInProcess() currently in progress} are - * not considered because their new state will be considered after replacing anyhow. - * - * @param newLeaderboard - * the new leaderboard before assigning to {@link #leaderboard} - * @return the columns that were collapsed in this step and that shall be expanded again after the leaderboard has - * been replaced - */ - private Collection> getExpandedRaceColumnsWhoseDisplayedLegCountChanged(LeaderboardDTO newLeaderboard) { - Set> result = new HashSet>(); - if (selectedRaceDetails.contains(DetailType.DISPLAY_LEGS)) { - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column c = getLeaderboardTable().getColumn(i); - if (c instanceof RaceColumn) { - RaceColumn rc = (RaceColumn) c; - // If the new leaderboard no longer contains the column, getLegCount will return -1, causing the - // column - // to be collapsed if it was expanded. This is correct because otherwise, removing it would no - // longer - // know the correct leg count. - if (!rc.isTogglingInProcess() && rc.isExpanded()) { - int oldLegCount = getLeaderboard().getLegCount(rc.getRaceColumnName()); - int newLegCount = newLeaderboard.getLegCount(rc.getRaceColumnName()); - if (oldLegCount != newLegCount) { - result.add(rc); - } - } - } - } - } - return result; - } - - /** - * Based on the {@link LeaderboardDTO#getDelayToLiveInMillisForLatestRace()} for the race that has the latest - * {@link RaceColumnDTO#getStartDate(FleetDTO) start time}, if no {@link #settingsUpdatedExplicitly explicit setting - * changes} were performed, automatically adjusts the delay accordingly. - */ - private void adjustDelayToLive() { - if (!settingsUpdatedExplicitly && leaderboard.getDelayToLiveInMillisForLatestRace() != null) { - setDelayInMilliseconds(leaderboard.getDelayToLiveInMillisForLatestRace()); - } - } - - /** - * Extracts the rows to display of the leaderboard. These are all {@link AbstractLeaderboardDTO#rows - * rows} in case {@link #preSelectedRace} is null, or only the rows of the competitors who scored in - * the race identified by {@link #preSelectedRace} otherwise. - */ - private Collection getRowsToDisplay(LeaderboardDTO leaderboard) { - Collection result; - if (preSelectedRace == null) { - result = leaderboard.rows.values(); - } else { - result = new ArrayList(); - for (CompetitorDTO competitorInPreSelectedRace : getCompetitors(preSelectedRace)) { - result.add(leaderboard.rows.get(competitorInPreSelectedRace)); - } - } - return result; - } - - /** - * The {@link LeaderboardDTO} holds {@link LeaderboardDTO#getRaceList() races} as {@link RaceColumnDTO} objects. - * Those map their fleet names to the {@link RegattaAndRaceIdentifier} which identifies the tracked race - * representing the fleet race within the race column. - *

- * - * On the other hand, a {@link LeaderboardRowDTO} has {@link LeaderboardEntryDTO}, keyed by race column name. The - * entry DTOs, in turn, store the {@link RaceIdentifier} of the race in which the respective competitor achieved the - * score. - *

- * - * With this information it is possible to identify the competitors who participated in a particular tracked race, - * as identified through the race parameter. - * - * @return all competitors for which the {@link #getLeaderboard() leaderboard} has an entry whose - * {@link LeaderboardEntryDTO#race} equals race - */ - private Iterable getCompetitors(RaceIdentifier race) { - Set result = new HashSet(); - for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) { - if (raceColumn.hasTrackedRace(race)) { - for (Map.Entry e : getLeaderboard().rows.entrySet()) { - LeaderboardEntryDTO entry = e.getValue().fieldsByRaceColumnName.get(raceColumn.getRaceColumnName()); - if (entry != null && entry.race != null && entry.race.equals(race)) { - result.add(e.getKey()); - } - } - } - } - return result; - } - - private SortableColumn getDefaultSortColumn() { - SortableColumn defaultSortColumn = null; - if (raceNameForDefaultSorting != null) { - defaultSortColumn = getRaceColumnByRaceName(raceNameForDefaultSorting); - } - if (defaultSortColumn == null) { - defaultSortColumn = getRankColumn(); - } - return defaultSortColumn; - } - - private void clearSelection() { - for (LeaderboardRowDTO row : getData().getList()) { - leaderboardSelectionModel.setSelected(row, false); - } - } - - private RankColumn getRankColumn() { - return rankColumn; - } - - protected void setLeaderboard(LeaderboardDTO leaderboard) { - this.leaderboard = leaderboard; - } - - @Override - public LeaderboardDTO getLeaderboard() { - return leaderboard; - } - - private void adjustColumnLayout(LeaderboardDTO leaderboard) { - ensureRankColumn(); - ensureSailIDAndCompetitorColumn(); - boolean hasCarryColumn = updateCarryColumn(leaderboard); - adjustOverallDetailColumns(leaderboard, hasCarryColumn?CARRY_COLUMN_INDEX+1:CARRY_COLUMN_INDEX); - // first remove race columns no longer needed: - removeUnusedRaceColumns(leaderboard); - if (leaderboard != null) { - createMissingAndAdjustExistingRaceColumns(leaderboard); - ensureTotalsColumn(); - } - } - - /** - * Ensures that the columns requested by {@link #selectedOverallDetailColumns} are in the table. Assumes that if there are - * any existing overall details columns, they start at indexOfFirstOverallDetailsColumn and are in the order - * defined by {@link #getAvailableOverallDetailColumnTypes()}. - * - * @param indexOfFirstOverallDetailsColumn - * tells the column index for the first overall details column - */ - private void adjustOverallDetailColumns(LeaderboardDTO leaderboard, int indexOfFirstOverallDetailsColumn) { - List> overallDetailColumnsToShow = new ArrayList>(); - // ensure the ordering in overallDetailColumnsToShow conforms to the ordering of getAvailableOverallDetailColumnTypes() - for (DetailType overallDetailType : getAvailableOverallDetailColumnTypes()) { - if (selectedOverallDetailColumns.contains(overallDetailType)) { - overallDetailColumnsToShow.add(overallDetailColumnMap.get(overallDetailType)); - } - } - int currentColumnIndex = indexOfFirstOverallDetailsColumn; - int i = 0; // index into overallDetailColumnToShow - Column currentColumn = currentColumnIndex < getLeaderboardTable().getColumnCount() ? - getLeaderboardTable().getColumn(currentColumnIndex) : null; - // repeat until no more column to check for removal and no more column left to check for need to insert - while (iraceColumn - */ - private void correctColumnData(RaceColumn raceColumn, RaceColumnDTO race) { - int columnIndex = getRaceColumnPosition(raceColumn); - if (raceColumn.isExpansionEnabled() != race.hasTrackedRaces() || race.isMedalRace() != raceColumn.isMedalRace()) { - if (raceColumn.isExpanded()) { - raceColumn.toggleExpansion(); // remove children from table - } - removeColumn(columnIndex); - insertColumn(columnIndex, createRaceColumn(race)); - } - } - - /** - * Removes all RaceColumns, starting at count {@link raceColumnStartIndex raceColumnStartIndex} - * - * @param raceColumnStartIndex - * The index of the race column should be deleted from. - * @param raceName - * The name of the racing column until the table should be cleared. - */ - private void removeRaceColumnFromRaceColumnStartIndexBeforeRace(int raceColumnStartIndex, RaceColumnDTO race) { - int counter = 0; - for (int leaderboardposition = 0; leaderboardposition < getLeaderboardTable().getColumnCount(); leaderboardposition++) { - Column c = getLeaderboardTable().getColumn(leaderboardposition); - if (c instanceof RaceColumn) { - RaceColumn raceColumn = (RaceColumn) c; - if (!raceColumn.getRaceColumnName().equals(race.getRaceColumnName())) { - if (raceColumnStartIndex == counter) { - removeColumn(raceColumn); - } - } else { - return; - } - counter++; - } - } - } - - /** - * Gets a ColumnPosition of a raceColumn - * - * @param raceColumn - * The column for which the position is to be found in the leaderboard table - * @return the position. Returns -1 if raceColumn not existing in leaderboardTable. - */ - private int getRaceColumnPosition(RaceColumn raceColumn) { - for (int leaderboardposition = 0; leaderboardposition < getLeaderboardTable().getColumnCount(); leaderboardposition++) { - Column c = getLeaderboardTable().getColumn(leaderboardposition); - if (c instanceof RaceColumn) { - RaceColumn rc = (RaceColumn) c; - if (rc.equals(raceColumn)) { - return leaderboardposition; - } - } - } - return -1; - } - - /** - * This method returns the position where a race column should get inserted. - * - * @param raceName - * the name of the race to insert - * @param listpos - * the position of the race in the {@link selectedRaceColumns selectedRaceColumns} - * @return the position of a race column right before which to insert the race column so that it is the - * listposth race in the table or -1 if no such race column was found, e.g., in case the column - * needs to be inserted as the last race in the table - */ - private int getColumnPositionToInsert(RaceColumnDTO race, int listpos) { - int raceColumnCounter = 0; - int noRaceColumnCounter = 0; - boolean raceColumnFound = false; - for (int leaderboardPosition = 0; !raceColumnFound - && leaderboardPosition < getLeaderboardTable().getColumnCount(); leaderboardPosition++) { - Column c = getLeaderboardTable().getColumn(leaderboardPosition); - if (c instanceof RaceColumn) { - // RaceColumn raceColumn = (RaceColumn) c; - if (raceColumnCounter == listpos) { - raceColumnFound = true; - } else { - raceColumnCounter++; - } - } else { - noRaceColumnCounter++; - } - } - if (raceColumnFound) { - return raceColumnCounter + noRaceColumnCounter; - } else { - return -1; - } - } - - /** - * Removes all columns of type {@link RaceColumn} from the leaderboard table if their name is not in the list of - * names of the selectedRaceColumns. - */ - private void removeRaceColumnsNotSelected(Iterable selectedRaceColumns) { - Set selectedRaceColumnNames = new HashSet(); - for (RaceColumnDTO selectedRaceColumn : selectedRaceColumns) { - selectedRaceColumnNames.add(selectedRaceColumn.getRaceColumnName()); - } - List> columnsToRemove = new ArrayList>(); - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column c = getLeaderboardTable().getColumn(i); - if (c instanceof RaceColumn - && (leaderboard == null || !selectedRaceColumnNames.contains(((RaceColumn) c) - .getRaceColumnName()))) { - columnsToRemove.add(c); - } - } - for (Column c : columnsToRemove) { - removeColumn(c); - } - } - - /** - * Existing and matching race columns may still need to be removed, re-created and inserted because the "tracked" - * property may have changed, changing the columns expandability. - */ - private void createMissingAndAdjustExistingRaceColumns(LeaderboardDTO leaderboard) { - // Correct order of races in selectedRaceColum - Iterable correctedOrderSelectedRaces = raceColumnSelection.getSelectedRaceColumnsOrderedAsInLeaderboard(leaderboard); - removeRaceColumnsNotSelected(correctedOrderSelectedRaces); - for (int selectedRaceCount = 0; selectedRaceCount < Util.size(correctedOrderSelectedRaces); selectedRaceCount++) { - RaceColumnDTO selectedRaceColumn = Util.get(correctedOrderSelectedRaces, selectedRaceCount); - final RaceColumn raceColumn = selectedRaceColumn == null ? null - : getRaceColumnByRaceColumnName(selectedRaceColumn.name); - if (raceColumn != null) { - // remove all raceColumns, starting at a specific selectedRaceCount, up to but excluding the selected - // raceName with the result that selectedRace is at position selectedRaceCount afterwards - removeRaceColumnFromRaceColumnStartIndexBeforeRace(selectedRaceCount, selectedRaceColumn); - correctColumnData(raceColumn, selectedRaceColumn); - } else { - // get correct position to insert the column - int positionToInsert = getColumnPositionToInsert(selectedRaceColumn, selectedRaceCount); - if (positionToInsert != -1) { - insertColumn(positionToInsert, createRaceColumn(selectedRaceColumn)); - } else { - // Add the raceColumn with addRaceColumn, if no RaceColumn is existing in leaderboard - addRaceColumn(createRaceColumn(selectedRaceColumn)); - } - } - } - } - - protected RaceColumn createRaceColumn(RaceColumnDTO raceInLeaderboard) { - TextRaceColumn textRaceColumn = new TextRaceColumn(raceInLeaderboard, shallExpandRaceColumn(raceInLeaderboard), - SortingOrder.ASCENDING, RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE); - return textRaceColumn; - } - - private boolean shallExpandRaceColumn(RaceColumnDTO raceColumnDTO) { - return showRaceDetails && raceColumnDTO.hasTrackedRaces(); - } - - private void removeUnusedRaceColumns(LeaderboardDTO leaderboard) { - List> columnsToRemove = new ArrayList>(); - for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { - Column c = getLeaderboardTable().getColumn(i); - if (c instanceof RaceColumn - && (leaderboard == null || !leaderboard.raceListContains(((RaceColumn) c).getRaceColumnName()))) { - columnsToRemove.add(c); - } - } - // Tricky issue: if the race column is currently expanded, we can't know anymore how many detail columns - // there are because the updated LeaderboardDTO object doesn't contain the race anymore. We have to - // collapse and remove all LegColumns following the RaceColumn - for (Column c : columnsToRemove) { - removeColumn(c); - } - } - - /** - * If the last column is the totals column, remove it. Add the race column as the last column. - */ - private void addRaceColumn(RaceColumn raceColumn) { - if (getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalsColumn) { - removeColumn(getLeaderboardTable().getColumnCount() - 1); - } - addColumn(raceColumn); - } - - private void ensureRankColumn() { - if (getLeaderboardTable().getColumnCount() == RANK_COLUMN_INDEX) { - addColumn(getDefaultSortColumn()); - } else { - if (!(getLeaderboardTable().getColumn(RANK_COLUMN_INDEX) instanceof RankColumn)) { - throw new RuntimeException("The first column must always be the rank column but it was of type " - + getLeaderboardTable().getColumn(RANK_COLUMN_INDEX).getClass().getName()); - } - } - } - - private void ensureSailIDAndCompetitorColumn() { - if (getLeaderboardTable().getColumnCount() <= SAIL_ID_COLUMN_INDEX) { - addColumn(new SailIDColumn(new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; - } - })); - addColumn(createCompetitorColumn()); - } else { - if (!(getLeaderboardTable().getColumn(SAIL_ID_COLUMN_INDEX) instanceof SailIDColumn)) { - throw new RuntimeException("The second column must always be the sail ID column but it was of type " - + getLeaderboardTable().getColumn(SAIL_ID_COLUMN_INDEX).getClass().getName()); - } - } - } - - protected CompetitorColumn createCompetitorColumn() { - return new CompetitorColumn(new CompetitorColumnBase(this, getStringMessages(), - new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; - } - })); - } - - private void ensureTotalsColumn() { - // add a totals column on the right - if (getLeaderboardTable().getColumnCount() == 0 - || !(getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalsColumn)) { - addColumn(new TotalsColumn(TOTAL_COLUMN_STYLE)); - } - } - - /** - * If the leaderboard {@link LeaderboardDTO#hasCarriedPoints has carried points} and if column #1 - * (second column, right of the competitor column) does not exist or is not of type {@link CarryColumn}, all columns - * starting from #1 will be removed and a {@link CarryColumn} will be added. If the leaderboard has no carried - * points but the display still shows a carry column, the column is removed. - * - * @return true if a carry column is now part of the table (regardless of whether it existed before), - * false otherwise - */ - protected boolean updateCarryColumn(LeaderboardDTO leaderboard) { - final boolean needsCarryColumn = leaderboard != null && leaderboard.hasCarriedPoints; - if (needsCarryColumn) { - ensureCarryColumn(); - } else { - ensureNoCarryColumn(); - } - return needsCarryColumn; - } - - private void ensureNoCarryColumn() { - if (getLeaderboardTable().getColumnCount() > CARRY_COLUMN_INDEX - && getLeaderboardTable().getColumn(CARRY_COLUMN_INDEX) instanceof CarryColumn) { - removeColumn(CARRY_COLUMN_INDEX); - } - } - - protected void ensureCarryColumn() { - if (getLeaderboardTable().getColumnCount() <= CARRY_COLUMN_INDEX - || !(getLeaderboardTable().getColumn(CARRY_COLUMN_INDEX) instanceof CarryColumn)) { - while (getLeaderboardTable().getColumnCount() > CARRY_COLUMN_INDEX) { - removeColumn(CARRY_COLUMN_INDEX); - } - addColumn(createCarryColumn()); - } - } - - protected CarryColumn createCarryColumn() { - return new CarryColumn(); - } - - protected SortedCellTable getLeaderboardTable() { - return leaderboardTable; - } - - protected SailingServiceAsync getSailingService() { - return sailingService; - } - - protected String getLeaderboardName() { - return leaderboardName; - } - - protected void setLeaderboardName(String leaderboardName) { - this.leaderboardName = leaderboardName; - } - - protected ErrorReporter getErrorReporter() { - return errorReporter; - } - - protected ListDataProvider getData() { - return getLeaderboardTable().getDataProvider(); - } - - @Override - public void timeChanged(Date date) { - loadCompleteLeaderboard(getLeaderboardDisplayDate()); - } - - @Override - public void playStateChanged(PlayStates playState, PlayModes playMode) { - currentlyHandlingPlayStateChange = true; - playPause.setHTML(getPlayPauseImgHtml(playState)); - playPause.setTitle(playState == PlayStates.Playing ? stringMessages.pauseAutomaticRefresh() : stringMessages - .autoRefresh()); - if (!settingsUpdatedExplicitly && playMode != oldPlayMode) { - // if settings weren't explicitly modified, auto-switch to live mode settings and sort for - // any pre-selected race; we need to copy the previously selected race columns to the new RaceColumnSelection - updateSettings(LeaderboardSettingsFactory.getInstance().createNewSettingsForPlayMode( - playMode, - /* don't touch columnToSort if no race was pre-selected */ preSelectedRace == null ? null - : preSelectedRace.getRaceName(), - /* don't change nameOfRaceColumnToShow */null, - /* set nameOfRaceToShow if race was pre-selected */preSelectedRace == null ? null : preSelectedRace - .getRaceName(), getRaceColumnSelection(), showOverallLeaderboardsOnSamePage)); - } - currentlyHandlingPlayStateChange = false; - oldPlayMode = playMode; - } - - private void showCompareCompetitorsDialog() { - int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); - if (selectedCompetitorsCount < 1) { - Window.alert(stringMessages.selectAtLeastOneCompetitor()); - } else { - List races = getTrackedRacesIdentifiers(); - RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, errorReporter, races, 5000l /* requestInterval*/); - CompareCompetitorsChartDialog chartDialog = new CompareCompetitorsChartDialog(sailingService, races, raceTimesInfoProvider, - competitorSelectionProvider, timer, stringMessages, errorReporter); - chartDialog.show(); - } - } - - private void showRankChartDialog() { - RankChartDialog chartDialog = new RankChartDialog(sailingService, leaderboardName, competitorSelectionProvider, - timer, stringMessages, errorReporter, /* compactChart */ false); - chartDialog.show(); - } - - private List getTrackedRacesIdentifiers() { - List result = new ArrayList(); - for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) { - for (FleetDTO fleet : raceColumn.getFleets()) { - if (raceColumn.getRaceIdentifier(fleet) != null) { - result.add(raceColumn.getRaceIdentifier(fleet)); - } - } - } - return result; - } - - @Override - public boolean hasSettings() { - return true; - } - - @Override - public boolean hasToolbar() { - return false; - } - - @Override - public SettingsDialogComponent getSettingsDialogComponent() { - return new LeaderboardSettingsDialogComponent(Collections.unmodifiableList(selectedManeuverDetails), - Collections.unmodifiableList(selectedLegDetails), Collections.unmodifiableList(selectedRaceDetails), - Collections.unmodifiableList(selectedOverallDetailColumns), /* All races to select */ leaderboard.getRaceList(), - raceColumnSelection.getSelectedRaceColumnsOrderedAsInLeaderboard(leaderboard), raceColumnSelection, autoExpandPreSelectedRace, - showOverallLeaderboardsOnSamePage, timer.getRefreshInterval(), timer.getLivePlayDelayInMillis(), stringMessages); - } - - @Override - public String getLocalizedShortName() { - return stringMessages.leaderboard(); - } - - private LeaderboardRowDTO getRow(CompetitorDTO competitor) { - for (LeaderboardRowDTO row : getData().getList()) { - if (row.competitor.equals(competitor)) { - return row; - } - } - return null; - } - - @Override - public void addedToSelection(CompetitorDTO competitor) { - LeaderboardRowDTO row = getRow(competitor); - if (row != null) { - leaderboardSelectionModel.setSelected(row, true); - } - } - - @Override - public void removedFromSelection(CompetitorDTO competitor) { - LeaderboardRowDTO row = getRow(competitor); - if (row != null) { - leaderboardSelectionModel.setSelected(row, false); - } - } - - @Override - public BusyIndicator getBusyIndicator() { - return busyIndicator; - } - - @Override - public boolean hasBusyIndicator() { - return true; - } - - private Iterable getSelectedRows() { - ArrayList selectedRows = new ArrayList(); - for (LeaderboardRowDTO row : getData().getList()) { - if (leaderboardSelectionModel.isSelected(row)) { - selectedRows.add(row); - } - } - return selectedRows; - } - - @Override - public void competitorsListChanged(Iterable competitors) { - timeChanged(timer.getTime()); - } - - public RaceColumnSelection getRaceColumnSelection() { - return raceColumnSelection; - } -} +package com.sap.sailing.gwt.ui.leaderboard; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +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.cell.client.Cell; +import com.google.gwt.cell.client.Cell.Context; +import com.google.gwt.cell.client.CompositeCell; +import com.google.gwt.cell.client.EditTextCell; +import com.google.gwt.cell.client.TextCell; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.FontWeight; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.safehtml.client.SafeHtmlTemplates; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.Header; +import com.google.gwt.user.cellview.client.TextHeader; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.AbstractImagePrototype; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.DockPanel; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.HasHorizontalAlignment; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.ImageResourceRenderer; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.MultiSelectionModel; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SelectionChangeEvent.Handler; +import com.google.gwt.view.client.SelectionModel; +import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.InvertibleComparator; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.SortingOrder; +import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; +import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; +import com.sap.sailing.gwt.ui.actions.GetLeaderboardByNameAction; +import com.sap.sailing.gwt.ui.client.Collator; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.FlagImageResolver; +import com.sap.sailing.gwt.ui.client.PlayStateListener; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.TimeListener; +import com.sap.sailing.gwt.ui.client.Timer; +import com.sap.sailing.gwt.ui.client.Timer.PlayModes; +import com.sap.sailing.gwt.ui.client.Timer.PlayStates; +import com.sap.sailing.gwt.ui.client.UserAgentDetails; +import com.sap.sailing.gwt.ui.leaderboard.LegDetailColumn.LegDetailField; +import com.sap.sailing.gwt.ui.shared.AbstractLeaderboardDTO; +import com.sap.sailing.gwt.ui.shared.CompetitorDTO; +import com.sap.sailing.gwt.ui.shared.FleetDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO; +import com.sap.sailing.gwt.ui.shared.LegEntryDTO; +import com.sap.sailing.gwt.ui.shared.RaceColumnDTO; +import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; +import com.sap.sailing.gwt.ui.shared.components.Component; +import com.sap.sailing.gwt.ui.shared.components.IsEmbeddableComponent; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.shared.panels.BusyIndicator; +import com.sap.sailing.gwt.ui.shared.panels.SimpleBusyIndicator; + +/** + * A leaderboard essentially consists of a table widget that in its columns displays the entries. + * + * @author Axel Uhl (D043530) + * + */ +public class LeaderboardPanel extends FormPanel implements TimeListener, PlayStateListener, + Component, IsEmbeddableComponent, CompetitorSelectionChangeListener, LeaderboardFetcher { + private static final int RANK_COLUMN_INDEX = 0; + + private static final int SAIL_ID_COLUMN_INDEX = 1; + + private static final int CARRY_COLUMN_INDEX = 3; + + protected static final NumberFormat scoreFormat = NumberFormat.getFormat("0.00"); + + private final SailingServiceAsync sailingService; + + private static String IS_LIVE_TEXT_COLOR = "#1876B3"; + private static String DEFAULT_TEXT_COLOR = "#000000"; + + private static final String STYLE_LEADERBOARD_CONTENT = "leaderboardContent"; + private static final String STYLE_LEADERBOARD_INFO = "leaderboardInfo"; + private static final String STYLE_LEADERBOARD_TOOLBAR = "leaderboardContent-toolbar"; + private static final String STYLE_LEADERBOARD_LIVE_RACE = "leaderboardContent-liverace"; + + interface RaceColumnTemplates extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("

") + SafeHtml cellFrameWithTextColorAndFleetBorder(String textColor, String borderStyle); + + @SafeHtmlTemplates.Template("
") + SafeHtml cellFrameWithTextColor(String textColor); + } + + private static RaceColumnTemplates raceColumnTemplate = GWT.create(RaceColumnTemplates.class); + + /** + * The leaderboard name is used to + * {@link SailingServiceAsync#getLeaderboardByName(String, java.util.Date, String[], com.google.gwt.user.client.rpc.AsyncCallback) + * obtain the leaderboard contents} from the server. It may change in case the leaderboard is renamed. + */ + private String leaderboardName; + + private final ErrorReporter errorReporter; + + private final StringMessages stringMessages; + + private final SortedCellTable leaderboardTable; + + private final SelectionModel leaderboardSelectionModel; + + private LeaderboardDTO leaderboard; + + private final RankColumn rankColumn; + + /** + * Passed to the {@link ManeuverCountRaceColumn}. Modifications to this list will modify the column's children list + * when updated the next time. + */ + private final List selectedManeuverDetails; + + /** + * Passed to the {@link LegColumn}. Modifications to this list will modify the column's children list when updated + * the next time. + */ + private final List selectedLegDetails; + + /** + * Passed to the {@link TextRaceColumn}. Modifications to this list will modify the column's children list when + * updated the next time. + */ + private final List selectedRaceDetails; + + private final List selectedOverallDetailColumns; + + private final Map> overallDetailColumnMap; + + private RaceColumnSelection raceColumnSelection; + + protected final String RACE_COLUMN_HEADER_STYLE; + + protected final String LEG_DETAIL_COLUMN_HEADER_STYLE; + + protected final String LEG_DETAIL_COLUMN_STYLE; + + protected final String LEG_COLUMN_HEADER_STYLE; + + protected final String RACE_COLUMN_STYLE; + + protected final String LEG_COLUMN_STYLE; + + protected final String TOTAL_COLUMN_STYLE; + + public static final String LEADERBOARD_MARGIN_STYLE = "leaderboardMargin"; + + private final Timer timer; + + private boolean autoExpandPreSelectedRace; + + private boolean autoExpandLastRaceColumn; + + private boolean showOverallLeaderboardsOnSamePage; + + /** + * Remembers whether the auto-expand of the pre-selected race (see {@link #autoExpandPreSelectedRace}) or last + * selected race {@link #autoExpandLastRaceColumn} has been performed once. It must not be performed another time. + */ + private boolean autoExpandPerformedOnce; + + /** + * This anchor's HTML holds the image tag for the play/pause button that needs to be updated when the {@link #timer} + * changes its playing state + */ + private final Anchor playPause; + + private final CompetitorSelectionProvider competitorSelectionProvider; + + /** + * If this is null, all leaderboard columns added by updating the leaderboard from the server are + * automatically added to the table. Otherwise, only the column whose + * {@link RaceColumnDTO#getRaceIdentifier(String) race identifier} matches the value of this attribute will be + * added. + */ + private final RaceIdentifier preSelectedRace; + + private final VerticalPanel contentPanel; + + /** + * Used to display one or more overall leaderboards if selected. See also + * {@link LeaderboardSettings#isShowOverallLeaderboardsOnSamePage()}. Always contained by the {@link #contentPanel} at the + * right position, but may be empty. + */ + private final VerticalPanel overallLeaderboardsPanel; + + /** + * The leaderboard panels displayed in {@link #overallLeaderboardsPanel}. + */ + private final List overallLeaderboardPanels; + + private final HorizontalPanel refreshAndSettingsPanel; + + private final FlowPanel informationPanel; + private final Label scoreCorrectionLastUpdateTimeLabel; + private final Label scoreCorrectionCommentLabel; + private final Label liveRaceLabel; + + private final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_LONG); + private final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss"); + + private boolean isEmbedded = false; + + private static LeaderboardResources resources = GWT.create(LeaderboardResources.class); + private static LeaderboardTableResources tableResources = GWT.create(LeaderboardTableResources.class); + + private final ImageResource pauseIcon; + private final ImageResource playIcon; + + private final BusyIndicator busyIndicator; + + /** + * Tells whether the leaderboard settings were explicitly changed by an external call to + * {@link #updateSettings(LeaderboardSettings)}. If so, a {@link #playStateChanged(PlayStates, PlayModes) play state + * change} will not automatically lead to a settings change. + */ + private boolean settingsUpdatedExplicitly = false; + + /** + * Tells if the leaderboard is currently handling a {@link #playStateChanged(PlayStates, PlayModes) play state + * change}. If this is the case, a call to {@link #updateSettings(LeaderboardSettings)} won't set the + * {@link #settingsUpdatedExplicitly} flag. + */ + private boolean currentlyHandlingPlayStateChange; + + private PlayModes oldPlayMode; + + private final AsyncActionsExecutor asyncActionsExecutor; + + /** + * See also {@link #getDefaultSortColumn()}. If no other column is explicitly selected for sorting and this + * attribute holds a non-null string identifying a valid race by name that is represented in this + * leaderboard panel then sort by it. Otherwise, default sorting will default to the overall rank column. + */ + private String raceNameForDefaultSorting; + + /** + * Can be used to disallow users to drill into the race details. + */ + private final boolean showRaceDetails; + + /** + * The {@link LastNRacesColumnSelection} column selection strategy requires a {@link RaceTimesInfoProvider}. This can either be injected + * by passing a non-null object of that type to the constructor, or such an object is created and remembered in this + * attribute when required the first time. + */ + private RaceTimesInfoProvider raceTimesInfoProvider; + + private final UserAgentDetails userAgent; + + private class SettingsClickHandler implements ClickHandler { + private final StringMessages stringMessages; + + private SettingsClickHandler(StringMessages stringMessages) { + this.stringMessages = stringMessages; + } + + @Override + public void onClick(ClickEvent event) { + new SettingsDialog(LeaderboardPanel.this, stringMessages).show(); + } + } + + protected StringMessages getStringMessages() { + return stringMessages; + } + + @Override + public Widget getEntryWidget() { + return this; + } + + @Override + public Widget getHeaderWidget() { + return null; + } + + @Override + public Widget getContentWidget() { + return contentPanel; + } + + @Override + public Widget getToolbarWidget() { + return null; + } + + @Override + public Widget getLegendWidget() { + return null; + } + + @Override + public boolean isEmbedded() { + return isEmbedded; + } + + protected VerticalPanel getContentPanel() { + return contentPanel; + } + + public void updateSettings(LeaderboardSettings newSettings) { + if (!newSettings.updateUponPlayStateChange() || !currentlyHandlingPlayStateChange) { + settingsUpdatedExplicitly = true; + } + List> columnsToExpandAgain = new ArrayList>(); + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column c = getLeaderboardTable().getColumn(i); + if (c instanceof ExpandableSortableColumn) { + ExpandableSortableColumn expandableSortableColumn = (ExpandableSortableColumn) c; + if (expandableSortableColumn.isExpanded()) { + // now toggle expansion back and forth, + // enforcing a re-build of the visible + // child columns + expandableSortableColumn.toggleExpansion(); + columnsToExpandAgain.add(expandableSortableColumn); + } + } + } + if (newSettings.getManeuverDetailsToShow() != null) { + selectedManeuverDetails.clear(); + selectedManeuverDetails.addAll(newSettings.getManeuverDetailsToShow()); + } + if (newSettings.getLegDetailsToShow() != null) { + selectedLegDetails.clear(); + selectedLegDetails.addAll(newSettings.getLegDetailsToShow()); + } + if (newSettings.getRaceDetailsToShow() != null) { + selectedRaceDetails.clear(); + selectedRaceDetails.addAll(newSettings.getRaceDetailsToShow()); + } + if (newSettings.getOverallDetailsToShow() != null) { + selectedOverallDetailColumns.clear(); + selectedOverallDetailColumns.addAll(newSettings.getOverallDetailsToShow()); + } + // update strategy for determining the race columns to show; if settings' race columns to show is null, use the + // previously selected race columns / number of race columns for the new configuration + Iterable oldNamesOfRaceColumnsToShow = null; + if (newSettings.getNamesOfRaceColumnsToShow() == null) { + oldNamesOfRaceColumnsToShow = raceColumnSelection.getSelectedRaceColumnNames(); + } + switch (newSettings.getActiveRaceColumnSelectionStrategy()) { + case EXPLICIT: + if (preSelectedRace == null) { + raceColumnSelection = new ExplicitRaceColumnSelection(); + } else { + raceColumnSelection = new ExplicitRaceColumnSelectionWithPreselectedRace(preSelectedRace); + } + if (newSettings.getNamesOfRaceColumnsToShow() != null) { + raceColumnSelection.requestClear(); + for (String nameOfRaceColumnToShow : newSettings.getNamesOfRaceColumnsToShow()) { + RaceColumnDTO raceColumnToShow = getRaceByColumnName(nameOfRaceColumnToShow); + if (raceColumnToShow != null) { + raceColumnSelection.requestRaceColumnSelection(raceColumnToShow.name, raceColumnToShow); + } + } + } else { + // apply the old column selections again + for (String oldNameOfRaceColumnToShow : oldNamesOfRaceColumnsToShow) { + raceColumnSelection.requestRaceColumnSelection(oldNameOfRaceColumnToShow, + getLeaderboard().getRaceColumnByName(oldNameOfRaceColumnToShow)); + } + if (newSettings.getNamesOfRacesToShow() != null) { + raceColumnSelection.requestClear(); + for (String nameOfRaceToShow : newSettings.getNamesOfRacesToShow()) { + RaceColumnDTO raceColumnToShow = getRaceByName(nameOfRaceToShow); + if (raceColumnToShow != null) { + raceColumnSelection.requestRaceColumnSelection(raceColumnToShow.name, raceColumnToShow); + } + } + } + } + break; + case LAST_N: + setRaceColumnSelectionToLastNStrategy(newSettings.getNumberOfLastRacesToShow()); + break; + } + setAutoExpandPreSelectedRace(false); // avoid expansion during updateLeaderboard(...); will expand later if it + // was expanded before + // update leaderboard after settings panel column selection change + updateLeaderboard(leaderboard); + setAutoExpandPreSelectedRace(newSettings.isAutoExpandPreSelectedRace()); + + if (newSettings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { + timer.setRefreshInterval(newSettings.getDelayBetweenAutoAdvancesInMilliseconds()); + } + if (newSettings.getDelayInMilliseconds() != null) { + setDelayInMilliseconds(newSettings.getDelayInMilliseconds()); + } + for (ExpandableSortableColumn expandableSortableColumn : columnsToExpandAgain) { + expandableSortableColumn.toggleExpansion(); + } + if (newSettings.getNameOfRaceToSort() != null) { + final RaceColumn raceColumnByRaceName = getRaceColumnByRaceName(newSettings.getNameOfRaceToSort()); + if (raceColumnByRaceName != null) { + getLeaderboardTable().sortColumn(raceColumnByRaceName, /* ascending */true); + } + } + if (newSettings.isShowOverallLeaderboardsOnSamePage() != showOverallLeaderboardsOnSamePage) { + showOverallLeaderboardsOnSamePage = newSettings.isShowOverallLeaderboardsOnSamePage(); + if (showOverallLeaderboardsOnSamePage) { + showOverallLeaderboards(); + } else { + hideOverallLeaderboards(); + } + } + } + + private void showOverallLeaderboards() { + overallLeaderboardsPanel.clear(); + final Label overallStandingsLabel = new Label(stringMessages.overallStandings()); + overallStandingsLabel.setStyleName("leaderboardHeading"); + overallLeaderboardsPanel.add(overallStandingsLabel); + updateOverallLeaderboardsAndAddToPanel(); + } + + /** + * Fetches from the server the list of overall ("meta") leaderboard descriptions in which this main leaderboard occurs and + * creates one {@link LeaderboardPanel} for each such overall leaderboard. Those panels are then added to + * {@link #overallLeaderboardPanels}, registered with the {@link #timer} for common timing with the main leaderboard panel, + * and the panels are made visible by adding them to the {@link #overallLeaderboardsPanel} panel. + */ + private void updateOverallLeaderboardsAndAddToPanel() { + getSailingService().getOverallLeaderboardNamesContaining(leaderboardName, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + getErrorReporter().reportError( + stringMessages.errorTryingToObtainOverallLeaderboards(caught.getMessage())); + } + + @Override + public void onSuccess(List overallLeaderboardNames) { + for (String overallLeaderboardName : overallLeaderboardNames) { + LeaderboardPanel overallLeaderboardPanel = new LeaderboardPanel( + getSailingService(), asyncActionsExecutor, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( + /* namesOfRaceColumnsToShow */ null, /* namesOfRacesToShow */ null, /* nameOfRaceToSort */ null, + /* autoExpandPreSelectedRace */ false, /* showOverallLeaderboardsOnSamePage */ false), + /* preSelectedRace */ null, new CompetitorSelectionModel(/* hasMultiSelection */ true), + timer, overallLeaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails, + /* optionalRaceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false); + overallLeaderboardPanels.add(overallLeaderboardPanel); + overallLeaderboardsPanel.add(overallLeaderboardPanel); + } + } + }); + + } + + /** + * Clears the panel used to display the overall leaderboards, removes all overall leaderboard panels from their listener + * relationship with the {@link #timer} and then clears the {@link #overallLeaderboardPanels} list. + */ + private void hideOverallLeaderboards() { + overallLeaderboardsPanel.clear(); + for (LeaderboardPanel overallLeaderboardPanel : overallLeaderboardPanels) { + timer.removeTimeListener(overallLeaderboardPanel); + timer.removePlayStateListener(overallLeaderboardPanel); + } + overallLeaderboardPanels.clear(); + } + + private void setRaceColumnSelectionToLastNStrategy(final Integer numberOfLastRacesToShow) { + raceColumnSelection = new LastNRacesColumnSelection(numberOfLastRacesToShow, getRaceTimesInfoProvider()); + if (timer.getPlayState() != Timer.PlayStates.Playing) { + // wait for the first update and adjust leaderboard once the race times have been received + final RaceTimesInfoProviderListener raceTimesInfoProviderListener = new RaceTimesInfoProviderListener() { + @Override + public void raceTimesInfosReceived(Map raceTimesInfo) { + updateLeaderboard(getLeaderboard()); + getRaceTimesInfoProvider().removeRaceTimesInfoProviderListener(this); + } + }; + getRaceTimesInfoProvider().addRaceTimesInfoProviderListener(raceTimesInfoProviderListener); + } + } + + /** + * A leaderboard panel may have been provided with a valid {@link RaceTimesInfoProvider} upon creation; in this case, that object + * will be returned. If none was provided to the constructor, one is created and remembered if no previously created/remembered + * object exists.

+ * + * Precondition: {@link #timer} is not null + */ + private RaceTimesInfoProvider getRaceTimesInfoProvider() { + if (raceTimesInfoProvider == null) { + final List trackedRacesIdentifiers; + if (leaderboard != null && getTrackedRacesIdentifiers() != null) { + trackedRacesIdentifiers = getTrackedRacesIdentifiers(); + } else { + trackedRacesIdentifiers = Collections.emptyList(); + } + raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), errorReporter, + trackedRacesIdentifiers, timer.getRefreshInterval()); + } + return raceTimesInfoProvider; + } + + protected class CompetitorColumn extends SortableColumn { + private final CompetitorColumnBase base; + + protected CompetitorColumn(CompetitorColumnBase base) { + super(base.getCell(getLeaderboard()), SortingOrder.ASCENDING); + this.base = base; + } + + public CompetitorColumn(CompositeCell compositeCell, CompetitorColumnBase base) { + super(compositeCell, SortingOrder.ASCENDING); + this.base = base; + } + + @Override + public InvertibleComparator getComparator() { + return new InvertibleComparatorAdapter() { + @Override + public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { + return Collator.getInstance().compare(getLeaderboard().getDisplayName(o1.competitor), + getLeaderboard().getDisplayName(o2.competitor)); + } + }; + } + + @Override + public Header getHeader() { + return base.getHeader(); + } + + @Override + public LeaderboardRowDTO getValue(LeaderboardRowDTO object) { + return object; + } + + protected void defaultRender(Context context, LeaderboardRowDTO object, SafeHtmlBuilder sb) { + super.render(context, object, sb); + } + + @Override + public void render(Context context, LeaderboardRowDTO object, SafeHtmlBuilder sb) { + String competitorColor = LeaderboardPanel.this.competitorSelectionProvider.getColor(object.competitor); + String competitorColorBarStyle; + if (LeaderboardPanel.this.isEmbedded) { + competitorColorBarStyle = "style=\"border-bottom: 2px solid " + competitorColor + ";\""; + } else { + competitorColorBarStyle = "style=\"border: none;\""; + } + base.render(object, competitorColorBarStyle, sb); + } + } + + /** + * Shows the country flag and sail ID, if present + * + * @author Axel Uhl (d043530) + * + */ + private class SailIDColumn extends SortableColumn { + private final CompetitorFetcher competitorFetcher; + + protected SailIDColumn(CompetitorFetcher competitorFetcher) { + super(new TextCell(), SortingOrder.ASCENDING); + this.competitorFetcher = competitorFetcher; + } + + @Override + public InvertibleComparator getComparator() { + return new InvertibleComparatorAdapter() { + @Override + public int compare(T o1, T o2) { + return competitorFetcher.getCompetitor(o1).sailID == null ? competitorFetcher.getCompetitor(o2).sailID == null ? 0 : -1 + : competitorFetcher.getCompetitor(o2).sailID == null ? 1 : Collator.getInstance().compare( + competitorFetcher.getCompetitor(o1).sailID, competitorFetcher.getCompetitor(o2).sailID); + } + }; + } + + @Override + public Header getHeader() { + return new TextHeader(stringMessages.competitor()); + } + + @Override + public void render(Context context, T object, SafeHtmlBuilder sb) { + ImageResourceRenderer renderer = new ImageResourceRenderer(); + ImageResource flagImageResource = FlagImageResolver + .getFlagImageResource(competitorFetcher.getCompetitor(object).twoLetterIsoCountryCode); + if (flagImageResource != null) { + sb.append(renderer.render(flagImageResource)); + sb.appendHtmlConstant(" "); + } + sb.appendEscaped(competitorFetcher.getCompetitor(object).sailID); + } + + @Override + public String getValue(T object) { + return competitorFetcher.getCompetitor(object).sailID; + } + } + + /** + * Displays net/total points and possible max-points reasons based on a {@link LeaderboardRowDTO} and a race name + * and makes the column sortable by the total points. + * + * @author Axel Uhl (D043530) + * + */ + protected abstract class RaceColumn extends ExpandableSortableColumn { + private RaceColumnDTO race; + + private final String headerStyle; + private final String columnStyle; + + public RaceColumn(RaceColumnDTO race, boolean enableExpansion, Cell cell, + SortingOrder preferredSortingOrder, String headerStyle, String columnStyle) { + super(LeaderboardPanel.this, enableExpansion, cell, preferredSortingOrder, stringMessages, + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, selectedRaceDetails); + setHorizontalAlignment(ALIGN_CENTER); + this.race = race; + this.headerStyle = headerStyle; + this.columnStyle = columnStyle; + } + + public RaceColumnDTO getRace() { + return race; + } + + public void setRace(RaceColumnDTO race) { + this.race = race; + } + + public String getRaceColumnName() { + return race.getRaceColumnName(); + } + + public boolean isMedalRace() { + return race.isMedalRace(); + } + + public boolean isLive(FleetDTO fleetDTO) { + return race.isLive(fleetDTO); + } + + @Override + public String getColumnStyle() { + return columnStyle; + } + + /** + * Displays a combination of total points and maxPointsReason in bold, transparent, strike-through, depending on + * various criteria. Here's how: + * + *

+         *                                  total points                |    maxPointsReason
+         * -------------------------------+-----------------------------+-----------------------
+         *  not discarded, no maxPoints   | bold                        | none
+         *  not discarded, maxPoints      | bold                        | transparent
+         *  discarded, no maxPoints       | transparent, strike-through | none
+         *  discarded, maxPoints          | transparent, strike-through | transparent, strike-through
+         * 
+ */ + @Override + public void render(Context context, LeaderboardRowDTO object, SafeHtmlBuilder html) { + LeaderboardEntryDTO entry = object.fieldsByRaceColumnName.get(getRaceColumnName()); + if (entry != null) { + boolean isLive = isLive(entry.fleet); + + String textColor = isLive ? IS_LIVE_TEXT_COLOR : DEFAULT_TEXT_COLOR; + String totalPointsAsText = entry.totalPoints == null ? "" : scoreFormat.format(entry.totalPoints); + String netPointsAsText = entry.netPoints == null ? "" : scoreFormat.format(entry.netPoints); + + if (entry.fleet != null && entry.fleet.getColor() != null) { + html.append(raceColumnTemplate.cellFrameWithTextColorAndFleetBorder(textColor, entry.fleet.getColor().getAsHtml())); + } else { + html.append(raceColumnTemplate.cellFrameWithTextColor(textColor)); + } + + // don't show points if max points / penalty + if (entry.reasonForMaxPoints == null || entry.reasonForMaxPoints == MaxPointsReason.NONE) { + if (!entry.discarded) { + html.appendHtmlConstant(""); + html.appendHtmlConstant(totalPointsAsText); + html.appendHtmlConstant(""); + } else { + html.appendHtmlConstant(" "); + html.appendHtmlConstant(netPointsAsText); + html.appendHtmlConstant(""); + } + } else { + html.appendHtmlConstant(" "); + if (entry.discarded) { + html.appendHtmlConstant(""); + } + html.appendEscaped(entry.reasonForMaxPoints == MaxPointsReason.NONE ? "" : entry.reasonForMaxPoints.name()); + if (entry.discarded) { + html.appendHtmlConstant(""); + } + html.appendHtmlConstant(""); + } + html.appendHtmlConstant("
"); + } + } + + @Override + public InvertibleComparator getComparator() { + return new InvertibleComparatorAdapter() { + @Override + public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { + List competitorsFromBestToWorst = getLeaderboard().getCompetitorsFromBestToWorst( + race); + int o1Rank = competitorsFromBestToWorst.indexOf(o1.competitor) + 1; + int o2Rank = competitorsFromBestToWorst.indexOf(o2.competitor) + 1; + return o1Rank == 0 ? o2Rank == 0 ? 0 : isAscending() ? 1 : -1 : o2Rank == 0 ? isAscending() ? -1 + : 1 : o1Rank - o2Rank; + } + }; + } + + @Override + public String getHeaderStyle() { + return headerStyle; + } + + @Override + public Header getHeader() { + SortableExpandableColumnHeader header = new SortableExpandableColumnHeader( + /* title */race.getRaceColumnName(), + /* iconURL */race.isMedalRace() ? "/gwt/images/medal_small.png" : null, LeaderboardPanel.this, this, + stringMessages); + return header; + } + } + + public static DetailType[] getAvailableRaceDetailColumnTypes() { + return new DetailType[] { DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, + DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, + DetailType.RACE_DISTANCE_TRAVELED, + DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, + DetailType.NUMBER_OF_MANEUVERS, DetailType.DISPLAY_LEGS, DetailType.CURRENT_LEG, + DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS }; + } + + public static DetailType[] getAvailableOverallDetailColumnTypes() { + return new DetailType[] { DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, + DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, + DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS, + DetailType.TOTAL_TIME_SAILED_IN_SECONDS, DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS }; + } + + private class TextRaceColumn extends RaceColumn implements RaceNameProvider { + /** + * Remembers the leg columns; null-padded, if {@link #getLegColumn(int)} asks for a column index + * not yet existing. It is important to remember the columns because column removal happens based on identity. + */ + private final List legColumns; + + public TextRaceColumn(RaceColumnDTO race, boolean expandable, SortingOrder preferredSortingOrder, + String headerStyle, String columnStyle) { + super(race, expandable, new TextCell(), preferredSortingOrder, headerStyle, columnStyle); + legColumns = new ArrayList(); + } + + public String getValue(LeaderboardRowDTO object) { + // The following code exists only for robustness. This method should never be called because + // RaceColumn implements its own render(...) method which doesn't make use of getValue(...) + final Double totalPoints = object.fieldsByRaceColumnName.get(getRaceColumnName()).totalPoints; + return "" + (totalPoints == null ? "" : scoreFormat.format(totalPoints)); + } + + @Override + protected void ensureExpansionDataIsLoaded(final Runnable callWhenExpansionDataIsLoaded) { + if (getLeaderboard().getLegCount(getRaceColumnName()) != -1) { + callWhenExpansionDataIsLoaded.run(); + } else { + getSailingService().getLeaderboardByName(getLeaderboardName(), + timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(), + /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), + new AsyncCallback() { + @Override + public void onSuccess(LeaderboardDTO result) { + updateLeaderboard(result); + callWhenExpansionDataIsLoaded.run(); + } + + @Override + public void onFailure(Throwable caught) { + getErrorReporter().reportError( + stringMessages.errorTryingToObtainLeaderboardContents(caught.getMessage()), + true /* silentMode */); + } + }); + } + } + + @Override + protected Map> getDetailColumnMap( + LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, + String detailColumnStyle) { + Map> result = new HashMap>(); + + result.put(DetailType.RACE_DISTANCE_TRAVELED, + new FormattedDoubleLegDetailColumn(DetailType.RACE_DISTANCE_TRAVELED, new RaceDistanceTraveledInMeters(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + + result.put(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, + new FormattedDoubleLegDetailColumn(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new RaceAverageSpeedInKnots(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + + result.put(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, + new FormattedDoubleLegDetailColumn(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, new RaceGapToLeaderInSeconds(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + + result.put(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, + new FormattedDoubleLegDetailColumn(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new CurrentSpeedOverGroundInKnots(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + + result.put(DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, + new FormattedDoubleLegDetailColumn(DetailType.RACE_DISTANCE_TO_LEADER_IN_METERS, new RaceDistanceToLeaderInMeters(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + + result.put(DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS, + new FormattedDoubleLegDetailColumn(DetailType.RACE_AVERAGE_CROSS_TRACK_ERROR_IN_METERS, new RaceAverageCrossTrackErrorInMeters(), + LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + + result.put(DetailType.NUMBER_OF_MANEUVERS, getManeuverCountRaceColumn()); + + result.put(DetailType.CURRENT_LEG, + new FormattedDoubleLegDetailColumn(DetailType.CURRENT_LEG, new CurrentLeg(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE)); + + return result; + } + + private ManeuverCountRaceColumn getManeuverCountRaceColumn() { + return new ManeuverCountRaceColumn(getLeaderboardPanel(), this, stringMessages, + LeaderboardPanel.this.selectedManeuverDetails, LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, + LEG_DETAIL_COLUMN_HEADER_STYLE, LEG_DETAIL_COLUMN_STYLE); + } + + @Override + protected Iterable> getDirectChildren() { + List> result = new ArrayList>(); + for (SortableColumn column : super.getDirectChildren()) { + result.add(column); + } + if (isExpanded() && selectedRaceDetails.contains(DetailType.DISPLAY_LEGS)) { + // it is important to re-use existing LegColumn objects because + // removing the columns from the table + // is based on column identity + int legCount = getLeaderboard().getLegCount(getRaceColumnName()); + if (legCount != -1) { + for (int i = 0; i < legCount; i++) { + LegColumn legColumn = getLegColumn(i); + result.add(legColumn); + } + } else { + // the race is no longer part of the LeaderboardDTO; consider the non-null legs in legColumns: + for (LegColumn legColumn : legColumns) { + if (legColumn != null) { + result.add(legColumn); + } + } + } + } + return result; + } + + private LegColumn getLegColumn(int legNumber) { + LegColumn result; + if (legColumns.size() > legNumber && legColumns.get(legNumber) != null) { + result = legColumns.get(legNumber); + } else { + result = new LegColumn(LeaderboardPanel.this, getRaceColumnName(), legNumber, SortingOrder.ASCENDING, + stringMessages, Collections.unmodifiableList(selectedLegDetails), LEG_COLUMN_HEADER_STYLE, + LEG_COLUMN_STYLE, LEG_DETAIL_COLUMN_HEADER_STYLE, LEG_DETAIL_COLUMN_STYLE); + while (legColumns.size() <= legNumber) { + legColumns.add(null); + } + legColumns.set(legNumber, result); + } + return result; + } + + /** + * Accumulates the average speed over all legs of a race + * + * @author Axel Uhl (D043530) + */ + private class RaceAverageSpeedInKnots implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null && fieldsForRace.legDetails != null) { + double distanceTraveledInMeters = 0; + long timeInMilliseconds = 0; + for (LegEntryDTO legDetail : fieldsForRace.legDetails) { + if (legDetail != null) { + distanceTraveledInMeters += legDetail.distanceTraveledInMeters; + timeInMilliseconds += legDetail.timeInMilliseconds; + } + } + if (timeInMilliseconds != 0) { + result = distanceTraveledInMeters / (double) timeInMilliseconds * 1000 * 3600 / 1852; + } + } + return result; + } + } + + /** + * Fetches the average cross-track error for the race + * + * @author Axel Uhl (D043530) + */ + private class RaceAverageCrossTrackErrorInMeters implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null) { + result = fieldsForRace.averageCrossTrackErrorInMeters; + } + return result; + } + } + + /** + * Accumulates the distance traveled over all legs of a race + * + * @author Axel Uhl (D043530) + */ + private class RaceDistanceTraveledInMeters implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null && fieldsForRace.legDetails != null) { + for (LegEntryDTO legDetail : fieldsForRace.legDetails) { + if (legDetail != null) { + if (legDetail.distanceTraveledInMeters != null) { + if (result == null) { + result = 0.0; + } + result += legDetail.distanceTraveledInMeters; + } + } + } + } + return result; + } + } + + private class CurrentLeg implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null && fieldsForRace.legDetails != null && !fieldsForRace.legDetails.isEmpty()) { + for (LegEntryDTO legDetail : fieldsForRace.legDetails) { + if (legDetail != null) { + if (legDetail.started) { + if (result == null) { + result = 0.0; + } + result++; + } else { + if (result != null) { + result++; + } + break; + } + } + } + } + return result; + } + } + + /** + * Computes the gap to leader exploiting the ordering of the leg detail columns + * + * @author Axel Uhl (D043530) + */ + private class RaceGapToLeaderInSeconds implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null && fieldsForRace.legDetails != null) { + int lastLegIndex = fieldsForRace.legDetails.size() - 1; + LegEntryDTO lastLegDetail = fieldsForRace.legDetails.get(lastLegIndex); + // competitor may be in leg prior to the one the leader is in; find competitors current leg + while (lastLegDetail == null && lastLegIndex > 0) { + lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex); + } + if (lastLegDetail != null) { + result = lastLegDetail.gapToLeaderInSeconds; + } + } + return result; + } + } + + /** + * Computes the windward distance to the overall leader in meters + * + * @author Axel Uhl (D043530) + */ + private class RaceDistanceToLeaderInMeters implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null && fieldsForRace.windwardDistanceToOverallLeaderInMeters != null) { + result = fieldsForRace.windwardDistanceToOverallLeaderInMeters; + } + return result; + } + } + + private class CurrentSpeedOverGroundInKnots implements LegDetailField { + @Override + public Double get(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null && fieldsForRace.legDetails != null) { + int lastLegIndex = fieldsForRace.legDetails.size() - 1; + LegEntryDTO lastLegDetail = fieldsForRace.legDetails.get(lastLegIndex); + // competitor may be in leg prior to the one the leader is in; find competitors current leg + while (lastLegDetail == null && lastLegIndex > 0) { + lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex); + } + if (lastLegDetail != null) { + result = lastLegDetail.currentSpeedOverGroundInKnots; + } + } + return result; + } + } + } + + /** + * Displays the totals for a competitor for the entire leaderboard. + * + * @author Axel Uhl (D043530) + * + */ + private class TotalsColumn extends SortableColumn { + private final String columnStyle; + + protected TotalsColumn(String columnStyle) { + super(new TextCell(), SortingOrder.ASCENDING); + this.columnStyle = columnStyle; + setHorizontalAlignment(ALIGN_CENTER); + } + + @Override + public String getValue(LeaderboardRowDTO object) { + Double totalPoints = getLeaderboard().getTotalPoints(object); + return "" + (totalPoints == null ? "" : scoreFormat.format(totalPoints)); + } + + @Override + public void render(Context context, LeaderboardRowDTO object, SafeHtmlBuilder sb) { + String textColor = getLeaderboard().hasLiveRace() ? IS_LIVE_TEXT_COLOR : DEFAULT_TEXT_COLOR; + + sb.appendHtmlConstant(""); + sb.appendEscaped(getValue(object)); + sb.appendHtmlConstant(""); + } + + @Override + public InvertibleComparator getComparator() { + return new InvertibleComparatorAdapter() { + @Override + public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { + return getLeaderboard().competitors.indexOf(o1.competitor) + - getLeaderboard().competitors.indexOf(o2.competitor); + } + }; + } + + @Override + public String getColumnStyle() { + return columnStyle; + } + + @Override + public Header getHeader() { + return new TextHeader(stringMessages.total()); + } + } + + protected class CarryColumn extends SortableColumn { + public CarryColumn() { + super(new TextCell(), SortingOrder.ASCENDING); + setSortable(true); + } + + protected CarryColumn(EditTextCell editTextCell) { + super(editTextCell, SortingOrder.ASCENDING); + setSortable(true); + } + + @Override + public String getValue(LeaderboardRowDTO object) { + return object.carriedPoints == null ? "" : scoreFormat.format(object.carriedPoints); + } + + @Override + public InvertibleComparator getComparator() { + return new InvertibleComparatorAdapter() { + @Override + public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { + Double o1CarriedPoints = o1.carriedPoints; + if (o1CarriedPoints == null) { + o1CarriedPoints = 0.0; + } + Double o2CarriedPoints = o2.carriedPoints; + if (o2CarriedPoints == null) { + o2CarriedPoints = 0.0; + } + return o1CarriedPoints.compareTo(o2CarriedPoints); + } + }; + } + + @Override + public Header getHeader() { + return new TextHeader(stringMessages.carry()); + } + } + + private class RankColumn extends SortableColumn { + public RankColumn() { + super(new TextCell(), SortingOrder.ASCENDING); + setHorizontalAlignment(ALIGN_CENTER); + setSortable(true); + } + + @Override + public String getValue(LeaderboardRowDTO object) { + final int rank = getLeaderboard().getRank(object.competitor); + return "" + (rank == 0 ? "" : rank); + } + + @Override + public InvertibleComparator getComparator() { + return new InvertibleComparatorAdapter() { + @Override + public int compare(LeaderboardRowDTO o1, LeaderboardRowDTO o2) { + final int rank1 = getLeaderboard().getRank(o1.competitor); + final int rank2 = getLeaderboard().getRank(o2.competitor); + return rank1 == 0 ? rank2 == 0 ? 0 : 1 : rank2 == 0 ? -1 : rank1 - rank2; + } + }; + } + + @Override + public Header getHeader() { + return new TextHeader(stringMessages.totalRank()); + } + } + + public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + LeaderboardSettings settings, CompetitorSelectionProvider competitorSelectionProvider, + String leaderboardName, ErrorReporter errorReporter, final StringMessages stringMessages, + final UserAgentDetails userAgent, boolean showRaceDetails) { + this(sailingService, asyncActionsExecutor, settings, /* preSelectedRace */null, competitorSelectionProvider, + leaderboardName, errorReporter, stringMessages, userAgent, showRaceDetails); + } + + public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + LeaderboardSettings settings, RaceIdentifier preSelectedRace, + CompetitorSelectionProvider competitorSelectionProvider, String leaderboardName, + ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, + boolean showRaceDetails) { + this(sailingService, asyncActionsExecutor, settings, preSelectedRace, competitorSelectionProvider, new Timer( + PlayModes.Replay, /* delayBetweenAutoAdvancesInMilliseconds */3000l), leaderboardName, + errorReporter, stringMessages, userAgent, showRaceDetails, /* optionalRaceTimesInfoProvider */ null, + /* autoExpandLastRaceColumn */ false); + } + + public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + LeaderboardSettings settings, RaceIdentifier preSelectedRace, + CompetitorSelectionProvider competitorSelectionProvider, Timer timer, String leaderboardName, + ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, + boolean showRaceDetails, RaceTimesInfoProvider optionalRaceTimesInfoProvider, boolean autoExpandLastRaceColumn) { + this.showRaceDetails = showRaceDetails; + this.sailingService = sailingService; + this.asyncActionsExecutor = asyncActionsExecutor; + this.preSelectedRace = preSelectedRace; + this.competitorSelectionProvider = competitorSelectionProvider; + competitorSelectionProvider.addCompetitorSelectionChangeListener(this); + this.setLeaderboardName(leaderboardName); + this.errorReporter = errorReporter; + this.stringMessages = stringMessages; + this.selectedLegDetails = new ArrayList(); + this.selectedRaceDetails = new ArrayList(); + this.selectedOverallDetailColumns = new ArrayList(); + this.raceTimesInfoProvider = optionalRaceTimesInfoProvider; + this.selectedManeuverDetails = new ArrayList(); + this.showOverallLeaderboardsOnSamePage = settings.isShowOverallLeaderboardsOnSamePage(); + overallDetailColumnMap = createOverallDetailColumnMap(); + settingsUpdatedExplicitly = !settings.updateUponPlayStateChange(); + if (settings.getLegDetailsToShow() != null) { + selectedLegDetails.addAll(settings.getLegDetailsToShow()); + } + if (settings.getManeuverDetailsToShow() != null) { + selectedManeuverDetails.addAll(settings.getManeuverDetailsToShow()); + } + if (settings.getRaceDetailsToShow() != null) { + selectedRaceDetails.addAll(settings.getRaceDetailsToShow()); + } + if (settings.getOverallDetailsToShow() != null) { + selectedOverallDetailColumns.addAll(settings.getOverallDetailsToShow()); + } + setAutoExpandPreSelectedRace(settings.isAutoExpandPreSelectedRace()); + this.autoExpandLastRaceColumn = autoExpandLastRaceColumn; + if (settings.getDelayBetweenAutoAdvancesInMilliseconds() != null) { + timer.setRefreshInterval(settings.getDelayBetweenAutoAdvancesInMilliseconds()); + } + this.timer = timer; + timer.addPlayStateListener(this); + timer.addTimeListener(this); + switch (settings.getActiveRaceColumnSelectionStrategy()) { + case EXPLICIT: + if (preSelectedRace == null) { + raceColumnSelection = new ExplicitRaceColumnSelection(); + } else { + raceColumnSelection = new ExplicitRaceColumnSelectionWithPreselectedRace(preSelectedRace); + } + break; + case LAST_N: + setRaceColumnSelectionToLastNStrategy(settings.getNumberOfLastRacesToShow()); + break; + } + rankColumn = new RankColumn(); + RACE_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableRaceColumnHeader(); + LEG_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegColumnHeader(); + LEG_DETAIL_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegDetailColumnHeader(); + RACE_COLUMN_STYLE = tableResources.cellTableStyle().cellTableRaceColumn(); + LEG_COLUMN_STYLE = tableResources.cellTableStyle().cellTableLegColumn(); + LEG_DETAIL_COLUMN_STYLE = tableResources.cellTableStyle().cellTableLegDetailColumn(); + TOTAL_COLUMN_STYLE = tableResources.cellTableStyle().cellTableTotalColumn(); + leaderboardTable = new SortedCellTableWithStylableHeaders( + /* pageSize */10000, tableResources); + getLeaderboardTable().setWidth("100%"); + this.userAgent = userAgent; + if (userAgent.isMobile() == UserAgentDetails.PlatformTypes.MOBILE) { + leaderboardSelectionModel = new ToggleSelectionModel(); + } else { + leaderboardSelectionModel = new MultiSelectionModel(); + } + leaderboardSelectionModel.addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + List selection = new ArrayList(); + for (LeaderboardRowDTO row : getSelectedRows()) { + selection.add(row.competitor); + } + LeaderboardPanel.this.competitorSelectionProvider.setSelection(selection, + /* listenersNotToNotify */LeaderboardPanel.this); + } + }); + leaderboardTable.setSelectionModel(leaderboardSelectionModel); + loadCompleteLeaderboard(getLeaderboardDisplayDate()); + + if (this.preSelectedRace == null) { + isEmbedded = false; + } else { + isEmbedded = true; + } + contentPanel = new VerticalPanel(); + contentPanel.setStyleName(STYLE_LEADERBOARD_CONTENT); + overallLeaderboardsPanel = new VerticalPanel(); + overallLeaderboardPanels = new ArrayList(); + + // the information panel + informationPanel = new FlowPanel(); + informationPanel.setStyleName(STYLE_LEADERBOARD_INFO); + scoreCorrectionLastUpdateTimeLabel = new Label(""); + scoreCorrectionCommentLabel = new Label(""); + informationPanel.add(scoreCorrectionCommentLabel); + informationPanel.add(scoreCorrectionLastUpdateTimeLabel); + + liveRaceLabel = new Label(stringMessages.live()); + liveRaceLabel.setStyleName(STYLE_LEADERBOARD_LIVE_RACE); + liveRaceLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); + liveRaceLabel.getElement().getStyle().setColor(IS_LIVE_TEXT_COLOR); + liveRaceLabel.setVisible(false); + informationPanel.add(liveRaceLabel); + + // the toolbar panel + DockPanel toolbarPanel = new DockPanel(); + toolbarPanel.setStyleName(STYLE_LEADERBOARD_TOOLBAR); + busyIndicator = new SimpleBusyIndicator(false, 0.8f); + if (!isEmbedded) { + toolbarPanel.add(informationPanel, DockPanel.WEST); + toolbarPanel.add(busyIndicator, DockPanel.WEST); + } + toolbarPanel.setWidth("100%"); + toolbarPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + ClickHandler playPauseHandler = new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (LeaderboardPanel.this.timer.getPlayState() == PlayStates.Playing) { + LeaderboardPanel.this.timer.pause(); + } else { + // playing the standalone leaderboard means putting it into live mode + LeaderboardPanel.this.timer.setPlayMode(PlayModes.Live); + } + } + }; + ImageResource chartIcon = resources.chartIcon(); + ImageResource rankChartIcon = resources.rankChartIcon(); + ImageResource leaderboardSettingsIcon = resources.leaderboardSettingsIcon(); + pauseIcon = resources.pauseIcon(); + playIcon = resources.playIcon(); + refreshAndSettingsPanel = new HorizontalPanel(); + refreshAndSettingsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + HorizontalPanel refreshPanel = new HorizontalPanel(); + refreshPanel.setSpacing(5); + refreshPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + refreshPanel.addStyleName("refreshPanel"); + toolbarPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT); + toolbarPanel.addStyleName("refreshAndSettings"); + playPause = new Anchor(getPlayPauseImgHtml(timer.getPlayState())); + playPause.addClickHandler(playPauseHandler); + playStateChanged(timer.getPlayState(), timer.getPlayMode()); + refreshPanel.add(playPause); + Anchor rankChartsAnchor = new Anchor(AbstractImagePrototype.create(rankChartIcon).getSafeHtml()); + rankChartsAnchor.setTitle(stringMessages.showRankChart()); + rankChartsAnchor.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + showRankChartDialog(); + } + }); + Anchor chartsAnchor = new Anchor(AbstractImagePrototype.create(chartIcon).getSafeHtml()); + chartsAnchor.setTitle(stringMessages.showCharts()); + chartsAnchor.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + showCompareCompetitorsDialog(); + } + }); + Anchor settingsAnchor = new Anchor(AbstractImagePrototype.create(leaderboardSettingsIcon).getSafeHtml()); + settingsAnchor.setTitle(stringMessages.settings()); + settingsAnchor.addClickHandler(new SettingsClickHandler(stringMessages)); + refreshAndSettingsPanel.add(rankChartsAnchor); + refreshAndSettingsPanel.add(chartsAnchor); + refreshAndSettingsPanel.add(refreshPanel); + refreshAndSettingsPanel.add(settingsAnchor); + toolbarPanel.add(refreshAndSettingsPanel, DockPanel.EAST); + if (!isEmbedded) { + contentPanel.add(toolbarPanel); + } + contentPanel.add(getLeaderboardTable()); + contentPanel.add(overallLeaderboardsPanel); + setWidget(contentPanel); + raceNameForDefaultSorting = settings.getNameOfRaceToSort(); + } + + private Map> createOverallDetailColumnMap() { + Map> result = new HashMap>(); + result.put(DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, new MaxSpeedOverallColumn(stringMessages, + RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); + result.put(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, new KingOfTheUpwindColumn(stringMessages, + RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); + result.put(DetailType.TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS, new KingOfTheDownwindColumn(stringMessages, + RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); + result.put(DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS, new KingOfTheReachingColumn(stringMessages, + RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); + result.put(DetailType.TOTAL_TIME_SAILED_IN_SECONDS, new TotalTimeSailedColumn(stringMessages, + RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE)); + return result; + } + + /** + * Largely for subclasses to add more stuff to this panel, such as more toolbar buttons. + */ + protected HorizontalPanel getRefreshAndSettingsPanel() { + return refreshAndSettingsPanel; + } + + private RaceColumnDTO getRaceByName(String raceName) { + if (getLeaderboard() != null) { + for (RaceColumnDTO race : getLeaderboard().getRaceList()) { + for (FleetDTO fleet : race.getFleets()) { + if (race.getRaceIdentifier(fleet) != null + && raceName.equals(race.getRaceIdentifier(fleet).getRaceName())) { + return race; + } + } + } + } + return null; + } + + private RaceColumnDTO getRaceByColumnName(String columnName) { + if (getLeaderboard() != null) { + for (RaceColumnDTO race : getLeaderboard().getRaceList()) { + if (columnName.equals(race.getRaceColumnName())) { + return race; + } + } + } + return null; + } + + private RaceColumn getRaceColumnByRaceName(String raceName) { + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column column = getLeaderboardTable().getColumn(i); + if (column instanceof RaceColumn) { + RaceColumnDTO raceInLeaderboard = ((RaceColumn) column).getRace(); + for (FleetDTO fleet : raceInLeaderboard.getFleets()) { + final RegattaAndRaceIdentifier raceIdentifier = raceInLeaderboard.getRaceIdentifier(fleet); + if (raceIdentifier != null && raceIdentifier.getRaceName().equals(raceName)) { + return (RaceColumn) column; + } + } + } + } + return null; + } + + private RaceColumn getRaceColumnByRaceColumnName(String raceColumnName) { + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column column = getLeaderboardTable().getColumn(i); + if (column instanceof RaceColumn) { + if (((RaceColumn) column).getRaceColumnName().equals(raceColumnName)) { + return (RaceColumn) column; + } + } + } + return null; + } + + private SafeHtml getPlayPauseImgHtml(PlayStates playState) { + if (playState == PlayStates.Playing) { + return AbstractImagePrototype.create(pauseIcon).getSafeHtml(); + } else { + return AbstractImagePrototype.create(playIcon).getSafeHtml(); + } + } + + private void setDelayInMilliseconds(long delayInMilliseconds) { + timer.setLivePlayDelayInMillis(delayInMilliseconds); + } + + private boolean isAutoExpandPreSelectedRace() { + return autoExpandPreSelectedRace; + } + + private boolean isAutoExpandLastRaceColumn() { + return autoExpandLastRaceColumn; + } + + private void setAutoExpandPreSelectedRace(boolean autoExpandPreSelectedRace) { + this.autoExpandPreSelectedRace = autoExpandPreSelectedRace; + if (autoExpandPreSelectedRace) { + autoExpandPerformedOnce = false; + } + } + + /** + * The time point for which the leaderboard currently shows results. In {@link PlayModes#Replay replay mode} this is + * the {@link #timer}'s time point. In {@link PlayModes#Live live mode} the {@link #timer}'s time is quantizes to + * the closest full second to increase the likelihood of cache hits in the back end. + */ + protected Date getLeaderboardDisplayDate() { + return timer.getTime(); + } + + /** + * adds the column to the right end of the {@link #getLeaderboardTable() leaderboard table} and sets + * the column style according to the {@link SortableColumn#getColumnStyle() column's style definition}. + */ + protected void addColumn(SortableColumn column) { + leaderboardTable.addColumn(column, column.getHeader(), column.getComparator(), column + .getPreferredSortingOrder().isAscending()); + String columnStyle = column.getColumnStyle(); + if (columnStyle != null) { + getLeaderboardTable().addColumnStyleName(getLeaderboardTable().getColumnCount() - 1, columnStyle); + } + } + + protected void insertColumn(int beforeIndex, SortableColumn column) { + // remove column styles of those columns whose index will shift right by + // one: + removeColumnStyles(beforeIndex); + getLeaderboardTable().insertColumn(beforeIndex, column, column.getHeader(), column.getComparator(), + column.getPreferredSortingOrder().isAscending()); + addColumnStyles(beforeIndex); + } + + private void addColumnStyles(int startColumn) { + for (int i = startColumn; i < getLeaderboardTable().getColumnCount(); i++) { + SortableColumn columnToRemoveStyleFor = (SortableColumn) getLeaderboardTable() + .getColumn(i); + String columnStyle = columnToRemoveStyleFor.getColumnStyle(); + if (columnStyle != null) { + getLeaderboardTable().addColumnStyleName(i, columnStyle); + } + } + } + + private void removeColumnStyles(int startColumn) { + for (int i = startColumn; i < getLeaderboardTable().getColumnCount(); i++) { + SortableColumn columnToRemoveStyleFor = (SortableColumn) getLeaderboardTable() + .getColumn(i); + String columnStyle = columnToRemoveStyleFor.getColumnStyle(); + if (columnStyle != null) { + getLeaderboardTable().removeColumnStyleName(i, columnStyle); + } + } + } + + /** + * removes the column specified by columnIndex from the {@link #getLeaderboardTable() leaderboard + * table} and fixes the column styles again (see {@link #addColumnStyles(int)}). + */ + protected void removeColumn(int columnIndex) { + Column c = getLeaderboardTable().getColumn(columnIndex); + if (c instanceof ExpandableSortableColumn) { + ExpandableSortableColumn expandableColumn = (ExpandableSortableColumn) c; + if (expandableColumn.isExpanded()) { + // remove expanded child columns from the leaderboard... + expandableColumn.toggleExpansion(); + // them remember that column c was expanded: + expandableColumn.setExpanded(true); + } + } + removeColumnStyles(/* startColumn */columnIndex); + getLeaderboardTable().removeColumn(columnIndex); + addColumnStyles(/* startColumn */columnIndex); + } + + protected void removeColumn(Column c) { + int columnIndex = getLeaderboardTable().getColumnIndex(c); + if (columnIndex != -1) { + removeColumn(columnIndex); + } + } + + private void loadCompleteLeaderboard(Date date) { + if (needsDataLoading()) { + GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(sailingService, + getLeaderboardName(), timer.getPlayMode() == PlayModes.Live ? null : date, + /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), + new AsyncCallback() { + @Override + public void onSuccess(LeaderboardDTO result) { + updateLeaderboard(result); + getBusyIndicator().setBusy(false); + } + + @Override + public void onFailure(Throwable caught) { + getBusyIndicator().setBusy(false); + getErrorReporter() + .reportError("Error trying to obtain leaderboard contents: " + caught.getMessage(), + true /* silentMode */); + } + }); + asyncActionsExecutor.execute(getLeaderboardByNameAction); + } else { + getBusyIndicator().setBusy(false); + } + } + + private boolean needsDataLoading() { + return isVisible(); + } + + /** + * Determine from column expansion state which races need their leg details + */ + private Collection getNamesOfExpandedRaces() { + Collection namesOfExpandedRaces = new ArrayList(); + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column column = getLeaderboardTable().getColumn(i); + if (column instanceof RaceColumn) { + RaceColumn raceColumn = (RaceColumn) column; + if (raceColumn.isExpanded()) { + namesOfExpandedRaces.add(raceColumn.getRaceColumnName()); + } + } + } + return namesOfExpandedRaces; + } + + /** + * Assigns leaderboard to {@link #leaderboard} and updates the UI accordingly. Also updates the min/max + * values on the columns. + */ + protected void updateLeaderboard(LeaderboardDTO leaderboard) { + if (leaderboard != null) { + Collection> columnsToCollapseAndExpandAgain = getExpandedRaceColumnsWhoseDisplayedLegCountChanged(leaderboard); + for (RaceColumn columnToCollapseAndExpandAgain : columnsToCollapseAndExpandAgain) { + columnToCollapseAndExpandAgain.toggleExpansion(); + } + competitorSelectionProvider.setCompetitors(leaderboard.competitors, /* listenersNotToNotify */this); + raceColumnSelection.autoUpdateRaceColumnSelectionForUpdatedLeaderboard(getLeaderboard(), leaderboard); + setLeaderboard(leaderboard); + adjustColumnLayout(leaderboard); + updateRaceColumnDTOsToRaceColumns(leaderboard); + for (RaceColumn columnToCollapseAndExpandAgain : columnsToCollapseAndExpandAgain) { + columnToCollapseAndExpandAgain.toggleExpansion(); + } + adjustDelayToLive(); + getData().getList().clear(); + getData().getList().addAll(getRowsToDisplay(leaderboard)); + RaceColumn lastRaceColumn = null; + for (int i=getLeaderboardTable().getColumnCount()-1; i>=0; i--) { + if (getLeaderboardTable().getColumn(i) instanceof RaceColumn) { + lastRaceColumn = (RaceColumn) getLeaderboardTable().getColumn(i); + break; + } + } + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + SortableColumn c = (SortableColumn) getLeaderboardTable().getColumn(i); + c.updateMinMax(leaderboard); + // Toggle pre-selected race, if the setting is set and it isn't open yet, or the last race column if that was requested + if ((!autoExpandPerformedOnce && isAutoExpandPreSelectedRace() && c instanceof RaceColumn + && ((RaceColumn) c).getRace().hasTrackedRace(preSelectedRace)) || + (isAutoExpandLastRaceColumn() && c == lastRaceColumn)) { + ExpandableSortableColumn expandableSortableColumn = (ExpandableSortableColumn) c; + if (!expandableSortableColumn.isExpanded()) { + expandableSortableColumn.toggleExpansion(); + autoExpandPerformedOnce = true; + } + } + } + if (leaderboardTable.getCurrentlySortedColumn() != null) { + leaderboardTable.sort(); + } else { + SortableColumn columnToSortFor = getDefaultSortColumn(); + leaderboardTable.sortColumn(columnToSortFor, columnToSortFor.getPreferredSortingOrder().isAscending()); + } + // Reselect the selected rows + clearSelection(); + for (LeaderboardRowDTO row : getLeaderboardTable().getDataProvider().getList()) { + if (competitorSelectionProvider.isSelected(row.competitor)) { + leaderboardSelectionModel.setSelected(row, true); + } + } + + scoreCorrectionCommentLabel.setText(leaderboard.getComment() != null ? leaderboard.getComment() : ""); + if (leaderboard.getTimePointOfLastCorrectionsValidity() != null) { + Date lastCorrectionDate = leaderboard.getTimePointOfLastCorrectionsValidity(); + String lastUpdate = dateFormatter.format(lastCorrectionDate) + ", " + + timeFormatter.format(lastCorrectionDate); + scoreCorrectionLastUpdateTimeLabel.setText(stringMessages.lastScoreUpdate() + ": " + lastUpdate); + } else { + scoreCorrectionLastUpdateTimeLabel.setText(""); + } + + List> liveRaces = leaderboard.getLiveRaces(); + boolean hasLiveRace = !liveRaces.isEmpty(); + if (hasLiveRace) { + String liveRaceText = ""; + if(liveRaces.size() == 1) { + Pair liveRace = liveRaces.get(0); + liveRaceText = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'"); + } else { + String raceNames = ""; + for(Pair liveRace: liveRaces) { + raceNames += "'" + liveRace.getA().getRaceColumnName() + "', "; + } + // remove last ", " + raceNames = raceNames.substring(0, raceNames.length()-2); + liveRaceText = stringMessages.racesAreLive(raceNames); + } + liveRaceLabel.setText(liveRaceText); + } else { + liveRaceLabel.setText(""); + } + scoreCorrectionLastUpdateTimeLabel.setVisible(!hasLiveRace); + liveRaceLabel.setVisible(hasLiveRace); + } + } + + /** + * The race columns hold a now outdated copy of a {@link RaceColumnDTO} which needs to be updated from the {@link LeaderboardDTO} just received + */ + private void updateRaceColumnDTOsToRaceColumns(LeaderboardDTO leaderboard) { + for (RaceColumnDTO newRace : leaderboard.getRaceList()) { + RaceColumn raceColumn = getRaceColumnByRaceColumnName(newRace.name); + if (raceColumn != null) { + raceColumn.setRace(newRace); + } + } + } + + /** + * Due to a course change, a race may change its number of legs. All expanded race columns that show leg columns and + * whose leg count changed need to be collapsed before the leaderboard is replaced, and expanded afterwards again. + * Race columns whose toggling is {@link ExpandableSortableColumn#isTogglingInProcess() currently in progress} are + * not considered because their new state will be considered after replacing anyhow. + * + * @param newLeaderboard + * the new leaderboard before assigning to {@link #leaderboard} + * @return the columns that were collapsed in this step and that shall be expanded again after the leaderboard has + * been replaced + */ + private Collection> getExpandedRaceColumnsWhoseDisplayedLegCountChanged(LeaderboardDTO newLeaderboard) { + Set> result = new HashSet>(); + if (selectedRaceDetails.contains(DetailType.DISPLAY_LEGS)) { + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column c = getLeaderboardTable().getColumn(i); + if (c instanceof RaceColumn) { + RaceColumn rc = (RaceColumn) c; + // If the new leaderboard no longer contains the column, getLegCount will return -1, causing the + // column + // to be collapsed if it was expanded. This is correct because otherwise, removing it would no + // longer + // know the correct leg count. + if (!rc.isTogglingInProcess() && rc.isExpanded()) { + int oldLegCount = getLeaderboard().getLegCount(rc.getRaceColumnName()); + int newLegCount = newLeaderboard.getLegCount(rc.getRaceColumnName()); + if (oldLegCount != newLegCount) { + result.add(rc); + } + } + } + } + } + return result; + } + + /** + * Based on the {@link LeaderboardDTO#getDelayToLiveInMillisForLatestRace()} for the race that has the latest + * {@link RaceColumnDTO#getStartDate(FleetDTO) start time}, if no {@link #settingsUpdatedExplicitly explicit setting + * changes} were performed, automatically adjusts the delay accordingly. + */ + private void adjustDelayToLive() { + if (!settingsUpdatedExplicitly && leaderboard.getDelayToLiveInMillisForLatestRace() != null) { + setDelayInMilliseconds(leaderboard.getDelayToLiveInMillisForLatestRace()); + } + } + + /** + * Extracts the rows to display of the leaderboard. These are all {@link AbstractLeaderboardDTO#rows + * rows} in case {@link #preSelectedRace} is null, or only the rows of the competitors who scored in + * the race identified by {@link #preSelectedRace} otherwise. + */ + private Collection getRowsToDisplay(LeaderboardDTO leaderboard) { + Collection result; + if (preSelectedRace == null) { + result = leaderboard.rows.values(); + } else { + result = new ArrayList(); + for (CompetitorDTO competitorInPreSelectedRace : getCompetitors(preSelectedRace)) { + result.add(leaderboard.rows.get(competitorInPreSelectedRace)); + } + } + return result; + } + + /** + * The {@link LeaderboardDTO} holds {@link LeaderboardDTO#getRaceList() races} as {@link RaceColumnDTO} objects. + * Those map their fleet names to the {@link RegattaAndRaceIdentifier} which identifies the tracked race + * representing the fleet race within the race column. + *

+ * + * On the other hand, a {@link LeaderboardRowDTO} has {@link LeaderboardEntryDTO}, keyed by race column name. The + * entry DTOs, in turn, store the {@link RaceIdentifier} of the race in which the respective competitor achieved the + * score. + *

+ * + * With this information it is possible to identify the competitors who participated in a particular tracked race, + * as identified through the race parameter. + * + * @return all competitors for which the {@link #getLeaderboard() leaderboard} has an entry whose + * {@link LeaderboardEntryDTO#race} equals race + */ + private Iterable getCompetitors(RaceIdentifier race) { + Set result = new HashSet(); + for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) { + if (raceColumn.hasTrackedRace(race)) { + for (Map.Entry e : getLeaderboard().rows.entrySet()) { + LeaderboardEntryDTO entry = e.getValue().fieldsByRaceColumnName.get(raceColumn.getRaceColumnName()); + if (entry != null && entry.race != null && entry.race.equals(race)) { + result.add(e.getKey()); + } + } + } + } + return result; + } + + private SortableColumn getDefaultSortColumn() { + SortableColumn defaultSortColumn = null; + if (raceNameForDefaultSorting != null) { + defaultSortColumn = getRaceColumnByRaceName(raceNameForDefaultSorting); + } + if (defaultSortColumn == null) { + defaultSortColumn = getRankColumn(); + } + return defaultSortColumn; + } + + private void clearSelection() { + for (LeaderboardRowDTO row : getData().getList()) { + leaderboardSelectionModel.setSelected(row, false); + } + } + + private RankColumn getRankColumn() { + return rankColumn; + } + + protected void setLeaderboard(LeaderboardDTO leaderboard) { + this.leaderboard = leaderboard; + } + + @Override + public LeaderboardDTO getLeaderboard() { + return leaderboard; + } + + private void adjustColumnLayout(LeaderboardDTO leaderboard) { + ensureRankColumn(); + ensureSailIDAndCompetitorColumn(); + boolean hasCarryColumn = updateCarryColumn(leaderboard); + adjustOverallDetailColumns(leaderboard, hasCarryColumn?CARRY_COLUMN_INDEX+1:CARRY_COLUMN_INDEX); + // first remove race columns no longer needed: + removeUnusedRaceColumns(leaderboard); + if (leaderboard != null) { + createMissingAndAdjustExistingRaceColumns(leaderboard); + ensureTotalsColumn(); + } + } + + /** + * Ensures that the columns requested by {@link #selectedOverallDetailColumns} are in the table. Assumes that if there are + * any existing overall details columns, they start at indexOfFirstOverallDetailsColumn and are in the order + * defined by {@link #getAvailableOverallDetailColumnTypes()}. + * + * @param indexOfFirstOverallDetailsColumn + * tells the column index for the first overall details column + */ + private void adjustOverallDetailColumns(LeaderboardDTO leaderboard, int indexOfFirstOverallDetailsColumn) { + List> overallDetailColumnsToShow = new ArrayList>(); + // ensure the ordering in overallDetailColumnsToShow conforms to the ordering of getAvailableOverallDetailColumnTypes() + for (DetailType overallDetailType : getAvailableOverallDetailColumnTypes()) { + if (selectedOverallDetailColumns.contains(overallDetailType)) { + overallDetailColumnsToShow.add(overallDetailColumnMap.get(overallDetailType)); + } + } + int currentColumnIndex = indexOfFirstOverallDetailsColumn; + int i = 0; // index into overallDetailColumnToShow + Column currentColumn = currentColumnIndex < getLeaderboardTable().getColumnCount() ? + getLeaderboardTable().getColumn(currentColumnIndex) : null; + // repeat until no more column to check for removal and no more column left to check for need to insert + while (iraceColumn + */ + private void correctColumnData(RaceColumn raceColumn, RaceColumnDTO race) { + int columnIndex = getRaceColumnPosition(raceColumn); + if (raceColumn.isExpansionEnabled() != race.hasTrackedRaces() || race.isMedalRace() != raceColumn.isMedalRace()) { + if (raceColumn.isExpanded()) { + raceColumn.toggleExpansion(); // remove children from table + } + removeColumn(columnIndex); + insertColumn(columnIndex, createRaceColumn(race)); + } + } + + /** + * Removes all RaceColumns, starting at count {@link raceColumnStartIndex raceColumnStartIndex} + * + * @param raceColumnStartIndex + * The index of the race column should be deleted from. + * @param raceName + * The name of the racing column until the table should be cleared. + */ + private void removeRaceColumnFromRaceColumnStartIndexBeforeRace(int raceColumnStartIndex, RaceColumnDTO race) { + int counter = 0; + for (int leaderboardposition = 0; leaderboardposition < getLeaderboardTable().getColumnCount(); leaderboardposition++) { + Column c = getLeaderboardTable().getColumn(leaderboardposition); + if (c instanceof RaceColumn) { + RaceColumn raceColumn = (RaceColumn) c; + if (!raceColumn.getRaceColumnName().equals(race.getRaceColumnName())) { + if (raceColumnStartIndex == counter) { + removeColumn(raceColumn); + } + } else { + return; + } + counter++; + } + } + } + + /** + * Gets a ColumnPosition of a raceColumn + * + * @param raceColumn + * The column for which the position is to be found in the leaderboard table + * @return the position. Returns -1 if raceColumn not existing in leaderboardTable. + */ + private int getRaceColumnPosition(RaceColumn raceColumn) { + for (int leaderboardposition = 0; leaderboardposition < getLeaderboardTable().getColumnCount(); leaderboardposition++) { + Column c = getLeaderboardTable().getColumn(leaderboardposition); + if (c instanceof RaceColumn) { + RaceColumn rc = (RaceColumn) c; + if (rc.equals(raceColumn)) { + return leaderboardposition; + } + } + } + return -1; + } + + /** + * This method returns the position where a race column should get inserted. + * + * @param raceName + * the name of the race to insert + * @param listpos + * the position of the race in the {@link selectedRaceColumns selectedRaceColumns} + * @return the position of a race column right before which to insert the race column so that it is the + * listposth race in the table or -1 if no such race column was found, e.g., in case the column + * needs to be inserted as the last race in the table + */ + private int getColumnPositionToInsert(RaceColumnDTO race, int listpos) { + int raceColumnCounter = 0; + int noRaceColumnCounter = 0; + boolean raceColumnFound = false; + for (int leaderboardPosition = 0; !raceColumnFound + && leaderboardPosition < getLeaderboardTable().getColumnCount(); leaderboardPosition++) { + Column c = getLeaderboardTable().getColumn(leaderboardPosition); + if (c instanceof RaceColumn) { + // RaceColumn raceColumn = (RaceColumn) c; + if (raceColumnCounter == listpos) { + raceColumnFound = true; + } else { + raceColumnCounter++; + } + } else { + noRaceColumnCounter++; + } + } + if (raceColumnFound) { + return raceColumnCounter + noRaceColumnCounter; + } else { + return -1; + } + } + + /** + * Removes all columns of type {@link RaceColumn} from the leaderboard table if their name is not in the list of + * names of the selectedRaceColumns. + */ + private void removeRaceColumnsNotSelected(Iterable selectedRaceColumns) { + Set selectedRaceColumnNames = new HashSet(); + for (RaceColumnDTO selectedRaceColumn : selectedRaceColumns) { + selectedRaceColumnNames.add(selectedRaceColumn.getRaceColumnName()); + } + List> columnsToRemove = new ArrayList>(); + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column c = getLeaderboardTable().getColumn(i); + if (c instanceof RaceColumn + && (leaderboard == null || !selectedRaceColumnNames.contains(((RaceColumn) c) + .getRaceColumnName()))) { + columnsToRemove.add(c); + } + } + for (Column c : columnsToRemove) { + removeColumn(c); + } + } + + /** + * Existing and matching race columns may still need to be removed, re-created and inserted because the "tracked" + * property may have changed, changing the columns expandability. + */ + private void createMissingAndAdjustExistingRaceColumns(LeaderboardDTO leaderboard) { + // Correct order of races in selectedRaceColum + Iterable correctedOrderSelectedRaces = raceColumnSelection.getSelectedRaceColumnsOrderedAsInLeaderboard(leaderboard); + removeRaceColumnsNotSelected(correctedOrderSelectedRaces); + for (int selectedRaceCount = 0; selectedRaceCount < Util.size(correctedOrderSelectedRaces); selectedRaceCount++) { + RaceColumnDTO selectedRaceColumn = Util.get(correctedOrderSelectedRaces, selectedRaceCount); + final RaceColumn raceColumn = selectedRaceColumn == null ? null + : getRaceColumnByRaceColumnName(selectedRaceColumn.name); + if (raceColumn != null) { + // remove all raceColumns, starting at a specific selectedRaceCount, up to but excluding the selected + // raceName with the result that selectedRace is at position selectedRaceCount afterwards + removeRaceColumnFromRaceColumnStartIndexBeforeRace(selectedRaceCount, selectedRaceColumn); + correctColumnData(raceColumn, selectedRaceColumn); + } else { + // get correct position to insert the column + int positionToInsert = getColumnPositionToInsert(selectedRaceColumn, selectedRaceCount); + if (positionToInsert != -1) { + insertColumn(positionToInsert, createRaceColumn(selectedRaceColumn)); + } else { + // Add the raceColumn with addRaceColumn, if no RaceColumn is existing in leaderboard + addRaceColumn(createRaceColumn(selectedRaceColumn)); + } + } + } + } + + protected RaceColumn createRaceColumn(RaceColumnDTO raceInLeaderboard) { + TextRaceColumn textRaceColumn = new TextRaceColumn(raceInLeaderboard, shallExpandRaceColumn(raceInLeaderboard), + SortingOrder.ASCENDING, RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE); + return textRaceColumn; + } + + private boolean shallExpandRaceColumn(RaceColumnDTO raceColumnDTO) { + return showRaceDetails && raceColumnDTO.hasTrackedRaces(); + } + + private void removeUnusedRaceColumns(LeaderboardDTO leaderboard) { + List> columnsToRemove = new ArrayList>(); + for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { + Column c = getLeaderboardTable().getColumn(i); + if (c instanceof RaceColumn + && (leaderboard == null || !leaderboard.raceListContains(((RaceColumn) c).getRaceColumnName()))) { + columnsToRemove.add(c); + } + } + // Tricky issue: if the race column is currently expanded, we can't know anymore how many detail columns + // there are because the updated LeaderboardDTO object doesn't contain the race anymore. We have to + // collapse and remove all LegColumns following the RaceColumn + for (Column c : columnsToRemove) { + removeColumn(c); + } + } + + /** + * If the last column is the totals column, remove it. Add the race column as the last column. + */ + private void addRaceColumn(RaceColumn raceColumn) { + if (getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalsColumn) { + removeColumn(getLeaderboardTable().getColumnCount() - 1); + } + addColumn(raceColumn); + } + + private void ensureRankColumn() { + if (getLeaderboardTable().getColumnCount() == RANK_COLUMN_INDEX) { + addColumn(getDefaultSortColumn()); + } else { + if (!(getLeaderboardTable().getColumn(RANK_COLUMN_INDEX) instanceof RankColumn)) { + throw new RuntimeException("The first column must always be the rank column but it was of type " + + getLeaderboardTable().getColumn(RANK_COLUMN_INDEX).getClass().getName()); + } + } + } + + private void ensureSailIDAndCompetitorColumn() { + if (getLeaderboardTable().getColumnCount() <= SAIL_ID_COLUMN_INDEX) { + addColumn(new SailIDColumn(new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + })); + addColumn(createCompetitorColumn()); + } else { + if (!(getLeaderboardTable().getColumn(SAIL_ID_COLUMN_INDEX) instanceof SailIDColumn)) { + throw new RuntimeException("The second column must always be the sail ID column but it was of type " + + getLeaderboardTable().getColumn(SAIL_ID_COLUMN_INDEX).getClass().getName()); + } + } + } + + protected CompetitorColumn createCompetitorColumn() { + return new CompetitorColumn(new CompetitorColumnBase(this, getStringMessages(), + new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + })); + } + + private void ensureTotalsColumn() { + // add a totals column on the right + if (getLeaderboardTable().getColumnCount() == 0 + || !(getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalsColumn)) { + addColumn(new TotalsColumn(TOTAL_COLUMN_STYLE)); + } + } + + /** + * If the leaderboard {@link LeaderboardDTO#hasCarriedPoints has carried points} and if column #1 + * (second column, right of the competitor column) does not exist or is not of type {@link CarryColumn}, all columns + * starting from #1 will be removed and a {@link CarryColumn} will be added. If the leaderboard has no carried + * points but the display still shows a carry column, the column is removed. + * + * @return true if a carry column is now part of the table (regardless of whether it existed before), + * false otherwise + */ + protected boolean updateCarryColumn(LeaderboardDTO leaderboard) { + final boolean needsCarryColumn = leaderboard != null && leaderboard.hasCarriedPoints; + if (needsCarryColumn) { + ensureCarryColumn(); + } else { + ensureNoCarryColumn(); + } + return needsCarryColumn; + } + + private void ensureNoCarryColumn() { + if (getLeaderboardTable().getColumnCount() > CARRY_COLUMN_INDEX + && getLeaderboardTable().getColumn(CARRY_COLUMN_INDEX) instanceof CarryColumn) { + removeColumn(CARRY_COLUMN_INDEX); + } + } + + protected void ensureCarryColumn() { + if (getLeaderboardTable().getColumnCount() <= CARRY_COLUMN_INDEX + || !(getLeaderboardTable().getColumn(CARRY_COLUMN_INDEX) instanceof CarryColumn)) { + while (getLeaderboardTable().getColumnCount() > CARRY_COLUMN_INDEX) { + removeColumn(CARRY_COLUMN_INDEX); + } + addColumn(createCarryColumn()); + } + } + + protected CarryColumn createCarryColumn() { + return new CarryColumn(); + } + + protected SortedCellTable getLeaderboardTable() { + return leaderboardTable; + } + + protected SailingServiceAsync getSailingService() { + return sailingService; + } + + protected String getLeaderboardName() { + return leaderboardName; + } + + protected void setLeaderboardName(String leaderboardName) { + this.leaderboardName = leaderboardName; + } + + protected ErrorReporter getErrorReporter() { + return errorReporter; + } + + protected ListDataProvider getData() { + return getLeaderboardTable().getDataProvider(); + } + + @Override + public void timeChanged(Date date) { + loadCompleteLeaderboard(getLeaderboardDisplayDate()); + } + + @Override + public void playStateChanged(PlayStates playState, PlayModes playMode) { + currentlyHandlingPlayStateChange = true; + playPause.setHTML(getPlayPauseImgHtml(playState)); + playPause.setTitle(playState == PlayStates.Playing ? stringMessages.pauseAutomaticRefresh() : stringMessages + .autoRefresh()); + if (!settingsUpdatedExplicitly && playMode != oldPlayMode) { + // if settings weren't explicitly modified, auto-switch to live mode settings and sort for + // any pre-selected race; we need to copy the previously selected race columns to the new RaceColumnSelection + updateSettings(LeaderboardSettingsFactory.getInstance().createNewSettingsForPlayMode( + playMode, + /* don't touch columnToSort if no race was pre-selected */ preSelectedRace == null ? null + : preSelectedRace.getRaceName(), + /* don't change nameOfRaceColumnToShow */null, + /* set nameOfRaceToShow if race was pre-selected */preSelectedRace == null ? null : preSelectedRace + .getRaceName(), getRaceColumnSelection(), showOverallLeaderboardsOnSamePage)); + } + currentlyHandlingPlayStateChange = false; + oldPlayMode = playMode; + } + + @Override + public void playSpeedFactorChanged(double newPlaySpeedFactor) { + // nothing to do + } + + private void showCompareCompetitorsDialog() { + int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); + if (selectedCompetitorsCount < 1) { + Window.alert(stringMessages.selectAtLeastOneCompetitor()); + } else { + List races = getTrackedRacesIdentifiers(); + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, errorReporter, races, 5000l /* requestInterval*/); + CompareCompetitorsChartDialog chartDialog = new CompareCompetitorsChartDialog(sailingService, races, raceTimesInfoProvider, + competitorSelectionProvider, timer, stringMessages, errorReporter); + chartDialog.show(); + } + } + + private void showRankChartDialog() { + RankChartDialog chartDialog = new RankChartDialog(sailingService, leaderboardName, competitorSelectionProvider, + timer, stringMessages, errorReporter, /* compactChart */ false); + chartDialog.show(); + } + + private List getTrackedRacesIdentifiers() { + List result = new ArrayList(); + for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) { + for (FleetDTO fleet : raceColumn.getFleets()) { + if (raceColumn.getRaceIdentifier(fleet) != null) { + result.add(raceColumn.getRaceIdentifier(fleet)); + } + } + } + return result; + } + + @Override + public boolean hasSettings() { + return true; + } + + @Override + public boolean hasToolbar() { + return false; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent() { + return new LeaderboardSettingsDialogComponent(Collections.unmodifiableList(selectedManeuverDetails), + Collections.unmodifiableList(selectedLegDetails), Collections.unmodifiableList(selectedRaceDetails), + Collections.unmodifiableList(selectedOverallDetailColumns), /* All races to select */ leaderboard.getRaceList(), + raceColumnSelection.getSelectedRaceColumnsOrderedAsInLeaderboard(leaderboard), raceColumnSelection, autoExpandPreSelectedRace, + showOverallLeaderboardsOnSamePage, timer.getRefreshInterval(), timer.getLivePlayDelayInMillis(), stringMessages); + } + + @Override + public String getLocalizedShortName() { + return stringMessages.leaderboard(); + } + + private LeaderboardRowDTO getRow(CompetitorDTO competitor) { + for (LeaderboardRowDTO row : getData().getList()) { + if (row.competitor.equals(competitor)) { + return row; + } + } + return null; + } + + @Override + public void addedToSelection(CompetitorDTO competitor) { + LeaderboardRowDTO row = getRow(competitor); + if (row != null) { + leaderboardSelectionModel.setSelected(row, true); + } + } + + @Override + public void removedFromSelection(CompetitorDTO competitor) { + LeaderboardRowDTO row = getRow(competitor); + if (row != null) { + leaderboardSelectionModel.setSelected(row, false); + } + } + + @Override + public BusyIndicator getBusyIndicator() { + return busyIndicator; + } + + @Override + public boolean hasBusyIndicator() { + return true; + } + + private Iterable getSelectedRows() { + ArrayList selectedRows = new ArrayList(); + for (LeaderboardRowDTO row : getData().getList()) { + if (leaderboardSelectionModel.isSelected(row)) { + selectedRows.add(row); + } + } + return selectedRows; + } + + @Override + public void competitorsListChanged(Iterable competitors) { + timeChanged(timer.getTime()); + } + + public RaceColumnSelection getRaceColumnSelection() { + return raceColumnSelection; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java index 697c83cf3a4..0de071ef28b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java @@ -103,7 +103,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone meneuverContent.addStyleName("dialogInnerContent"); List currentMeneuverDetailSelection = maneuverDetailSelection; for (DetailType detailType : ManeuverCountRaceColumn.getAvailableManeuverDetailColumnTypes()) { - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(detailType, stringMessages)); + CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(detailType)); checkbox.setValue(currentMeneuverDetailSelection.contains(detailType)); maneuverDetailCheckboxes.put(detailType, checkbox); meneuverContent.add(checkbox); @@ -150,7 +150,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone raceDetailDialogContent.addStyleName("dialogInnerContent"); List currentRaceDetailSelection = raceDetailSelection; for (DetailType type : LeaderboardPanel.getAvailableRaceDetailColumnTypes()) { - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type, stringMessages)); + CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type)); checkbox.setValue(currentRaceDetailSelection.contains(type)); raceDetailCheckboxes.put(type, checkbox); raceDetailDialogContent.add(checkbox); @@ -167,7 +167,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone overallDetailDialogContent.addStyleName("dialogInnerContent"); List currentOverallDetailSelection = overallDetailSelection; for (DetailType type : LeaderboardPanel.getAvailableOverallDetailColumnTypes()) { - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type, stringMessages)); + CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type)); checkbox.setValue(currentOverallDetailSelection.contains(type)); overallDetailCheckboxes.put(type, checkbox); overallDetailDialogContent.add(checkbox); @@ -184,7 +184,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone legDetailsContent.addStyleName("dialogInnerContent"); List currentLegDetailSelection = legDetailSelection; for (DetailType type : LegColumn.getAvailableLegDetailColumnTypes()) { - CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type, stringMessages)); + CheckBox checkbox = dialog.createCheckbox(DetailTypeFormatter.format(type)); checkbox.setValue(currentLegDetailSelection.contains(type)); legDetailCheckboxes.put(type, checkbox); legDetailsContent.add(checkbox); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java index b8d0c1ae918..e11bb8bf3e7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegColumn.java @@ -239,59 +239,47 @@ public class LegColumn extends ExpandableSortableColumn { LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, String detailColumnStyle) { Map> result = new HashMap>(); + result.put(DetailType.DISTANCE_TRAVELED, - new FormattedDoubleLegDetailColumn(stringMessages.distanceInMeters(), "["+stringMessages.distanceInMetersUnit()+"]", - new DistanceTraveledInMeters(), DetailType.DISTANCE_TRAVELED.getPrecision(), - DetailType.DISTANCE_TRAVELED.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new FormattedDoubleLegDetailColumn( - stringMessages.averageSpeedInKnots(), "["+stringMessages.averageSpeedInKnotsUnit()+"]", new AverageSpeedOverGroundInKnots(), - DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS.getPrecision(), - DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new FormattedDoubleLegDetailColumn( - stringMessages.currentSpeedOverGroundInKnots(), "["+stringMessages.currentSpeedOverGroundInKnotsUnit()+"]", new CurrentSpeedOverGroundInKnots(), - DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS.getPrecision(), - DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleLegDetailColumn(DetailType.DISTANCE_TRAVELED, new DistanceTraveledInMeters(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, + new FormattedDoubleLegDetailColumn(DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, new AverageSpeedOverGroundInKnots(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, + new FormattedDoubleLegDetailColumn(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new CurrentSpeedOverGroundInKnots(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, - new FormattedDoubleLegDetailColumn(stringMessages.estimatedTimeToNextWaypointInSeconds(), - "["+stringMessages.estimatedTimeToNextWaypointInSecondsUnit()+"]", - new EstimatedTimeToNextWaypointInSeconds(), - DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS.getPrecision(), - DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS.getDefaultSortingOrder(), + new FormattedDoubleLegDetailColumn(DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, new EstimatedTimeToNextWaypointInSeconds(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.GAP_TO_LEADER_IN_SECONDS, - new FormattedDoubleLegDetailColumn(stringMessages.gapToLeaderInSeconds(), "["+stringMessages.gapToLeaderInSecondsUnit()+"]", - new GapToLeaderInSeconds(), DetailType.GAP_TO_LEADER_IN_SECONDS.getPrecision(), - DetailType.GAP_TO_LEADER_IN_SECONDS.getDefaultSortingOrder(), - detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleLegDetailColumn(DetailType.GAP_TO_LEADER_IN_SECONDS, new GapToLeaderInSeconds(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.GAP_CHANGE_SINCE_LEG_START_IN_SECONDS, - new FormattedDoubleLegDetailColumn(stringMessages.gapChangeSinceLegStartInSeconds(), "["+stringMessages.gapToLeaderInSecondsUnit()+"]", - new GapChangeSinceLegStartInSeconds(), DetailType.GAP_CHANGE_SINCE_LEG_START_IN_SECONDS.getPrecision(), - DetailType.GAP_CHANGE_SINCE_LEG_START_IN_SECONDS.getDefaultSortingOrder(), + new FormattedDoubleLegDetailColumn(DetailType.GAP_CHANGE_SINCE_LEG_START_IN_SECONDS, new GapChangeSinceLegStartInSeconds(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED, new SideToWhichMarkAtLegStartWasRoundedColumn(stringMessages.sideToWhichMarkAtLegStartWasRounded(), new SideToWhichMarkAtLegStartWasRounded(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS, - new FormattedDoubleLegDetailColumn(stringMessages.velocityMadeGoodInKnots(), - "["+stringMessages.velocityMadeGoodInKnotsUnit()+"]", new VelocityMadeGoodInKnots(), - DetailType.VELOCITY_MADE_GOOD_IN_KNOTS.getPrecision(), - DetailType.VELOCITY_MADE_GOOD_IN_KNOTS.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, new FormattedDoubleLegDetailColumn( - stringMessages.windwardDistanceToGoInMeters(), "["+stringMessages.windwardDistanceToGoInMetersUnit()+"]", new WindwardDistanceToGoInMeters(), - DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS.getPrecision(), - DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.RANK_GAIN, new RankGainColumn(stringMessages.rankGain(), new RankGain(), - detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.NUMBER_OF_MANEUVERS, new ManeuverCountLegDetailsColumn( - stringMessages.numberOfManeuvers(), detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.TIME_TRAVELED, new FormattedDoubleLegDetailColumn(stringMessages.time(), - "[" + stringMessages.secondsUnit() + "]", new TimeTraveledInSeconds(), DetailType.TIME_TRAVELED.getPrecision(), - DetailType.TIME_TRAVELED.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS, new FormattedDoubleLegDetailColumn(stringMessages.averageCrossTrackErrorInMeters(), - "[" + stringMessages.metersUnit() + "]", new AverageCrossTrackErrorInMeters(), - DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS.getPrecision(), - DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS.getDefaultSortingOrder(), - detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleLegDetailColumn(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS, new VelocityMadeGoodInKnots(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, + new FormattedDoubleLegDetailColumn(DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, new WindwardDistanceToGoInMeters(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.RANK_GAIN, new RankGainColumn(stringMessages.rankGain(), new RankGain(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.NUMBER_OF_MANEUVERS, new ManeuverCountLegDetailsColumn(stringMessages.numberOfManeuvers(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.TIME_TRAVELED, + new FormattedDoubleLegDetailColumn(DetailType.TIME_TRAVELED, new TimeTraveledInSeconds(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS, + new FormattedDoubleLegDetailColumn(DetailType.AVERAGE_CROSS_TRACK_ERROR_IN_METERS, new AverageCrossTrackErrorInMeters(), detailHeaderStyle, detailColumnStyle)); + return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegDetailColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegDetailColumn.java index b8587bbf090..2e958c5f37d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegDetailColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LegDetailColumn.java @@ -16,6 +16,8 @@ public abstract class LegDetailColumn, Rendering private final String headerStyle; private final String columnStyle; private final String unit; + + private SafeHtmlHeader header; public interface LegDetailField> { T get(LeaderboardRowDTO row); @@ -30,9 +32,25 @@ public abstract class LegDetailColumn, Rendering this.field = field; this.headerStyle = headerStyle; this.columnStyle = columnStyle; + InitializeHeader(); } - protected String getTitle() { + private void InitializeHeader() { + //Code to add a tooltip to the title. Do NOT delete. This is saved to use it, after some refactorings are done. +// SafeHtmlBuilder builder = new SafeHtmlBuilder().appendHtmlConstant("

"); +// builder.appendEscaped(title).appendHtmlConstant("
"); + + SafeHtmlBuilder builder = new SafeHtmlBuilder().appendEscaped(title).appendHtmlConstant("
"); + if (unit == null) { + builder.appendHtmlConstant(" "); + } else { + builder.appendEscaped(unit); + } + + header = new SafeHtmlHeader(builder.toSafeHtml()); + } + + protected String getTitle() { return title; } @@ -70,13 +88,6 @@ public abstract class LegDetailColumn, Rendering @Override public Header getHeader() { - SafeHtmlBuilder builder = new SafeHtmlBuilder().appendEscaped(title).appendHtmlConstant("
"); - if (unit == null) { - builder.appendHtmlConstant(" "); - } else { - builder.appendEscaped(unit); - } - SafeHtmlHeader header = new SafeHtmlHeader(builder.toSafeHtml()); return header; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java index c6aa4254a4b..78255ad5416 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ManeuverCountRaceColumn.java @@ -238,29 +238,25 @@ public class ManeuverCountRaceColumn extends ExpandableSortableColumn im LeaderboardPanel leaderboardPanel, StringMessages stringMessages, String detailHeaderStyle, String detailColumnStyle) { Map> result = new HashMap>(); - result.put(DetailType.TACK, new FormattedDoubleLegDetailColumn(stringMessages.tack(), "", new NumberOfTacks(), - DetailType.TACK.getPrecision(), DetailType.TACK.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); - result.put(DetailType.JIBE, new FormattedDoubleLegDetailColumn(stringMessages.jibe(), "", new NumberOfJibes(), - DetailType.JIBE.getPrecision(), DetailType.JIBE.getDefaultSortingOrder(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.TACK, new FormattedDoubleLegDetailColumn(DetailType.TACK, new NumberOfTacks(), detailHeaderStyle, detailColumnStyle)); + + result.put(DetailType.JIBE, new FormattedDoubleLegDetailColumn(DetailType.JIBE, new NumberOfJibes(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.PENALTY_CIRCLE, - new FormattedDoubleLegDetailColumn(stringMessages.penaltyCircle(), "", new NumberOfPenaltyCircles(), - DetailType.PENALTY_CIRCLE.getPrecision(), DetailType.PENALTY_CIRCLE.getDefaultSortingOrder(), - detailHeaderStyle, detailColumnStyle)); + new FormattedDoubleLegDetailColumn(DetailType.PENALTY_CIRCLE, new NumberOfPenaltyCircles(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.AVERAGE_TACK_LOSS_IN_METERS, - new FormattedDoubleLegDetailColumn(stringMessages.averageTackLossInMeters(), - stringMessages.distanceInMetersUnit(), new AverageTackLossInMeters(), - DetailType.AVERAGE_TACK_LOSS_IN_METERS.getPrecision(), DetailType.AVERAGE_TACK_LOSS_IN_METERS.getDefaultSortingOrder(), + new FormattedDoubleLegDetailColumn(DetailType.AVERAGE_TACK_LOSS_IN_METERS, new AverageTackLossInMeters(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.AVERAGE_JIBE_LOSS_IN_METERS, - new FormattedDoubleLegDetailColumn(stringMessages.averageJibeLossInMeters(), - stringMessages.distanceInMetersUnit(), new AverageJibeLossInMeters(), - DetailType.AVERAGE_JIBE_LOSS_IN_METERS.getPrecision(), DetailType.AVERAGE_JIBE_LOSS_IN_METERS.getDefaultSortingOrder(), + new FormattedDoubleLegDetailColumn(DetailType.AVERAGE_JIBE_LOSS_IN_METERS, new AverageJibeLossInMeters(), detailHeaderStyle, detailColumnStyle)); + result.put(DetailType.AVERAGE_MANEUVER_LOSS_IN_METERS, - new FormattedDoubleLegDetailColumn(stringMessages.averageManeuverLossInMeters(), - stringMessages.distanceInMetersUnit(), new AverageManeuverLossInMeters(), - DetailType.AVERAGE_MANEUVER_LOSS_IN_METERS.getPrecision(), DetailType.AVERAGE_MANEUVER_LOSS_IN_METERS.getDefaultSortingOrder(), + new FormattedDoubleLegDetailColumn(DetailType.AVERAGE_MANEUVER_LOSS_IN_METERS, new AverageManeuverLossInMeters(), detailHeaderStyle, detailColumnStyle)); + return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java index df3343a30d6..d230052c2cd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MaxSpeedOverallColumn.java @@ -22,11 +22,7 @@ public class MaxSpeedOverallColumn extends FormattedDoubleLegDetailColumn { } public MaxSpeedOverallColumn(StringMessages stringMessages, String headerStyle, String columnStyle) { - super(stringMessages.maximumSpeedOverGroundInKnots(), "[" + stringMessages.currentSpeedOverGroundInKnotsUnit() + "]", - new MaxSpeedOverallField(), - DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS.getPrecision(), - DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS.getDefaultSortingOrder(), - headerStyle, columnStyle); + super(DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, new MaxSpeedOverallField(), headerStyle, columnStyle); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeSailedColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeSailedColumn.java index fdf2eb79aed..dcdcaa39ddc 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeSailedColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeSailedColumn.java @@ -22,11 +22,7 @@ public class TotalTimeSailedColumn extends FormattedDoubleLegDetailColumn { } public TotalTimeSailedColumn(StringMessages stringMessages, String headerStyle, String columnStyle) { - super(stringMessages.totalTimeSailedInSeconds(), "[" + stringMessages.hhmmssUnit() + "]", - new TotalTimeSailedField(), - DetailType.TOTAL_TIME_SAILED_IN_SECONDS.getPrecision(), - DetailType.TOTAL_TIME_SAILED_IN_SECONDS.getDefaultSortingOrder(), - headerStyle, columnStyle); + super(DetailType.TOTAL_TIME_SAILED_IN_SECONDS, new TotalTimeSailedField(), headerStyle, columnStyle); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractMediaPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractMediaPlayer.java new file mode 100644 index 00000000000..f8e1e0b2722 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractMediaPlayer.java @@ -0,0 +1,38 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.Date; + +import com.sap.sailing.gwt.ui.client.MediaPlayer; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public abstract class AbstractMediaPlayer implements MediaPlayer { + + private static final int TOLERATED_LAG_IN_MILLISECONDS = 2000; + private final MediaTrack mediaTrack; + + protected AbstractMediaPlayer(MediaTrack mediaTrack) { + this.mediaTrack = mediaTrack; + } + + public MediaTrack getMediaTrack() { + return mediaTrack; + } + + public void alignTime(Date raceTime) { + long mediaStartTimeInMillis = mediaTrack.startTime.getTime(); + long mediaTimeInMillis = mediaStartTimeInMillis + Math.round(getTime() * 1000); + long raceTimeInMillis = raceTime.getTime(); + long mediaLaggingBehindRaceInMillis = raceTimeInMillis - mediaTimeInMillis; + if (Math.abs(mediaLaggingBehindRaceInMillis) > TOLERATED_LAG_IN_MILLISECONDS) { + double mediaTime = (raceTimeInMillis - mediaStartTimeInMillis) / 1000; + if (mediaTime < 0) { + pause(); + } else if (mediaTime > getDuration()) { + pause(); + } else { + setTime(mediaTime); + } + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AudioPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AudioPlayer.java new file mode 100644 index 00000000000..3ea197569d4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AudioPlayer.java @@ -0,0 +1,132 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import com.google.gwt.dom.client.AudioElement; +import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.media.client.Audio; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public class AudioPlayer extends AbstractMediaPlayer { + + /** + * isReady and the ..Cache fields are required because setting the audio element's play state + * needs to be deferred until the media's metadata has been loaded. + */ + private boolean isReady = false; + private double deferredMediaTime; + private double deferredPlaybackSpeed = 1; + private boolean isDeferredPlaying; + private boolean isDeferredMuted; + + private final Audio audio; + + public AudioPlayer(MediaTrack mediaTrack) { + super(mediaTrack); + audio = Audio.createIfSupported(); + if (audio != null) { + audio.setControls(false); + audio.setAutoplay(false); + audio.setLoop(false); + audio.setMuted(false); + audio.setPreload(MediaElement.PRELOAD_AUTO); + addNativeEventHandlers(audio.getAudioElement()); + audio.setSrc(mediaTrack.url); + audio.setTitle(mediaTrack.title); + } + } + + native void addNativeEventHandlers(AudioElement audioElement) /*-{ + var that = this; + audioElement.addEventListener('canplay', + function() { + that.@com.sap.sailing.gwt.ui.raceboard.AudioPlayer::initPlayState()(); + }); + }-*/; + + private void initPlayState() { + if (!isReady && (audio != null)) { + audio.setCurrentTime(deferredMediaTime); + audio.setMuted(isDeferredMuted); + audio.setPlaybackRate(deferredPlaybackSpeed); + if (isDeferredPlaying) { + audio.play(); + } else { + audio.pause(); + } + } + isReady = true; + } + + @Override + public boolean isPaused() { + if (isReady) { + return audio.isPaused(); + } else { + return !isDeferredPlaying; + } + } + + @Override + public void pause() { + isDeferredPlaying = false; + if (isReady && audio != null) { + audio.pause(); + } + } + + @Override + public void play() { + isDeferredPlaying = true; + if (isReady && audio != null) { + audio.play(); + } + } + + @Override + public void setPlaybackSpeed(double playbackSpeed) { + this.deferredPlaybackSpeed = playbackSpeed; + if (isReady && audio != null) { + audio.setPlaybackRate(playbackSpeed); + } + } + + @Override + public void setMuted(boolean isToBeMuted) { + isDeferredMuted = isToBeMuted; + if (isReady && audio != null) { + audio.setMuted(isToBeMuted); + } + } + + @Override + public double getDuration() { + if (isReady) { + return audio.getDuration(); + } else { + return Double.NaN; + } + } + + @Override + public double getTime() { + if (isReady) { + return audio.getCurrentTime(); + } else { + return Double.NaN; + } + } + + @Override + public void setTime(double mediaTime) { + this.deferredMediaTime = mediaTime; + if (isReady && audio != null) { + audio.setCurrentTime(mediaTime); + } + } + + @Override + public void destroy() { + audio.pause(); + audio.setSrc(null); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/MediaSelectionDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/MediaSelectionDialog.java new file mode 100644 index 00000000000..4a8746573f5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/MediaSelectionDialog.java @@ -0,0 +1,142 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RadioButton; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public class MediaSelectionDialog implements CloseHandler { + + static interface MediaSelectionListener { + + void audioChanged(MediaTrack audioTrack); + + void videoSelected(MediaTrack videoTrack); + + void videoDeselected(MediaTrack videoTrack); + + } + + private final DialogBox dialogControl; + private final MediaSelectionListener mediaSelectionListener; + private final Map videoCheckBoxes = new HashMap(); + + MediaSelectionDialog(MediaSelectionListener mediaSelectionListener) { + this.mediaSelectionListener = mediaSelectionListener; + dialogControl = new DialogBox(true, false); + dialogControl.setText("Select Playback Media"); + dialogControl.addCloseHandler(this); + } + + public void show(Collection videoTracks, Set selectedVideos, Collection audioTracks, MediaTrack selectedAudioTrack, UIObject popupLocation) { + Panel grid = new VerticalPanel(); + if (!audioTracks.isEmpty()) { + grid.add(createAudioHeader()); + grid.add((RadioButton) createAudioButton(null, selectedAudioTrack)); + for (MediaTrack audioTrack : audioTracks) { + grid.add(createAudioButton(audioTrack, selectedAudioTrack)); + } + } + if (!videoTracks.isEmpty()) { + grid.add(createVideoHeader()); + for (MediaTrack videoTrack : videoTracks) { + grid.add(createVideoCheckBox(videoTrack, selectedVideos)); + } + } + dialogControl.add(grid); + dialogControl.showRelativeTo(popupLocation); + } + + private Widget createVideoCheckBox(final MediaTrack videoTrack, Set selectedVideos) { + CheckBox videoCheckBox = new CheckBox(videoTrack.title); + videoCheckBoxes.put(videoTrack, videoCheckBox); + videoCheckBox.setTitle(videoTrack.toString()); + videoCheckBox.setValue(selectedVideos.contains(videoTrack)); + videoCheckBox.addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange(ValueChangeEvent changeEvent) { + if (changeEvent.getValue()) { + mediaSelectionListener.videoSelected(videoTrack); + } else { + mediaSelectionListener.videoDeselected(videoTrack); + } + } + }); + + return videoCheckBox; + } + + public void selectVideo(MediaTrack videoTrack) { + CheckBox videoCheckBox = videoCheckBoxes.get(videoTrack); + if (videoCheckBox != null) { + videoCheckBox.setValue(true); + } + } + + public void unselectVideo(MediaTrack videoTrack) { + CheckBox videoCheckBox = videoCheckBoxes.get(videoTrack); + if (videoCheckBox != null) { + videoCheckBox.setValue(false); + } + } + + private Widget createVideoHeader() { + Label audioHeader = new Label("Videos"); + return audioHeader; + } + + private Widget createAudioButton(final MediaTrack audioTrack, MediaTrack selectedAudioTrack) { + String title = audioTrack != null ? audioTrack.title: "Sound off"; + String tooltip = audioTrack != null ? audioTrack.toString() : "Turn off all sound channels."; + RadioButton audioButton = new RadioButton("group-name", title); + audioButton.setTitle(tooltip); + audioButton.setValue(audioTrack == selectedAudioTrack); + audioButton.addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange(ValueChangeEvent changeEvent) { + if (changeEvent.getValue()) { + mediaSelectionListener.audioChanged(audioTrack); + } + } + }); + return audioButton; + } + + private Widget createAudioHeader() { + Label audioHeader = new Label("Audio Tracks"); + return audioHeader; + } + + @Override + public void onClose(CloseEvent arg0) { + dialogControl.clear(); + videoCheckBoxes.clear(); + } + + public boolean isShowing() { + return dialogControl.isShowing(); + } + + public void hide() { + dialogControl.hide(false); + } + +} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/MediaSelector.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/MediaSelector.java new file mode 100644 index 00000000000..1e4b038fc98 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/MediaSelector.java @@ -0,0 +1,401 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.dom.client.AudioElement; +import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.dom.client.Style; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.media.client.Audio; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.Window.ClosingEvent; +import com.google.gwt.user.client.Window.ClosingHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.MediaPlayer; +import com.sap.sailing.gwt.ui.client.PlayStateListener; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; +import com.sap.sailing.gwt.ui.client.TimeListener; +import com.sap.sailing.gwt.ui.client.Timer.PlayModes; +import com.sap.sailing.gwt.ui.client.Timer.PlayStates; +import com.sap.sailing.gwt.ui.raceboard.MediaSelectionDialog.MediaSelectionListener; +import com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer.PopupCloseListener; +import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public class MediaSelector implements RaceTimesInfoProviderListener, PlayStateListener, TimeListener, + AsyncCallback>, MediaSelectionListener, CloseHandler, ClosingHandler { + + private final CheckBox toggleMediaButton; + private final Button manageMediaButton; + + private final MediaSelectionDialog mediaSelectionDialog; + private final AudioElement mediaCanPlayTester; + + private MediaPlayer activeAudioPlayer; + private final Map videoPlayers = new HashMap(); + private final Set audioTracks = new HashSet(); + private final Set videoTracks = new HashSet(); + private final ErrorReporter errorReporter; + + private Date currentRaceTime; + private double currentPlaybackSpeed = 1.0d; + private PlayStates currentPlayState = PlayStates.Paused; + + public MediaSelector(ErrorReporter errorReporter) { + this.errorReporter = errorReporter; + + Window.addCloseHandler(this); + Window.addWindowClosingHandler(this); + + mediaSelectionDialog = new MediaSelectionDialog(this); + + manageMediaButton = new Button(); + manageMediaButton.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + if (mediaSelectionDialog.isShowing()) { + mediaSelectionDialog.hide(); + } else { + MediaTrack playingAudioTrack = activeAudioPlayer != null ? activeAudioPlayer.getMediaTrack() : null; + Set playingVideoTracks = videoPlayers.keySet(); + mediaSelectionDialog.show(videoTracks, playingVideoTracks, audioTracks, playingAudioTrack, toggleMediaButton); + } + } + }); + manageMediaButton.addStyleName("raceBoardNavigation-settingsButton"); + manageMediaButton.getElement().getStyle().setFloat(Style.Float.LEFT); + manageMediaButton.setTitle("Configure Media"); + + + toggleMediaButton = new CheckBox("Audio & Video"); + toggleMediaButton.addStyleName("raceBoardNavigation-innerElement"); + toggleMediaButton.getElement().getStyle().setFloat(Style.Float.LEFT); + toggleMediaButton.addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange(ValueChangeEvent event) { + + if (toggleMediaButton.getValue()) { + playDefault(); + } else { + stopAll(); + } + + } + + }); + + setWidgetsVisible(false); + + Audio audio = Audio.createIfSupported(); + if (audio != null) { + mediaCanPlayTester = audio.getAudioElement(); + } else { + mediaCanPlayTester = null; + } + } + + private boolean canPlay(MediaTrack mediaTrack) { + if (mediaCanPlayTester != null) { + if (mediaTrack.isYoutube()) { + return true; + } else { + String canPlay = mediaCanPlayTester.canPlayType(mediaTrack.typeToString()); + if (mediaCanPlayTester != null) { + return MediaElement.CAN_PLAY_PROBABLY.equals(canPlay) || MediaElement.CAN_PLAY_MAYBE.equals(canPlay); + } else { + return false; + } + } + } else { + return false; + } + } + + + private void playDefault() { + MediaTrack defaultVideo = getDefaultVideo(); + if (defaultVideo != null) { + videoSelected(defaultVideo ); + } + MediaTrack defaultAudio = getDefaultAudio(); + if (defaultAudio != null) { + audioChanged(defaultAudio); + } + } + + private MediaTrack getDefaultAudio() { + if (!audioTracks.isEmpty()) { + return audioTracks.iterator().next(); + } else { + return getDefaultVideo(); + } + } + + private MediaTrack getDefaultVideo() { + if (!videoTracks.isEmpty()) { + return videoTracks.iterator().next(); + } else { + return null; + } + } + + private void stopAll() { + clear(); + } + + + @Override + public void playStateChanged(PlayStates playState, PlayModes playMode) { + this.currentPlayState = playState; + if (PlayModes.Replay.equals(playMode)) { + switch (this.currentPlayState) { + case Playing: + startPlaying(); + break; + case Paused: + //fall through to Stopped + case Stopped: + pausePlaying(); + default: + break; + } + } else { + // TODO: Live mode not supported, yet. + } + } + + @Override + public void playSpeedFactorChanged(double newPlaySpeedFactor) { + this.currentPlaybackSpeed = newPlaySpeedFactor; + if (activeAudioPlayer != null) { + activeAudioPlayer.setPlaybackSpeed(this.currentPlaybackSpeed); + } + for (MediaPlayer videoPlayer : videoPlayers.values()) { + videoPlayer.setPlaybackSpeed(this.currentPlaybackSpeed); + } + } + + private void pausePlaying() { + if (activeAudioPlayer != null) { + activeAudioPlayer.pause(); + } + + for (MediaPlayer player : videoPlayers.values()) { + if (!player.isPaused()) { + player.pause(); + } + } + } + + private void startPlaying() { + if (activeAudioPlayer != null) { + activeAudioPlayer.play(); + } + for (MediaPlayer player : videoPlayers.values()) { + if (player.isPaused()) { + player.play(); + } + } + } + + @Override + public void timeChanged(Date raceTime) { + this.currentRaceTime = raceTime; + if (activeAudioPlayer != null) { + activeAudioPlayer.alignTime(this.currentRaceTime); + } + for (MediaPlayer player : videoPlayers.values()) { + player.alignTime(this.currentRaceTime); + } + } + + @Override + public void onFailure(Throwable caught) { + setWidgetsVisible(false); + errorReporter + .reportError("Remote Procedure Call getMediaTracksForRace(...) - Failure: " + caught.getMessage()); + } + + @Override + public void onSuccess(Collection mediaTracks) { + for (MediaTrack mediaTrack : mediaTracks) { + if (canPlay(mediaTrack)) { + switch (mediaTrack.mimeType.mediaType) { + case video: + videoTracks.add(mediaTrack); + case audio: //Intentional fall through. Video tracks are also considered audio tracks! + audioTracks.add(mediaTrack); + break; + } + } + } + setWidgetsVisible(mediaTracks.size() > 0); + } + + private void setWidgetsVisible(boolean isVisible) { + manageMediaButton.setVisible(isVisible); + toggleMediaButton.setVisible(isVisible); + } + + @Override + public void raceTimesInfosReceived(Map raceTimesInfo) { + // TODO Auto-generated method stub + + } + + @Override + public void audioChanged(MediaTrack audioTrack) { + if (activeAudioPlayer != null) { // --> then reset active audio player + + if (activeAudioPlayer.getMediaTrack() == audioTrack) { + return; //nothing changed + } + + if (videoPlayers.containsKey(activeAudioPlayer.getMediaTrack())) { //pre-change audioPlayer is one of the videoPlayers + activeAudioPlayer.setMuted(true); + } else { //pre-change audioPlayer is a dedicated audio-only player + activeAudioPlayer.destroy(); + } + activeAudioPlayer = null; + } + if ((audioTrack != null) && audioTrack.isYoutube()) { // --> Youtube videos can't be played for audio-only. So add a video player first. + videoSelected(audioTrack); + mediaSelectionDialog.selectVideo(audioTrack); + } + MediaPlayer playingVideo = videoPlayers.get(audioTrack); + if (playingVideo != null) { + activeAudioPlayer = playingVideo; + activeAudioPlayer.setMuted(false); + } else { + assignNewAudioPlayer(audioTrack); + } + + updateToggleButton(); + + } + + @Override + public void videoSelected(final MediaTrack videoTrack) { + MediaPlayer playingVideo = videoPlayers.get(videoTrack); + if (playingVideo == null) { + PopupCloseListener popCloseListener = new PopupCloseListener() { + @Override + public void popupClosed() { + videoDeselected(videoTrack); + } + }; + final PopupWindowPlayer popupPlayer; + if (videoTrack.isYoutube()) { + popupPlayer = new YoutubeWindowPlayer(videoTrack, popCloseListener); + } else { + popupPlayer = new VideoWindowPlayer(videoTrack, popCloseListener); + } + videoPlayers.put(videoTrack, popupPlayer); + if ((activeAudioPlayer != null) && (activeAudioPlayer.getMediaTrack() == videoTrack)) { //selected video track has been playing as audio-only + activeAudioPlayer.pause(); + activeAudioPlayer = popupPlayer; + popupPlayer.setMuted(false); + } else { + popupPlayer.setMuted(true); + } + synchPlayState(popupPlayer); + } else { + //nothing changed + } + + updateToggleButton(); + + } + + @Override + public void videoDeselected(MediaTrack videoTrack) { + mediaSelectionDialog.unselectVideo(videoTrack); + MediaPlayer removedVideoPlayer = videoPlayers.remove(videoTrack); + removedVideoPlayer.destroy(); + if (removedVideoPlayer == activeAudioPlayer) { //in case this video has been the sound source, replace the video player with a dedicated audio player + if (removedVideoPlayer.getMediaTrack().isYoutube()) { + assignNewAudioPlayer(null); + } else { + assignNewAudioPlayer(removedVideoPlayer.getMediaTrack()); + } + } + + updateToggleButton(); + + } + + private void updateToggleButton() { + toggleMediaButton.setValue((activeAudioPlayer != null) || (!videoPlayers.isEmpty())); + } + + private void assignNewAudioPlayer(MediaTrack audioTrack) { + if (audioTrack != null) { + activeAudioPlayer = new AudioPlayer(audioTrack); + + synchPlayState(activeAudioPlayer); + } else { + activeAudioPlayer = null; + } + } + + private void synchPlayState(final MediaPlayer mediaPlayer) { + mediaPlayer.setPlaybackSpeed(currentPlaybackSpeed); + mediaPlayer.alignTime(this.currentRaceTime); + switch (this.currentPlayState) { + case Playing: + mediaPlayer.play(); + break; + case Paused: + //fall through to Stopped + case Stopped: + mediaPlayer.pause(); + default: + break; + } + } + + public Widget[] widgets() { + return new Widget[] {toggleMediaButton, manageMediaButton}; + } + + @Override + public void onClose(CloseEvent arg0) { + clear(); + } + + @Override + public void onWindowClosing(ClosingEvent arg0) { + clear(); + } + + private void clear() { + if (activeAudioPlayer != null) { + videoPlayers.remove(activeAudioPlayer); //just to ensure that a potentially audio-playing video player is not destroyed a second time in the following video loop. + activeAudioPlayer.destroy(); + activeAudioPlayer = null; + } + for (MediaPlayer videoControl : videoPlayers.values()) { + videoControl.destroy(); + } + videoPlayers.clear(); + updateToggleButton(); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/PopupWindowPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/PopupWindowPlayer.java new file mode 100644 index 00000000000..88dc40fdf03 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/PopupWindowPlayer.java @@ -0,0 +1,149 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.user.client.Window; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public abstract class PopupWindowPlayer extends AbstractMediaPlayer { + + interface PopupCloseListener { + void popupClosed(); + } + + private final JavaScriptObject playerWindow; + private final PopupCloseListener popupCloseListener; + + protected PopupWindowPlayer(MediaTrack mediaTrack, PopupCloseListener popupCloseListener) { + super(mediaTrack); + this.popupCloseListener = popupCloseListener; + + String playerWindowUrl = getPlayerWindowUrl(); + + String codesvr = Window.Location.getParameter("gwt.codesvr"); + if (codesvr != null) { + playerWindowUrl = playerWindowUrl + "&gwt.codesvr=" + codesvr; + } + + playerWindow = openWindow(playerWindowUrl); + registerNativeStuff(); + } + + protected abstract String getPlayerWindowUrl(); + + native JavaScriptObject openWindow(String url) /*-{ + return $wnd.open(url, '_blank', "resizable=yes, scrollbars=no, toolbar=no, menubar=no, status=no, location=no, directories=no, personalbar=no, width=660, height=380"); + }-*/; + + @Override + public native void destroy() /*-{ + var playerWindow = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!playerWindow.closed) { + playerWindow.close(); + } + }-*/; + + native JavaScriptObject registerNativeStuff() /*-{ + var that = this; + var window = that.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + window.onbeforeunload = function() { + that.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::onClosingPopup()(); + } + window.parent.deferredPlayState = { + isDeferredPlaying: false, + isDeferredMuted: true, + deferredMediaTime: 0, + playbackSpeed: 1 + }; + }-*/; + + private void onClosingPopup() { + pause(); + if (popupCloseListener != null) { + popupCloseListener.popupClosed(); + } + } + + @Override + public native void play() /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + window.parent.deferredPlayState.isDeferredPlaying = true; + } else { + window.parent.videoPlayer.play(); + } + + }-*/; + + @Override + public native void pause() /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + window.parent.deferredPlayState.isDeferredPlaying = false; + } else { + window.parent.videoPlayer.pause(); + } + + }-*/; + + @Override + public native void setTime(double mediaTime) /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + window.parent.deferredPlayState.deferredMediaTime = mediaTime; + } else { + window.parent.videoPlayer.setTime(mediaTime); + } + + }-*/; + + @Override + public native void setMuted(boolean muted) /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + window.parent.deferredPlayState.isDeferredMuted = muted; + } else { + window.parent.videoPlayer.setMuted(muted); + } + }-*/; + + @Override + public native boolean isPaused() /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + return !window.parent.deferredPlayState.isDeferredPlaying; + } else { + return window.parent.videoPlayer.isPaused(); + } + }-*/; + + @Override + public native double getDuration() /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + return NaN; + } else { + return window.parent.videoPlayer.getDuration(); + } + }-*/; + + @Override + public native double getTime() /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + return window.parent.deferredPlayState.deferredMediaTime; + } else { + return window.parent.videoPlayer.getTime(); + } + }-*/; + + @Override + public native void setPlaybackSpeed(double playbackSpeed) /*-{ + var window = this.@com.sap.sailing.gwt.ui.raceboard.PopupWindowPlayer::playerWindow; + if (!window.parent.videoPlayer) { + window.parent.deferredPlayState.deferredPlaybackSpeed = playbackSpeed; + } else { + window.parent.videoPlayer.setPlaybackSpeed(playbackSpeed); + } + }-*/; + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java index 17bae804c8f..0754e23ea43 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java @@ -1,214 +1,214 @@ - -package com.sap.sailing.gwt.ui.raceboard; - -import java.util.Collections; -import java.util.List; - -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.DockLayoutPanel; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.RootLayoutPanel; -import com.sap.sailing.domain.common.LeaderboardNameConstants; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; -import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; -import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback; -import com.sap.sailing.gwt.ui.client.ParallelExecutionHolder; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; -import com.sap.sailing.gwt.ui.client.Timer; -import com.sap.sailing.gwt.ui.client.Timer.PlayModes; -import com.sap.sailing.gwt.ui.client.UserAgentChecker; -import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; -import com.sap.sailing.gwt.ui.shared.RaceDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.UserDTO; - -public class RaceBoardEntryPoint extends AbstractEntryPoint { - private RaceDTO selectedRace; - - private String regattaName; - private String raceName; - private String leaderboardName; - private String leaderboardGroupName; - private RaceBoardViewModes viewMode; - - private GlobalNavigationPanel globalNavigationPanel; - - @Override - protected void doOnModuleLoad() { - super.doOnModuleLoad(); - regattaName = Window.Location.getParameter("regattaName"); - raceName = Window.Location.getParameter("raceName"); - String leaderboardNameParamValue = Window.Location.getParameter("leaderboardName"); - String leaderboardGroupNameParamValue = Window.Location.getParameter("leaderboardGroupName"); - String viewModeParamValue = Window.Location.getParameter("viewMode"); - // set the view mode for the race board - if (viewModeParamValue != null && !viewModeParamValue.isEmpty()) { - try { - viewMode = RaceBoardViewModes.valueOf(viewModeParamValue); - } catch (IllegalArgumentException e) { - viewMode = RaceBoardViewModes.ONESCREEN; - } - } else { - viewMode = RaceBoardViewModes.ONESCREEN; - } - if (leaderboardNameParamValue == null || leaderboardNameParamValue.isEmpty()) { - leaderboardName = LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME; - } else { - leaderboardName = leaderboardNameParamValue; - } - if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) { - leaderboardGroupName = leaderboardGroupNameParamValue; - } - if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty()) { - createErrorPage("This page requires a valid regatta name and race name."); - return; - } - final ParallelExecutionCallback> getLeaderboardNamesCallback = new ParallelExecutionCallback>(); - final ParallelExecutionCallback> getRegattasCallback = new ParallelExecutionCallback>(); - final ParallelExecutionCallback getLeaderboardGroupByNameCallback = new ParallelExecutionCallback(); - final ParallelExecutionCallback getUserCallback = new ParallelExecutionCallback(); - if (leaderboardGroupName != null) { - new ParallelExecutionHolder(getLeaderboardNamesCallback, getLeaderboardGroupByNameCallback, getRegattasCallback, getUserCallback) { - @Override - public void handleSuccess() { - checkUrlParameters(getLeaderboardNamesCallback.getData(), - getLeaderboardGroupByNameCallback.getData(), getRegattasCallback.getData(), getUserCallback.getData()); - } - @Override - public void handleFailure(Throwable t) { - reportError("Error trying to create the raceboard: " + t.getMessage()); - } - }; - } else { - new ParallelExecutionHolder(getLeaderboardNamesCallback, getRegattasCallback, getUserCallback) { - @Override - public void handleSuccess() { - checkUrlParameters(getLeaderboardNamesCallback.getData(), null, getRegattasCallback.getData(), getUserCallback.getData()); - } - @Override - public void handleFailure(Throwable t) { - reportError("Error trying to create the raceboard: " + t.getMessage()); - } - }; - } - sailingService.getRegattas(getRegattasCallback); - sailingService.getLeaderboardNames(getLeaderboardNamesCallback); - if (leaderboardGroupName != null) { - sailingService.getLeaderboardGroupByName(leaderboardGroupNameParamValue, false /*withGeoLocationData*/, getLeaderboardGroupByNameCallback); - } - userManagementService.getUser(getUserCallback); - } - - private void checkUrlParameters(List leaderboardNames, LeaderboardGroupDTO leaderboardGroup, List regattas, UserDTO user) { - if (!leaderboardNames.contains(leaderboardName)) { - createErrorPage(stringMessages.noSuchLeaderboard()); - return; - } - if (leaderboardGroupName != null && leaderboardGroup != null) { - boolean foundLeaderboard = false; - for(StrippedLeaderboardDTO leaderBoard: leaderboardGroup.leaderboards) { - if(leaderBoard.name.equals(leaderboardName)) { - foundLeaderboard = true; - break; - } - } - if (!foundLeaderboard) { - createErrorPage("the leaderboard is not contained in this leaderboard group."); - return; - } - } - selectedRace = findRace(regattaName, raceName, regattas); - if (selectedRace == null) { - createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName); - return; - } - - RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); - List singletonList = Collections.singletonList(selectedRace.getRaceIdentifier()); - raceSelectionModel.setSelection(singletonList); - Timer timer = new Timer(PlayModes.Replay, 1000l); - RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, this, singletonList, 5000l /* requestInterval*/); - RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, user, timer, raceSelectionModel, leaderboardName, leaderboardGroupName, - RaceBoardEntryPoint.this, stringMessages, userAgent, viewMode, raceTimesInfoProvider); - raceBoardPanel.fillRegattas(regattas); - - switch (viewMode) { - case ONESCREEN: - createRaceBoardInOneScreenMode(raceBoardPanel); - break; - } - } - - private RaceDTO findRace(String regattaName, String raceName, List regattas) { - for (RegattaDTO regattaDTO : regattas) { - if (regattaDTO.name.equals(regattaName)) { - for (RaceDTO raceDTO : regattaDTO.races) { - if (raceDTO.name.equals(raceName)) { - return raceDTO; - } - } - } - } - return null; - } - - private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) { - FlowPanel timeLineInnerBgPanel = new FlowPanel(); - timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel"); - timeLineInnerBgPanel.add(raceBoardPanel.getTimeWidget()); - - FlowPanel timeLineInnerPanel = new FlowPanel(); - timeLineInnerPanel.add(timeLineInnerBgPanel); - timeLineInnerPanel.addStyleName("timeLineInnerPanel"); - - FlowPanel timelinePanel = new FlowPanel(); - timelinePanel.add(timeLineInnerPanel); - timelinePanel.addStyleName("timeLinePanel"); - - return timelinePanel; - } - - private FlowPanel createLogoAndTitlePanel(RaceBoardPanel raceBoardPanel) { - globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, leaderboardName, leaderboardGroupName); - LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(regattaName, selectedRace.name, stringMessages, this) { - @Override - public void onResize() { - super.onResize(); - if (isSmallWidth()) { - remove(globalNavigationPanel); - } else { - add(globalNavigationPanel); - } - } - }; - logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); - if (!isSmallWidth()) { - logoAndTitlePanel.add(globalNavigationPanel); - } - return logoAndTitlePanel; - } - - private void createRaceBoardInOneScreenMode(RaceBoardPanel raceBoardPanel) { - DockLayoutPanel p = new DockLayoutPanel(Unit.PX); - RootLayoutPanel.get().add(p); - FlowPanel toolbarPanel = new FlowPanel(); - toolbarPanel.add(raceBoardPanel.getNavigationWidget()); - if (!UserAgentChecker.INSTANCE.isUserAgentSupported(userAgent)) { - HTML lbl = new HTML(stringMessages.warningBrowserUnsupported()); - lbl.setStyleName("browserOptimizedMessage"); - toolbarPanel.add(lbl); - } - FlowPanel logoAndTitlePanel = createLogoAndTitlePanel(raceBoardPanel); - FlowPanel timePanel = createTimePanel(raceBoardPanel); - p.addNorth(logoAndTitlePanel, 68); - p.addNorth(toolbarPanel, 40); - p.addSouth(timePanel, 90); - p.add(raceBoardPanel); - p.addStyleName("dockLayoutPanel"); - } -} + +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.Collections; +import java.util.List; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.sap.sailing.domain.common.LeaderboardNameConstants; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; +import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; +import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback; +import com.sap.sailing.gwt.ui.client.ParallelExecutionHolder; +import com.sap.sailing.gwt.ui.client.RaceSelectionModel; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; +import com.sap.sailing.gwt.ui.client.Timer; +import com.sap.sailing.gwt.ui.client.Timer.PlayModes; +import com.sap.sailing.gwt.ui.client.UserAgentChecker; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.RaceDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sailing.gwt.ui.shared.UserDTO; + +public class RaceBoardEntryPoint extends AbstractEntryPoint { + private RaceDTO selectedRace; + + private String regattaName; + private String raceName; + private String leaderboardName; + private String leaderboardGroupName; + private RaceBoardViewModes viewMode; + + private GlobalNavigationPanel globalNavigationPanel; + + @Override + protected void doOnModuleLoad() { + super.doOnModuleLoad(); + regattaName = Window.Location.getParameter("regattaName"); + raceName = Window.Location.getParameter("raceName"); + String leaderboardNameParamValue = Window.Location.getParameter("leaderboardName"); + String leaderboardGroupNameParamValue = Window.Location.getParameter("leaderboardGroupName"); + String viewModeParamValue = Window.Location.getParameter("viewMode"); + // set the view mode for the race board + if (viewModeParamValue != null && !viewModeParamValue.isEmpty()) { + try { + viewMode = RaceBoardViewModes.valueOf(viewModeParamValue); + } catch (IllegalArgumentException e) { + viewMode = RaceBoardViewModes.ONESCREEN; + } + } else { + viewMode = RaceBoardViewModes.ONESCREEN; + } + if (leaderboardNameParamValue == null || leaderboardNameParamValue.isEmpty()) { + leaderboardName = LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME; + } else { + leaderboardName = leaderboardNameParamValue; + } + if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) { + leaderboardGroupName = leaderboardGroupNameParamValue; + } + if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty()) { + createErrorPage("This page requires a valid regatta name and race name."); + return; + } + final ParallelExecutionCallback> getLeaderboardNamesCallback = new ParallelExecutionCallback>(); + final ParallelExecutionCallback> getRegattasCallback = new ParallelExecutionCallback>(); + final ParallelExecutionCallback getLeaderboardGroupByNameCallback = new ParallelExecutionCallback(); + final ParallelExecutionCallback getUserCallback = new ParallelExecutionCallback(); + if (leaderboardGroupName != null) { + new ParallelExecutionHolder(getLeaderboardNamesCallback, getLeaderboardGroupByNameCallback, getRegattasCallback, getUserCallback) { + @Override + public void handleSuccess() { + checkUrlParameters(getLeaderboardNamesCallback.getData(), + getLeaderboardGroupByNameCallback.getData(), getRegattasCallback.getData(), getUserCallback.getData()); + } + @Override + public void handleFailure(Throwable t) { + reportError("Error trying to create the raceboard: " + t.getMessage()); + } + }; + } else { + new ParallelExecutionHolder(getLeaderboardNamesCallback, getRegattasCallback, getUserCallback) { + @Override + public void handleSuccess() { + checkUrlParameters(getLeaderboardNamesCallback.getData(), null, getRegattasCallback.getData(), getUserCallback.getData()); + } + @Override + public void handleFailure(Throwable t) { + reportError("Error trying to create the raceboard: " + t.getMessage()); + } + }; + } + sailingService.getRegattas(getRegattasCallback); + sailingService.getLeaderboardNames(getLeaderboardNamesCallback); + if (leaderboardGroupName != null) { + sailingService.getLeaderboardGroupByName(leaderboardGroupNameParamValue, false /*withGeoLocationData*/, getLeaderboardGroupByNameCallback); + } + userManagementService.getUser(getUserCallback); + } + + private void checkUrlParameters(List leaderboardNames, LeaderboardGroupDTO leaderboardGroup, List regattas, UserDTO user) { + if (!leaderboardNames.contains(leaderboardName)) { + createErrorPage(stringMessages.noSuchLeaderboard()); + return; + } + if (leaderboardGroupName != null && leaderboardGroup != null) { + boolean foundLeaderboard = false; + for(StrippedLeaderboardDTO leaderBoard: leaderboardGroup.leaderboards) { + if(leaderBoard.name.equals(leaderboardName)) { + foundLeaderboard = true; + break; + } + } + if (!foundLeaderboard) { + createErrorPage("the leaderboard is not contained in this leaderboard group."); + return; + } + } + selectedRace = findRace(regattaName, raceName, regattas); + if (selectedRace == null) { + createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName); + return; + } + + RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); + List singletonList = Collections.singletonList(selectedRace.getRaceIdentifier()); + raceSelectionModel.setSelection(singletonList); + Timer timer = new Timer(PlayModes.Replay, 1000l); + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, this, singletonList, 5000l /* requestInterval*/); + RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, user, timer, raceSelectionModel, leaderboardName, leaderboardGroupName, + RaceBoardEntryPoint.this, stringMessages, userAgent, viewMode, raceTimesInfoProvider); + raceBoardPanel.fillRegattas(regattas); + + switch (viewMode) { + case ONESCREEN: + createRaceBoardInOneScreenMode(raceBoardPanel); + break; + } + } + + private RaceDTO findRace(String regattaName, String raceName, List regattas) { + for (RegattaDTO regattaDTO : regattas) { + if (regattaDTO.name.equals(regattaName)) { + for (RaceDTO raceDTO : regattaDTO.races) { + if (raceDTO.name.equals(raceName)) { + return raceDTO; + } + } + } + } + return null; + } + + private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) { + FlowPanel timeLineInnerBgPanel = new FlowPanel(); + timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel"); + timeLineInnerBgPanel.add(raceBoardPanel.getTimeWidget()); + + FlowPanel timeLineInnerPanel = new FlowPanel(); + timeLineInnerPanel.add(timeLineInnerBgPanel); + timeLineInnerPanel.addStyleName("timeLineInnerPanel"); + + FlowPanel timelinePanel = new FlowPanel(); + timelinePanel.add(timeLineInnerPanel); + timelinePanel.addStyleName("timeLinePanel"); + + return timelinePanel; + } + + private FlowPanel createLogoAndTitlePanel(RaceBoardPanel raceBoardPanel) { + globalNavigationPanel = new GlobalNavigationPanel(stringMessages, true, leaderboardName, leaderboardGroupName); + LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(regattaName, selectedRace.name, stringMessages, this) { + @Override + public void onResize() { + super.onResize(); + if (isSmallWidth()) { + remove(globalNavigationPanel); + } else { + add(globalNavigationPanel); + } + } + }; + logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); + if (!isSmallWidth()) { + logoAndTitlePanel.add(globalNavigationPanel); + } + return logoAndTitlePanel; + } + + private void createRaceBoardInOneScreenMode(RaceBoardPanel raceBoardPanel) { + DockLayoutPanel p = new DockLayoutPanel(Unit.PX); + RootLayoutPanel.get().add(p); + FlowPanel toolbarPanel = new FlowPanel(); + toolbarPanel.add(raceBoardPanel.getNavigationWidget()); + if (!UserAgentChecker.INSTANCE.isUserAgentSupported(userAgent)) { + HTML lbl = new HTML(stringMessages.warningBrowserUnsupported()); + lbl.setStyleName("browserOptimizedMessage"); + toolbarPanel.add(lbl); + } + FlowPanel logoAndTitlePanel = createLogoAndTitlePanel(raceBoardPanel); + FlowPanel timePanel = createTimePanel(raceBoardPanel); + p.addNorth(logoAndTitlePanel, 68); + p.addNorth(toolbarPanel, 40); + p.addSouth(timePanel, 90); + p.add(raceBoardPanel); + p.addStyleName("dockLayoutPanel"); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java index 117656c0a0c..f2e975405b8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java @@ -1,382 +1,399 @@ -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 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.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.MenuBar; -import com.google.gwt.user.client.ui.ToggleButton; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -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.RegattaDisplayer; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.TimeListener; -import com.sap.sailing.gwt.ui.client.TimeRangeWithZoomModel; -import com.sap.sailing.gwt.ui.client.Timer; -import com.sap.sailing.gwt.ui.client.UserAgentDetails; -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.sailing.gwt.ui.shared.RaceDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sailing.gwt.ui.shared.UserDTO; -import com.sap.sailing.gwt.ui.shared.charts.MultiChartPanel; -import com.sap.sailing.gwt.ui.shared.charts.WindChart; -import com.sap.sailing.gwt.ui.shared.charts.WindChartSettings; -import com.sap.sailing.gwt.ui.shared.components.Component; -import com.sap.sailing.gwt.ui.shared.components.ComponentViewer; -import com.sap.sailing.gwt.ui.shared.components.SettingsDialog; -import com.sap.sailing.gwt.ui.shared.racemap.RaceMap; - -/** - * A panel 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 FormPanel implements RegattaDisplayer, RaceSelectionChangeListener { - private final SailingServiceAsync sailingService; - private final StringMessages stringMessages; - private final ErrorReporter errorReporter; - private String raceBoardName; - private RaceBoardViewModes viewMode; - - /** - * Updated upon each {@link #fillRegattas(List)} - */ - private final Map racesByIdentifier; - - /** - * The offset when scrolling with the menu entry anchors (in the top right corner). - */ - private int scrollOffset; - - private final List componentViewers; - private FlowPanel componentsNavigationPanel; - private FlowPanel settingsPanel; - private RaceTimePanel timePanel; - private final Timer timer; - private final RaceSelectionProvider raceSelectionProvider; - private final UserAgentDetails userAgent; - private final CompetitorSelectionModel competitorSelectionModel; - private final TimeRangeWithZoomModel timeRangeWithZoomModel; - private final RegattaAndRaceIdentifier selectedRaceIdentifier; - - private LeaderboardPanel leaderboardPanel; - private WindChart windChart; - private MultiChartPanel competitorChart; - - /** - * The component viewer in ONESCREEN view mode. null if in CASCADE view mode - */ - private SideBySideComponentViewer leaderboardAndMapViewer; - - private final AsyncActionsExecutor asyncActionsExecutor; - - private final RaceTimesInfoProvider raceTimesInfoProvider; - - public RaceBoardPanel(SailingServiceAsync sailingService, UserDTO theUser, Timer timer, - RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, String leaderboardGroupName, - ErrorReporter errorReporter, final StringMessages stringMessages, UserAgentDetails userAgent, - RaceBoardViewModes viewMode, RaceTimesInfoProvider raceTimesInfoProvider) { - this.sailingService = sailingService; - this.stringMessages = stringMessages; - this.raceSelectionProvider = theRaceSelectionProvider; - this.raceTimesInfoProvider = raceTimesInfoProvider; - this.scrollOffset = 0; - raceSelectionProvider.addRaceSelectionChangeListener(this); - racesByIdentifier = new HashMap(); - selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); - this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); - this.errorReporter = errorReporter; - this.userAgent = userAgent; - this.viewMode = viewMode; - asyncActionsExecutor = new AsyncActionsExecutor(); - FlowPanel mainPanel = new FlowPanel(); - mainPanel.setSize("100%", "100%"); - setWidget(mainPanel); - this.timer = timer; - timeRangeWithZoomModel = new TimeRangeWithZoomModel(); - componentViewers = new ArrayList(); - competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); - componentsNavigationPanel = new FlowPanel(); - componentsNavigationPanel.addStyleName("raceBoardNavigation"); - switch (this.viewMode) { - case ONESCREEN: - createOneScreenView(leaderboardName, leaderboardGroupName, mainPanel); - getElement().getStyle().setMarginLeft(12, Unit.PX); - getElement().getStyle().setMarginRight(12, Unit.PX); - break; - } - timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider); - timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); - raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); - raceSelectionProvider.addRaceSelectionChangeListener(timePanel); - timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - } - - private void createOneScreenView(String leaderboardName, String leaderboardGroupName, FlowPanel mainPanel) { - // create the default leaderboard and select the right race - leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName); - leaderboardPanel.addStyleName(LeaderboardPanel.LEADERBOARD_MARGIN_STYLE); - RaceMap raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, competitorSelectionModel, stringMessages); - raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); - raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); - List> components = new ArrayList>(); - competitorChart = new MultiChartPanel(sailingService, asyncActionsExecutor, competitorSelectionModel, raceSelectionProvider, - timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true); - competitorChart.setVisible(false); - competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - components.add(competitorChart); - windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), +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 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.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.ToggleButton; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +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.RegattaDisplayer; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.TimeListener; +import com.sap.sailing.gwt.ui.client.TimeRangeWithZoomModel; +import com.sap.sailing.gwt.ui.client.Timer; +import com.sap.sailing.gwt.ui.client.UserAgentDetails; +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.sailing.gwt.ui.shared.RaceDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.UserDTO; +import com.sap.sailing.gwt.ui.shared.charts.MultiChartPanel; +import com.sap.sailing.gwt.ui.shared.charts.WindChart; +import com.sap.sailing.gwt.ui.shared.charts.WindChartSettings; +import com.sap.sailing.gwt.ui.shared.components.Component; +import com.sap.sailing.gwt.ui.shared.components.ComponentViewer; +import com.sap.sailing.gwt.ui.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.shared.racemap.RaceMap; + +/** + * A panel 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 FormPanel implements RegattaDisplayer, RaceSelectionChangeListener { + private final SailingServiceAsync sailingService; + private final MediaServiceAsync mediaService; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private String raceBoardName; + private RaceBoardViewModes viewMode; + + /** + * Updated upon each {@link #fillRegattas(List)} + */ + private final Map racesByIdentifier; + + /** + * The offset when scrolling with the menu entry anchors (in the top right corner). + */ + private int scrollOffset; + + private final List componentViewers; + private FlowPanel componentsNavigationPanel; + private FlowPanel settingsPanel; + private RaceTimePanel timePanel; + private final Timer timer; + private final RaceSelectionProvider raceSelectionProvider; + private final UserAgentDetails userAgent; + private final CompetitorSelectionModel competitorSelectionModel; + private final TimeRangeWithZoomModel timeRangeWithZoomModel; + private final RegattaAndRaceIdentifier selectedRaceIdentifier; + + private LeaderboardPanel leaderboardPanel; + private WindChart windChart; + private MultiChartPanel competitorChart; + + /** + * The component viewer in ONESCREEN view mode. null if in CASCADE view mode + */ + private SideBySideComponentViewer leaderboardAndMapViewer; + + private final AsyncActionsExecutor asyncActionsExecutor; + + private final RaceTimesInfoProvider raceTimesInfoProvider; + + public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, UserDTO theUser, Timer timer, + RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, String leaderboardGroupName, + ErrorReporter errorReporter, final StringMessages stringMessages, UserAgentDetails userAgent, + RaceBoardViewModes viewMode, RaceTimesInfoProvider raceTimesInfoProvider) { + this.sailingService = sailingService; + this.mediaService = mediaService; + this.stringMessages = stringMessages; + this.raceSelectionProvider = theRaceSelectionProvider; + this.raceTimesInfoProvider = raceTimesInfoProvider; + this.scrollOffset = 0; + raceSelectionProvider.addRaceSelectionChangeListener(this); + racesByIdentifier = new HashMap(); + selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); + this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); + this.errorReporter = errorReporter; + this.userAgent = userAgent; + this.viewMode = viewMode; + asyncActionsExecutor = new AsyncActionsExecutor(); + FlowPanel mainPanel = new FlowPanel(); + mainPanel.setSize("100%", "100%"); + setWidget(mainPanel); + this.timer = timer; + timeRangeWithZoomModel = new TimeRangeWithZoomModel(); + componentViewers = new ArrayList(); + competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); + componentsNavigationPanel = new FlowPanel(); + componentsNavigationPanel.addStyleName("raceBoardNavigation"); + switch (this.viewMode) { + case ONESCREEN: + createOneScreenView(leaderboardName, leaderboardGroupName, mainPanel); + getElement().getStyle().setMarginLeft(12, Unit.PX); + getElement().getStyle().setMarginRight(12, Unit.PX); + break; + } + timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider); + timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); + raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); + raceSelectionProvider.addRaceSelectionChangeListener(timePanel); + timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + } + + private void createOneScreenView(String leaderboardName, String leaderboardGroupName, FlowPanel mainPanel) { + // create the default leaderboard and select the right race + leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName); + leaderboardPanel.addStyleName(LeaderboardPanel.LEADERBOARD_MARGIN_STYLE); + RaceMap raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, competitorSelectionModel, stringMessages); + raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); + raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); + List> components = new ArrayList>(); + competitorChart = new MultiChartPanel(sailingService, asyncActionsExecutor, competitorSelectionModel, raceSelectionProvider, + timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true); + competitorChart.setVisible(false); + competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + components.add(competitorChart); + windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); - windChart.setVisible(false); - windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - components.add(windChart); - leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, components); - componentViewers.add(leaderboardAndMapViewer); - for (ComponentViewer componentViewer : componentViewers) { - mainPanel.add(componentViewer.getViewerWidget()); - } - - addComponentToNavigationMenu(leaderboardAndMapViewer, leaderboardPanel, true); - addComponentToNavigationMenu(leaderboardAndMapViewer, windChart, true); - addComponentToNavigationMenu(leaderboardAndMapViewer, competitorChart, true); - addComponentToNavigationMenu(leaderboardAndMapViewer, raceMap, false); - } - - @SuppressWarnings("unused") - private void addSettingsMenuItem(MenuBar settingsMenu, final Component component) { - if (component.hasSettings()) { - settingsMenu.addItem(component.getLocalizedShortName(), new Command() { - public void execute() { - new SettingsDialog(component, stringMessages).show(); - } - }); - } - } - - private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName) { - LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() - .createNewSettingsForPlayMode(timer.getPlayMode(), - /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), - /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), - new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), - /* showOverallLeaderboardsOnSamePage */ false); - return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier, - competitorSelectionModel, timer, leaderboardName, errorReporter, stringMessages, userAgent, - /* showRaceDetails */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false); - } - - private void addComponentToNavigationMenu(final ComponentViewer componentViewer, - final Component component, boolean withCheckbox) { - final CheckBox checkBox= new CheckBox(component.getLocalizedShortName()); - checkBox.getElement().getStyle().setFloat(Style.Float.LEFT); - - checkBox.setEnabled(withCheckbox); - checkBox.setValue(component.isVisible()); - checkBox.setTitle(stringMessages.showHideComponent(component.getLocalizedShortName())); - checkBox.addStyleName("raceBoardNavigation-innerElement"); - - checkBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent newValue) { - // make the map invisible is this is not supported yet due to problems with disabling the center element - // of a DockPanel - if (component instanceof RaceMap) - return; - - boolean visible = checkBox.getValue(); - setComponentVisible(componentViewer, component, visible); - - if (visible && component instanceof TimeListener) { - // trigger the component to update its data - ((TimeListener) component).timeChanged(timer.getTime()); - } - } - }); - - componentsNavigationPanel.add(checkBox); - - if(component.hasSettings()) { - Button settingsButton = new Button(""); - settingsButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - new SettingsDialog(component, stringMessages).show(); - } - }); - settingsButton.addStyleName("raceBoardNavigation-settingsButton"); - settingsButton.getElement().getStyle().setFloat(Style.Float.LEFT); - settingsButton.setTitle(stringMessages.settingsForComponent(component.getLocalizedShortName())); - - componentsNavigationPanel.add(settingsButton); - } - - } - - public void addComponentAsToogleButtonToNavigationMenu(final ComponentViewer componentViewer, - final Component component) { - final ToggleButton toggleButton = new ToggleButton(component.getLocalizedShortName(), - "\u2713 " + component.getLocalizedShortName()); - toggleButton.getElement().getStyle().setFloat(Style.Float.LEFT); - toggleButton.setDown(component.isVisible()); - toggleButton.setTitle(stringMessages.showHideComponent(component.getLocalizedShortName())); - - toggleButton.addClickHandler(new ClickHandler() { - public void onClick(ClickEvent event) { - // make the map invisible is this is not supported yet due to problems with disabling the center element - // of a DockPanel - if (component instanceof RaceMap) - return; - - boolean visible = toggleButton.isDown(); - setComponentVisible(componentViewer, component, visible); - - if (visible && component instanceof TimeListener) { - // trigger the component to update its data - ((TimeListener) component).timeChanged(timer.getTime()); - } - } - }); - - componentsNavigationPanel.add(toggleButton); - } - - 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 CASCADE view mode.
- * Displays or hides the leaderboard, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the leaderboard shall be open/visible - */ - public void setLeaderboardVisible(boolean visible) { - switch (viewMode) { - case ONESCREEN: - setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible); - break; - } - } - - /** - * Sets the collapsable panel for the wind chart open or close, if in CASCADE view mode.
- * Displays or hides the wind chart, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the wind chart shall be open/visible - */ - public void setWindChartVisible(boolean visible) { - switch (viewMode) { - case ONESCREEN: - setComponentVisible(leaderboardAndMapViewer, windChart, visible); - break; - } - } - - /** - * Sets the collapsable panel for the competitor chart open or close, if in CASCADE view mode.
- * Displays or hides the competitor chart, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the competitor chart shall be open/visible - */ - public void setCompetitorChartVisible(boolean visible) { - switch (viewMode) { - case ONESCREEN: - setComponentVisible(leaderboardAndMapViewer, competitorChart, visible); - break; - } - } - - public Widget getNavigationWidget() { - return componentsNavigationPanel; - } - - public Widget getSettingsWidget() { - return settingsPanel; - } - - public Widget getTimeWidget() { - 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; - } - - public int getScrollOffset() { - return scrollOffset; - } - - /** - * Sets the offset, when scrolling with the menu entry anchors (in the top right corner).
- * Only the absolute value of scrollOffset will be used. - * @param scrollOffset The new scrolling offset. Only the absolute value will be used. - */ - public void setScrollOffset(int scrollOffset) { - this.scrollOffset = Math.abs(scrollOffset); - } - - @Override - public void fillRegattas(List regattas) { - racesByIdentifier.clear(); - for (RegattaDTO regatta : regattas) { - for (RaceDTO race : regatta.races) { - if (race != null && race.getRaceIdentifier() != null) { - racesByIdentifier.put(race.getRaceIdentifier(), race); - } - } - } - } - - @Override - public void onRaceSelectionChange(List selectedRaces) { - } -} - + windChart.setVisible(false); + windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + components.add(windChart); + leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, components); + componentViewers.add(leaderboardAndMapViewer); + for (ComponentViewer componentViewer : componentViewers) { + mainPanel.add(componentViewer.getViewerWidget()); + } + + addComponentToNavigationMenu(leaderboardAndMapViewer, leaderboardPanel, true); + addComponentToNavigationMenu(leaderboardAndMapViewer, windChart, true); + addComponentToNavigationMenu(leaderboardAndMapViewer, competitorChart, true); + addComponentToNavigationMenu(leaderboardAndMapViewer, raceMap, false); + + addMediaSelectorToNavigationMenu(); + + } + + private void addMediaSelectorToNavigationMenu() { + MediaSelector mediaSelector = new MediaSelector(errorReporter); + raceTimesInfoProvider.addRaceTimesInfoProviderListener(mediaSelector); + timer.addPlayStateListener(mediaSelector); + timer.addTimeListener(mediaSelector); + mediaService.getMediaTracksForRace(selectedRaceIdentifier, mediaSelector); + for (Widget widget : mediaSelector.widgets()) { + componentsNavigationPanel.add(widget); + } + } + + @SuppressWarnings("unused") + private void addSettingsMenuItem(MenuBar settingsMenu, final Component component) { + if (component.hasSettings()) { + settingsMenu.addItem(component.getLocalizedShortName(), new Command() { + public void execute() { + new SettingsDialog(component, stringMessages).show(); + } + }); + } + } + + private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName) { + LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() + .createNewSettingsForPlayMode(timer.getPlayMode(), + /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), + /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), + new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), + /* showOverallLeaderboardsOnSamePage */ false); + return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier, + competitorSelectionModel, timer, leaderboardName, errorReporter, stringMessages, userAgent, + /* showRaceDetails */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false); + } + + private void addComponentToNavigationMenu(final ComponentViewer componentViewer, + final Component component, boolean withCheckbox) { + final CheckBox checkBox= new CheckBox(component.getLocalizedShortName()); + checkBox.getElement().getStyle().setFloat(Style.Float.LEFT); + + checkBox.setEnabled(withCheckbox); + checkBox.setValue(component.isVisible()); + checkBox.setTitle(stringMessages.showHideComponent(component.getLocalizedShortName())); + checkBox.addStyleName("raceBoardNavigation-innerElement"); + + checkBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent newValue) { + // make the map invisible is this is not supported yet due to problems with disabling the center element + // of a DockPanel + if (component instanceof RaceMap) + return; + + boolean visible = checkBox.getValue(); + setComponentVisible(componentViewer, component, visible); + + if (visible && component instanceof TimeListener) { + // trigger the component to update its data + ((TimeListener) component).timeChanged(timer.getTime()); + } + } + }); + + componentsNavigationPanel.add(checkBox); + + if(component.hasSettings()) { + Button settingsButton = new Button(""); + settingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + new SettingsDialog(component, stringMessages).show(); + } + }); + settingsButton.addStyleName("raceBoardNavigation-settingsButton"); + settingsButton.getElement().getStyle().setFloat(Style.Float.LEFT); + settingsButton.setTitle(stringMessages.settingsForComponent(component.getLocalizedShortName())); + + componentsNavigationPanel.add(settingsButton); + } + + } + + public void addComponentAsToogleButtonToNavigationMenu(final ComponentViewer componentViewer, + final Component component) { + final ToggleButton toggleButton = new ToggleButton(component.getLocalizedShortName(), + "\u2713 " + component.getLocalizedShortName()); + toggleButton.getElement().getStyle().setFloat(Style.Float.LEFT); + toggleButton.setDown(component.isVisible()); + toggleButton.setTitle(stringMessages.showHideComponent(component.getLocalizedShortName())); + + toggleButton.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + // make the map invisible is this is not supported yet due to problems with disabling the center element + // of a DockPanel + if (component instanceof RaceMap) + return; + + boolean visible = toggleButton.isDown(); + setComponentVisible(componentViewer, component, visible); + + if (visible && component instanceof TimeListener) { + // trigger the component to update its data + ((TimeListener) component).timeChanged(timer.getTime()); + } + } + }); + + componentsNavigationPanel.add(toggleButton); + } + + 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 CASCADE view mode.
+ * Displays or hides the leaderboard, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the leaderboard shall be open/visible + */ + public void setLeaderboardVisible(boolean visible) { + switch (viewMode) { + case ONESCREEN: + setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible); + break; + } + } + + /** + * Sets the collapsable panel for the wind chart open or close, if in CASCADE view mode.
+ * Displays or hides the wind chart, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the wind chart shall be open/visible + */ + public void setWindChartVisible(boolean visible) { + switch (viewMode) { + case ONESCREEN: + setComponentVisible(leaderboardAndMapViewer, windChart, visible); + break; + } + } + + /** + * Sets the collapsable panel for the competitor chart open or close, if in CASCADE view mode.
+ * Displays or hides the competitor chart, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the competitor chart shall be open/visible + */ + public void setCompetitorChartVisible(boolean visible) { + switch (viewMode) { + case ONESCREEN: + setComponentVisible(leaderboardAndMapViewer, competitorChart, visible); + break; + } + } + + public Widget getNavigationWidget() { + return componentsNavigationPanel; + } + + public Widget getSettingsWidget() { + return settingsPanel; + } + + public Widget getTimeWidget() { + 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; + } + + public int getScrollOffset() { + return scrollOffset; + } + + /** + * Sets the offset, when scrolling with the menu entry anchors (in the top right corner).
+ * Only the absolute value of scrollOffset will be used. + * @param scrollOffset The new scrolling offset. Only the absolute value will be used. + */ + public void setScrollOffset(int scrollOffset) { + this.scrollOffset = Math.abs(scrollOffset); + } + + @Override + public void fillRegattas(List regattas) { + racesByIdentifier.clear(); + for (RegattaDTO regatta : regattas) { + for (RaceDTO race : regatta.races) { + if (race != null && race.getRaceIdentifier() != null) { + racesByIdentifier.put(race.getRaceIdentifier(), race); + } + } + } + } + + @Override + public void onRaceSelectionChange(List selectedRaces) { + } +} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/VideoTrack.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/VideoTrack.java new file mode 100644 index 00000000000..9aad135872d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/VideoTrack.java @@ -0,0 +1,37 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.Date; + +import com.sap.sailing.domain.common.RaceIdentifier; + +public class VideoTrack { + + private final RaceIdentifier raceIdentifier; + private Date startTime; + // private final double lengthInSeconds; + private final String url; + + public RaceIdentifier getRaceIdentifier() { + return raceIdentifier; + } + + public Date getStartTime() { + return startTime; + } + + // public double getLengthInSeconds() { + // return lengthInSeconds; + // } + // + public VideoTrack(RaceIdentifier raceName, Date startTime, /* double lengthInSeconds, */String url) { + this.raceIdentifier = raceName; + this.startTime = startTime; + // this.lengthInSeconds = lengthInSeconds; + this.url = url; + } + + public String getUrl() { + return url; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/VideoWindowPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/VideoWindowPlayer.java new file mode 100644 index 00000000000..f3d05dfc83b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/VideoWindowPlayer.java @@ -0,0 +1,22 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import com.google.gwt.http.client.URL; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public class VideoWindowPlayer extends PopupWindowPlayer { + + public VideoWindowPlayer(MediaTrack mediaTrack, PopupCloseListener popCloseListener) { + super(mediaTrack, popCloseListener); + } + + @Override + protected String getPlayerWindowUrl() { + String videoUrl = getMediaTrack().url; + String title = getMediaTrack().title; + + String videoPlayerUrl = "/gwt/VideoPopup.html?url=" + URL.encodeQueryString(videoUrl) + "&title=" + URL.encodeQueryString(title); + + return videoPlayerUrl; + + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/YoutubeWindowPlayer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/YoutubeWindowPlayer.java new file mode 100644 index 00000000000..b39795ff7ba --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/YoutubeWindowPlayer.java @@ -0,0 +1,22 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import com.google.gwt.http.client.URL; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; + +public class YoutubeWindowPlayer extends PopupWindowPlayer { + + public YoutubeWindowPlayer(MediaTrack mediaTrack, PopupCloseListener popupCloseListener) { + super(mediaTrack, popupCloseListener); + } + + @Override + protected String getPlayerWindowUrl() { + String youtubeId = getMediaTrack().url; + String title = getMediaTrack().title; + + String youtubePlayerUrl = "/gwt/YoutubePopup.html?id=" + URL.encodeQueryString(youtubeId) + "&title=" + URL.encodeQueryString(title); + + return youtubePlayerUrl; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java new file mode 100644 index 00000000000..9119beb2126 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java @@ -0,0 +1,122 @@ +package com.sap.sailing.gwt.ui.server; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +import com.google.gwt.user.server.rpc.RemoteServiceServlet; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.persistence.media.DBMediaTrack; +import com.sap.sailing.domain.persistence.media.MediaDB; +import com.sap.sailing.domain.persistence.media.MediaDBFactory; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.ui.client.MediaService; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack; +import com.sap.sailing.gwt.ui.shared.media.MediaTrack.MimeType; +import com.sap.sailing.server.RacingEventService; + +public class MediaServiceImpl extends RemoteServiceServlet implements MediaService { + +// private static final Logger logger = Logger.getLogger(MediaServiceImpl.class.getName()); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); + + private ServiceTracker racingEventServiceTracker; + + private static final long serialVersionUID = -8917349579281305977L; + + public MediaServiceImpl() { + super(); + BundleContext context = Activator.getDefault(); + racingEventServiceTracker = new ServiceTracker( + context, RacingEventService.class.getName(), null); + racingEventServiceTracker.open(); + } + + @Override + public Collection getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) { + TrackedRace trackedRace = racingEventServiceTracker.getService().getExistingTrackedRace(regattaAndRaceIdentifier); + if (trackedRace != null) { + Date raceStart = trackedRace.getStartOfRace().asDate(); + Date raceEnd = trackedRace.getEndOfRace().asDate(); + return createMediaTracksFromDB(mediaDB().queryOverlappingMediaTracks(raceStart , raceEnd)); + } else { + return Collections.emptyList(); + } + } + + @Override + public Collection getAllMediaTracks() { + List allDBMediaTracks = mediaDB().loadAllMediaTracks(); + Collection result = createMediaTracksFromDB(allDBMediaTracks); + return result; +// try { +// Date date = dateFormat.parse("2012-09-15 14:42:10 +0100"); +// return Arrays +// .asList(new MediaTrack[] { new MediaTrack( +// MediaType.VIDEO, +// "Day 2 Final - 49er Europeans 2012", +// "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv", +// date) }); +// } catch (ParseException e) { +// throw new RuntimeException(e); // just due to annoying date parse error potential +// } + } + + private Collection createMediaTracksFromDB(Collection allDBMediaTracks) { + Collection result = new ArrayList(); + for (DBMediaTrack dbMediaTrack : allDBMediaTracks) { + MediaTrack mediaTrack = createMediaTrackFromDB(dbMediaTrack); + result.add(mediaTrack ); + } + return result; + } + + private MediaTrack createMediaTrackFromDB(DBMediaTrack dbMediaTrack) { + MimeType mimeType = dbMediaTrack.mimeType != null ? MimeType.valueOf(dbMediaTrack.mimeType) : null; + MediaTrack mediaTrack = new MediaTrack(dbMediaTrack.dbId, dbMediaTrack.title, dbMediaTrack.url, dbMediaTrack.startTime, dbMediaTrack.durationInMillis, mimeType); + return mediaTrack; + } + + @Override + public void addMediaTrack(MediaTrack mediaTrack) { + String mimeType = mediaTrack.mimeType != null ? mediaTrack.mimeType.name() : null; + mediaDB().insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime, mediaTrack.durationInMillis, mimeType); + } + + private MediaDB mediaDB() { + return MediaDBFactory.INSTANCE.getDefaultMediaDB(); + } + + @Override + public void deleteMediaTrack(MediaTrack mediaTrack) { + mediaDB().deleteMediaTrack(mediaTrack.dbId); + } + + @Override + public void updateTitle(MediaTrack mediaTrack) { + mediaDB().updateTitle(mediaTrack.dbId, mediaTrack.title); + } + + @Override + public void updateUrl(MediaTrack mediaTrack) { + mediaDB().updateUrl(mediaTrack.dbId, mediaTrack.url); + } + + @Override + public void updateStartTime(MediaTrack mediaTrack) { + mediaDB().updateStartTime(mediaTrack.dbId, mediaTrack.startTime); + } + + @Override + public void updateDuration(MediaTrack mediaTrack) { + mediaDB().updateDuration(mediaTrack.dbId, mediaTrack.durationInMillis); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/ProxiedRemoteServiceServlet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/ProxiedRemoteServiceServlet.java index 87627a56520..2f336d70af2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/ProxiedRemoteServiceServlet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/ProxiedRemoteServiceServlet.java @@ -23,9 +23,12 @@ public abstract class ProxiedRemoteServiceServlet extends RemoteServiceServlet { protected SerializationPolicy doGetSerializationPolicy( HttpServletRequest request, String moduleBaseURL, String strongName) { String moduleBaseURLHdr = request.getHeader("X-GWT-Module-Base"); + if(moduleBaseURLHdr != null){ moduleBaseURL = moduleBaseURLHdr; } + return super.doGetSerializationPolicy(request, moduleBaseURL, strongName); - } + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 3fccc3e6a83..8e42b0487fd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -702,7 +702,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceDetails = new FutureTask(new Callable() { @Override public RaceDetails call() throws Exception { - return calculateRaceDetails(trackedRace, competitor, trackedRace.getEndOfTracking(), + TimePoint end = trackedRace.getEndOfRace(); + if (end == null) { + end = trackedRace.getEndOfTracking(); + } + return calculateRaceDetails(trackedRace, competitor, end, /* waitForLatestManeuverAnalysis */ true /* because this is done only once after end of tracking */, legRanksCache); } }); @@ -2482,7 +2486,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S TimePoint newestEvent = trackedRace.getTimePointOfNewestEvent(); TimePoint startTime = from == null ? trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); TimePoint endTime = (to == null || to.after(newestEvent.asDate())) ? newestEvent : new MillisecondsTimePoint(to); - result = new CompetitorsRaceDataDTO(detailType, startTime.asDate(), endTime.asDate()); + result = new CompetitorsRaceDataDTO(detailType, startTime==null?null:startTime.asDate(), endTime==null?null:endTime.asDate()); for (CompetitorDTO competitorDTO : competitors) { Competitor competitor = getCompetitorById(trackedRace.getRace().getCompetitors(), competitorDTO.id); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/AbstractChartPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/AbstractChartPanel.java index 36d424a0f89..0b80c625749 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/AbstractChartPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/AbstractChartPanel.java @@ -358,34 +358,6 @@ public abstract class AbstractChartPanel ext return result; } - private String getDetailTypeUnit() { - String detailTypeUnit = ""; - switch (getSelectedDetailType()) { - case CURRENT_SPEED_OVER_GROUND_IN_KNOTS: - detailTypeUnit = getStringMessages().currentSpeedOverGroundInKnotsUnit(); - break; - case DISTANCE_TRAVELED: - detailTypeUnit = getStringMessages().distanceInMetersUnit(); - break; - case GAP_TO_LEADER_IN_SECONDS: - detailTypeUnit = getStringMessages().gapToLeaderInSecondsUnit(); - break; - case VELOCITY_MADE_GOOD_IN_KNOTS: - detailTypeUnit = getStringMessages().velocityMadeGoodInKnotsUnit(); - break; - case WINDWARD_DISTANCE_TO_OVERALL_LEADER: - detailTypeUnit = getStringMessages().windwardDistanceToGoInMetersUnit(); - break; - case RACE_RANK: - //Case for detail types without unit, so that an empty string is returned. - break; - default: - //Throwing an exception to get notificated if an implementation of an detail type is missing. - throw new UnsupportedOperationException("Theres currently no support for the enum value '" + getSelectedDetailType() + "' in this method."); - } - return detailTypeUnit; - } - /** * * @param competitor @@ -409,7 +381,7 @@ public abstract class AbstractChartPanel ext if(visible) { // Workaround for a highcharts bug: // Set a chart title, overwrite the title, switch chart to invisible and visible again -> the old title appears - chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType, stringMessages)), + chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType)), null); } } @@ -426,7 +398,7 @@ public abstract class AbstractChartPanel ext } public String getLocalizedShortName() { - return DetailTypeFormatter.format(getSelectedDetailType(), getStringMessages()); + return DetailTypeFormatter.format(getSelectedDetailType()); } public Widget getEntryWidget() { @@ -500,12 +472,12 @@ public abstract class AbstractChartPanel ext resetMinMaxAndExtremesInterval(/* redraw */ true); } } - chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType, stringMessages)), null); - final String unit = getDetailTypeUnit(); + chart.setTitle(new ChartTitle().setText(DetailTypeFormatter.format(selectedDetailType)), null); + final String unit = DetailTypeFormatter.getUnit(getSelectedDetailType()); final String label = unit.isEmpty() ? "" : "[" + unit + "]"; if (!compactChart) { chart.getYAxis().setAxisTitleText( - DetailTypeFormatter.format(selectedDetailType, stringMessages) + " " + label); + DetailTypeFormatter.format(selectedDetailType) + " " + label); } else { chart.getYAxis().setAxisTitleText(label); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/MultiChartSettingsComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/MultiChartSettingsComponent.java index 76f65817fb6..5bad1ee83f5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/MultiChartSettingsComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/charts/MultiChartSettingsComponent.java @@ -42,7 +42,7 @@ public class MultiChartSettingsComponent extends AbstractChartSettingsComponent< chartTypeSelectionListBox = dialog.createListBox(/* isMultiSelect */ false); int i=0; for (DetailType detailType : availableDetailsTypes) { - chartTypeSelectionListBox.addItem(DetailTypeFormatter.format(detailType, stringMessages), detailType.name()); + chartTypeSelectionListBox.addItem(DetailTypeFormatter.format(detailType), detailType.name()); if (detailType == initialDetailType) { chartTypeSelectionListBox.setSelectedIndex(i); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaTrack.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaTrack.java new file mode 100644 index 00000000000..fd5499bd5e6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/media/MediaTrack.java @@ -0,0 +1,85 @@ +package com.sap.sailing.gwt.ui.shared.media; + +import java.util.Date; + +import com.google.gwt.user.client.rpc.IsSerializable; + +/** + * See http://my.opera.com/core/blog/2010/03/03/everything-you-need-to-know-about-html5-video-and-audio-2 + * @author D047974 + * + */ +public class MediaTrack implements IsSerializable { + + public enum MimeType { + + mp4(MediaType.video, MediaSubType.mp4), ogv(MediaType.video, MediaSubType.ogg), qt(MediaType.video, MediaSubType.quicktime), mp3(MediaType.audio, MediaSubType.mpeg), ogg(MediaType.audio, MediaSubType.ogg), aac(MediaType.audio, MediaSubType.aac), webm(MediaType.video, MediaSubType.webm), youtube(MediaType.video, MediaSubType.youtube); + + public final MediaType mediaType; + public final MediaSubType mediaSubType; + + MimeType(MediaType mediaType, MediaSubType mediaSubType) { + this.mediaType = mediaType; + this.mediaSubType = mediaSubType; + } + + @Override + public String toString() { + return mediaType.name() + '/' + mediaSubType.toString(); + } + } + + public enum MediaType { + audio, video; + } + + public enum MediaSubType { + ogg, mp4, mpeg, x_aiff, quicktime, aac, webm, youtube; + + public String toString() { + return name().replace('_', '-'); + }; + + } + + public class MediaSection implements IsSerializable { + public String title; + public long offsetInMillis; + public Date startTime; + public Date endTime; + } + + public String dbId; + public String title; + public String url; + public Date startTime; + public int durationInMillis; + public MimeType mimeType; + + public MediaTrack() { + super(); + } + + public MediaTrack(String dbId, String title, String url, Date startTime, int durationInMillis, MimeType mimeType) { + super(); + this.dbId = dbId; + this.title = title; + this.url = url; + this.startTime = startTime; + this.durationInMillis = durationInMillis; + this.mimeType = mimeType; + } + + public String toString() { + return title + " - " + url + " [" + typeToString() + ']' + startTime + " [" + durationInMillis + ']'; + } + + public String typeToString() { + return mimeType == null ? "undefined" : mimeType.toString(); + } + + public boolean isYoutube() { + return (mimeType != null) && MediaSubType.youtube.equals(mimeType.mediaSubType); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java index 8e6ca2ce438..f9bccf0571c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVEntryPoint.java @@ -64,7 +64,7 @@ public class TVEntryPoint extends AbstractEntryPoint { mainPanel.addNorth(logoAndTitlePanel, 68); } - tvViewController = new TVViewController(sailingService, stringMessages, this, leaderboardName, + tvViewController = new TVViewController(sailingService, mediaService, stringMessages, this, leaderboardName, userAgent, logoAndTitlePanel, mainPanel, delayToLiveMillis, showRaceDetails); tvViewController.updateTvView(TVViews.Leaderboard); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java index cab5fe16d5e..9faa86dd84f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/tv/TVViewController.java @@ -1,245 +1,248 @@ -package com.sap.sailing.gwt.ui.tv; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import com.google.gwt.user.client.ui.DockLayoutPanel; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.ScrollPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.Timer; -import com.sap.sailing.gwt.ui.client.Timer.PlayModes; -import com.sap.sailing.gwt.ui.client.UserAgentDetails; -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.sailing.gwt.ui.raceboard.RaceBoardPanel; -import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewModes; -import com.sap.sailing.gwt.ui.shared.FleetDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.RaceColumnDTO; -import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; - -public class TVViewController implements RaceTimesInfoProviderListener { - private final SailingServiceAsync sailingService; - private final StringMessages stringMessages; - private final ErrorReporter errorReporter; - private final UserAgentDetails userAgent; - private final LogoAndTitlePanel logoAndTitlePanel; - private final DockLayoutPanel dockPanel; - - private final Timer timer; - private final RaceTimesInfoProvider raceTimesInfoProvider; - private TVViews activeTvView; - - // leaderboard related attributes - private LeaderboardDTO leaderboard; - private final String leaderboardName; - private LeaderboardSettings leaderboardSettings; - - // raceboard related attributes - private RegattaAndRaceIdentifier currentLiveRace; - private boolean showRaceDetails; - private boolean showWindChart; - - /** - * @param logoAndTitlePanel allowed to be null - */ - public TVViewController(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter, - String leaderboardName, UserAgentDetails userAgent, LogoAndTitlePanel logoAndTitlePanel, - DockLayoutPanel dockPanel, long delayToLiveInMillis, boolean showRaceDetails) { - this.sailingService = sailingService; - this.stringMessages = stringMessages; - this.errorReporter = errorReporter; - this.leaderboardName = leaderboardName; - this.userAgent = userAgent; - this.logoAndTitlePanel = logoAndTitlePanel; - this.dockPanel = dockPanel; - this.showRaceDetails = showRaceDetails; - - showWindChart = false; - leaderboard = null; - leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, - /* showMetaLeaderboardsOnSamePage */ false); - - timer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l); - timer.setLivePlayDelayInMillis(delayToLiveInMillis); - timer.play(); - - raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, errorReporter, new ArrayList(), 3000l); - raceTimesInfoProvider.addRaceTimesInfoProviderListener(this); - } - - private LeaderboardPanel createLeaderboardPanel(String leaderboardName, boolean showRaceDetails) { - CompetitorSelectionModel selectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); - LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardSettings, - /* preSelectedRace */null, selectionModel, timer, leaderboardName, errorReporter, stringMessages, userAgent, - showRaceDetails, /* raceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false) { - @Override - protected void setLeaderboard(LeaderboardDTO leaderboard) { - super.setLeaderboard(leaderboard); - TVViewController.this.leaderboard = leaderboard; - updateRaceTimesInfoProvider(); - } - }; - leaderboardPanel.addStyleName(LeaderboardPanel.LEADERBOARD_MARGIN_STYLE); - return leaderboardPanel; - } - - private void updateRaceTimesInfoProvider() { - boolean addedRaces = false; - for (RaceColumnDTO race : leaderboard.getRaceList()) { - for (FleetDTO fleet : race.getFleets()) { - RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(fleet); - if (raceIdentifier != null && !raceTimesInfoProvider.containsRaceIdentifier(raceIdentifier)) { - raceTimesInfoProvider.addRaceIdentifier(raceIdentifier, false); - addedRaces = true; - } - } - } - if (addedRaces) { - raceTimesInfoProvider.forceTimesInfosUpdate(); - } - } - - private RaceBoardPanel createRaceBoardPanel(String leaderboardName, RegattaAndRaceIdentifier raceToShow) { - RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); - List singletonList = Collections.singletonList(raceToShow); - raceSelectionModel.setSelection(singletonList); - RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, null, timer, raceSelectionModel, leaderboardName, null, - errorReporter, stringMessages, userAgent, RaceBoardViewModes.ONESCREEN, raceTimesInfoProvider); - return raceBoardPanel; - } - - private void clearContentPanels() { - int childWidgetCount = dockPanel.getWidgetCount(); - for(int i = 0; i < childWidgetCount; i++) { - Widget widget = dockPanel.getWidget(i); - - // don't remove the logoAndTitlePanel if exist - if(logoAndTitlePanel == null || widget != logoAndTitlePanel) { - dockPanel.remove(widget); - } - } - } - - private void showLeaderboard() { - if (activeTvView != TVViews.Leaderboard) { - clearContentPanels(); - LeaderboardPanel leaderboardPanel = createLeaderboardPanel(leaderboardName, showRaceDetails); - ScrollPanel leaderboardContentPanel = new ScrollPanel(); - leaderboardContentPanel.add(leaderboardPanel); - dockPanel.add(leaderboardContentPanel); - if(logoAndTitlePanel != null) { - logoAndTitlePanel.setSubTitle(leaderboardName); - } - currentLiveRace = null; - activeTvView = TVViews.Leaderboard; - } - } - - private void showRaceBoard() { - if(activeTvView != TVViews.Raceboard) { - clearContentPanels(); - - RaceBoardPanel raceBoardPanel = createRaceBoardPanel(leaderboardName, currentLiveRace); - raceBoardPanel.setSize("100%", "100%"); - if(showWindChart) { - raceBoardPanel.setWindChartVisible(true); - } - -// FlowPanel toolbarPanel = new FlowPanel(); -// toolbarPanel.add(raceBoardPanel.getNavigationWidget()); -// dockPanel.addNorth(toolbarPanel, 40); - - FlowPanel timePanel = createTimePanel(raceBoardPanel); - - dockPanel.addSouth(timePanel, 90); - dockPanel.add(raceBoardPanel); - - if(logoAndTitlePanel != null) { - logoAndTitlePanel.setSubTitle(currentLiveRace.getRaceName()); - } - - activeTvView = TVViews.Raceboard; - } - } - - private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) { - FlowPanel timeLineInnerBgPanel = new FlowPanel(); - timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel"); - timeLineInnerBgPanel.add(raceBoardPanel.getTimeWidget()); - - FlowPanel timeLineInnerPanel = new FlowPanel(); - timeLineInnerPanel.add(timeLineInnerBgPanel); - timeLineInnerPanel.addStyleName("timeLineInnerPanel"); - - FlowPanel timelinePanel = new FlowPanel(); - timelinePanel.add(timeLineInnerPanel); - timelinePanel.addStyleName("timeLinePanel"); - - return timelinePanel; - } - - public void updateTvView(TVViews tvView) { - switch (tvView) { - case Leaderboard: - showLeaderboard(); - break; - case Raceboard: - showRaceBoard(); - break; - } - } - - @Override - public void raceTimesInfosReceived(Map raceTimesInfo) { - if (currentLiveRace != null) { - RaceTimesInfoDTO currentRaceTimes = raceTimesInfo.get(currentLiveRace); - - Date endOfRace = currentRaceTimes.endOfRace; - long waitTimeAfterEndOfRace = 60 * 1000; // 1 min - if (endOfRace != null && timer.getTime().getTime() > endOfRace.getTime() + waitTimeAfterEndOfRace - && timer.getPlayMode() == PlayModes.Live) { - updateTvView(TVViews.Leaderboard); - } - } else { - currentLiveRace = checkForLiveRace(); - if (currentLiveRace != null) { - updateTvView(TVViews.Raceboard); - } else { - updateTvView(TVViews.Leaderboard); - } - } - } - - private RegattaAndRaceIdentifier checkForLiveRace() { - RegattaAndRaceIdentifier result = null; - Map raceTimesInfos = raceTimesInfoProvider.getRaceTimesInfos(); - for (RaceColumnDTO race : leaderboard.getRaceList()) { - for (FleetDTO fleet : race.getFleets()) { - RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(fleet); - if (raceIdentifier != null) { - RaceTimesInfoDTO raceTimes = raceTimesInfos.get(raceIdentifier); - if (raceTimes != null && raceTimes.startOfTracking != null && raceTimes.endOfRace == null) { - return raceIdentifier; - } - } - } - } - return result; - } -} +package com.sap.sailing.gwt.ui.tv; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; +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.RaceTimesInfoProviderListener; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.Timer; +import com.sap.sailing.gwt.ui.client.Timer.PlayModes; +import com.sap.sailing.gwt.ui.client.UserAgentDetails; +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.sailing.gwt.ui.raceboard.RaceBoardPanel; +import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewModes; +import com.sap.sailing.gwt.ui.shared.FleetDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; +import com.sap.sailing.gwt.ui.shared.RaceColumnDTO; +import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; + +public class TVViewController implements RaceTimesInfoProviderListener { + private final SailingServiceAsync sailingService; + private final MediaServiceAsync mediaService; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private final UserAgentDetails userAgent; + private final LogoAndTitlePanel logoAndTitlePanel; + private final DockLayoutPanel dockPanel; + + private final Timer timer; + private final RaceTimesInfoProvider raceTimesInfoProvider; + private TVViews activeTvView; + + // leaderboard related attributes + private LeaderboardDTO leaderboard; + private final String leaderboardName; + private LeaderboardSettings leaderboardSettings; + + // raceboard related attributes + private RegattaAndRaceIdentifier currentLiveRace; + private boolean showRaceDetails; + private boolean showWindChart; + + /** + * @param logoAndTitlePanel allowed to be null + */ + public TVViewController(SailingServiceAsync sailingService, MediaServiceAsync mediaService, StringMessages stringMessages, ErrorReporter errorReporter, + String leaderboardName, UserAgentDetails userAgent, LogoAndTitlePanel logoAndTitlePanel, + DockLayoutPanel dockPanel, long delayToLiveInMillis, boolean showRaceDetails) { + this.sailingService = sailingService; + this.mediaService = mediaService; + this.stringMessages = stringMessages; + this.errorReporter = errorReporter; + this.leaderboardName = leaderboardName; + this.userAgent = userAgent; + this.logoAndTitlePanel = logoAndTitlePanel; + this.dockPanel = dockPanel; + this.showRaceDetails = showRaceDetails; + + showWindChart = false; + leaderboard = null; + leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, + /* showMetaLeaderboardsOnSamePage */ false); + + timer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l); + timer.setLivePlayDelayInMillis(delayToLiveInMillis); + timer.play(); + + raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, errorReporter, new ArrayList(), 3000l); + raceTimesInfoProvider.addRaceTimesInfoProviderListener(this); + } + + private LeaderboardPanel createLeaderboardPanel(String leaderboardName, boolean showRaceDetails) { + CompetitorSelectionModel selectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); + LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardSettings, + /* preSelectedRace */null, selectionModel, timer, leaderboardName, errorReporter, stringMessages, userAgent, + showRaceDetails, /* raceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false) { + @Override + protected void setLeaderboard(LeaderboardDTO leaderboard) { + super.setLeaderboard(leaderboard); + TVViewController.this.leaderboard = leaderboard; + updateRaceTimesInfoProvider(); + } + }; + leaderboardPanel.addStyleName(LeaderboardPanel.LEADERBOARD_MARGIN_STYLE); + return leaderboardPanel; + } + + private void updateRaceTimesInfoProvider() { + boolean addedRaces = false; + for (RaceColumnDTO race : leaderboard.getRaceList()) { + for (FleetDTO fleet : race.getFleets()) { + RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(fleet); + if (raceIdentifier != null && !raceTimesInfoProvider.containsRaceIdentifier(raceIdentifier)) { + raceTimesInfoProvider.addRaceIdentifier(raceIdentifier, false); + addedRaces = true; + } + } + } + if (addedRaces) { + raceTimesInfoProvider.forceTimesInfosUpdate(); + } + } + + private RaceBoardPanel createRaceBoardPanel(String leaderboardName, RegattaAndRaceIdentifier raceToShow) { + RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); + List singletonList = Collections.singletonList(raceToShow); + raceSelectionModel.setSelection(singletonList); + RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, null, timer, raceSelectionModel, leaderboardName, null, + errorReporter, stringMessages, userAgent, RaceBoardViewModes.ONESCREEN, raceTimesInfoProvider); + return raceBoardPanel; + } + + private void clearContentPanels() { + int childWidgetCount = dockPanel.getWidgetCount(); + for(int i = 0; i < childWidgetCount; i++) { + Widget widget = dockPanel.getWidget(i); + + // don't remove the logoAndTitlePanel if exist + if(logoAndTitlePanel == null || widget != logoAndTitlePanel) { + dockPanel.remove(widget); + } + } + } + + private void showLeaderboard() { + if (activeTvView != TVViews.Leaderboard) { + clearContentPanels(); + LeaderboardPanel leaderboardPanel = createLeaderboardPanel(leaderboardName, showRaceDetails); + ScrollPanel leaderboardContentPanel = new ScrollPanel(); + leaderboardContentPanel.add(leaderboardPanel); + dockPanel.add(leaderboardContentPanel); + if(logoAndTitlePanel != null) { + logoAndTitlePanel.setSubTitle(leaderboardName); + } + currentLiveRace = null; + activeTvView = TVViews.Leaderboard; + } + } + + private void showRaceBoard() { + if(activeTvView != TVViews.Raceboard) { + clearContentPanels(); + + RaceBoardPanel raceBoardPanel = createRaceBoardPanel(leaderboardName, currentLiveRace); + raceBoardPanel.setSize("100%", "100%"); + if(showWindChart) { + raceBoardPanel.setWindChartVisible(true); + } + +// FlowPanel toolbarPanel = new FlowPanel(); +// toolbarPanel.add(raceBoardPanel.getNavigationWidget()); +// dockPanel.addNorth(toolbarPanel, 40); + + FlowPanel timePanel = createTimePanel(raceBoardPanel); + + dockPanel.addSouth(timePanel, 90); + dockPanel.add(raceBoardPanel); + + if(logoAndTitlePanel != null) { + logoAndTitlePanel.setSubTitle(currentLiveRace.getRaceName()); + } + + activeTvView = TVViews.Raceboard; + } + } + + private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) { + FlowPanel timeLineInnerBgPanel = new FlowPanel(); + timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel"); + timeLineInnerBgPanel.add(raceBoardPanel.getTimeWidget()); + + FlowPanel timeLineInnerPanel = new FlowPanel(); + timeLineInnerPanel.add(timeLineInnerBgPanel); + timeLineInnerPanel.addStyleName("timeLineInnerPanel"); + + FlowPanel timelinePanel = new FlowPanel(); + timelinePanel.add(timeLineInnerPanel); + timelinePanel.addStyleName("timeLinePanel"); + + return timelinePanel; + } + + public void updateTvView(TVViews tvView) { + switch (tvView) { + case Leaderboard: + showLeaderboard(); + break; + case Raceboard: + showRaceBoard(); + break; + } + } + + @Override + public void raceTimesInfosReceived(Map raceTimesInfo) { + if (currentLiveRace != null) { + RaceTimesInfoDTO currentRaceTimes = raceTimesInfo.get(currentLiveRace); + + Date endOfRace = currentRaceTimes.endOfRace; + long waitTimeAfterEndOfRace = 60 * 1000; // 1 min + if (endOfRace != null && timer.getTime().getTime() > endOfRace.getTime() + waitTimeAfterEndOfRace + && timer.getPlayMode() == PlayModes.Live) { + updateTvView(TVViews.Leaderboard); + } + } else { + currentLiveRace = checkForLiveRace(); + if (currentLiveRace != null) { + updateTvView(TVViews.Raceboard); + } else { + updateTvView(TVViews.Leaderboard); + } + } + } + + private RegattaAndRaceIdentifier checkForLiveRace() { + RegattaAndRaceIdentifier result = null; + Map raceTimesInfos = raceTimesInfoProvider.getRaceTimesInfos(); + for (RaceColumnDTO race : leaderboard.getRaceList()) { + for (FleetDTO fleet : race.getFleets()) { + RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(fleet); + if (raceIdentifier != null) { + RaceTimesInfoDTO raceTimes = raceTimesInfos.get(raceIdentifier); + if (raceTimes != null && raceTimes.startOfTracking != null && raceTimes.endOfRace == null) { + return raceIdentifier; + } + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/AbstractPopupWindow.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/AbstractPopupWindow.java new file mode 100644 index 00000000000..ee35d1d741a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/AbstractPopupWindow.java @@ -0,0 +1,97 @@ +package com.sap.sailing.gwt.ui.video; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.user.client.Window; + +public abstract class AbstractPopupWindow implements EntryPoint { + + @Override + public void onModuleLoad() { + initializePlayer(); + } + + protected abstract void initializePlayer(); + + native void addCallbackMethods() /*-{ + var that = this; + $wnd.videoPlayer = { + play : function() { + that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::play()(); + }, + pause : function() { + that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::pause()(); + }, + setTime : function(time) { + that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::setTime(D)(time); + }, + setMuted : function(muted) { + that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::setMuted(Z)(muted); + }, + isPaused : function() { + return that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::isPaused()(); + }, + getDuration : function() { + return that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::getDuration()(); + }, + getTime : function() { + return that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::getTime()(); + }, + setPlaybackSpeed : function(newPlaySpeedFactor) { + that.@com.sap.sailing.gwt.ui.video.AbstractPopupWindow::setPlaybackSpeed(D)(newPlaySpeedFactor); + } + }; + }-*/; + + public abstract void play(); + + public abstract void pause(); + + public abstract void setTime(double time); + + public abstract void setMuted(boolean muted); + + public abstract boolean isPaused(); + + public abstract double getDuration(); + + public abstract double getTime(); + + public abstract void setPlaybackSpeed(double newPlaySpeedFactor); + + public void initPlayState(double deferredMediaTime, boolean isDeferredMuted, double deferredPlaybackSpeed, + boolean isDeferredPlaying) { + + addCallbackMethods(); + + setTime(deferredMediaTime); + setMuted(isDeferredMuted); + setPlaybackSpeed(deferredPlaybackSpeed); + if (isDeferredPlaying) { + play(); + } else { + pause(); + } + + } + + protected void adjustWindowSize() { + int windowWidth = Window.getClientWidth(); + int videoWidth = getVideoWidth(); + int widthDelta = videoWidth - windowWidth; + + int windowHeight = Window.getClientHeight(); + int videoHeight = getVideoHeight(); + int heightDelta = videoHeight - windowHeight; + + Window.resizeBy(widthDelta, heightDelta); + } + + protected abstract boolean hasVideoSizes(); + + protected abstract int getVideoWidth(); + + protected abstract int getVideoHeight(); + + protected abstract void setVideoSize(int width, int height); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/VideoPopupWindow.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/VideoPopupWindow.java new file mode 100644 index 00000000000..f790735ea93 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/VideoPopupWindow.java @@ -0,0 +1,141 @@ +package com.sap.sailing.gwt.ui.video; + +import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.dom.client.VideoElement; +import com.google.gwt.event.dom.client.ContextMenuEvent; +import com.google.gwt.event.dom.client.ContextMenuHandler; +import com.google.gwt.media.client.Video; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RootLayoutPanel; + +public class VideoPopupWindow extends AbstractPopupWindow implements ContextMenuHandler { + + private Video video; + private boolean isDebug; + + @Override + protected void initializePlayer() { + + RootLayoutPanel mainPanel = RootLayoutPanel.get(); + + String title = Window.Location.getParameter("title"); + Window.setTitle(title); + + String videoUrl = Window.Location.getParameter("url"); + + isDebug = Window.Location.getParameter("gwt.codesvr") != null; + + if (videoUrl != null) { + video = Video.createIfSupported(); + if (video != null) { + + addNativeEventHandlers(video.getVideoElement()); + video.addDomHandler(this, ContextMenuEvent.getType()); + + video.setPreload(MediaElement.PRELOAD_AUTO); + video.setMuted(true); + video.setAutoplay(false); + video.setControls(false); + video.setLoop(false); + video.setSrc(videoUrl); + mainPanel.add(video); + + } else { + mainPanel.add(new Label("Video not supported")); + } + } else { + mainPanel.add(new Label("Parameter 'url' not assigned.")); + } + + } + + native void addNativeEventHandlers(VideoElement videoElement) /*-{ + var that = this; + videoElement.addEventListener('loadedmetadata', function() { + that.@com.sap.sailing.gwt.ui.video.VideoPopupWindow::loadedmetadata()(); + }); + videoElement.addEventListener('canplay', function() { //see http://www.w3schools.com/tags/av_event_canplay.asp + var deferredPlayState = $wnd.deferredPlayState + if (deferredPlayState && !$wnd.videoPlayer) { + that.@com.sap.sailing.gwt.ui.video.VideoPopupWindow::initPlayState(DZDZ)(deferredPlayState.deferredMediaTime, deferredPlayState.isDeferredMuted, deferredPlayState.deferredPlaybackSpeed, deferredPlayState.isDeferredPlaying); + } + }); + }-*/; + + public void loadedmetadata() { + adjustWindowSize(); + } + + @Override + public void play() { + video.play(); + } + + @Override + public void pause() { + video.pause(); + } + + @Override + public void setTime(double time) { + video.setCurrentTime(time); + } + + @Override + public void setMuted(boolean muted) { + video.setMuted(muted); + } + + @Override + public boolean isPaused() { + return video.isPaused(); + } + + @Override + public double getDuration() { + return video.getDuration(); + } + + @Override + public double getTime() { + return video.getCurrentTime(); + } + + @Override + public void setPlaybackSpeed(double newPlaySpeedFactor) { + video.setPlaybackRate(newPlaySpeedFactor); + } + + /** + * Suppressing context menu. + */ + @Override + public void onContextMenu(ContextMenuEvent event) { + if (!isDebug) { + event.preventDefault(); + event.stopPropagation(); + } + } + + @Override + protected boolean hasVideoSizes() { + return video != null; + } + + @Override + protected int getVideoWidth() { + return video.getVideoWidth(); + } + + @Override + protected int getVideoHeight() { + return video.getVideoHeight(); + } + + @Override + protected void setVideoSize(int width, int height) { + video.setPixelSize(width, height); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/YoutubePopupWindow.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/YoutubePopupWindow.java new file mode 100644 index 00000000000..1a400bbfd15 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/video/YoutubePopupWindow.java @@ -0,0 +1,141 @@ +package com.sap.sailing.gwt.ui.video; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.google.gwt.user.client.ui.RootPanel; + +public class YoutubePopupWindow extends AbstractPopupWindow { + + private static final String VIDEO_CONTAINER_ID = "videoContainer"; + + private JavaScriptObject youtubePlayer; + + @Override + protected void initializePlayer() { + String title = Window.Location.getParameter("title"); + Window.setTitle(title); + + RootLayoutPanel.get().getElement().setId(VIDEO_CONTAINER_ID); + + String videoUrl = Window.Location.getParameter("id"); + if (videoUrl != null) { + loadYoutube(videoUrl, VIDEO_CONTAINER_ID); + } + } + + // Inspired by https://developers.google.com/youtube/iframe_api_reference#Examples + // See also: https://developers.google.com/youtube/js_api_reference + // Code Playground: https://code.google.com/apis/ajax/playground/?exp=youtube#chromeless_player + private native void loadYoutube(String videoUrl, String videoContainerId) /*-{ + + var that = this; + + // This is a protocol-relative URL as described here: http://paulirish.com/2010/the-protocol-relative-url/ + var tag = $doc.createElement('script'); + tag.src = "//www.youtube.com/iframe_api"; + var firstScriptTag = $doc.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + // This function creates an