From 23738d4dbc1630477b4b9387f423759fa8e66bb3 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 13:48:43 +0100 Subject: [PATCH 01/14] Refactoring of the WelcomeWidget concept --- .../components/ClosableWelcomeWidget.java | 43 +++++++++++--- .../shared/components/HasWelcomeWidget.java | 9 ++- .../components/SimpleWelcomeWidget.java | 20 ++++--- .../ui/shared/components/WelcomeWidget.java | 14 ++++- .../ui/spectator/LeaderboardGroupPanel.java | 59 +++++++++++-------- .../gwt/ui/spectator/SpectatorEntryPoint.java | 3 +- 6 files changed, 102 insertions(+), 46 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java index e4880c88f1c..e080a0355cf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java @@ -2,14 +2,22 @@ package com.sap.sailing.gwt.ui.shared.components; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; import com.sap.sailing.gwt.ui.client.StringMessages; -public class ClosableWelcomeWidget extends SimpleWelcomeWidget { +public class ClosableWelcomeWidget extends WelcomeWidget { private StringMessages stringConstants; - private Button closeButton; private boolean closable; + private FormPanel mainPanel; + + private Button closeButton; + private Label welcomeHeader; + private HTML welcomeText; /** * Creates a new closable welcome widget. It has the same components as the {@link SimpleWelcomeWidget} and a Button @@ -18,13 +26,16 @@ public class ClosableWelcomeWidget extends SimpleWelcomeWidget { * @param closable Sets the visibility of the close Button * @param headerText The text of the header component * @param welcomeText The text of the component under the header (welcome component) - * @param horizontalAlignment The alignment of the header and the welcomen component */ - public ClosableWelcomeWidget(boolean closable, String headerText, String welcomeText, HorizontalAlignmentConstant horizontalAlignment, StringMessages stringConstants) { - super(headerText, welcomeText, horizontalAlignment); + public ClosableWelcomeWidget(boolean closable, String headerText, String welcomeText, StringMessages stringConstants) { + super(); this.stringConstants = stringConstants; this.closable = closable; + mainPanel = new FormPanel(); + mainPanel.setStyleName(STYLE_NAME_PREFIX + "WelcomePanel"); + add(mainPanel); + closeButton = new Button(this.stringConstants.close()); closeButton.addClickHandler(new ClickHandler() { @Override @@ -33,8 +44,15 @@ public class ClosableWelcomeWidget extends SimpleWelcomeWidget { } }); closeButton.setVisible(this.closable); - setHorizontalAlignment(ALIGN_RIGHT); - insert(closeButton, 0); + closeButton.setStyleName(STYLE_NAME_PREFIX + "CloseButton"); + mainPanel.add(closeButton); + + welcomeHeader = new Label(headerText); + welcomeHeader.setStyleName(STYLE_NAME_PREFIX + "Header"); + mainPanel.add(welcomeHeader); + this.welcomeText = new HTML(new SafeHtmlBuilder().appendEscapedLines(welcomeText).toSafeHtml()); + this.welcomeText.setStyleName(STYLE_NAME_PREFIX + "WelcomeText"); + mainPanel.add(this.welcomeText); } /** @@ -52,4 +70,15 @@ public class ClosableWelcomeWidget extends SimpleWelcomeWidget { closeButton.setVisible(closable); } + @Override + public void setWelcomeText(String welcomeText) { + this.welcomeText = new HTML(new SafeHtmlBuilder().appendEscapedLines(welcomeText).toSafeHtml()); + this.welcomeText.setStyleName(STYLE_NAME_PREFIX + "WelcomeText"); + } + + @Override + public void setWelcomeHeaderText(String headerText) { + welcomeHeader.setText(headerText); + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/HasWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/HasWelcomeWidget.java index 21c433032a1..717fbbddb2f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/HasWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/HasWelcomeWidget.java @@ -1,7 +1,10 @@ package com.sap.sailing.gwt.ui.shared.components; -import com.google.gwt.user.client.ui.Widget; - +/** + * Classes which implement this interface have the ability to display a {@link WelcomeWidget}. + * + * @author Lennart Hensler (D054527) + */ public interface HasWelcomeWidget { /** @@ -9,7 +12,7 @@ public interface HasWelcomeWidget { * @param welcome The new welcome widget * @see {@link WelcomeWidget}, {@link SimpleWelcomeWidget}, {@link ClosableWelcomeWidget}, ... */ - void setWelcomeWidget(Widget welcome); + void setWelcomeWidget(WelcomeWidget welcome); void setWelcomeWidgetVisible(boolean isVisible); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java index 8543d466f64..5f4f9809242 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java @@ -1,12 +1,13 @@ package com.sap.sailing.gwt.ui.shared.components; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.VerticalPanel; -public class SimpleWelcomeWidget extends VerticalPanel implements WelcomeWidget { +public class SimpleWelcomeWidget extends WelcomeWidget { + private FormPanel mainPanel; private Label welcomeHeader; private HTML welcomeText; @@ -14,20 +15,25 @@ public class SimpleWelcomeWidget extends VerticalPanel implements WelcomeWidget * Creates a VerticalPanel as welcome widget with a header and a welcome message * @param headerText The text of the header component * @param welcomeText The text of the welcome message - * @param horizontalAlignment The alignment of the components in the VerticalPanel */ - public SimpleWelcomeWidget(String headerText, String welcomeText, HorizontalAlignmentConstant horizontalAlignment) { + public SimpleWelcomeWidget(String headerText, String welcomeText) { super(); - setHorizontalAlignment(horizontalAlignment); + mainPanel = new FormPanel(); + mainPanel.setStyleName(STYLE_NAME_PREFIX + "WelcomePanel"); + add(mainPanel); + welcomeHeader = new Label(headerText); - add(welcomeHeader); + welcomeHeader.setStyleName(STYLE_NAME_PREFIX + "Header"); + mainPanel.add(welcomeHeader); this.welcomeText = new HTML(new SafeHtmlBuilder().appendEscapedLines(welcomeText).toSafeHtml()); - add(this.welcomeText); + this.welcomeText.setStyleName(STYLE_NAME_PREFIX + "WelcomeText"); + mainPanel.add(this.welcomeText); } @Override public void setWelcomeText(String welcomeText) { this.welcomeText = new HTML(new SafeHtmlBuilder().appendEscapedLines(welcomeText).toSafeHtml()); + this.welcomeText.setStyleName(STYLE_NAME_PREFIX + "WelcomeText"); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/WelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/WelcomeWidget.java index aeafc846b68..6897d6a0e47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/WelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/WelcomeWidget.java @@ -1,17 +1,25 @@ package com.sap.sailing.gwt.ui.shared.components; -public interface WelcomeWidget { +import com.google.gwt.user.client.ui.SimplePanel; + +/** + * A WelcomeWidget displays a short message to the user to show him the functionality (or whatever) of the component on which it's displayed. + * @author Lennart Hensler (D054527) + */ +public abstract class WelcomeWidget extends SimplePanel { + + final static String STYLE_NAME_PREFIX = "welcomeWidget-"; /** * Sets the text of the header component. * @param headerText The new header text */ - void setWelcomeHeaderText(String headerText); + abstract void setWelcomeHeaderText(String headerText); /** * Sets the text of the component under the header * @param welcomeText The new welcome text */ - void setWelcomeText(String welcomeText); + abstract void setWelcomeText(String welcomeText); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java index 9b0a145f218..67176b63213 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java @@ -13,12 +13,10 @@ import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.NoSelectionModel; import com.sap.sailing.domain.common.EventNameAndRaceName; @@ -30,23 +28,31 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceInLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.components.HasWelcomeWidget; +import com.sap.sailing.gwt.ui.shared.components.WelcomeWidget; public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget { interface AnchorTemplates extends SafeHtmlTemplates { - @SafeHtmlTemplates.Template("{1}") - SafeHtml anchor(String url, String displayName); + @SafeHtmlTemplates.Template("{1}") + SafeHtml anchor(String url, String displayName, String styleClass); + } + + interface TextWithClassTemplate extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("{0}") + SafeHtml textWithClass(String text, String styleClass); } private static final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); - private static final int MAX_COLUMNS_IN_ROW = 10; + private static final TextWithClassTemplate TEXTTEMPLATE = GWT.create(TextWithClassTemplate.class); + private static final int MAX_COLUMNS_IN_ROW = 10; + private static final String STYLE_NAME_PREFIX = "leaderboardGroupPanel-"; private SailingServiceAsync sailingService; private StringMessages stringConstants; private ErrorReporter errorReporter; private LeaderboardGroupDTO group; - private VerticalPanel mainPanel; + private FlowPanel mainPanel; private Widget welcomeWidget = null; public LeaderboardGroupPanel(SailingServiceAsync sailingService, StringMessages stringConstants, @@ -56,7 +62,7 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget this.stringConstants = stringConstants; this.errorReporter = errorReporter; - mainPanel = new VerticalPanel(); + mainPanel = new FlowPanel(); mainPanel.setWidth("95%"); add(mainPanel); final Runnable buildGUI = new Runnable() { @@ -90,23 +96,27 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget private void buildGUI() { //Create group details GUI - HorizontalPanel groupDetailsPanel = new HorizontalPanel(); - groupDetailsPanel.setSpacing(8); + FlowPanel groupDetailsPanel = new FlowPanel(); + groupDetailsPanel.setStyleName(STYLE_NAME_PREFIX + "GroupDetailsPanel"); mainPanel.add(groupDetailsPanel); - + Label groupNameLabel = new Label(group.name + ":"); + groupNameLabel.setStyleName(STYLE_NAME_PREFIX + "GroupName"); groupDetailsPanel.add(groupNameLabel); //Using HTML to display the line breaks in the description HTML groupDescriptionLabel = new HTML(new SafeHtmlBuilder().appendEscapedLines(group.description).toSafeHtml()); + groupDescriptionLabel.setStyleName(STYLE_NAME_PREFIX + "GroupDescription"); groupDetailsPanel.add(groupDescriptionLabel); //Create group leaderboards GUI - CaptionPanel leaderboardsCaptionPanel = new CaptionPanel(stringConstants.leaderboards()); - mainPanel.add(leaderboardsCaptionPanel); + Label leaderboardsTableLabel = new Label(stringConstants.leaderboards()); + leaderboardsTableLabel.setStyleName(STYLE_NAME_PREFIX + "LeaderboardsTableLabel"); + mainPanel.add(leaderboardsTableLabel); if (group.leaderboards.size() <= MAX_COLUMNS_IN_ROW) { - CellTable leaderboardsTable = new CellTable(); + LeaderboardGroupFullTableResources tableResources = GWT.create(LeaderboardGroupFullTableResources.class); + CellTable leaderboardsTable = new CellTable(200, tableResources); leaderboardsTable.setWidth("100%"); leaderboardsTable.setSelectionModel(new NoSelectionModel()); @@ -133,7 +143,7 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget } leaderboardsTable.setRowData(tableData); - leaderboardsCaptionPanel.add(leaderboardsTable); + mainPanel.add(leaderboardsTable); } else { AnchorCell nameAnchorCell = new AnchorCell(); Column nameColumn = new Column(nameAnchorCell) { @@ -143,7 +153,7 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget return ANCHORTEMPLATE.anchor("/gwt/Leaderboard.html?name=" + leaderboard.name + "&leaderboardGroupName=" + group.name + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""), - leaderboard.name); + leaderboard.name, STYLE_NAME_PREFIX + "ActiveLeaderboard"); } }; @@ -155,7 +165,8 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget } }; - CellTable leaderboardsTable = new CellTable(); + LeaderboardGroupCompactTableResources tableResources = GWT.create(LeaderboardGroupCompactTableResources.class); + CellTable leaderboardsTable = new CellTable(200, tableResources); leaderboardsTable.setWidth("100%"); leaderboardsTable.setSelectionModel(new NoSelectionModel()); @@ -165,7 +176,7 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget leaderboardsTable.setColumnWidth(racesColumn, "70%"); leaderboardsTable.setRowData(group.leaderboards); - leaderboardsCaptionPanel.add(leaderboardsTable); + mainPanel.add(leaderboardsTable); } } @@ -176,16 +187,16 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget if (race.getRaceColumnName().equals(stringConstants.overview())) { String link = "/gwt/Leaderboard.html?name=" + leaderboard.name + "&leaderboardGroupName=" + group.name + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr="+debugParam : ""); - b.append(ANCHORTEMPLATE.anchor(link, displayName)); + b.append(ANCHORTEMPLATE.anchor(link, displayName, STYLE_NAME_PREFIX + "ActiveLeaderboard")); } else { if (race.getRaceIdentifier() != null) { EventNameAndRaceName raceId = (EventNameAndRaceName) race.getRaceIdentifier(); String link = "/gwt/RaceBoard.html?leaderboardName=" + leaderboard.name + "&raceName=" + raceId.getRaceName() + "&eventName=" + raceId.getEventName() + "&leaderboardGroupName=" + group.name + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""); - b.append(ANCHORTEMPLATE.anchor(link, displayName)); + b.append(ANCHORTEMPLATE.anchor(link, displayName, STYLE_NAME_PREFIX + "ActiveRace")); } else { - b.appendHtmlConstant(race.getRaceColumnName()); + b.append(TEXTTEMPLATE.textWithClass(race.getRaceColumnName(), STYLE_NAME_PREFIX + "InactiveRace")); } } return b.toSafeHtml(); @@ -204,9 +215,9 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget String link = "/gwt/RaceBoard.html?leaderboardName=" + leaderboard.name + "&raceName=" + raceId.getRaceName() + "&eventName=" + raceId.getEventName() + "&leaderboardGroupName=" + group.name + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""); - b.append(ANCHORTEMPLATE.anchor(link, race.getRaceColumnName())); + b.append(ANCHORTEMPLATE.anchor(link, race.getRaceColumnName(), STYLE_NAME_PREFIX + "ActiveRace")); } else { - b.appendHtmlConstant(race.getRaceColumnName()); + b.append(TEXTTEMPLATE.textWithClass(race.getRaceColumnName(), STYLE_NAME_PREFIX + "InactiveRace")); } first = false; } @@ -221,7 +232,7 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget } @Override - public void setWelcomeWidget(Widget welcome) { + public void setWelcomeWidget(WelcomeWidget welcome) { boolean needsToBeInserted = welcomeWidget == null; welcomeWidget = welcome; welcomeWidget.setWidth("100%"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java index c409b0c0c33..d312f04ae35 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java @@ -9,7 +9,6 @@ import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.components.ClosableWelcomeWidget; -import com.sap.sailing.gwt.ui.shared.components.SimpleWelcomeWidget; /** * @@ -55,7 +54,7 @@ public class SpectatorEntryPoint extends AbstractEntryPoint implements EventRefr panelToDisplay = new LeaderboardGroupPanel(sailingService, stringMessages, this, groupName); LeaderboardGroupPanel groupPanel = (LeaderboardGroupPanel) panelToDisplay; groupPanel.setWelcomeWidget(new ClosableWelcomeWidget(true, stringMessages.welcomeToSailingAnalytics(), - "Ipsum lorum\nHello World!", SimpleWelcomeWidget.ALIGN_RIGHT, stringMessages)); + "Ipsum lorum\nHello World!", stringMessages)); } panelToDisplay.setSize("100%", "100%"); rootPanel.add(panelToDisplay); From 6f87fd5945ed193840d8e0dbc0b9873ed97426ee Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 13:49:53 +0100 Subject: [PATCH 02/14] Added stylability to the LeaderboardGroupPanel and updated Spectator.css --- java/com.sap.sailing.gwt.ui/Spectator.css | 39 +++++ .../LeaderboardGroupCompactTable.css | 134 ++++++++++++++++++ ...LeaderboardGroupCompactTableResources.java | 12 ++ .../spectator/LeaderboardGroupFullTable.css | 134 ++++++++++++++++++ .../LeaderboardGroupFullTableResources.java | 12 ++ 5 files changed, 331 insertions(+) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTable.css create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTableResources.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTable.css create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTableResources.java diff --git a/java/com.sap.sailing.gwt.ui/Spectator.css b/java/com.sap.sailing.gwt.ui/Spectator.css index c46c62491bf..f7896688d1d 100644 --- a/java/com.sap.sailing.gwt.ui/Spectator.css +++ b/java/com.sap.sailing.gwt.ui/Spectator.css @@ -236,3 +236,42 @@ input.magnifier { text-decoration: none; } +.leaderboardGroupPanel-GroupName { + float: left; +} + +.leaderboardGroupPanel-GroupDescription { + float: left; +} + +.leaderboardGroupPanel-LeaderboardsTableLabel { + +} + +.leaderboardGroupPanel-ActiveLeaderboard{ + +} + +.leaderboardGroupPanel-ActiveRace { + +} + +.leaderboardGroupPanel-InactiveRace { + +} + +.welcomeWidget-WelcomePanel{ + +} + +.welcomeWidget-CloseButton { + +} + +.welcomeWidget-Header { + +} + +.welcomeWidget-WelcomeText { + +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTable.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTable.css new file mode 100644 index 00000000000..5deab622b9c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTable.css @@ -0,0 +1,134 @@ +@CHARSET "ISO-8859-1"; +.cellTableWidget { + border-left: 1px solid #c9caca; + border-top: 1px solid #c9caca; +} + +/** v-align images in the table header + * grey background for table header + */ +.cellTableHeader { + position: relative; + vertical-align: middle; + background-color: #e5e5e5; + border-right: 1px solid #ffffff !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 3px 12px; + text-align: center; +} + +.cellTableFirstColumnHeader { + position: relative; + vertical-align: middle; + background-color: #e5e5e5; + border-left: none !important; + border-right: 1px solid #ffffff !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 3px 12px; + text-align: center; +} + +.cellTableLastColumnHeader { + position: relative; + vertical-align: middle; + background-color: #e5e5e5; + border-right: 1px solid #c9caca !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 3px 12px; + text-align: center; +} + +.cellTableHeader > span > img:first-child { + position: relative; + vertical-align: middle; +} + +.cellTableEvenRow { + border-bottom: 1px solid #e5e5e5; + background-color: rgba(255,255,255,0.5); +} + +.cellTableEvenRowCell { + border-right: 1px solid #c9caca !important; + border-left: none; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 5px 17px 5px !important; + white-space: nowrap; +} + +.cellTableOddRow { + border-bottom: 1px solid #e5e5e5; + background-color: rgba(242,242,242,0.5); +} + +.cellTableOddRowCell { + border-left: none !important; + border-right: 1px solid #c9caca !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 5px 17px 5px !important; + white-space: nowrap; +} + +.cellTableHoveredRow { + background-color: rgba(229,242,249,0.5); + border-bottom: 1px solid #e5e5e5; +} + +.cellTableSelectedRow { + color: #000000; + background-color: rgba(229,242,249,0.5); + border-right: 2px solid #21a0e4 !important; + border-left: 2px solid #21a0e4 !important; + border: 2px solid #21a0e4; +} + +.cellTableSelectedRow > td:FIRST-CHILD { + border-left: 2px solid rgba(33,160,228,0.5) !important; +} + +.cellTableSelectedRow > td:LAST-CHILD { + border-right: 2px solid rgba(33,160,228,0.5) !important; +} + +.cellTableSelectedRowCell { + border-left: none; + border-right: 1px solid #e5e5e5; + border-top: 2px solid #21a0e4 !important; + border-bottom: 2px solid #21a0e4 !important; +} + +.cellTableRaceColumnHeader { + background-color: #e0e0e0; + padding: 3px 12px; +} + +.cellTableLegColumnHeader { + background-color: #efefef; + padding: 3px 12px; +} + +.cellTableLegDetailColumnHeader { + background-color: #f4f5f6; + padding: 3px 12px; +} + +.cellTableRaceColumn { + background-color: #e0e0e0 !important; +} + +.cellTableLegColumn { + background-color: #efefef !important; +} + +.cellTableLegDetailColumn { + background-color: #ffffff !important; +} + +.cellTableTotalColumn { + background-color: #e0e0e0 !important; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTableResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTableResources.java new file mode 100644 index 00000000000..9aed999a0d3 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupCompactTableResources.java @@ -0,0 +1,12 @@ +package com.sap.sailing.gwt.ui.spectator; + +import com.google.gwt.user.cellview.client.CellTable; +import com.sap.sailing.gwt.ui.adminconsole.AdminConsoleTableResources.AdminConsoleTableStyle; + +public interface LeaderboardGroupCompactTableResources extends CellTable.Resources { + @Source({ CellTable.Style.DEFAULT_CSS, "LeaderboardGroupFullTable.css" }) + AdminConsoleTableStyle cellTableStyle(); + + interface LeaderboardGroupTableStyle extends CellTable.Style { + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTable.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTable.css new file mode 100644 index 00000000000..5deab622b9c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTable.css @@ -0,0 +1,134 @@ +@CHARSET "ISO-8859-1"; +.cellTableWidget { + border-left: 1px solid #c9caca; + border-top: 1px solid #c9caca; +} + +/** v-align images in the table header + * grey background for table header + */ +.cellTableHeader { + position: relative; + vertical-align: middle; + background-color: #e5e5e5; + border-right: 1px solid #ffffff !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 3px 12px; + text-align: center; +} + +.cellTableFirstColumnHeader { + position: relative; + vertical-align: middle; + background-color: #e5e5e5; + border-left: none !important; + border-right: 1px solid #ffffff !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 3px 12px; + text-align: center; +} + +.cellTableLastColumnHeader { + position: relative; + vertical-align: middle; + background-color: #e5e5e5; + border-right: 1px solid #c9caca !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 3px 12px; + text-align: center; +} + +.cellTableHeader > span > img:first-child { + position: relative; + vertical-align: middle; +} + +.cellTableEvenRow { + border-bottom: 1px solid #e5e5e5; + background-color: rgba(255,255,255,0.5); +} + +.cellTableEvenRowCell { + border-right: 1px solid #c9caca !important; + border-left: none; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 5px 17px 5px !important; + white-space: nowrap; +} + +.cellTableOddRow { + border-bottom: 1px solid #e5e5e5; + background-color: rgba(242,242,242,0.5); +} + +.cellTableOddRowCell { + border-left: none !important; + border-right: 1px solid #c9caca !important; + border-top: none !important; + border-bottom: 1px solid #c9caca !important; + padding: 5px 17px 5px !important; + white-space: nowrap; +} + +.cellTableHoveredRow { + background-color: rgba(229,242,249,0.5); + border-bottom: 1px solid #e5e5e5; +} + +.cellTableSelectedRow { + color: #000000; + background-color: rgba(229,242,249,0.5); + border-right: 2px solid #21a0e4 !important; + border-left: 2px solid #21a0e4 !important; + border: 2px solid #21a0e4; +} + +.cellTableSelectedRow > td:FIRST-CHILD { + border-left: 2px solid rgba(33,160,228,0.5) !important; +} + +.cellTableSelectedRow > td:LAST-CHILD { + border-right: 2px solid rgba(33,160,228,0.5) !important; +} + +.cellTableSelectedRowCell { + border-left: none; + border-right: 1px solid #e5e5e5; + border-top: 2px solid #21a0e4 !important; + border-bottom: 2px solid #21a0e4 !important; +} + +.cellTableRaceColumnHeader { + background-color: #e0e0e0; + padding: 3px 12px; +} + +.cellTableLegColumnHeader { + background-color: #efefef; + padding: 3px 12px; +} + +.cellTableLegDetailColumnHeader { + background-color: #f4f5f6; + padding: 3px 12px; +} + +.cellTableRaceColumn { + background-color: #e0e0e0 !important; +} + +.cellTableLegColumn { + background-color: #efefef !important; +} + +.cellTableLegDetailColumn { + background-color: #ffffff !important; +} + +.cellTableTotalColumn { + background-color: #e0e0e0 !important; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTableResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTableResources.java new file mode 100644 index 00000000000..07a8677c30f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupFullTableResources.java @@ -0,0 +1,12 @@ +package com.sap.sailing.gwt.ui.spectator; + +import com.google.gwt.user.cellview.client.CellTable; +import com.sap.sailing.gwt.ui.adminconsole.AdminConsoleTableResources.AdminConsoleTableStyle; + +public interface LeaderboardGroupFullTableResources extends CellTable.Resources { + @Source({ CellTable.Style.DEFAULT_CSS, "LeaderboardGroupCompactTable.css" }) + AdminConsoleTableStyle cellTableStyle(); + + interface LeaderboardGroupTableStyle extends CellTable.Style { + } +} From cc0079d6f2d40b0b9595b00969572365393c39af Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 14:15:28 +0100 Subject: [PATCH 03/14] Fixed ClosableWelcomeWidget (extends now SimpleWelcomeWidget) --- .../gwt/ui/leaderboard/LeaderboardPanel.java | 6 ++++ .../components/ClosableWelcomeWidget.java | 36 ++----------------- .../components/SimpleWelcomeWidget.java | 6 ++-- 3 files changed, 12 insertions(+), 36 deletions(-) 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 index f944fbcb029..a8a893d6e81 100644 --- 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 @@ -1040,6 +1040,7 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta * Also updates the min/max values on the columns */ protected void updateLeaderboard(LeaderboardDTO leaderboard) { + Set selectedRows = leaderboardSelectionModel.getSelectedSet(); competitorSelectionProvider.setCompetitors(leaderboard.competitors); selectedRaceColumns.addAll(getRaceColumnsToAddImplicitly(leaderboard)); setLeaderboard(leaderboard); @@ -1060,6 +1061,11 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta Collections.sort(getData().getList(), getRankColumn().getComparator()); getLeaderboardTable().getColumnSortList().push(getRankColumn()); } + //Reselect the selected rows + leaderboardSelectionModel.clear(); + for (LeaderboardRowDTO row : selectedRows) { + leaderboardSelectionModel.setSelected(row, true); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java index e080a0355cf..20f328e44ac 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java @@ -2,22 +2,14 @@ package com.sap.sailing.gwt.ui.shared.components; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; import com.sap.sailing.gwt.ui.client.StringMessages; -public class ClosableWelcomeWidget extends WelcomeWidget { +public class ClosableWelcomeWidget extends SimpleWelcomeWidget { private StringMessages stringConstants; private boolean closable; - private FormPanel mainPanel; - private Button closeButton; - private Label welcomeHeader; - private HTML welcomeText; /** * Creates a new closable welcome widget. It has the same components as the {@link SimpleWelcomeWidget} and a Button @@ -28,14 +20,10 @@ public class ClosableWelcomeWidget extends WelcomeWidget { * @param welcomeText The text of the component under the header (welcome component) */ public ClosableWelcomeWidget(boolean closable, String headerText, String welcomeText, StringMessages stringConstants) { - super(); + super(headerText, welcomeText); this.stringConstants = stringConstants; this.closable = closable; - mainPanel = new FormPanel(); - mainPanel.setStyleName(STYLE_NAME_PREFIX + "WelcomePanel"); - add(mainPanel); - closeButton = new Button(this.stringConstants.close()); closeButton.addClickHandler(new ClickHandler() { @Override @@ -45,14 +33,7 @@ public class ClosableWelcomeWidget extends WelcomeWidget { }); closeButton.setVisible(this.closable); closeButton.setStyleName(STYLE_NAME_PREFIX + "CloseButton"); - mainPanel.add(closeButton); - - welcomeHeader = new Label(headerText); - welcomeHeader.setStyleName(STYLE_NAME_PREFIX + "Header"); - mainPanel.add(welcomeHeader); - this.welcomeText = new HTML(new SafeHtmlBuilder().appendEscapedLines(welcomeText).toSafeHtml()); - this.welcomeText.setStyleName(STYLE_NAME_PREFIX + "WelcomeText"); - mainPanel.add(this.welcomeText); + mainPanel.insert(closeButton, 0); } /** @@ -70,15 +51,4 @@ public class ClosableWelcomeWidget extends WelcomeWidget { closeButton.setVisible(closable); } - @Override - public void setWelcomeText(String welcomeText) { - this.welcomeText = new HTML(new SafeHtmlBuilder().appendEscapedLines(welcomeText).toSafeHtml()); - this.welcomeText.setStyleName(STYLE_NAME_PREFIX + "WelcomeText"); - } - - @Override - public void setWelcomeHeaderText(String headerText) { - welcomeHeader.setText(headerText); - } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java index 5f4f9809242..749d864efe4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java @@ -1,13 +1,13 @@ package com.sap.sailing.gwt.ui.shared.components; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; public class SimpleWelcomeWidget extends WelcomeWidget { - private FormPanel mainPanel; + protected FlowPanel mainPanel; private Label welcomeHeader; private HTML welcomeText; @@ -18,7 +18,7 @@ public class SimpleWelcomeWidget extends WelcomeWidget { */ public SimpleWelcomeWidget(String headerText, String welcomeText) { super(); - mainPanel = new FormPanel(); + mainPanel = new FlowPanel(); mainPanel.setStyleName(STYLE_NAME_PREFIX + "WelcomePanel"); add(mainPanel); From 88f46d934d068b9f587183bf1ed22ef9b07eaa40 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 14:38:33 +0100 Subject: [PATCH 04/14] Refactoring of shared elements --- .../components => client}/HasWelcomeWidget.java | 6 +++++- .../com/sap/sailing/gwt/ui/client/TimePanel.java | 2 +- .../ui/shared/controls/{ => slider}/SliderBar.css | 0 .../ui/shared/controls/{ => slider}/SliderBar.java | 2 +- .../gwt/ui/shared/controls/{ => slider}/slider.gif | Bin .../shared/controls/{ => slider}/sliderDisabled.gif | Bin .../shared/controls/{ => slider}/sliderSliding.gif | Bin .../ClosableWelcomeWidget.java | 2 +- .../{components => panels}/SimpleWelcomeWidget.java | 2 +- .../{components => panels}/WelcomeWidget.java | 2 +- .../gwt/ui/spectator/LeaderboardGroupPanel.java | 4 ++-- .../gwt/ui/spectator/SpectatorEntryPoint.java | 2 +- .../com/sap/sailing/gwt/ui/Spectator.gwt.xml | 1 - 13 files changed, 13 insertions(+), 10 deletions(-) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/{shared/components => client}/HasWelcomeWidget.java (66%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/{ => slider}/SliderBar.css (100%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/{ => slider}/SliderBar.java (99%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/{ => slider}/slider.gif (100%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/{ => slider}/sliderDisabled.gif (100%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/{ => slider}/sliderSliding.gif (100%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/{components => panels}/ClosableWelcomeWidget.java (95%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/{components => panels}/SimpleWelcomeWidget.java (94%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/{components => panels}/WelcomeWidget.java (90%) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/HasWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HasWelcomeWidget.java similarity index 66% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/HasWelcomeWidget.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HasWelcomeWidget.java index 717fbbddb2f..7de178c715a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/HasWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/HasWelcomeWidget.java @@ -1,4 +1,8 @@ -package com.sap.sailing.gwt.ui.shared.components; +package com.sap.sailing.gwt.ui.client; + +import com.sap.sailing.gwt.ui.shared.panels.ClosableWelcomeWidget; +import com.sap.sailing.gwt.ui.shared.panels.SimpleWelcomeWidget; +import com.sap.sailing.gwt.ui.shared.panels.WelcomeWidget; /** * Classes which implement this interface have the ability to display a {@link WelcomeWidget}. 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 f5bbad2a95d..e9c7f031294 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 @@ -25,7 +25,7 @@ import com.sap.sailing.gwt.ui.client.Timer.PlayModes; 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.SliderBar; +import com.sap.sailing.gwt.ui.shared.controls.slider.SliderBar; public class TimePanel extends FormPanel implements Component, TimeListener, PlayStateListener { private final Timer timer; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/SliderBar.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/SliderBar.css similarity index 100% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/SliderBar.css rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/SliderBar.css diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/SliderBar.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/SliderBar.java similarity index 99% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/SliderBar.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/SliderBar.java index 52f1420a290..cacec0b62a0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/SliderBar.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/SliderBar.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.shared.controls; +package com.sap.sailing.gwt.ui.shared.controls.slider; /* * Copyright 2008 Google Inc. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider.gif b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/slider.gif similarity index 100% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider.gif rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/slider.gif diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/sliderDisabled.gif b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/sliderDisabled.gif similarity index 100% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/sliderDisabled.gif rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/sliderDisabled.gif diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/sliderSliding.gif b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/sliderSliding.gif similarity index 100% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/sliderSliding.gif rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/controls/slider/sliderSliding.gif diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/ClosableWelcomeWidget.java similarity index 95% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/ClosableWelcomeWidget.java index 20f328e44ac..906f1819176 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/ClosableWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/ClosableWelcomeWidget.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.shared.components; +package com.sap.sailing.gwt.ui.shared.panels; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/SimpleWelcomeWidget.java similarity index 94% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/SimpleWelcomeWidget.java index 749d864efe4..30f352f22d7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/SimpleWelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/SimpleWelcomeWidget.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.shared.components; +package com.sap.sailing.gwt.ui.shared.panels; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.client.ui.FlowPanel; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/WelcomeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/WelcomeWidget.java similarity index 90% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/WelcomeWidget.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/WelcomeWidget.java index 6897d6a0e47..b206e507d60 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/components/WelcomeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/WelcomeWidget.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.shared.components; +package com.sap.sailing.gwt.ui.shared.panels; import com.google.gwt.user.client.ui.SimplePanel; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java index 67176b63213..c04a70c1976 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java @@ -22,13 +22,13 @@ import com.google.gwt.view.client.NoSelectionModel; import com.sap.sailing.domain.common.EventNameAndRaceName; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.HasWelcomeWidget; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceInLeaderboardDTO; -import com.sap.sailing.gwt.ui.shared.components.HasWelcomeWidget; -import com.sap.sailing.gwt.ui.shared.components.WelcomeWidget; +import com.sap.sailing.gwt.ui.shared.panels.WelcomeWidget; public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java index d312f04ae35..ab8e6978408 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java @@ -8,7 +8,7 @@ import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; import com.sap.sailing.gwt.ui.client.EventRefresher; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; -import com.sap.sailing.gwt.ui.shared.components.ClosableWelcomeWidget; +import com.sap.sailing.gwt.ui.shared.panels.ClosableWelcomeWidget; /** * diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Spectator.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Spectator.gwt.xml index 34ff6acbe67..151f75089c2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Spectator.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Spectator.gwt.xml @@ -25,7 +25,6 @@ - From e1ca2fa1c42bbac0554c922ab31154870c177786 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 2 Feb 2012 14:42:35 +0100 Subject: [PATCH 05/14] use confidence-based averager to average speed, not only bearing; prepared forwarding of confidences --- .../domain/base/SpeedWithConfidence.java | 5 +- .../base/impl/SpeedWithConfidenceImpl.java | 48 +++++++++++++ .../confidence/ConfidenceBasedAverager.java | 3 +- .../impl/ConfidenceBasedAveragerImpl.java | 1 - .../sailing/domain/tracking/GPSFixTrack.java | 15 ++++ .../impl/DynamicGPSFixMovingTrackImpl.java | 48 ++++++++----- .../domain/tracking/impl/GPSFixTrackImpl.java | 68 ++++++++++++++----- .../domain/tracking/impl/WindTrackImpl.java | 3 +- .../com.google.gwt.eclipse.core.prefs | 4 +- 9 files changed, 150 insertions(+), 45 deletions(-) create mode 100755 java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java index 0ba42b2613e..f1a1ab5d8c9 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/SpeedWithConfidence.java @@ -1,8 +1,7 @@ package com.sap.sailing.domain.base; import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.confidence.HasConfidence; +import com.sap.sailing.domain.confidence.HasConfidenceAndIsScalable; -public interface SpeedWithConfidence extends HasConfidence { - Speed getObject(); +public interface SpeedWithConfidence extends HasConfidenceAndIsScalable { } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java new file mode 100755 index 00000000000..50d9a7609c1 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.base.SpeedWithConfidence; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.confidence.ScalableValue; + +public class SpeedWithConfidenceImpl extends HasConfidenceImpl implements SpeedWithConfidence { + public SpeedWithConfidenceImpl(Speed object, double confidence, RelativeTo relativeTo) { + super(object, confidence, relativeTo); + } + + @Override + public ScalableValue getScalableValue() { + return new ScalableSpeed(getObject()); + } + + private static class ScalableSpeed implements ScalableValue { + private final double knots; + + public ScalableSpeed(Speed speed) { + this.knots = speed.getKnots(); + } + + private ScalableSpeed(double knots) { + this.knots = knots; + } + + @Override + public ScalableValue multiply(double factor) { + return new ScalableSpeed(factor*knots); + } + + @Override + public ScalableValue add(ScalableValue t) { + return new ScalableSpeed(knots+t.getValue()); + } + + @Override + public Speed divide(double divisor, double confidence) { + return new KnotSpeedImpl(knots / divisor); + } + + @Override + public Double getValue() { + return knots; + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAverager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAverager.java index 6654f9a0f02..73e476d270f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAverager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ConfidenceBasedAverager.java @@ -10,7 +10,8 @@ public interface ConfidenceBasedAverager { * null average with confidence is returned. Note, however, that the * {@link HasConfidence#getObject() object} in the {@link HasConfidence} result may be null, * e.g., if the combined confidence went to 0.0 and the averager was unable to determine a reasonable - * average value. + * average value. The result's confidence is the arithmetic average of the confidences of all values + * that contributed to the result. */ HasConfidence getAverage( Iterable> values, RelativeTo at); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java index bcced8a0332..30e07b50457 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java @@ -61,7 +61,6 @@ public class ConfidenceBasedAveragerImpl implem } confidenceSum += relativeWeight; } - // TODO determine new confidence, e.g., based on the variance of estimates averaged double newConfidence = confidenceSum / Util.size(values); BaseType result = numerator.divide(confidenceSum, newConfidence); return new HasConfidenceImpl(result, newConfidence, at); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java index 69d1fab1c37..28031c44bb5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java @@ -1,11 +1,13 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.confidence.Weigher; /** * A track records the {@link GPSFix}es received for an object of type @@ -74,6 +76,19 @@ public interface GPSFixTrack extends Trackat. */ SpeedWithBearing getEstimatedSpeed(TimePoint at); + + /** + * Estimates the tracked item's speed/bearing, returning the result as a value with confidence. The confidences of + * the individual fixes contributing to the estimation are computed using the weigher. The result's + * confidence is the average confidence of the fixes used. While this is probably not a mathematically meaningful + * confidence value, it helps in comparing confidences of different estimations relative to each other. + */ + SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, Weigher weigher); + + /** + * Same as {@link #getEstimatedSpeed(TimePoint, Weigher)}, but using the raw fixes. + */ + SpeedWithBearingWithConfidence getRawEstimatedSpeed(TimePoint at, Weigher weigher); long getMillisecondsOverWhichToAverageSpeed(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java index 01ebcaf44c1..ff3eae4f398 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.tracking.impl; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -7,15 +8,21 @@ import java.util.NavigableSet; import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; +import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl; +import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; +import com.sap.sailing.domain.confidence.HasConfidence; import com.sap.sailing.domain.confidence.Weigher; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -44,42 +51,47 @@ public class DynamicGPSFixMovingTrackImpl extends DynamicTrackImpl fixesToUseForSpeedEstimation) { + protected SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, + NavigableSet fixesToUseForSpeedEstimation, Weigher weigher) { List relevantFixes = getFixesRelevantForSpeedEstimation(at, fixesToUseForSpeedEstimation); - double knotSum = 0; - Weigher weigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( - // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero - /* halfConfidenceAfterMilliseconds */ getMillisecondsOverWhichToAverageSpeed()); + List> speeds = new ArrayList>(); BearingWithConfidenceCluster bearingCluster = new BearingWithConfidenceCluster(weigher); - int count = 0; if (!relevantFixes.isEmpty()) { Iterator fixIter = relevantFixes.iterator(); GPSFixMoving last = fixIter.next(); - knotSum = last.getSpeed().getKnots(); + SpeedWithConfidenceImpl speedWithConfidence = new SpeedWithConfidenceImpl(last.getSpeed(), + /* original confidence */ 0.9, last.getTimePoint()); + speeds.add(speedWithConfidence); bearingCluster.add(new BearingWithConfidenceImpl(last.getSpeed().getBearing(), /* confidence */ 0.9, last.getTimePoint())); - count = 1; while (fixIter.hasNext()) { - // TODO bug #169: use confidence-based averager for speed, too, and not only for bearing // add to average the position and time difference GPSFixMoving next = fixIter.next(); - knotSum += last.getPosition().getDistance(next.getPosition()) - .inTime(next.getTimePoint().asMillis() - last.getTimePoint().asMillis()).getKnots(); + SpeedWithConfidenceImpl measuredSpeedWithConfidence = new SpeedWithConfidenceImpl( + last.getPosition().getDistance(next.getPosition()) + .inTime(next.getTimePoint().asMillis() - last.getTimePoint().asMillis()), + /* original confidence */0.9, new MillisecondsTimePoint((last.getTimePoint().asMillis()+next.getTimePoint().asMillis())/2)); + speeds.add(measuredSpeedWithConfidence); bearingCluster.add(new BearingWithConfidenceImpl(last.getPosition().getBearingGreatCircle(next.getPosition()), /* confidence */ weigher.getConfidence(last.getTimePoint(), next.getTimePoint()), new MillisecondsTimePoint((last.getTimePoint().asMillis()+next.getTimePoint().asMillis())/2))); - count++; // add to average the speed and bearing provided by the GPSFixMoving - knotSum += next.getSpeed().getKnots(); + SpeedWithConfidenceImpl computedSpeedWithConfidence = new SpeedWithConfidenceImpl( + next.getSpeed(), /* original confidence */0.9, next.getTimePoint()); + speeds.add(computedSpeedWithConfidence); bearingCluster.add(new BearingWithConfidenceImpl(next.getSpeed().getBearing(), /* confidence */ 0.9, next.getTimePoint())); - count++; last = next; } } - BearingWithConfidence average = bearingCluster.getAverage(at); - Bearing bearing = average == null ? null : average.getObject(); - SpeedWithBearing avgSpeed = (count == 0 || bearing == null) ? null : new KnotSpeedWithBearingImpl(knotSum / count, bearing); - return avgSpeed; + ConfidenceBasedAverager speedAverager = ConfidenceFactory.INSTANCE.createAverager(weigher); + HasConfidence speedWithConfidence = speedAverager.getAverage(speeds, at); + BearingWithConfidence bearingAverage = bearingCluster.getAverage(at); + Bearing bearing = bearingAverage == null ? null : bearingAverage.getObject(); + SpeedWithBearing avgSpeed = (speedWithConfidence == null || bearing == null) ? null : + new KnotSpeedWithBearingImpl(speedWithConfidence.getObject().getKnots(), bearing); + SpeedWithBearingWithConfidence result = new SpeedWithBearingWithConfidenceImpl(avgSpeed, + /* confidence */ (speedWithConfidence.getConfidence() + bearingAverage.getConfidence())/2., at); + return result; } private List getFixesRelevantForSpeedEstimation(TimePoint at, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index a1520bf2c85..75d079ea4c5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.tracking.impl; +import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Iterator; @@ -10,10 +11,14 @@ import java.util.Set; import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.KnotSpeedImpl; import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; +import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Position; @@ -21,7 +26,10 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.NauticalMileDistance; import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; +import com.sap.sailing.domain.confidence.HasConfidence; +import com.sap.sailing.domain.confidence.Weigher; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; @@ -301,12 +309,26 @@ public class GPSFixTrackImpl extends TrackImpl */ @Override public synchronized SpeedWithBearing getEstimatedSpeed(TimePoint at) { - return getEstimatedSpeed(at, getInternalFixes()); + return getEstimatedSpeed(at, getInternalFixes(), ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( + // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero + getMillisecondsOverWhichToAverageSpeed())).getObject(); } @Override public synchronized SpeedWithBearing getRawEstimatedSpeed(TimePoint at) { - return getEstimatedSpeed(at, getRawFixes()); + return getEstimatedSpeed(at, getRawFixes(), ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( + // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero + getMillisecondsOverWhichToAverageSpeed())).getObject(); + } + + @Override + public SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, Weigher weigher) { + return getEstimatedSpeed(at, getInternalFixes(), weigher); + } + + @Override + public SpeedWithBearingWithConfidence getRawEstimatedSpeed(TimePoint at, Weigher weigher) { + return getEstimatedSpeed(at, getRawFixes(), weigher); } /** @@ -315,17 +337,20 @@ public class GPSFixTrackImpl extends TrackImpl * {@link #millisecondsOverWhichToAverage} milliseconds around at. Subclasses that know about the * particular fix type may redefine this to exploit a {@link SpeedWithBearing} attached, e.g., to a * {@link GPSFixMoving}. + * + * @param weigher + * If null, a confidence of 1.0 is assumed for all fixes used in the speed/bearing + * estimation. Otherwise, the confidence of each fix contributing to the estimation is computed using the + * weigher. + * @return a speed/bearing with the average confidence attributed to the fixes that contributed to the estimation */ - protected SpeedWithBearing getEstimatedSpeed(TimePoint at, NavigableSet fixesToUseForSpeedEstimation) { + protected SpeedWithBearingWithConfidence getEstimatedSpeed(TimePoint at, + NavigableSet fixesToUseForSpeedEstimation, Weigher weigher) { @SuppressWarnings("unchecked") NavigableSet gpsFixesToUseForSpeedEstimation = (NavigableSet) fixesToUseForSpeedEstimation; List relevantFixes = getFixesRelevantForSpeedEstimation(at, gpsFixesToUseForSpeedEstimation); - double knotSum = 0; - BearingWithConfidenceCluster bearingCluster = new BearingWithConfidenceCluster( - ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( - // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero - getMillisecondsOverWhichToAverageSpeed())); - int count = 0; + List> speeds = new ArrayList>(); + BearingWithConfidenceCluster bearingCluster = new BearingWithConfidenceCluster(weigher); if (!relevantFixes.isEmpty()) { Iterator fixIter = relevantFixes.iterator(); GPSFix last = fixIter.next(); @@ -333,20 +358,27 @@ public class GPSFixTrackImpl extends TrackImpl // TODO bug #169: consider time difference between next.getTimepoint() and at to compute a confidence GPSFix next = fixIter.next(); // TODO bug #169: use SpeedWithConfidence to aggregate confidence-tagged speed values - knotSum += last.getPosition().getDistance(next.getPosition()) - .inTime(next.getTimePoint().asMillis() - last.getTimePoint().asMillis()).getKnots(); + MillisecondsTimePoint relativeTo = new MillisecondsTimePoint((last.getTimePoint().asMillis() + next.getTimePoint().asMillis())/2); + Speed speed = last.getPosition().getDistance(next.getPosition()) + .inTime(next.getTimePoint().asMillis() - last.getTimePoint().asMillis()); + SpeedWithConfidenceImpl speedWithConfidence = new SpeedWithConfidenceImpl(speed, /* original confidence */ + 0.9, relativeTo); + speeds.add(speedWithConfidence); bearingCluster.add(new BearingWithConfidenceImpl(last.getPosition().getBearingGreatCircle(next.getPosition()), /* confidence */ 0.9, // TODO use number of tracked satellites to determine confidence of single fix - new MillisecondsTimePoint((last.getTimePoint().asMillis() + next.getTimePoint().asMillis())/2))); - count++; + relativeTo)); last = next; } } - // TODO bug #169: return SpeedWithBearingWithConfidence to reflect the confidence reduction incurred by the difference of the fix's time point and "at" - BearingWithConfidence average = bearingCluster.getAverage(at); - Bearing bearing = average == null ? null : average.getObject(); - SpeedWithBearing avgSpeed = (count == 0 || bearing == null) ? null : new KnotSpeedWithBearingImpl(knotSum / count, bearing); - return avgSpeed; + ConfidenceBasedAverager speedAverager = ConfidenceFactory.INSTANCE.createAverager(weigher); + HasConfidence speedWithConfidence = speedAverager.getAverage(speeds, at); + BearingWithConfidence bearingAverage = bearingCluster.getAverage(at); + Bearing bearing = bearingAverage == null ? null : bearingAverage.getObject(); + SpeedWithBearing avgSpeed = (speedWithConfidence == null || bearing == null) ? null : + new KnotSpeedWithBearingImpl(speedWithConfidence.getObject().getKnots(), bearing); + SpeedWithBearingWithConfidence result = avgSpeed == null ? null : + new SpeedWithBearingWithConfidenceImpl(avgSpeed, (bearingAverage.getConfidence() + speedWithConfidence.getConfidence())/2., at); + return result; } private List getFixesRelevantForSpeedEstimation(TimePoint at, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index fb084a94bc7..8b516a7c755 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -134,8 +134,7 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { Iterator afterIter = afterSet.iterator(); double knotSum = 0; // TODO bug #169: also measure speed with confidence; return confidence - Weigher weigher = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(millisecondsOverWhichToAverage); - // Weigher weigher = ConfidenceFactory.INSTANCE.createConstantWeigher(1.0); + Weigher weigher = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(millisecondsOverWhichToAverage/10); BearingWithConfidenceCluster bearingCluster = new BearingWithConfidenceCluster(weigher); int count = 0; long beforeDistanceToAt = 0; diff --git a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs index 5c1dfe07e80..e6887c76391 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs @@ -1,5 +1,5 @@ -#Tue Jan 24 01:20:58 CET 2012 +#Thu Feb 02 12:04:27 CET 2012 eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDUxMm1dXT48L3ZtLWFyZ3M+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkFkbWluQ29uc29sZTwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5MZWFkZXJib2FyZDwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5MZWFkZXJib2FyZEVkaXRpbmc8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuVXNlck1hbmFnZW1lbnQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuU3BlY3RhdG9yPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDUxMm1dXT48L3ZtLWFyZ3M+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= From d99ff3065c908b3b0b3364917a415c247f56329c Mon Sep 17 00:00:00 2001 From: Marcus Kammer Date: Thu, 2 Feb 2012 14:58:38 +0100 Subject: [PATCH 06/14] Add meta tags for apple devices && link for the iPad App Icon --- java/com.sap.sailing.gwt.ui/Spectator.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/com.sap.sailing.gwt.ui/Spectator.html b/java/com.sap.sailing.gwt.ui/Spectator.html index 40be4f43ec8..c024a8532e0 100644 --- a/java/com.sap.sailing.gwt.ui/Spectator.html +++ b/java/com.sap.sailing.gwt.ui/Spectator.html @@ -9,6 +9,9 @@ + + + From 9c34f12e444ff4f32b8467daaae40cf1d5640d4d Mon Sep 17 00:00:00 2001 From: Marcus Kammer Date: Thu, 2 Feb 2012 14:59:19 +0100 Subject: [PATCH 07/14] ipad app icon --- .../images/sap-sailing-app-icon.png | Bin 0 -> 47640 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/images/sap-sailing-app-icon.png diff --git a/java/com.sap.sailing.gwt.ui/images/sap-sailing-app-icon.png b/java/com.sap.sailing.gwt.ui/images/sap-sailing-app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6aee00964b3b42888c8239ef47933707767836fd GIT binary patch literal 47640 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 B88H9= literal 0 HcmV?d00001 From 2c613e5139e12e34f954ff6dbea96b5f266d3069 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 16:00:06 +0100 Subject: [PATCH 08/14] Created BreadcrumbPanel --- .../gwt/ui/shared/panels/BreadcrumbPanel.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java new file mode 100644 index 00000000000..e74c2fe667d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.shared.panels; + +import com.google.gwt.dom.client.Style; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; + +public class BreadcrumbPanel extends FlowPanel { + + public static final String STYLE_NAME_PREFIX = "breadcrumbPanel-"; + + public BreadcrumbPanel(Iterable breadcrumbLinks, HTML actualBreadcrumb) { + super(); + //Adding the active breadcrumbs + for (HTML breadcrumb : breadcrumbLinks) { + breadcrumb.addStyleName(STYLE_NAME_PREFIX + "ActiveBreadcrumb"); + breadcrumb.getElement().getStyle().setFloat(Style.Float.LEFT); + breadcrumb.getElement().getStyle().setPadding(5, Style.Unit.PX); + add(breadcrumb); + + Label nextArrow = new Label(">"); + nextArrow.setStyleName(STYLE_NAME_PREFIX + "NextArrow"); + nextArrow.getElement().getStyle().setFloat(Style.Float.LEFT); + nextArrow.getElement().getStyle().setPadding(5, Style.Unit.PX); + add(nextArrow); + } + //Adding the actual breadcrumb + actualBreadcrumb.addStyleName(STYLE_NAME_PREFIX + "InactiveBreadcrumb"); + actualBreadcrumb.getElement().getStyle().setFloat(Style.Float.LEFT); + actualBreadcrumb.getElement().getStyle().setPadding(5, Style.Unit.PX); + add(actualBreadcrumb); + } + +} From 2a02bb843874c9e68337d98c587d8d8b73ae7558 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 16:16:26 +0100 Subject: [PATCH 09/14] Fixed Bug 216 (Leaderboard selection disappears during Leaderboard refresh) --- .../sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index a8a893d6e81..f31b0b6475f 100644 --- 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 @@ -1040,7 +1040,6 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta * Also updates the min/max values on the columns */ protected void updateLeaderboard(LeaderboardDTO leaderboard) { - Set selectedRows = leaderboardSelectionModel.getSelectedSet(); competitorSelectionProvider.setCompetitors(leaderboard.competitors); selectedRaceColumns.addAll(getRaceColumnsToAddImplicitly(leaderboard)); setLeaderboard(leaderboard); @@ -1063,8 +1062,10 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta } //Reselect the selected rows leaderboardSelectionModel.clear(); - for (LeaderboardRowDTO row : selectedRows) { - leaderboardSelectionModel.setSelected(row, true); + for (LeaderboardRowDTO row : data.getList()) { + if (competitorSelectionProvider.isSelected(row.competitor)) { + leaderboardSelectionModel.setSelected(row, true); + } } } } From 511bb189c9bd5b6b5114c0a344cfc8264b49c4fc Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 16:33:23 +0100 Subject: [PATCH 10/14] Redesign of the BreadcrumbPanel --- .../gwt/ui/shared/panels/BreadcrumbPanel.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java index e74c2fe667d..e74d6fb26e4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/panels/BreadcrumbPanel.java @@ -1,18 +1,34 @@ package com.sap.sailing.gwt.ui.shared.panels; +import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style; +import com.google.gwt.safehtml.client.SafeHtmlTemplates; +import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; +import com.sap.sailing.domain.common.impl.Util.Pair; public class BreadcrumbPanel extends FlowPanel { + + interface AnchorTemplates extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("{1}") + SafeHtml anchor(String url, String displayName); + } + public static final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); public static final String STYLE_NAME_PREFIX = "breadcrumbPanel-"; - public BreadcrumbPanel(Iterable breadcrumbLinks, HTML actualBreadcrumb) { + /** + * Creates a new BreadcrumbPanel with the breadcrumbLinks as active links and actualBreadcrumb as Label + * @param breadcrumbLinksData The data of the breadcrumbs which will be displayed as links. Part A of the pair is the URL and part B the displayed text. + * @param actualBreadcrumbName The text as Label. Should be the name of the component in which the BreadcrumbPanel is displayed. + */ + public BreadcrumbPanel(Iterable> breadcrumbLinksData, String actualBreadcrumbName) { super(); //Adding the active breadcrumbs - for (HTML breadcrumb : breadcrumbLinks) { + for (Pair breadcrumbData : breadcrumbLinksData) { + HTML breadcrumb = new HTML(ANCHORTEMPLATE.anchor(breadcrumbData.getA(), breadcrumbData.getB())); breadcrumb.addStyleName(STYLE_NAME_PREFIX + "ActiveBreadcrumb"); breadcrumb.getElement().getStyle().setFloat(Style.Float.LEFT); breadcrumb.getElement().getStyle().setPadding(5, Style.Unit.PX); @@ -25,6 +41,7 @@ public class BreadcrumbPanel extends FlowPanel { add(nextArrow); } //Adding the actual breadcrumb + Label actualBreadcrumb = new Label(actualBreadcrumbName); actualBreadcrumb.addStyleName(STYLE_NAME_PREFIX + "InactiveBreadcrumb"); actualBreadcrumb.getElement().getStyle().setFloat(Style.Float.LEFT); actualBreadcrumb.getElement().getStyle().setPadding(5, Style.Unit.PX); From 51e6ca4e85345245210e00c864d2226f2d5be761 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 16:34:26 +0100 Subject: [PATCH 11/14] Added root parameter to the spectator view --- .../ui/spectator/LeaderboardGroupPanel.java | 18 ++++++++++++------ .../gwt/ui/spectator/SpectatorEntryPoint.java | 9 ++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java index c04a70c1976..6e51ea7a41c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java @@ -51,16 +51,21 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget private StringMessages stringConstants; private ErrorReporter errorReporter; private LeaderboardGroupDTO group; + private String root; private FlowPanel mainPanel; private Widget welcomeWidget = null; public LeaderboardGroupPanel(SailingServiceAsync sailingService, StringMessages stringConstants, - ErrorReporter errorReporter, final String groupName) { + ErrorReporter errorReporter, final String groupName, String root) { super(); this.sailingService = sailingService; this.stringConstants = stringConstants; this.errorReporter = errorReporter; + this.root = root; + if (this.root == null || this.root.length() == 0) { + this.root = "leaderboardGroupPanel"; + } mainPanel = new FlowPanel(); mainPanel.setWidth("95%"); @@ -151,7 +156,7 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget public SafeHtml getValue(LeaderboardDTO leaderboard) { String debugParam = Window.Location.getParameter("gwt.codesvr"); return ANCHORTEMPLATE.anchor("/gwt/Leaderboard.html?name=" + leaderboard.name - + "&leaderboardGroupName=" + group.name + + "&leaderboardGroupName=" + group.name + "&root=" + root + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""), leaderboard.name, STYLE_NAME_PREFIX + "ActiveLeaderboard"); } @@ -185,14 +190,15 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget SafeHtmlBuilder b = new SafeHtmlBuilder(); String displayName = race.getRaceColumnName(); if (race.getRaceColumnName().equals(stringConstants.overview())) { - String link = "/gwt/Leaderboard.html?name=" + leaderboard.name + "&leaderboardGroupName=" + group.name + - (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr="+debugParam : ""); + String link = "/gwt/Leaderboard.html?name=" + leaderboard.name + "&leaderboardGroupName=" + group.name + + "&root=" + root + + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""); b.append(ANCHORTEMPLATE.anchor(link, displayName, STYLE_NAME_PREFIX + "ActiveLeaderboard")); } else { if (race.getRaceIdentifier() != null) { EventNameAndRaceName raceId = (EventNameAndRaceName) race.getRaceIdentifier(); String link = "/gwt/RaceBoard.html?leaderboardName=" + leaderboard.name + "&raceName=" + raceId.getRaceName() - + "&eventName=" + raceId.getEventName() + "&leaderboardGroupName=" + group.name + + "&eventName=" + raceId.getEventName() + "&leaderboardGroupName=" + group.name + "&root=" + root + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""); b.append(ANCHORTEMPLATE.anchor(link, displayName, STYLE_NAME_PREFIX + "ActiveRace")); } else { @@ -212,7 +218,7 @@ public class LeaderboardGroupPanel extends FormPanel implements HasWelcomeWidget } if (race.getRaceIdentifier() != null) { EventNameAndRaceName raceId = (EventNameAndRaceName) race.getRaceIdentifier(); - String link = "/gwt/RaceBoard.html?leaderboardName=" + leaderboard.name + "&raceName=" + String link = "/gwt/RaceBoard.html?leaderboardName=" + leaderboard.name + "&raceName=" + "&root=" + root + raceId.getRaceName() + "&eventName=" + raceId.getEventName() + "&leaderboardGroupName=" + group.name + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""); b.append(ANCHORTEMPLATE.anchor(link, race.getRaceColumnName(), STYLE_NAME_PREFIX + "ActiveRace")); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java index ab8e6978408..fb644226652 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java @@ -21,9 +21,7 @@ public class SpectatorEntryPoint extends AbstractEntryPoint implements EventRefr public void onModuleLoad() { super.onModuleLoad(); - //Fill fixed leaderboard selection String groupParamValue = Window.Location.getParameter("leaderboardGroupName"); - final String groupName; if(groupParamValue == null || groupParamValue.isEmpty()) { groupName = null; @@ -39,6 +37,11 @@ public class SpectatorEntryPoint extends AbstractEntryPoint implements EventRefr }); } + String root = Window.Location.getParameter("root"); + //Check if the root contains an allowed value + if (root != null) { + root = (root.equals("leaderboardGroupPanel") || root.equals("overview")) ? root : null; + } RootPanel rootPanel = RootPanel.get(); rootPanel.setSize("100%", "100%"); @@ -51,7 +54,7 @@ public class SpectatorEntryPoint extends AbstractEntryPoint implements EventRefr if (groupName == null) { panelToDisplay = new OverviewEventPanel(sailingService, this, this, stringMessages); } else { - panelToDisplay = new LeaderboardGroupPanel(sailingService, stringMessages, this, groupName); + panelToDisplay = new LeaderboardGroupPanel(sailingService, stringMessages, this, groupName, root); LeaderboardGroupPanel groupPanel = (LeaderboardGroupPanel) panelToDisplay; groupPanel.setWelcomeWidget(new ClosableWelcomeWidget(true, stringMessages.welcomeToSailingAnalytics(), "Ipsum lorum\nHello World!", stringMessages)); From d6b419a3b479c16361140df1f6da6570cbd92540 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 2 Feb 2012 16:39:08 +0100 Subject: [PATCH 12/14] Changes of Spectator.html --- java/com.sap.sailing.gwt.ui/Spectator.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/Spectator.html b/java/com.sap.sailing.gwt.ui/Spectator.html index c024a8532e0..40be4f43ec8 100644 --- a/java/com.sap.sailing.gwt.ui/Spectator.html +++ b/java/com.sap.sailing.gwt.ui/Spectator.html @@ -9,9 +9,6 @@ - - - From eeed5fc1e4fabc5676067a9af94ef5290bc5ce01 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 2 Feb 2012 16:47:23 +0100 Subject: [PATCH 13/14] switched to the use of confidences in wind estimation --- .../impl/ConfidenceBasedAveragerImpl.java | 1 + .../domain/tracking/impl/TrackedRaceImpl.java | 72 ++++++++++++------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java index 30e07b50457..262b437e028 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/impl/ConfidenceBasedAveragerImpl.java @@ -61,6 +61,7 @@ public class ConfidenceBasedAveragerImpl implem } confidenceSum += relativeWeight; } + // TODO consider greater variance to reduce the confidence double newConfidence = confidenceSum / Util.size(values); BaseType result = numerator.divide(confidenceSum, newConfidence); return new HasConfidenceImpl(result, newConfidence, at); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index d14aa822853..168d71fd41b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -16,6 +16,7 @@ import java.util.logging.Logger; import org.json.simple.parser.ParseException; +import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Buoy; import com.sap.sailing.domain.base.Competitor; @@ -24,8 +25,10 @@ import com.sap.sailing.domain.base.CourseListener; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.SpeedWithBearing; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.DouglasPeucker; import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; @@ -47,6 +50,8 @@ import com.sap.sailing.domain.common.impl.RacePlaceOrderImpl; import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.confidence.ConfidenceFactory; +import com.sap.sailing.domain.confidence.Weigher; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; @@ -646,9 +651,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { @Override public Wind getEstimatedWindDirection(Position position, TimePoint timePoint) { - Map bearings = new HashMap(); + Weigher weigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( + // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero + getMillisecondsOverWhichToAverageSpeed()); + Map> bearings = new HashMap>(); for (LegType legType : LegType.values()) { - bearings.put(legType, new BearingCluster()); + bearings.put(legType, new BearingWithConfidenceCluster(weigher)); } for (Competitor competitor : getRace().getCompetitors()) { TrackedLegOfCompetitor leg = getTrackedLeg(competitor, timePoint); @@ -665,44 +673,58 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { GPSFixTrack track = getTrack(competitor); // TODO bug #167 could be fixed here by excluding competitors that are x seconds before/after a mark passing if (!track.hasDirectionChange(timePoint, getManeuverDegreeAngleThreshold())) { - SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(timePoint); - if (estimatedSpeed != null) { - // TODO but #169: preserve confidence of estimatedSpeed relative to timePoint so as to feed into a BearingWithConfidenceCluster here - Bearing bearing = estimatedSpeed.getBearing(); - BearingCluster bearingClusterForLegType = bearings.get(legType); + SpeedWithBearingWithConfidence estimatedSpeedWithConfidence = track.getEstimatedSpeed(timePoint, + weigher); + if (estimatedSpeedWithConfidence != null) { + BearingWithConfidence bearing = new BearingWithConfidenceImpl( + estimatedSpeedWithConfidence.getObject().getBearing(), estimatedSpeedWithConfidence.getConfidence(), + estimatedSpeedWithConfidence.getRelativeTo()); + BearingWithConfidenceCluster bearingClusterForLegType = bearings.get(legType); bearingClusterForLegType.add(bearing); } } } } } - Bearing reversedUpwindAverage = null; - int upwindConfidence = 0; - BearingCluster[] bearingClustersUpwind = bearings.get(LegType.UPWIND).splitInTwo(getMinimumAngleBetweenDifferentTacksUpwind()); + // use the minimum confidence of the four "quadrants" as the result's confidence + BearingWithConfidenceImpl reversedUpwindAverage = null; + int upwindNumberOfRelevantBoats = 0; + double confidence = 0; + BearingWithConfidenceCluster[] bearingClustersUpwind = bearings.get(LegType.UPWIND).splitInTwo( + getMinimumAngleBetweenDifferentTacksUpwind(), timePoint); if (!bearingClustersUpwind[0].isEmpty() && !bearingClustersUpwind[1].isEmpty()) { - reversedUpwindAverage = bearingClustersUpwind[0].getAverage().middle(bearingClustersUpwind[1].getAverage()).reverse(); - upwindConfidence = Math.min(bearingClustersUpwind[0].size(), bearingClustersUpwind[1].size()); + BearingWithConfidence average0 = bearingClustersUpwind[0].getAverage(timePoint); + BearingWithConfidence average1 = bearingClustersUpwind[1].getAverage(timePoint); + confidence = Math.min(average0.getConfidence(), average1.getConfidence()); + reversedUpwindAverage = new BearingWithConfidenceImpl(average0.getObject().middle(average1.getObject()).reverse(), + confidence, timePoint); + upwindNumberOfRelevantBoats = Math.min(bearingClustersUpwind[0].size(), bearingClustersUpwind[1].size()); } - Bearing downwindAverage = null; - int downwindConfidence = 0; - BearingCluster[] bearingClustersDownwind = bearings.get(LegType.DOWNWIND).splitInTwo(getMinimumAngleBetweenDifferentTacksDownwind()); + BearingWithConfidenceImpl downwindAverage = null; + int downwindNumberOfRelevantBoats = 0; + BearingWithConfidenceCluster[] bearingClustersDownwind = bearings.get(LegType.DOWNWIND).splitInTwo( + getMinimumAngleBetweenDifferentTacksDownwind(), timePoint); if (!bearingClustersDownwind[0].isEmpty() && !bearingClustersDownwind[1].isEmpty()) { - downwindAverage = bearingClustersDownwind[0].getAverage().middle(bearingClustersDownwind[1].getAverage()); - downwindConfidence = Math.min(bearingClustersDownwind[0].size(), bearingClustersDownwind[1].size()); + BearingWithConfidence average0 = bearingClustersDownwind[0].getAverage(timePoint); + BearingWithConfidence average1 = bearingClustersDownwind[1].getAverage(timePoint); + double downwindConfidence = Math.min(average0.getConfidence(), average1.getConfidence()); + confidence = Math.min(confidence, downwindConfidence); + downwindAverage = new BearingWithConfidenceImpl(average0.getObject().middle(average1.getObject()), downwindConfidence, timePoint); + downwindNumberOfRelevantBoats = Math.min(bearingClustersDownwind[0].size(), bearingClustersDownwind[1].size()); } - int confidence = upwindConfidence + downwindConfidence; - BearingCluster resultCluster = new BearingCluster(); - assert upwindConfidence == 0 || reversedUpwindAverage != null; - for (int i=0; i resultCluster = new BearingWithConfidenceCluster(weigher); + assert upwindNumberOfRelevantBoats == 0 || reversedUpwindAverage != null; + for (int i=0; i resultBearing = resultCluster.getAverage(timePoint); return resultBearing == null ? null : new WindImpl(null, timePoint, - new KnotSpeedWithBearingImpl(/* speedInKnots */ confidence, resultBearing)); + new KnotSpeedWithBearingImpl(/* speedInKnots */ numberOfBoatsRelevantForEstimate, resultBearing.getObject())); } /** From 1badfe288edee97bcfa8ebdbcc3f64f18fd28953 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 2 Feb 2012 16:59:03 +0100 Subject: [PATCH 14/14] fixed weights / confidences in wind estimation algorithm --- .../com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 168d71fd41b..49e91529a6e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -715,11 +715,11 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { int numberOfBoatsRelevantForEstimate = upwindNumberOfRelevantBoats + downwindNumberOfRelevantBoats; BearingWithConfidenceCluster resultCluster = new BearingWithConfidenceCluster(weigher); assert upwindNumberOfRelevantBoats == 0 || reversedUpwindAverage != null; - for (int i=0; i 0) { resultCluster.add(reversedUpwindAverage); } assert downwindNumberOfRelevantBoats == 0 || downwindAverage != null; - for (int i=0; i 0) { resultCluster.add(downwindAverage); } BearingWithConfidence resultBearing = resultCluster.getAverage(timePoint);