diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 92aab87a..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,6 +62,7 @@ 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("livedata.americascup.com", 4941, "RaceStream"); } @@ -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 6b34732d..ff7a294f 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -17,9 +17,6 @@ import seng302.models.stream.packets.BoatPositionPacket; import seng302.models.stream.XMLParser; import seng302.models.stream.XMLParser.RaceXMLObject.Limit; import seng302.models.mark.Mark; -import seng302.server.simulator.Boat; - -import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.PriorityBlockingQueue; @@ -54,17 +51,15 @@ public class CanvasController { private Mark maxLonPoint; private double referencePointX; private double referencePointY; + private List markGroups = new ArrayList<>(); private List boatGroups = new ArrayList<>(); - private List raceMarks = new ArrayList<>(); //FRAME RATE - private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps + private Double frameRate = 60.0; private final long[] frameTimes = new long[30]; private int frameTimeIndex = 0; private boolean arrayFilled = false; - private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00"); - private double lastPacketTime = 0; public AnimationTimer timer; @@ -87,8 +82,6 @@ public class CanvasController { // Bind canvas size to stack pane size. canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); - //group.minWidth(CANVAS_WIDTH); - //group.minHeight(CANVAS_HEIGHT); } public void initializeCanvas (){ @@ -102,7 +95,7 @@ public class CanvasController { // TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml - drawBoats(); + initializeBoats(); timer = new AnimationTimer() { @Override @@ -119,13 +112,13 @@ public class CanvasController { if (arrayFilled) { elapsedNanos = now - oldFrameTime ; long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ; - Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ; + frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ; drawFps(frameRate.intValue()); } // TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay. elapsedNanos = 1000 / 60; - //updateRaceObjects(); + updateGroups(); if (StreamParser.isRaceFinished()) { this.stop(); } @@ -156,7 +149,7 @@ public class CanvasController { Point2D borderPoint1 = findScaledXY(thisMark1); Point2D borderPoint2 = findScaledXY(thisMark2); gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(), - borderPoint2.getX(), borderPoint2.getY()); + borderPoint2.getX(), borderPoint2.getY()); xBoundaryPoints[i] = borderPoint1.getX(); yBoundaryPoints[i] = borderPoint1.getY(); } @@ -167,7 +160,7 @@ public class CanvasController { Point2D borderPoint1 = findScaledXY(thisMark1); Point2D borderPoint2 = findScaledXY(thisMark2); gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(), - borderPoint2.getX(), borderPoint2.getY()); + borderPoint2.getX(), borderPoint2.getY()); xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX(); yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY(); gc.setFill(Color.LIGHTBLUE); @@ -176,42 +169,44 @@ public class CanvasController { private void updateGroups(){ for (BoatGroup boatGroup : boatGroups) { - boatGroup.updatePosition(1000 / 60); - // some raceObjects will have multiply ID's (for instance gate marks) - //checking if the current "ID" has any updates associated with it - if (StreamParser.boatPositions.containsKey(boatGroup.getRaceId())) { - moveBoatGroup(boatGroup); + // some raceObjects will have multiple ID's (for instance gate marks) + //checking if the current "ID" has any updates associated with it + if (StreamParser.boatPositions.containsKey(boatGroup.getRaceId())) { + if (boatGroup.isStopped()) { + updateBoatGroup(boatGroup); + } } + boatGroup.move(); } for (MarkGroup markGroup : markGroups) { for (int id : markGroup.getRaceIds()) { if (StreamParser.boatPositions.containsKey(id)) { - moveMarkGroup(id, markGroup); + UpdateMarkGroup(id, markGroup); } } } + checkForCourseChanges(); } - private void moveBoatGroup(BoatGroup boatGroup) { + 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) { PriorityBlockingQueue movementQueue = StreamParser.boatPositions.get(boatGroup.getRaceId()); + // giving the movementQueue a 5 packet buffer to account for slightly out of order packets if (movementQueue.size() > 0){ -// BoatPositionPacket positionPacket = movementQueue.peek(); -// -// //this code adds a delay to reading from the movementQueue -// //in case things being put into the movement queue are slightly -// //out of order -// int delayTime = 1000; -// int loopTime = delayTime * 10; -// long timeDiff = (System.currentTimeMillis()%loopTime - positionPacket.getTimeValid()%loopTime); -// if (timeDiff < 0){ -// timeDiff = loopTime + timeDiff; -// } -// if (timeDiff > delayTime) { try { BoatPositionPacket positionPacket = movementQueue.take(); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); double heading = 360.0 / 0xffff * positionPacket.getHeading(); - boatGroup.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), boatGroup.getRaceId()); + boatGroup.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), positionPacket.getTimeValid(), frameRate, boatGroup.getRaceId()); } catch (InterruptedException e){ e.printStackTrace(); } @@ -219,21 +214,9 @@ public class CanvasController { } } - void moveMarkGroup (int raceId, MarkGroup markGroup) { + void UpdateMarkGroup (int raceId, MarkGroup markGroup) { PriorityBlockingQueue movementQueue = StreamParser.boatPositions.get(raceId); if (movementQueue.size() > 0){ -// BoatPositionPacket positionPacket = movementQueue.peek(); -// -// //this code adds a delay to reading from the movementQueue -// //in case things being put into the movement queue are slightly -// //out of order -// int delayTime = 1000; -// int loopTime = delayTime * 10; -// long timeDiff = (System.currentTimeMillis()%loopTime - positionPacket.getTimeValid()%loopTime); -// if (timeDiff < 0){ -// timeDiff = loopTime + timeDiff; -// } -// if (timeDiff > delayTime) { try { BoatPositionPacket positionPacket = movementQueue.take(); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); @@ -244,6 +227,23 @@ public class CanvasController { } } + /** + * Draws all the boats. + */ + private void initializeBoats() { + Map boats = StreamParser.getBoats(); + Group boatAnnotations = new Group(); + + for (Yacht boat : boats.values()) { + boat.setColour(Colors.getColor()); + BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); + boatGroups.add(boatGroup); + boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations()); + } + group.getChildren().add(boatAnnotations); + group.getChildren().addAll(boatGroups); + } + class ResizableCanvas extends Canvas { ResizableCanvas() { @@ -269,11 +269,11 @@ public class CanvasController { public double prefWidth(double height) { return getWidth(); } - @Override public double prefHeight(double width) { return getHeight(); } + } private void drawFps(int fps){ @@ -292,34 +292,12 @@ public class CanvasController { } } - /** - * Draws all the boats. - */ - private void drawBoats() { -// Map timelineInfos = raceViewController.getTimelineInfos(); -// List boats = raceViewController.getStartingBoats(); - Map boats = StreamParser.getBoats(); -// Double startingX = raceObjects.get(0).getLayoutX(); -// Double startingY = raceObjects.get(0).getLayoutY(); - Group boatAnnotations = new Group(); - - for (Yacht boat : boats.values()) { -// for (Boat boat : boats) { - boat.setColour(Colors.getColor()); - BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); -// boatGroup.moveTo(startingX, startingY, 0d); - //boatGroup.setStage(raceViewController.getStage()); - boatGroups.add(boatGroup); - boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations()); - } - group.getChildren().add(boatAnnotations); - group.getChildren().addAll(boatGroups); - } - /** * 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); @@ -418,36 +396,6 @@ public class CanvasController { return horiDistance; } - /** - * Give all markers in the course an x,y location relative to a given reference with a known x,y location. Distances - * are scaled according to the distanceScaleFactor variable. - */ - private void givePointsXY() { - Map allPoints = StreamParser.getXmlObject().getRaceXML().getCompoundMarks(); - List processed = new ArrayList<>(); - MarkGroup markGroup; - - for (Map.Entry cMark : allPoints) { - Integer cMarkId = cMark.getKey(); - Mark mark = cMark.getValue(); - if (!processed.contains(mark)) { - if (mark.getMarkType() != MarkType.SINGLE_MARK) { - GateMark gMark = (GateMark) mark; - - markGroup = new MarkGroup(mark, findScaledXY(gMark.getSingleMark1()), findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list. - markGroups.add(markGroup); - } else { - SingleMark sMark = (SingleMark) mark; - - markGroup = new MarkGroup(mark, findScaledXY(sMark)); - markGroups.add(markGroup); - } - processed.add((mark)); - } - } - group.getChildren().addAll(boatGroups); - } - private Point2D findScaledXY (Mark unscaled) { return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude()); } diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index 8eb2eee2..a222cfc3 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,142 +1,39 @@ package seng302.controllers; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; -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.fxml.Initializable; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import seng302.models.Yacht; -import seng302.models.stream.StreamParser; -import seng302.models.stream.XMLParser; - import java.io.IOException; -import java.util.Timer; -import java.util.TimerTask; +import java.net.URL; +import java.util.ResourceBundle; +import seng302.models.stream.StreamParser; + +public class Controller implements Initializable { -public class Controller { @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 XMLParser xmlParser; - - private void setContentPane(String jfxUrl){ - try{ + private void setContentPane(String jfxUrl) { + try { contentPane.getChildren().removeAll(); contentPane.getChildren().clear(); - contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); - } - catch(javafx.fxml.LoadException e){ + 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){ + } catch (IOException e) { System.err.println(e); } } - /** - * Running a timer to update the livestream status on welcome screen. Update interval is 1 second. - */ - public void startStream() { - if (StreamParser.isStreamStatus()) { - xmlParser = StreamParser.getXmlObject(); - 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.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() { - 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(); - + @Override + public void initialize(URL location, ResourceBundle resources) { + contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + setContentPane("/views/StartScreenView.fxml"); + StreamParser.boatPositions.clear(); } } diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 4ff0327d..8492015b 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -2,26 +2,40 @@ package seng302.controllers; 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; import javafx.scene.text.Text; +import javafx.stage.Stage; +import javafx.stage.StageStyle; import javafx.util.Duration; import javafx.util.StringConverter; +import seng302.controllers.annotations.Annotation; +import seng302.controllers.annotations.ImportantAnnotationController; +import seng302.controllers.annotations.ImportantAnnotationDelegate; +import seng302.controllers.annotations.ImportantAnnotationsState; import seng302.models.*; import seng302.models.stream.StreamParser; +import java.io.IOException; import java.util.*; /** * Created by ptg19 on 29/03/17. */ -public class RaceViewController extends Thread{ +public class RaceViewController extends Thread implements ImportantAnnotationDelegate { + @FXML private VBox positionVbox; @FXML @@ -35,44 +49,96 @@ public class RaceViewController extends Thread{ @FXML private Slider annotationSlider; @FXML + private Button selectAnnotationBtn; + @FXML + private ComboBox boatSelectionComboBox; + @FXML private CanvasController includedCanvasController; private ArrayList startingBoats = new ArrayList<>(); private boolean displayFps; private Timeline timerTimeline; - private ArrayList boatOrder = new ArrayList<>(); + private Stage stage; + + private ImportantAnnotationsState importantAnnotations; + private Yacht selectedBoat; public void initialize() { + // Load a default important annotation state + importantAnnotations = new ImportantAnnotationsState(); includedCanvasController.setup(this); includedCanvasController.initializeCanvas(); - initializeTimer(); - initializeSettings(); - initialiseWindDirection(); - initialisePositionVBox(); + initializeUpdateTimer(); + initialiseFPSCheckBox(); + initialiseAnnotationSlider(); + initialiseBoatSelectionComboBox(); includedCanvasController.timer.start(); + + selectAnnotationBtn.setOnAction(event -> { + loadSelectAnnotationView(); + }); + } + + /** + * The important annotations have been changed, update this view + * @param importantAnnotationsState The current state of the selected annotations + */ + public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) { + this.importantAnnotations = importantAnnotationsState; + setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations + } + + /** + * Loads the "select annotations" view in a new window + */ + private void loadSelectAnnotationView() { + try { + FXMLLoader fxmlLoader = new FXMLLoader(); + Stage stage = new Stage(); + + // Set controller + ImportantAnnotationController controller = new ImportantAnnotationController(this, + stage); + fxmlLoader.setController(controller); + + // Load FXML and set CSS + fxmlLoader + .setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml")); + Scene scene = new Scene(fxmlLoader.load(), 469, 248); + scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + stage.initStyle(StageStyle.UNDECORATED); + + stage.setScene(scene); + stage.show(); + + controller.loadState(importantAnnotations); + + } catch (IOException e) { + e.printStackTrace(); + } } - - 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; - } - }); - - //SLIFER STUFF BELOW + private void initialiseAnnotationSlider() { annotationSlider.setLabelFormatter(new StringConverter() { @Override public String toString(Double n) { - if (n == 0) return "None"; - if (n == 1) return "Low"; - if (n == 2) return "Medium"; - if (n == 3) return "All"; + if (n == 0) { + return "None"; + } + if (n == 1) { + return "Important"; + } + if (n == 2) { + return "All"; + } return "All"; } @@ -82,85 +148,146 @@ public class RaceViewController extends Thread{ switch (s) { case "None": return 0d; - case "Low": + case "Important": return 1d; - case "Medium": - return 2d; case "All": - return 3d; + return 2d; default: - return 3d; + return 2d; } } }); annotationSlider.valueProperty().addListener((obs, oldval, newVal) -> - setAnnotations((int)annotationSlider.getValue())); + 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()); - } - }) + new KeyFrame(Duration.seconds(1), + event -> { + updateRaceTime(); + updateWindDirection(); + updateOrder(); + updateBoatSelectionComboBox(); + + }) ); // Start the timer 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()); + for (Yacht boat : StreamParser.getBoatsPos().values()) { if (boat.getBoatStatus() == 3) { // 3 is finish status - positionVbox.getChildren().add(new Text(boat.getPosition() + ". " + - boat.getShortName() + " (Finished)")); + Text textToAdd = new Text(boat.getPosition() + ". " + + boat.getShortName() + " (Finished)"); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + positionVbox.getChildren().add(textToAdd); + } else { - positionVbox.getChildren().add(new Text(boat.getPosition() + ". " + - boat.getShortName() + " ")); + Text textToAdd = new Text(boat.getPosition() + ". " + + boat.getShortName() + " "); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + textToAdd.setStyle(""); + positionVbox.getChildren().add(textToAdd); } } } + + /** + * 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")); + + 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 * @@ -174,7 +301,7 @@ public class RaceViewController extends Thread{ 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); @@ -194,44 +321,110 @@ public class RaceViewController extends Thread{ return timerString; } + public boolean isDisplayFps() { return displayFps; } + /** + * Display the important annotations for a specific BoatGroup + * @param bg The boat group to set the annotations for + */ + private void setBoatGroupImportantAnnotations(BoatGroup bg) { + if (importantAnnotations.getAnnotationState(Annotation.NAME)) { + bg.setTeamNameObjectVisible(true); + } else { + bg.setTeamNameObjectVisible(false); + } + + if (importantAnnotations.getAnnotationState(Annotation.SPEED)) { + bg.setVelocityObjectVisible(true); + } else { + bg.setVelocityObjectVisible(false); + } + + if (importantAnnotations.getAnnotationState(Annotation.TRACK)) { + bg.setLineGroupVisible(true); + } else { + bg.setLineGroupVisible(false); + } + + if (importantAnnotations.getAnnotationState(Annotation.WAKE)) { + bg.setWakeVisible(true); + } else { + bg.setWakeVisible(false); + } + //TODO fix boat annotations with new boatgroup + 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); + 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); + setBoatGroupImportantAnnotations(bg); } break; + // All Annotations case 2: for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setTeamNameObjectVisible(true); - bg.setVelocityObjectVisible(false); - bg.setLineGroupVisible(true); - bg.setWakeVisible(false); - } - break; - case 3: - for (BoatGroup bg : includedCanvasController.getBoatGroups()) { - bg.setTeamNameObjectVisible(true); - bg.setVelocityObjectVisible(true); - bg.setLineGroupVisible(true); - bg.setWakeVisible(true); + bg.setTeamNameObjectVisible(true); + bg.setVelocityObjectVisible(true); + bg.setEstTimeToNextMarkObjectVisible(true); + bg.setLegTimeObjectVisible(true); + bg.setLineGroupVisible(true); + bg.setWakeVisible(true); } break; } } + + + /** + * 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 (BoatGroup bg : includedCanvasController.getBoatGroups()) { + //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); + } + } + } + + void setStage(Stage stage) { + this.stage = stage; + } + + Stage getStage() { + return stage; + } } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java new file mode 100644 index 00000000..94851d05 --- /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.stream.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 new file mode 100644 index 00000000..20a2c265 --- /dev/null +++ b/src/main/java/seng302/controllers/annotations/Annotation.java @@ -0,0 +1,13 @@ +package seng302.controllers.annotations; + +/** + * Annotations the user can select as important + */ +public enum Annotation { + SPEED, + WAKE, + TRACK, + NAME, + ESTTIMETONEXTMARK, + LEGTIME +} diff --git a/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java b/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java new file mode 100644 index 00000000..8dfa8df7 --- /dev/null +++ b/src/main/java/seng302/controllers/annotations/ImportantAnnotationController.java @@ -0,0 +1,145 @@ +package seng302.controllers.annotations; + +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; +import seng302.controllers.RaceViewController; +import seng302.controllers.annotations.Annotation; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +public class ImportantAnnotationController implements Initializable { + + /* + * JavaFX Outlets + */ + @FXML + private CheckBox boatWakeSelect; + + @FXML + private CheckBox boatSpeedSelect; + + @FXML + private CheckBox boatTrackSelect; + + @FXML + private CheckBox boatNameSelect; + + @FXML + private CheckBox boatEstTimeToNextMarkSelect; + + @FXML + private CheckBox boatElapsedTimeSelect; + + @FXML + private AnchorPane annotationSelectWindow; + + @FXML + private Button closeButton; + + private ImportantAnnotationDelegate delegate; + private ImportantAnnotationsState importantAnnotationsState; + private Stage stage; + + public ImportantAnnotationController(ImportantAnnotationDelegate delegate, Stage stage) { + this.delegate = delegate; + importantAnnotationsState = new ImportantAnnotationsState(); + this.stage = stage; + } + + /** + * 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) { + importantAnnotationsState.setAnnotationState(annotation, isSet); + sendUpdate(); + } + + /** + * Sends an update to the delegate when the important + * annotations have changed + */ + 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) { + this.importantAnnotationsState = currentState; + + // Initialise checkboxes + for (Annotation annotation : importantAnnotationsState.getAnnotations()) { + switch (annotation) { + case WAKE: + boatWakeSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + break; + + case SPEED: + boatSpeedSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + break; + + case TRACK: + boatTrackSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + break; + + case NAME: + boatNameSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + break; + + case ESTTIMETONEXTMARK: + boatEstTimeToNextMarkSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + break; + + case LEGTIME: + boatElapsedTimeSelect + .setSelected(importantAnnotationsState.getAnnotationState(annotation)); + + default: + break; + } + } + } + + /** + * 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())); + 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/controllers/annotations/ImportantAnnotationDelegate.java b/src/main/java/seng302/controllers/annotations/ImportantAnnotationDelegate.java new file mode 100644 index 00000000..ba50726e --- /dev/null +++ b/src/main/java/seng302/controllers/annotations/ImportantAnnotationDelegate.java @@ -0,0 +1,16 @@ +package seng302.controllers.annotations; + +/** + * An ImportantAnnotationDelegate handles updating the important annotations + * displayed to the user on behalf of the ImportantAnnotationController + */ +public interface ImportantAnnotationDelegate { + /** + * The important annotations have been changed, update the + * annotations displayed to the user + * @param importantAnnotationsState The current state of the selected annotations + */ + void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState); + + +} diff --git a/src/main/java/seng302/controllers/annotations/ImportantAnnotationsState.java b/src/main/java/seng302/controllers/annotations/ImportantAnnotationsState.java new file mode 100644 index 00000000..5cc97a7f --- /dev/null +++ b/src/main/java/seng302/controllers/annotations/ImportantAnnotationsState.java @@ -0,0 +1,52 @@ +package seng302.controllers.annotations; + +import java.util.HashMap; +import java.util.Map; + +public class ImportantAnnotationsState { + public static final Boolean DEFAULT_ANNOTATION_STATE = true; + private Map currentState; + + /** + * Stores the users preference for the annotations + * they consider to be important + */ + public ImportantAnnotationsState(){ + this.currentState = new HashMap<>(); + initialiseState(); + } + + /** + * Set each annotation to the default annotation state + */ + private void initialiseState(){ + for (Annotation annotation : getAnnotations()){ + currentState.put(annotation, DEFAULT_ANNOTATION_STATE); + } + } + + /** + * Sets the state (visibility) of an annotation + * @param annotation The annotation to set + * @param visible Whether or not the annotation should be visible + */ + public void setAnnotationState(Annotation annotation, Boolean visible){ + this.currentState.put(annotation, visible); + } + + /** + * Returns the state (visibility) of a specific annotation + * @param annotation The annotation to check + * @return True if visible, else false + */ + public Boolean getAnnotationState(Annotation annotation){ + return this.currentState.containsKey(annotation) && this.currentState.get(annotation); + } + + /** + * @return Return an array containing all defined annotations + */ + public Annotation[] getAnnotations(){ + return Annotation.class.getEnumConstants(); + } +} diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 45b707e1..28e9561f 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -1,16 +1,18 @@ package seng302.models; -import javafx.geometry.Point2D; +import javafx.event.EventHandler; +import javafx.scene.CacheHint; import javafx.scene.Group; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; -import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; -import javafx.stage.Stage; +import seng302.models.stream.StreamParser; -import java.util.ArrayList; -import java.util.List; +import java.text.DateFormat; +import java.text.SimpleDateFormat; /** * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat. @@ -23,36 +25,32 @@ public class BoatGroup extends Group{ //Constants for drawing private static final double TEAMNAME_X_OFFSET = 10d; - private static final double TEAMNAME_Y_OFFSET = -15d; + private static final double TEAMNAME_Y_OFFSET = -29d; private static final double VELOCITY_X_OFFSET = 10d; - private static final double VELOCITY_Y_OFFSET = -5d; + private static final double VELOCITY_Y_OFFSET = -17d; + private static final double ESTTIMETONEXTMARK_X_OFFSET = 10d; + private static final double ESTTIMETONEXTMARK_Y_OFFSET = -5d; + private static final double LEGTIME_X_OFFSET = 10d; + private static final double LEGTIME_Y_OFFSET = 7d; private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; //Variables for boat logic. - private Point2D lastPoint; - private int wakeGenerationDelay = 10; - private double distanceTravelled; - private double pixelVelocityX; - private double pixelVelocityY; - private double currentRotation; - private double rotationalGoal; - private double rotationalVelocity; - private static final int expectedUpdateInterval = 200; + private boolean isStopped = true; + private double xIncrement; + private double yIncrement; + private long lastTimeValid = 0; + private long framesToMove; //Graphical objects private Yacht boat; private Group lineGroup = new Group(); private Polygon boatPoly; private Text teamNameObject; private Text velocityObject; + private Text estTimeToNextMarkObject; + private Text legTimeObject; private Wake wake; - //Handles boat moving when connecting to a stream - private boolean setToInitialLocation = false; - private boolean destinationSet; - //Variables for handling minimization - private Stage stage; - private boolean isMaximized= true; - private List lineStorage = new ArrayList<>(); - private int setCallCount = 5; + + private Boolean isSelected = true; //All boats are initalised as selected /** * Creates a BoatGroup with the default triangular boat polygon. @@ -86,9 +84,30 @@ public class BoatGroup extends Group{ private void initChildren (Color color, double... points) { boatPoly = new Polygon(points); boatPoly.setFill(color); + boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE)); + boatPoly.setOnMouseExited(event -> boatPoly.setFill(color)); + boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); + boatPoly.setCache(true); + boatPoly.setCacheHint(CacheHint.SPEED); + teamNameObject = new Text(boat.getShortName()); + teamNameObject.setCache(true); + teamNameObject.setCacheHint(CacheHint.SPEED); velocityObject = new Text(String.valueOf(boat.getVelocity())); + DateFormat format = new SimpleDateFormat("mm:ss"); + String timeToNextMark = format + .format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong()); + estTimeToNextMarkObject = new Text("Next mark: " + timeToNextMark); + if (boat.getMarkRoundingTime() != null) { + String elapsedTime = format + .format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime()); + legTimeObject = new Text("Last mark: " + elapsedTime); + } else { + legTimeObject = new Text("Last mark: -"); + } + velocityObject.setCache(true); + velocityObject.setCacheHint(CacheHint.SPEED); teamNameObject.setX(TEAMNAME_X_OFFSET); teamNameObject.setY(TEAMNAME_Y_OFFSET); @@ -97,10 +116,20 @@ public class BoatGroup extends Group{ velocityObject.setX(VELOCITY_X_OFFSET); velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - destinationSet = false; + + estTimeToNextMarkObject.setX(ESTTIMETONEXTMARK_X_OFFSET); + estTimeToNextMarkObject.setY(ESTTIMETONEXTMARK_Y_OFFSET); + estTimeToNextMarkObject + .relocate(estTimeToNextMarkObject.getX(), estTimeToNextMarkObject.getY()); + + legTimeObject.setX(LEGTIME_X_OFFSET); + legTimeObject.setY(LEGTIME_Y_OFFSET); + legTimeObject.relocate(legTimeObject.getX(), legTimeObject.getY()); wake = new Wake(0, -BOAT_HEIGHT); - super.getChildren().addAll(teamNameObject, velocityObject, boatPoly); + super.getChildren() + .addAll(teamNameObject, velocityObject, boatPoly, estTimeToNextMarkObject, + legTimeObject); } /** @@ -109,9 +138,9 @@ public class BoatGroup extends Group{ */ private void initChildren (Color color) { initChildren(color, - -BOAT_WIDTH / 2, BOAT_HEIGHT / 2, - 0.0, -BOAT_HEIGHT / 2, - BOAT_WIDTH / 2, BOAT_HEIGHT / 2); + -BOAT_WIDTH / 2, BOAT_HEIGHT / 2, + 0.0, -BOAT_HEIGHT / 2, + BOAT_WIDTH / 2, BOAT_HEIGHT / 2); } /** @@ -119,77 +148,48 @@ public class BoatGroup extends Group{ * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ - public void moveGroupBy(double dx, double dy, double rotation) { + public void moveGroupBy(double dx, double dy) { boatPoly.setLayoutX(boatPoly.getLayoutX() + dx); boatPoly.setLayoutY(boatPoly.getLayoutY() + dy); teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx); teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); - wake.setLayoutX(wake.getLayoutX() + dx); - wake.setLayoutY(wake.getLayoutY() + dy); - rotateTo(rotation + currentRotation); + estTimeToNextMarkObject.setLayoutX(estTimeToNextMarkObject.getLayoutX() + dx); + estTimeToNextMarkObject.setLayoutY(estTimeToNextMarkObject.getLayoutY() + dy); + legTimeObject.setLayoutX(legTimeObject.getLayoutX() + dx); + legTimeObject.setLayoutY(legTimeObject.getLayoutY() + dy); } + /** * Moves the boat and its children annotations to coordinates specified * @param x The X coordinate to move the boat to * @param y The Y coordinate to move the boat to - * @param rotation The heading in degrees from north the boat should rotate to. */ public void moveTo (double x, double y, double rotation) { rotateTo(rotation); - moveTo(x, y); - } - - /** - * Moves the boat and its children annotations to coordinates specified - * @param x The X coordinate to move the boat to - * @param y The Y coordinate to move the boat to - */ - public void moveTo (double x, double y) { boatPoly.setLayoutX(x); boatPoly.setLayoutY(y); teamNameObject.setLayoutX(x); teamNameObject.setLayoutY(y); velocityObject.setLayoutX(x); velocityObject.setLayoutY(y); - wake.setLayoutX(x); - wake.setLayoutY(y); - wake.rotate(currentRotation); + estTimeToNextMarkObject.setLayoutX(x); + estTimeToNextMarkObject.setLayoutY(y); + legTimeObject.setLayoutX(x); + legTimeObject.setLayoutY(y); } - /** - * Updates the position of all graphics in the BoatGroup based off of the given time interval. - * @param timeInterval The interval, in milliseconds, the boat should update it's position based on. - */ - public void updatePosition (long timeInterval) { - //Calculate the movement of the boat. - if (isMaximized) { - double dx = pixelVelocityX * timeInterval; - double dy = pixelVelocityY * timeInterval; - double rotation = rotationalVelocity * timeInterval; - distanceTravelled += Math.abs(dx) + Math.abs(dy); - moveGroupBy(dx, dy, rotation); - //Draw a new section of the trail every 20 pixels of movement. - if (distanceTravelled > 20) { - distanceTravelled = 0; - if (lastPoint != null) { - Line l = new Line( - lastPoint.getX(), - lastPoint.getY(), - boatPoly.getLayoutX(), - boatPoly.getLayoutY() - ); - l.getStrokeDashArray().setAll(3d, 7d); - l.setStroke(boatPoly.getFill()); - lineGroup.getChildren().add(l); - } - if (destinationSet) { //Only begin drawing after the first destination is set - lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - } - } - wake.updatePosition(timeInterval); + public void rotateTo (double rotation) { + boatPoly.getTransforms().setAll(new Rotate(rotation)); + } + + public void move() { + moveGroupBy(xIncrement, yIncrement); + framesToMove = framesToMove - 1; + if (framesToMove <= 0){ + isStopped = true; } } @@ -198,73 +198,38 @@ public class BoatGroup extends Group{ * @param newXValue The X co-ordinate the boat needs to move to. * @param newYValue The Y co-ordinate the boat needs to move to. * @param rotation Rotation to move graphics to. - * @param raceIds RaceID of the object to move. + * @param timeValid the time the position values are valid for */ - public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int raceIds) { - if (hasRaceId(raceIds)) { - if (setToInitialLocation) { - destinationSet = true; - boat.setVelocity(groundSpeed); - if (currentRotation < 0) - currentRotation = 360 - currentRotation; - double dx = newXValue - boatPoly.getLayoutX(); - double dy = newYValue - boatPoly.getLayoutY(); - //Check movement is reasonable. Assumes a 1000 * 1000 canvas - if (Math.abs(dx) > 50 || Math.abs(dy) > 50) { - dx = 0; - dy = 0; - moveTo(newXValue, newYValue); - } - - pixelVelocityX = dx / expectedUpdateInterval; - pixelVelocityY = dy / expectedUpdateInterval; - rotationalGoal = rotation; - calculateRotationalVelocity(); - - if (wakeGenerationDelay > 0) { - wake.rotate(rotationalGoal); - rotateTo(rotationalGoal); //Need to test with this removed. - rotationalVelocity = 0; - wakeGenerationDelay--; - } else { - wake.setRotationalVelocity(rotationalVelocity, rotationalGoal, boat.getVelocity()); - } - velocityObject.setText(String.format("%.2f m/s", boat.getVelocity())); - } else { - setToInitialLocation = true; - rotationalGoal = rotation; - moveTo(newXValue, newYValue, rotation); - } - } - //If minimized generate lines every 5 calls to set destination. - if (!isMaximized) { - setToInitialLocation = false; - wakeGenerationDelay = 2; - if(setCallCount-- == 0) { - setCallCount = 5; - if (lastPoint != null) { - Line l = new Line( - lastPoint.getX(), - lastPoint.getY(), - newXValue, - newYValue - ); - l.getStrokeDashArray().setAll(3d, 7d); - l.setStroke(boatPoly.getFill()); - lineStorage.add(l); - } - if (destinationSet) { //Only begin drawing after the first destination is set - lastPoint = new Point2D(newXValue, newYValue); - } - } + public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, long timeValid, double frameRate, long id) { + if (lastTimeValid == 0){ + lastTimeValid = timeValid - 200; + moveTo(newXValue, newYValue, rotation); } + framesToMove = Math.round((frameRate/(1000.0f/(timeValid-lastTimeValid)))); + double dx = newXValue - boatPoly.getLayoutX(); + double dy = newYValue - boatPoly.getLayoutY(); + xIncrement = dx/framesToMove; + yIncrement = dy/framesToMove; + rotateTo(rotation); + + velocityObject.setText(String.format("%.2f m/s", groundSpeed)); + lastTimeValid = timeValid; + isStopped = false; } - public void rotateTo (double rotation) { - currentRotation = rotation; - boatPoly.getTransforms().setAll(new Rotate(rotation)); + + public void setIsSelected(Boolean isSelected) { + this.isSelected = isSelected; + setTeamNameObjectVisible(isSelected); + setVelocityObjectVisible(isSelected); + setLineGroupVisible(isSelected); + setWakeVisible(isSelected); + setEstTimeToNextMarkObjectVisible(isSelected); + setLegTimeObjectVisible(isSelected); } + + public void setTeamNameObjectVisible(Boolean visible) { teamNameObject.setVisible(visible); } @@ -273,6 +238,14 @@ public class BoatGroup extends Group{ velocityObject.setVisible(visible); } + public void setEstTimeToNextMarkObjectVisible(Boolean visible) { + estTimeToNextMarkObject.setVisible(visible); + } + + public void setLegTimeObjectVisible(Boolean visible) { + legTimeObject.setVisible(visible); + } + public void setLineGroupVisible(Boolean visible) { lineGroup.setVisible(visible); } @@ -285,26 +258,12 @@ public class BoatGroup extends Group{ return boat; } - /** - * Returns true if this BoatGroup contains at least one of the given IDs. - * - * @param raceIds The ID's to check the BoatGroup for. - * @return True if the BoatGroup contains at east one of the given IDs, false otherwise. - */ - public boolean hasRaceId (int... raceIds) { - for (int id : raceIds) { - if (id == boat.getSourceID()) - return true; - } - return false; - } - /** * Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat. * * @return An array containing all ID's associated with this RaceObject. */ - public int getRaceId() { + public long getRaceId() { return boat.getSourceID(); } @@ -321,44 +280,12 @@ public class BoatGroup extends Group{ return group; } - /** - * Use this function to let the BoatGroup know about the stage it is in. If it knows about it's stage then it will - * listen to the iconified property of that stage and change it's behaviour upon minimization. Without setting the - * Stage there is guarantee that the BoatGroup will draw properly when the stage is minimized. - * - * @param stage The stage that the BoatGroup is added to. - */ - public void setStage (Stage stage) { - /* TODO: 4/05/17 cir27 - Find a way to get the stage to this point. Need to pass it through multiple controllers. - App.start() -> Controller.setContentPane -> RaceViewController -> CanvasController - */ - this.stage = stage; - this.stage.iconifiedProperty().addListener(e -> { - isMaximized = !stage.isIconified(); - if (!lineStorage.isEmpty()) { - lineGroup.getChildren().addAll(lineStorage); - lineStorage.clear(); - } - }); + public boolean isStopped() { + return isStopped; } - /** - * Calculates the rotational velocity required to reach the rotationalGoal from the currentRotation. - */ - protected void calculateRotationalVelocity () { - if (Math.abs(rotationalGoal - currentRotation) > 180) { - if (rotationalGoal - currentRotation >= 0) { - this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; - } else { - this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; - } - } else { - this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; - } - //Sometimes the rotation is too large to be realistic. In that case just do it instantly. - if (Math.abs(rotationalVelocity) > 1) { - rotationalVelocity = 0; - rotateTo(rotationalGoal); - } + @Override + public String toString() { + return boat.toString(); } -} +} \ No newline at end of file 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/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index b428c5ed..0e886abc 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -5,6 +5,7 @@ import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; +import javafx.scene.transform.Rotate; import java.util.ArrayList; import java.util.List; @@ -34,33 +35,33 @@ public class MarkGroup extends Group { Circle markCircle; if (mark.getMarkType() == MarkType.SINGLE_MARK) { markCircle = new Circle( - points[0].getX(), - points[0].getY(), - MARK_RADIUS, - color + points[0].getX(), + points[0].getY(), + MARK_RADIUS, + color ); super.getChildren().add(markCircle); } else { markCircle = new Circle( - points[0].getX(), - points[0].getY(), - MARK_RADIUS, - color + points[0].getX(), + points[0].getY(), + MARK_RADIUS, + color ); super.getChildren().add(markCircle); markCircle = new Circle( - points[1].getX(), - points[1].getY(), - MARK_RADIUS, - color + points[1].getX(), + points[1].getY(), + MARK_RADIUS, + color ); super.getChildren().add(markCircle); Line line = new Line( - points[0].getX(), - points[0].getY(), - points[1].getX(), - points[1].getY() + points[0].getX(), + points[0].getY(), + points[1].getX(), + points[1].getY() ); line.setStrokeWidth(LINE_THICKNESS); line.setStroke(color); @@ -98,8 +99,8 @@ public class MarkGroup extends Group { public boolean hasRaceId (int... raceIds) { for (int id : raceIds) for (Mark mark : marks) - if (id == mark.getId()) - return true; + if (id == mark.getId()) + return true; return false; } @@ -110,4 +111,4 @@ public class MarkGroup extends Group { idArray[i++] = mark.getId(); return idArray; } -} +} \ No newline at end of file diff --git a/src/main/java/seng302/models/stream/StreamPacket.java b/src/main/java/seng302/models/stream/StreamPacket.java deleted file mode 100644 index 1cb35bb5..00000000 --- a/src/main/java/seng302/models/stream/StreamPacket.java +++ /dev/null @@ -1,46 +0,0 @@ -package seng302.models.stream; - -import seng302.models.stream.packets.PacketType; - -/** - * Created by kre39 on 23/04/17. - */ -public class StreamPacket { - - //Change int to an ENUM for the type - private PacketType type; - - private long messageLength; - private long timeStamp; - private byte[] payload; - - StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { - this.type = PacketType.assignPacketType(type); - this.messageLength = messageLength; - this.timeStamp = timeStamp; - this.payload = payload; -// System.out.println("type = " + this.type.toString()); - //switch the packet type to deal with what ever specific packet you want to deal with -// if (this.type == PacketType.XML_MESSAGE){ -// //System.out.println("--------"); -// System.out.println(new String(payload)); -// //StreamParser.parsePacket(this); -// } - } - - PacketType getType() { - return type; - } - - public long getMessageLength() { - return messageLength; - } - - byte[] getPayload() { - return payload; - } - - long getTimeStamp() { - return timeStamp; - } -} diff --git a/src/main/java/seng302/models/stream/StreamParser.java b/src/main/java/seng302/models/stream/StreamParser.java index fb6e1d1e..f7093306 100644 --- a/src/main/java/seng302/models/stream/StreamParser.java +++ b/src/main/java/seng302/models/stream/StreamParser.java @@ -17,28 +17,31 @@ 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; +import seng302.models.stream.XMLParser; /** * The purpose of this class is to take in the stream of divided packets so they can be read * 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; @@ -66,24 +69,10 @@ public class StreamParser extends Thread{ Thread.sleep(1); } while (appRunning){ - StreamPacket packet = StreamReceiver.packetBuffer.peek(); - //this code adds a delay to reading from the packetBuffer so - //out of order packets have time to order themselves in the queue -// int delayTime = 1000; -// int loopTime = delayTime * 10; -// long transitTime = (System.currentTimeMillis()%loopTime - packet.getTimeStamp()%loopTime); -// if (transitTime < 0){ -// transitTime = loopTime + transitTime; -// } -// if (transitTime < delayTime) { -// long sleepTime = delayTime - (transitTime); -// Thread.sleep(sleepTime); -// } - packet = StreamReceiver.packetBuffer.take(); + StreamPacket packet = StreamReceiver.packetBuffer.take(); parsePacket(packet); -// Thread.sleep(1); + Thread.sleep(1); while (StreamReceiver.packetBuffer.peek() == null) { -// Thread.sleep(1); } } } catch (Exception e){ @@ -122,6 +111,7 @@ public class StreamParser extends Thread{ extractDisplayMessage(packet); break; case XML_MESSAGE: + newRaceXmlReceived = true; extractXmlMessage(packet); break; case RACE_START_STATUS: @@ -196,9 +186,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())); @@ -206,7 +198,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"); @@ -220,27 +211,25 @@ public class StreamParser extends Thread{ raceFinished = false; System.out.println("[CLIENT] Race has started"); } - //System.out.println("Time since start: " + -1 * timeTillStart + " Seconds"); 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]; -// ArrayList boatStatuses = new ArrayList<>(); boatsPos = new TreeMap<>(); for (int i = 0; i < noBoats; i++){ - Long boatStatusSourceID = bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20))); - Yacht boat = boats.get((int)(long) boatStatusSourceID); + long boatStatusSourceID = bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20))); + 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; @@ -294,9 +283,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(); - //System.out.println("xmlMessage2 = " + xmlMessage); + 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(); @@ -313,6 +301,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; + } } /** @@ -359,7 +350,6 @@ public class StreamParser extends Thread{ long subjectId = bytesToLong(Arrays.copyOfRange(payload,9,13)); long incidentId = bytesToLong(Arrays.copyOfRange(payload,13,17)); int eventId = payload[17]; -// System.out.println("eventId = " + eventId); } /** @@ -397,19 +387,18 @@ public class StreamParser extends Thread{ double groundSpeed = bytesToLong(Arrays.copyOfRange(payload,38,40))/1000.0; //type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat - if (deviceType == 1 || deviceType == 3){ + if (deviceType == 1){ BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed); //add a new priority que to the boatPositions HashMap if (!boatPositions.containsKey(boatId)){ - boatPositions.put(boatId, new PriorityBlockingQueue(256, new Comparator() { + boatPositions.put(boatId, new PriorityBlockingQueue<>(256, new Comparator() { @Override public int compare(BoatPositionPacket p1, BoatPositionPacket p2) { return (int) (p1.getTimeValid() - p2.getTimeValid()); } })); } - //Adding the boatPacket to the priority que boatPositions.get(boatId).put(boatPacket); } } @@ -429,6 +418,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); } /** @@ -575,9 +567,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 6cf0739d..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; @@ -44,7 +41,7 @@ public class BoatLocationMessage extends Message { public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ boatSpeed /= 10; messageVersionNumber = 1; - time = System.currentTimeMillis() / 1000L; + time = System.currentTimeMillis(); this.sourceId = sourceId; this.sequenceNum = sequenceNum; this.deviceType = DeviceType.RACING_YACHT; diff --git a/src/main/resources/css/master.css b/src/main/resources/css/master.css new file mode 100644 index 00000000..8223fc00 --- /dev/null +++ b/src/main/resources/css/master.css @@ -0,0 +1,184 @@ +/** +Background colours + */ +.background-blue{ + -fx-background-color: #119796; +} + +.background-dark{ + -fx-background-color: #2C2c36; +} + +/** +Exit button with no background + */ +.clear-exit-btn{ + -fx-background-insets: 0; + -fx-background-color: #119796; + -fx-border-style: none; +} + +/** +Buttons + */ +.blue-ui-btn{ + -fx-background-color: #119796; + -fx-text-fill: #fff; +} + +.text-white { + -fx-text-fill: white !important; + -fx-fill:white !important; +} + +/** +Sliders + */ +.ui-slider .thumb { + -fx-background-color: rgb(60, 60, 60); + -fx-border-radius: 10; + -fx-border-color: darkgray; +} + +.ui-slider .track{ + -fx-background-color: #119796; +} + +.ui-slider .axis{ + -fx-tick-label-fill: white; +} + +.ui-slider .axis .axis-label{ + -fx-text-fill: white; +} + +/** +Checkbox + */ +.ui-checkbox .box{ + -fx-background-color: white; + -fx-graphic:none; + -fx-shape: none; +} + +.ui-checkbox .box .mark{ + -fx-background-image: none; + -fx-image: none; + -fx-graphic: none; + -fx-shape: none; +} + +.ui-checkbox:selected .box{ + -fx-background-color: #119796; + -fx-shape: none; +} + +.ui-checkbox:selected .box .mark{ + -fx-background-color: #119796; + -fx-shape: none; + -fx-graphic: none; +} + +/** +Table + */ +.ui-table{ + -fx-background-color: transparent; +} + +.ui-table:focused{ + -fx-background-color: transparent; +} + +.ui-table .column-header-background{ + -fx-background-color: white +} + +.ui-table .column-header-background .label{ + -fx-background-color: transparent; + -fx-text-fill: black; +} + +.ui-table .column-header { + -fx-background-color: transparent; +} + +.ui-table .table-cell{ + -fx-text-fill: white; + -fx-border-style: none; +} + +.table-row-cell{ + -fx-background-color: #119796; + -fx-background-insets: 0, 0 0 1 0; + -fx-padding: 0.0em; /* 0 */ + -fx-border-style: none; +} + +.table-row-cell:odd{ + -fx-background-color: #0e6d6c; + -fx-background-insets: 0, 0 0 1 0; + -fx-padding: 0.0em; /* 0 */ + -fx-border-style: none; +} + +.table-row-cell:selected { + -fx-background-color: #005797; + -fx-background-insets: 0; + -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 + */ +.ui-table *.scroll-bar:horizontal *.increment-button, +.ui-table *.scroll-bar:horizontal *.decrement-button { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-padding: 0; +} + +.ui-table *.scroll-bar:horizontal *.increment-arrow, +.ui-table *.scroll-bar:horizontal *.decrement-arrow { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-padding: 0; + -fx-shape: null; +} + +.ui-table *.scroll-bar:vertical *.increment-arrow, +.ui-table *.scroll-bar:vertical *.decrement-arrow { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-padding: 0; + -fx-shape: null; +} + +.ui-table *.scroll-bar:vertical *.increment-button, +.ui-table *.scroll-bar:vertical *.decrement-button { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-padding: 0; +} \ No newline at end of file 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 ac72bd8c..dab6e135 100644 --- a/src/main/resources/views/MainView.fxml +++ b/src/main/resources/views/MainView.fxml @@ -7,58 +7,4 @@ - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/src/test/java/seng302/models/stream/StreamReceiverTest.java b/src/test/java/seng302/models/stream/StreamReceiverTest.java index ff9755bb..4d0bea44 100644 --- a/src/test/java/seng302/models/stream/StreamReceiverTest.java +++ b/src/test/java/seng302/models/stream/StreamReceiverTest.java @@ -8,6 +8,7 @@ import java.lang.reflect.Method; import java.net.Socket; import java.util.Comparator; import java.util.concurrent.PriorityBlockingQueue; +import seng302.models.stream.packets.StreamPacket; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/src/test/java/seng302/visualizer/annotations/TestImportantAnnotationState.java b/src/test/java/seng302/visualizer/annotations/TestImportantAnnotationState.java new file mode 100644 index 00000000..72ef4a57 --- /dev/null +++ b/src/test/java/seng302/visualizer/annotations/TestImportantAnnotationState.java @@ -0,0 +1,53 @@ +package seng302.visualizer.annotations; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import seng302.controllers.annotations.Annotation; +import seng302.controllers.annotations.ImportantAnnotationsState; + +import static org.junit.Assert.assertEquals; + +public class TestImportantAnnotationState { + private ImportantAnnotationsState importantAnnotationsState; + + @Before + public void setUpForTest(){ + importantAnnotationsState = new ImportantAnnotationsState(); + } + + @After + public void tearDownAfterTest(){ + importantAnnotationsState = null; + } + + /** + * Check whether each annotation has its default value set to the default value when + * the class is initialized + */ + @Test + public void testDefaultValueSet(){ + for (Annotation annotation : importantAnnotationsState.getAnnotations()){ + assertEquals(ImportantAnnotationsState.DEFAULT_ANNOTATION_STATE, + importantAnnotationsState.getAnnotationState(annotation)); + } + } + + /** + * Check whether an annotations state can be changed + */ + @Test + public void testAnnotationStateChange(){ + Annotation[] annotations = importantAnnotationsState.getAnnotations(); + + // do not run test if there are no annotations + if (annotations.length <= 0){ + return; + } + + Boolean currentAnnotationState = importantAnnotationsState.getAnnotationState(annotations[0]); + importantAnnotationsState.setAnnotationState(annotations[0], !currentAnnotationState); + + assertEquals(!currentAnnotationState, importantAnnotationsState.getAnnotationState(annotations[0])); + } +}