diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index aab0a8eb..ac264db6 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -9,8 +9,8 @@ import seng302.models.stream.StreamParser; import seng302.models.stream.StreamReceiver; import seng302.server.ServerThread; -public class App extends Application -{ +public class App extends Application { + @Override public void start(Stage primaryStage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); @@ -39,15 +39,15 @@ public class App extends Application e.printStackTrace(); } - if (args.length == 1 && args[0].equals("-standalone")){ + if (args.length == 1 && args[0].equals("-standalone")) { return; } - if (args.length == 3 && args[0].equals("-server")){ + if (args.length == 3 && args[0].equals("-server")) { sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream"); - } else if(args.length == 2 && args[0].equals("-server")){ + } else if (args.length == 2 && args[0].equals("-server")) { switch (args[1]) { case "internal": sr = new StreamReceiver("localhost", 4949, "RaceStream"); @@ -62,7 +62,8 @@ public class App extends Application } //Change the StreamReceiver in this else block to change the default data source. else{ - sr = new StreamReceiver("localhost", 4949, "RaceStream"); +// sr = new StreamReceiver("localhost", 4949, "RaceStream"); + sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); } sr.start(); @@ -71,8 +72,6 @@ public class App extends Application launch(args); - - } } diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 26d7fec7..ff7a294f 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -185,6 +185,17 @@ public class CanvasController { } } } + checkForCourseChanges(); + } + + private void checkForCourseChanges() { + if (StreamParser.isNewRaceXmlReceived()){ + gc.setFill(Color.SKYBLUE); + gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT); + gc.restore(); + addRaceBorder(); + canvas.toBack(); + } } private void updateBoatGroup(BoatGroup boatGroup) { @@ -285,6 +296,8 @@ public class CanvasController { * Calculates x and y location for every marker that fits it to the canvas the race will be drawn on. */ private void fitMarksToCanvas() { + //Check is called once to avoid unnecessarily change the course limits once the race is running + StreamParser.isNewRaceXmlReceived(); findMinMaxPoint(); double minLonToMaxLon = scaleRaceExtremities(); calculateReferencePointLocation(minLonToMaxLon); diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index c27e6192..a222cfc3 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,65 +1,31 @@ package seng302.controllers; -import static seng302.models.stream.StreamParser.boatPositions; - -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import seng302.models.Yacht; import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; -import java.util.Timer; -import java.util.TimerTask; import seng302.models.stream.StreamParser; public class Controller implements Initializable { + @FXML private AnchorPane contentPane; - @FXML - private Label timeTillLive; - @FXML - private Button streamButton; - @FXML - private Button switchToRaceViewButton; - @FXML - private TableView teamList; - @FXML - private TableColumn boatNameCol; - @FXML - private TableColumn shortNameCol; - @FXML - private TableColumn countryCol; - @FXML - private TableColumn posCol; - @FXML - private Label realTime; - private boolean switchedToRaceView = false; - - private void setContentPane(String jfxUrl){ - try{ + private void setContentPane(String jfxUrl) { + try { contentPane.getChildren().removeAll(); contentPane.getChildren().clear(); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); - } - catch(javafx.fxml.LoadException e){ + contentPane.getChildren() + .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + } catch (javafx.fxml.LoadException e) { System.err.println(e.getCause()); - } - catch(IOException e){ + } catch (IOException e) { System.err.println(e); } } @@ -67,94 +33,7 @@ public class Controller implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - } - - /** - * Running a timer to update the livestream status on welcome screen. Update interval is 1 second. - */ - public void startStream() { - if (StreamParser.isStreamStatus()) { - streamButton.setVisible(false); - realTime.setVisible(true); - timeTillLive.setVisible(true); - timeTillLive.setTextFill(Color.GREEN); - timeTillLive.setText("Connecting..."); - Timer timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - Platform.runLater(() -> { - if (StreamParser.isRaceStarted()) { - if (!switchedToRaceView) { - switchToRaceView(); - } - timer.cancel(); - } - if (StreamParser.isRaceFinished()) { - realTime.setText(StreamParser.getCurrentTimeString()); - timeTillLive.setTextFill(Color.RED); - timeTillLive.setText("Race finished! Waiting for new race..."); - switchToRaceViewButton.setDisable(true); - } else if (StreamParser.getTimeSinceStart() > 0) { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.RED); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = "-" + timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } else { - realTime.setText(StreamParser.getCurrentTimeString()); - updateTeamList(); - timeTillLive.setTextFill(Color.BLACK); - switchToRaceViewButton.setDisable(false); - String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60); - if (timerSecond.length() == 1) { - timerSecond = "0" + timerSecond; - } - String timerString = timerMinute + ":" + timerSecond; - timeTillLive.setText(timerString); - } - }); - } - }, 0, 1000); - } else { - timeTillLive.setText("Stream not available."); - timeTillLive.setTextFill(Color.RED); - } - } - - public void switchToRaceView() { - switchedToRaceView = true; - setContentPane("/views/RaceView.fxml"); + setContentPane("/views/StartScreenView.fxml"); StreamParser.boatPositions.clear(); } - - private void updateTeamList() { - ObservableList data = FXCollections.observableArrayList(); - - teamList.setItems(data); - - boatNameCol.setCellValueFactory( - new PropertyValueFactory<>("boatName") - ); - shortNameCol.setCellValueFactory( - new PropertyValueFactory<>("shortName") - ); - countryCol.setCellValueFactory( - new PropertyValueFactory<>("country") - ); - posCol.setCellValueFactory( - new PropertyValueFactory<>("position") - ); - - data.addAll(StreamParser.getBoatsPos().values()); - teamList.refresh(); - } } diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 85029772..22c96f13 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -1,17 +1,18 @@ package seng302.controllers; -import java.io.IOException; import javafx.animation.KeyFrame; import javafx.animation.Timeline; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; import javafx.scene.control.Slider; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; @@ -24,9 +25,11 @@ import seng302.controllers.annotations.Annotation; import seng302.controllers.annotations.ImportantAnnotationController; import seng302.controllers.annotations.ImportantAnnotationDelegate; import seng302.controllers.annotations.ImportantAnnotationsState; -import seng302.models.BoatGroup; -import seng302.models.Yacht; -import seng302.models.stream.StreamParser; +import seng302.models.*; +import seng302.models.parsers.StreamParser; + +import java.io.IOException; +import java.util.*; /** * Created by ptg19 on 29/03/17. @@ -48,23 +51,36 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel @FXML private Button selectAnnotationBtn; @FXML + private ComboBox boatSelectionComboBox; + @FXML private CanvasController includedCanvasController; + private ArrayList startingBoats = new ArrayList<>(); private boolean displayFps; private Timeline timerTimeline; + private Race race; private Stage stage; + private ImportantAnnotationsState importantAnnotations; + private Yacht selectedBoat; public void initialize() { // Load a default important annotation state importantAnnotations = new ImportantAnnotationsState(); + //Initialise race controller + RaceController raceController = new RaceController(); + raceController.initializeRace(); + race = raceController.getRace(); + + startingBoats = new ArrayList<>(Arrays.asList(race.getBoats())); + includedCanvasController.setup(this); includedCanvasController.initializeCanvas(); - initializeTimer(); - initializeSettings(); - initialiseWindDirection(); - initialisePositionVBox(); + initializeUpdateTimer(); + initialiseFPSCheckBox(); + initialiseAnnotationSlider(); + initialiseBoatSelectionComboBox(); includedCanvasController.timer.start(); selectAnnotationBtn.setOnAction(event -> { @@ -74,7 +90,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel /** * The important annotations have been changed, update this view - * * @param importantAnnotationsState The current state of the selected annotations */ public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) { @@ -112,18 +127,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } } - private void initializeSettings() { + + private void initialiseFPSCheckBox() { displayFps = true; + toggleFps.selectedProperty().addListener( + (observable, oldValue, newValue) -> displayFps = !displayFps); + } - toggleFps.selectedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, - Boolean newValue) { - displayFps = !displayFps; - } - }); - - //SLIDER STUFF BELOW + private void initialiseAnnotationSlider() { annotationSlider.setLabelFormatter(new StringConverter() { @Override public String toString(Double n) { @@ -131,12 +142,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel return "None"; } if (n == 1) { - return "Low"; - } - if (n == 2) { return "Important"; } - if (n == 3) { + if (n == 2) { return "All"; } @@ -148,15 +156,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel switch (s) { case "None": return 0d; - case "Low": - return 1d; case "Important": - return 2d; + return 1d; case "All": - return 3d; + return 2d; default: - return 3d; + return 2d; } } }); @@ -164,22 +170,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel annotationSlider.valueProperty().addListener((obs, oldval, newVal) -> setAnnotations((int) annotationSlider.getValue())); - annotationSlider.setValue(3); + annotationSlider.setValue(2); } - private void initializeTimer() { + + /** + * Initalises a timer which updates elements of the RaceView such as wind direction, boat + * orderings etc.. which are dependent on the info from the stream parser constantly. + * Updates of each of these attributes are called ONCE EACH SECOND + */ + private void initializeUpdateTimer() { timerTimeline = new Timeline(); timerTimeline.setCycleCount(Timeline.INDEFINITE); // Run timer update every second timerTimeline.getKeyFrames().add( new KeyFrame(Duration.seconds(1), event -> { - if (StreamParser.isRaceFinished()) { - timerLabel.setFill(Color.RED); - timerLabel.setText("Race Finished!"); - } else { - timerLabel.setText(currentTimer()); - } + updateRaceTime(); + updateWindDirection(); + updateOrder(); + updateBoatSelectionComboBox(); + }) ); @@ -187,34 +198,45 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel timerTimeline.playFromStart(); } - private void initialiseWindDirection() { - Timeline windDirTimeline = new Timeline(); - windDirTimeline.setCycleCount(Timeline.INDEFINITE); - windDirTimeline.getKeyFrames().add( - new KeyFrame(Duration.seconds(1), - event -> { - windDirectionText - .setText(String.format("%.1f°", StreamParser.getWindDirection())); - windArrowText.setRotate(StreamParser.getWindDirection()); - }) - ); - windDirTimeline.playFromStart(); + + /** + * Updates the wind direction arrow and text as from info from the StreamParser + */ + private void updateWindDirection() { + windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection())); + windArrowText.setRotate(StreamParser.getWindDirection()); } - private void initialisePositionVBox() { - Timeline posVBoxTimeline = new Timeline(); - posVBoxTimeline.setCycleCount(Timeline.INDEFINITE); - posVBoxTimeline.getKeyFrames().add( - new KeyFrame(Duration.seconds(1), - event -> { - showOrder(); - }) - ); - posVBoxTimeline.playFromStart(); + /** + * Updates the clock for the race + */ + private void updateRaceTime() { + if (StreamParser.isRaceFinished()) { + timerLabel.setFill(Color.RED); + timerLabel.setText("Race Finished!"); + } else { + timerLabel.setText(getTimeSinceStartOfRace()); + } } - private void showOrder() { + + /** + * Grabs the boats currently in the race as from the StreamParser and sets them to be selectable + * in the boat selection combo box + */ + private void updateBoatSelectionComboBox() { + ObservableList observableBoats = FXCollections + .observableArrayList(StreamParser.getBoatsPos().values()); + boatSelectionComboBox.setItems(observableBoats); + } + + + /** + * Updates the order of the boats as from the StreamParser and sets them in the boat order + * section + */ + private void updateOrder() { positionVbox.getChildren().clear(); positionVbox.getChildren().removeAll(); positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); @@ -237,6 +259,44 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } } + + /** + * Initialised the combo box with any boats currently in the race and adds the required listener + * for the combobox to take action upon selection + */ + private void initialiseBoatSelectionComboBox() { + updateBoatSelectionComboBox(); + boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { + //This listener is fired whenever the combo box changes. This means when the values are updated + //We dont want to set the selected value if the values are updated but nothing clicked (null) + if (newValue != null && newValue != selectedBoat) { + Yacht thisYacht = (Yacht) newValue; + setSelectedBoat(thisYacht); + } + }); + } + + + /** + * Display the list of boats in the order they finished the race + */ + private void loadRaceResultView() { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); + loader.setController(new RaceResultController(race)); + + try { + contentAnchorPane.getChildren().removeAll(); + contentAnchorPane.getChildren().clear(); + contentAnchorPane.getChildren().addAll((Pane) loader.load()); + + } catch (javafx.fxml.LoadException e) { + System.err.println(e.getCause()); + } catch (IOException e) { + System.err.println(e); + } + } + + /** * Convert seconds to a string of the format mm:ss * @@ -250,7 +310,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel return String.format("%02d:%02d", time / 60, time % 60); } - private String currentTimer() { + private String getTimeSinceStartOfRace() { String timerString = "0:00"; if (StreamParser.getTimeSinceStart() > 0) { String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); @@ -270,13 +330,18 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel return timerString; } + public boolean isDisplayFps() { return displayFps; } + public Race getRace() { + return race; + } + + /** * Display the important annotations for a specific BoatGroup - * * @param bg The boat group to set the annotations for */ private void setBoatGroupImportantAnnotations(BoatGroup bg) { @@ -303,42 +368,81 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } else { bg.setWakeVisible(false); } + + if (importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK)) { + bg.setEstTimeToNextMarkObjectVisible(true); + } else { + bg.setEstTimeToNextMarkObjectVisible(false); + } + + if (importantAnnotations.getAnnotationState(Annotation.LEGTIME)) { + bg.setLegTimeObjectVisible(true); + } else { + bg.setLegTimeObjectVisible(false); + } } private void setAnnotations(Integer annotationLevel) { switch (annotationLevel) { + // No Annotations case 0: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setTeamNameObjectVisible(false); - bg.setVelocityObjectVisible(false); - bg.setLineGroupVisible(false); - bg.setWakeVisible(false); + for (RaceObject ro : includedCanvasController.getRaceObjects()) { + if (ro instanceof BoatGroup) { + BoatGroup bg = (BoatGroup) ro; + bg.setTeamNameObjectVisible(false); + bg.setVelocityObjectVisible(false); + bg.setEstTimeToNextMarkObjectVisible(false); + bg.setLegTimeObjectVisible(false); + bg.setLineGroupVisible(false); + bg.setWakeVisible(false); + } } break; + // Important Annotations case 1: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setTeamNameObjectVisible(true); - bg.setVelocityObjectVisible(false); - bg.setLineGroupVisible(false); - bg.setWakeVisible(false); + for (RaceObject ro : includedCanvasController.getRaceObjects()) { + if (ro instanceof BoatGroup) { + BoatGroup bg = (BoatGroup) ro; + setBoatGroupImportantAnnotations(bg); + } } break; + // All Annotations case 2: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setTeamNameObjectVisible(true); - bg.setVelocityObjectVisible(false); - bg.setLineGroupVisible(true); - bg.setWakeVisible(false); + for (RaceObject ro : includedCanvasController.getRaceObjects()) { + if (ro instanceof BoatGroup) { + BoatGroup bg = (BoatGroup) ro; + bg.setTeamNameObjectVisible(true); + bg.setVelocityObjectVisible(true); + bg.setEstTimeToNextMarkObjectVisible(true); + bg.setLegTimeObjectVisible(true); + bg.setLineGroupVisible(true); + bg.setWakeVisible(true); + } } break; - case 3: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setTeamNameObjectVisible(true); - bg.setVelocityObjectVisible(true); - bg.setLineGroupVisible(true); - bg.setWakeVisible(true); + } + } + + + /** + * Sets all the annotations of the selected boat to be visible and all others to be hidden + * + * @param yacht The yacht for which we want to view all annotations + */ + private void setSelectedBoat(Yacht yacht) { + for (RaceObject ro : includedCanvasController.getRaceObjects()) { + if (ro instanceof BoatGroup) { + BoatGroup bg = (BoatGroup) ro; + //We need to iterate over all race groups to get the matching boat group belonging to this boat if we + //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup. + if (bg.getBoat().getHullID().equals(yacht.getHullID())) { + bg.setIsSelected(true); + selectedBoat = yacht; + } else { + bg.setIsSelected(false); } - break; + } } } diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java new file mode 100644 index 00000000..8a4d563b --- /dev/null +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -0,0 +1,160 @@ +package seng302.controllers; + +import java.io.IOException; +import java.net.URL; +import java.util.ResourceBundle; +import java.util.Timer; +import java.util.TimerTask; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Parent; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import seng302.models.Yacht; +import seng302.models.parsers.StreamParser; + +public class StartScreenController implements Initializable { + @FXML + private GridPane gridPane; + @FXML + private Label timeTillLive; + @FXML + private Button streamButton; + @FXML + private Button switchToRaceViewButton; + @FXML + private TableView teamList; + @FXML + private TableColumn boatNameCol; + @FXML + private TableColumn shortNameCol; + @FXML + private TableColumn countryCol; + @FXML + private TableColumn posCol; + @FXML + private Label realTime; + + private boolean switchedToRaceView = false; + + private void setContentPane(String jfxUrl){ + try{ + // get the main controller anchor pane (MainView.fxml) + AnchorPane contentPane = (AnchorPane) gridPane.getParent(); + contentPane.getChildren().removeAll(); + contentPane.getChildren().clear(); + contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + } + catch(javafx.fxml.LoadException e){ + System.err.println(e.getCause()); + } + catch(IOException e){ + System.err.println(e); + } + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + } + + /** + * Running a timer to update the livestream status on welcome screen. Update interval is 1 second. + */ + public void startStream() { + if (StreamParser.isStreamStatus()) { + streamButton.setVisible(false); + realTime.setVisible(true); + timeTillLive.setVisible(true); + timeTillLive.setTextFill(Color.GREEN); + timeTillLive.setText("Connecting..."); + Timer timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + Platform.runLater(() -> { + if (StreamParser.isRaceStarted()) { + if (!switchedToRaceView) { + switchToRaceView(); + } + timer.cancel(); + } + if (StreamParser.isRaceFinished()) { + realTime.setText(StreamParser.getCurrentTimeString()); + timeTillLive.setTextFill(Color.RED); + timeTillLive.setText("Race finished! Waiting for new race..."); + switchToRaceViewButton.setDisable(true); + } else if (StreamParser.getTimeSinceStart() > 0) { + realTime.setText(StreamParser.getCurrentTimeString()); + updateTeamList(); + timeTillLive.setTextFill(Color.RED); + switchToRaceViewButton.setDisable(false); + String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); + String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60); + if (timerSecond.length() == 1) { + timerSecond = "0" + timerSecond; + } + String timerString = "-" + timerMinute + ":" + timerSecond; + timeTillLive.setText(timerString); + } else { + realTime.setText(StreamParser.getCurrentTimeString()); + updateTeamList(); + timeTillLive.setTextFill(Color.BLACK); + switchToRaceViewButton.setDisable(false); + String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60); + String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60); + if (timerSecond.length() == 1) { + timerSecond = "0" + timerSecond; + } + String timerString = timerMinute + ":" + timerSecond; + timeTillLive.setText(timerString); + } + }); + } + }, 0, 1000); + } else { + timeTillLive.setText("Stream not available."); + timeTillLive.setTextFill(Color.RED); + } + } + + public void switchToRaceView() { + switchedToRaceView = true; + setContentPane("/views/RaceView.fxml"); + } + + private void updateTeamList() { + ObservableList data = FXCollections.observableArrayList(); + + teamList.setItems(data); + + boatNameCol.setCellValueFactory( + new PropertyValueFactory<>("boatName") + ); + shortNameCol.setCellValueFactory( + new PropertyValueFactory<>("shortName") + ); + countryCol.setCellValueFactory( + new PropertyValueFactory<>("country") + ); + posCol.setCellValueFactory( + new PropertyValueFactory<>("position") + ); + + data.addAll(StreamParser.getBoatsPos().values()); + teamList.refresh(); + } +} diff --git a/src/main/java/seng302/controllers/annotations/Annotation.java b/src/main/java/seng302/controllers/annotations/Annotation.java index eed70162..20a2c265 100644 --- a/src/main/java/seng302/controllers/annotations/Annotation.java +++ b/src/main/java/seng302/controllers/annotations/Annotation.java @@ -7,5 +7,7 @@ public enum Annotation { SPEED, WAKE, TRACK, - NAME + NAME, + ESTTIMETONEXTMARK, + LEGTIME } diff --git a/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java b/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java index 0d1585af..8dfa8df7 100644 --- a/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java +++ b/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.ResourceBundle; public class ImportantAnnotationController implements Initializable { + /* * JavaFX Outlets */ @@ -30,6 +31,12 @@ public class ImportantAnnotationController implements Initializable { @FXML private CheckBox boatNameSelect; + @FXML + private CheckBox boatEstTimeToNextMarkSelect; + + @FXML + private CheckBox boatElapsedTimeSelect; + @FXML private AnchorPane annotationSelectWindow; @@ -40,7 +47,7 @@ public class ImportantAnnotationController implements Initializable { private ImportantAnnotationsState importantAnnotationsState; private Stage stage; - public ImportantAnnotationController(ImportantAnnotationDelegate delegate, Stage stage){ + public ImportantAnnotationController(ImportantAnnotationDelegate delegate, Stage stage) { this.delegate = delegate; importantAnnotationsState = new ImportantAnnotationsState(); this.stage = stage; @@ -49,10 +56,11 @@ public class ImportantAnnotationController implements Initializable { /** * Sets whether or not an annotation is considered important, then * sends an update to the delegate + * * @param annotation The annotation * @param isSet True if annotation is important */ - private void setAnnotation(Annotation annotation, Boolean isSet){ + private void setAnnotation(Annotation annotation, Boolean isSet) { importantAnnotationsState.setAnnotationState(annotation, isSet); sendUpdate(); } @@ -61,36 +69,50 @@ public class ImportantAnnotationController implements Initializable { * Sends an update to the delegate when the important * annotations have changed */ - private void sendUpdate(){ + private void sendUpdate() { this.delegate.importantAnnotationsChanged(importantAnnotationsState); } /** * Load the current state of the 'important annotations' + * * @param currentState hashmap containing the states of each annotation */ - public void loadState(ImportantAnnotationsState currentState){ + public void loadState(ImportantAnnotationsState currentState) { this.importantAnnotationsState = currentState; // Initialise checkboxes - for (Annotation annotation : importantAnnotationsState.getAnnotations()){ - switch (annotation){ + for (Annotation annotation : importantAnnotationsState.getAnnotations()) { + switch (annotation) { case WAKE: - boatWakeSelect.setSelected(importantAnnotationsState.getAnnotationState(annotation)); + boatWakeSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); break; case SPEED: - boatSpeedSelect.setSelected(importantAnnotationsState.getAnnotationState(annotation)); + boatSpeedSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); break; case TRACK: - boatTrackSelect.setSelected(importantAnnotationsState.getAnnotationState(annotation)); + boatTrackSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); break; case NAME: - boatNameSelect.setSelected(importantAnnotationsState.getAnnotationState(annotation)); + boatNameSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); break; + case ESTTIMETONEXTMARK: + boatEstTimeToNextMarkSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + break; + + case LEGTIME: + boatElapsedTimeSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + default: break; } @@ -99,15 +121,24 @@ public class ImportantAnnotationController implements Initializable { /** * View did load + * * @param location . * @param resources . */ @Override public void initialize(URL location, ResourceBundle resources) { - boatWakeSelect.setOnAction(event -> setAnnotation(Annotation.WAKE, boatWakeSelect.isSelected())); - boatSpeedSelect.setOnAction(event -> setAnnotation(Annotation.SPEED, boatSpeedSelect.isSelected())); - boatTrackSelect.setOnAction(event -> setAnnotation(Annotation.TRACK, boatTrackSelect.isSelected())); - boatNameSelect.setOnAction(event -> setAnnotation(Annotation.NAME, boatNameSelect.isSelected())); + boatWakeSelect + .setOnAction(event -> setAnnotation(Annotation.WAKE, boatWakeSelect.isSelected())); + boatSpeedSelect + .setOnAction(event -> setAnnotation(Annotation.SPEED, boatSpeedSelect.isSelected())); + boatTrackSelect + .setOnAction(event -> setAnnotation(Annotation.TRACK, boatTrackSelect.isSelected())); + boatNameSelect + .setOnAction(event -> setAnnotation(Annotation.NAME, boatNameSelect.isSelected())); + boatEstTimeToNextMarkSelect.setOnAction(event -> setAnnotation(Annotation.ESTTIMETONEXTMARK, + boatEstTimeToNextMarkSelect.isSelected())); + boatElapsedTimeSelect.setOnAction( + event -> setAnnotation(Annotation.LEGTIME, boatElapsedTimeSelect.isSelected())); closeButton.setOnAction(event -> stage.close()); } diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index 68970268..23ba616a 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -12,9 +12,9 @@ import java.text.SimpleDateFormat; * also done outside Boat class because some old variables are not used anymore. */ public class Yacht { + // Used in boat group private Color colour; private double velocity; - private Integer markLastPast; private String boatType; private Integer sourceID; @@ -30,6 +30,8 @@ public class Yacht { private Long estimateTimeAtNextMark; private Long estimateTimeAtFinish; private String position; + // Mark rounding + private Long markRoundingTime; /** * Used in EventTest and RaceTest. @@ -157,11 +159,16 @@ public class Yacht { this.velocity = velocity; } - public Integer getMarkLastPast() { - return markLastPast; + public Long getMarkRoundingTime() { + return markRoundingTime; } - public void setMarkLastPast(Integer markLastPast) { - this.markLastPast = markLastPast; + public void setMarkRoundingTime(Long markRoundingTime) { + this.markRoundingTime = markRoundingTime; + } + + @Override + public String toString() { + return boatName; } } diff --git a/src/main/java/seng302/models/stream/StreamParser.java b/src/main/java/seng302/models/stream/StreamParser.java index 51769a1d..b6f0f0be 100644 --- a/src/main/java/seng302/models/stream/StreamParser.java +++ b/src/main/java/seng302/models/stream/StreamParser.java @@ -1,12 +1,12 @@ -package seng302.models.stream; +package seng302.models.parsers; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import seng302.models.Yacht; -import seng302.models.stream.packets.BoatPositionPacket; -import seng302.models.stream.packets.StreamPacket; +import seng302.models.parsers.packets.BoatPositionPacket; +import seng302.models.parsers.packets.StreamPacket; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -17,6 +17,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.PriorityBlockingQueue; /** @@ -24,21 +25,22 @@ import java.util.concurrent.PriorityBlockingQueue; * and parsed in by turning the byte arrays into useful data. There are two public static hashmaps * that are threadsafe so the visualiser can always access the latest speed and position available * Created by kre39 on 23/04/17. - * */ public class StreamParser extends Thread{ public static ConcurrentHashMap> boatPositions = new ConcurrentHashMap<>(); private String threadName; private Thread t; + private static boolean newRaceXmlReceived = false; private static boolean raceStarted = false; private static XMLParser xmlObject; private static boolean raceFinished = false; private static boolean streamStatus = false; private static long timeSinceStart = -1; - private static Map boats = new HashMap<>(); - private static Map boatsPos = new TreeMap<>(); + private static Map boats = new ConcurrentHashMap<>(); + private static Map boatsPos = new ConcurrentSkipListMap<>(); private static double windDirection = 0; + private static Long currentTimeLong; private static String currentTimeString; private static boolean appRunning; @@ -68,6 +70,7 @@ public class StreamParser extends Thread{ while (appRunning){ StreamPacket packet = StreamReceiver.packetBuffer.take(); parsePacket(packet); + Thread.sleep(1); while (StreamReceiver.packetBuffer.peek() == null) { } } @@ -107,6 +110,7 @@ public class StreamParser extends Thread{ extractDisplayMessage(packet); break; case XML_MESSAGE: + newRaceXmlReceived = true; extractXmlMessage(packet); break; case RACE_START_STATUS: @@ -181,9 +185,11 @@ public class StreamParser extends Thread{ long currentTime = bytesToLong(Arrays.copyOfRange(payload,1,7)); long raceId = bytesToLong(Arrays.copyOfRange(payload,7,11)); int raceStatus = payload[11]; -// System.out.println("raceStatus = " + raceStatus); long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18)); + long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); + long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); + currentTimeLong = currentTime; DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); if (xmlObject.getRegattaXML() != null) { format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString())); @@ -191,7 +197,6 @@ public class StreamParser extends Thread{ } long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000; - if (timeTillStart > 0) { timeSinceStart = timeTillStart; //System.out.println("Time till start: " + timeTillStart + " Seconds"); @@ -207,10 +212,10 @@ public class StreamParser extends Thread{ } timeSinceStart = timeTillStart; } - long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); + double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc... windDirection = windDir / windDirFactor; - long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); + int noBoats = payload[22]; int raceType = payload[23]; boatsPos = new TreeMap<>(); @@ -219,11 +224,11 @@ public class StreamParser extends Thread{ Yacht boat = boats.get((int) boatStatusSourceID); boat.setBoatStatus((int)payload[28 + (i * 20)]); boat.setLegNumber((int)payload[29 + (i * 20)]); - boat.setPenaltiesAwarded((int)payload[29 + (i * 20)]); - boat.setPenaltiesServed((int)payload[30 + (i * 20)]); - Long estTimeAtNextMark = bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20))); + boat.setPenaltiesAwarded((int)payload[30 + (i * 20)]); + boat.setPenaltiesServed((int)payload[31 + (i * 20)]); + Long estTimeAtNextMark = bytesToLong(Arrays.copyOfRange(payload,32 + (i * 20),38+ (i * 20))); boat.setEstimateTimeAtNextMark(estTimeAtNextMark); - Long estTimeAtFinish = bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20))); + Long estTimeAtFinish = bytesToLong(Arrays.copyOfRange(payload,38 + (i * 20),44+ (i * 20))); boat.setEstimateTimeAtFinish(estTimeAtFinish); boatsPos.put(estTimeAtFinish, boat); // String boatStatus = "SourceID: " + boatStatusSourceID; @@ -277,8 +282,8 @@ public class StreamParser extends Thread{ byte[] payload = packet.getPayload(); int messageType = payload[9]; - long messagelength = bytesToLong(Arrays.copyOfRange(payload,12,14)); - String xmlMessage = new String((Arrays.copyOfRange(payload,14,(int) (14 + messagelength)))).trim(); + long messageLength = bytesToLong(Arrays.copyOfRange(payload,12,14)); + String xmlMessage = new String((Arrays.copyOfRange(payload,14,(int) (14 + messageLength)))).trim(); //Create XML document Object DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -295,6 +300,9 @@ public class StreamParser extends Thread{ if (messageType == 7) { //7 is the boat XML boats = xmlObject.getBoatXML().getCompetingBoats(); } + if (messageType == 6) { //6 is race info xml + newRaceXmlReceived = true; + } } /** @@ -409,6 +417,9 @@ public class StreamParser extends Thread{ int roundingSide = payload[18]; int markType = payload[19]; int markId = payload[20]; + + // assign mark rounding time to boat + boats.get((int)subjectId).setMarkRoundingTime(timeStamp); } /** @@ -555,9 +566,33 @@ public class StreamParser extends Thread{ return boatsPos; } + /** + * returns current time in stream in long + * + * @return a long value of current time + */ + public static Long getCurrentTimeLong() { + return currentTimeLong; + } + public static void appClose(){ appRunning = false; System.out.println("[CLIENT] Shutting down stream parser"); } + + /** + * Used to check if a new un-processed xml has been found, if so will return true before + * toggling off so that the next check will return false. + * + * @return the status of if new xml has been received + */ + public static boolean isNewRaceXmlReceived(){ + if (newRaceXmlReceived){ + newRaceXmlReceived = false; + return true; + } else { + return false; + } + } } diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index c09eb8dc..094845ab 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -235,6 +235,28 @@ public class ServerThread implements Runnable, Observer { } } + /** + * Send the post-start race course information + */ + private void sendPostStartCourseXml(){ + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + try { + Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE); + if (raceData != null) { + server.send(raceData); + serverLog("Sending race data", 0); + } + }catch (IOException e) { + serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); + } + } + },25000); + //Delays the new course xml data for 25 seconds so the boats are able to pass the starting line + } + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -252,12 +274,13 @@ public class ServerThread implements Runnable, Observer { sendXml(); startSendingRaceStartStatusMessages(); startSendingRaceStatusMessages(); + sendPostStartCourseXml(); } /** * Start sending static boat position updates when race has finished */ - private void startSendingRaceFinishedBoatPostions(){ + private void startSendingRaceFinishedBoatPositions(){ Timer t = new Timer(); t.schedule(new TimerTask() { @Override @@ -316,7 +339,7 @@ public class ServerThread implements Runnable, Observer { } if (numOfBoatsFinished == ((List) arg).size()) { - startSendingRaceFinishedBoatPostions(); + startSendingRaceFinishedBoatPositions(); } } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index 57f31dd6..5e605170 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -1,10 +1,7 @@ package seng302.server.messages; -import java.io.DataOutputStream; import java.io.IOException; -import java.nio.channels.Channels; import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; public class BoatLocationMessage extends Message { private final int MESSAGE_SIZE = 56; diff --git a/src/main/resources/css/master.css b/src/main/resources/css/master.css index 81661bfe..8223fc00 100644 --- a/src/main/resources/css/master.css +++ b/src/main/resources/css/master.css @@ -128,6 +128,24 @@ Table -fx-border-style: none; } + +/** +Combo Box + */ + +.combo-box-base { + -fx-background-color: #119796; + -fx-text-fill: white; +} + +.combo-box-popup .list-view .list-cell:hover { + -fx-text-fill: white; +} + +.combo-box .cell:selected { + -fx-text-fill: white; +} + /** Remove scroll bars */ diff --git a/src/main/resources/server_config/courseLimits.xml b/src/main/resources/server_config/courseLimits.xml new file mode 100644 index 00000000..646f6ade --- /dev/null +++ b/src/main/resources/server_config/courseLimits.xml @@ -0,0 +1,105 @@ + + +2015-08-29T13:12:40+02:00 + +15082901 +Fleet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/race.xml b/src/main/resources/server_config/race.xml index 845f2044..06f4f626 100644 --- a/src/main/resources/server_config/race.xml +++ b/src/main/resources/server_config/race.xml @@ -1,6 +1,6 @@ - 2015-08-29T13:12:40+02:00 + 2015-08-29T11:27:15+02:00 15082901 Fleet @@ -80,6 +80,8 @@ - + + + \ No newline at end of file diff --git a/src/main/resources/views/MainView.fxml b/src/main/resources/views/MainView.fxml index 054dd6cd..dab6e135 100644 --- a/src/main/resources/views/MainView.fxml +++ b/src/main/resources/views/MainView.fxml @@ -7,58 +7,4 @@ - - - - - - - - - - - - - - - - - - - - - - +