diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 4aa14563..3625db47 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -9,8 +9,8 @@ import seng302.models.parsers.StreamParser; import seng302.models.parsers.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"); @@ -63,7 +63,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("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); + sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); } sr.start(); @@ -72,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 8fc7ed54..9a2a9797 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -1,31 +1,31 @@ package seng302.controllers; -import javafx.animation.*; +import javafx.animation.AnimationTimer; import javafx.beans.property.SimpleDoubleProperty; import javafx.fxml.FXML; import javafx.geometry.Point2D; import javafx.scene.Group; -import javafx.scene.Node; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; import javafx.scene.paint.Color; -import javafx.scene.shape.Polygon; import javafx.scene.text.Font; -import javafx.stage.Stage; -import seng302.models.*; +import seng302.models.BoatGroup; +import seng302.models.Colors; +import seng302.models.RaceObject; +import seng302.models.Yacht; import seng302.models.mark.*; import seng302.models.parsers.StreamParser; -import seng302.models.parsers.StreamReceiver; -import seng302.models.parsers.packets.BoatPositionPacket; import seng302.models.parsers.XMLParser; import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark; import seng302.models.parsers.XMLParser.RaceXMLObject.Limit; -import seng302.models.mark.Mark; +import seng302.models.parsers.packets.BoatPositionPacket; import java.text.DecimalFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; import java.util.concurrent.PriorityBlockingQueue; /** @@ -240,6 +240,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 move(long id, RaceObject raceObject){ @@ -345,6 +356,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 9d964243..a1488a84 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,168 +1,37 @@ 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.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 seng302.models.parsers.StreamParser; -import seng302.models.parsers.XMLParser; - import java.io.IOException; import java.net.URL; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; import java.util.ResourceBundle; -import java.util.Timer; -import java.util.TimerTask; 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 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); } } @Override public void initialize(URL location, ResourceBundle resources) { - //DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); - //format.setTimeZone(TimeZone.getTimeZone("GMT-8")); - //realTime.setText(format.format(new Date())); - } - - /** - * 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.isRaceStarted()) { - 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() { - 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") - ); -// if (StreamParser.isRaceStarted()) { - data.addAll(StreamParser.getBoatsPos().values()); -// } else { -// for (Yacht boat : StreamParser.getBoats().values()) { -// boat.setPosition("-"); -// data.add(boat); -// } -// } - teamList.refresh(); - -// posCol.setSortType(TableColumn.SortType.ASCENDING); -// teamList.getSortOrder().add(posCol); -// posCol.setSortable(false); + contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + setContentPane("/views/StartScreenView.fxml"); } } diff --git a/src/main/java/seng302/controllers/RaceController.java b/src/main/java/seng302/controllers/RaceController.java index d0ea0d42..e5e95d59 100644 --- a/src/main/java/seng302/controllers/RaceController.java +++ b/src/main/java/seng302/controllers/RaceController.java @@ -25,7 +25,7 @@ public class RaceController { String teamsConfigFile = "/config/teams.xml"; try { - race = createRace(raceConfigFile, teamsConfigFile); + race = createRace(raceConfigFile, teamsConfigFile); //These config files arent actually used } catch (Exception e) { System.out.println("There was an error creating the race."); } diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 12069084..22c96f13 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -1,26 +1,31 @@ package seng302.controllers; -import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer; -import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; +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.parsers.ConfigParser; import seng302.models.parsers.StreamParser; import java.io.IOException; @@ -29,7 +34,8 @@ 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 @@ -43,64 +49,104 @@ 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 Map timelineInfos = new HashMap<>(); - private ArrayList boatOrder = new ArrayList<>(); private Race race; private Stage stage; - public void initialize() { + 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(); - for (Yacht boat : race.getBoats()) { - startingBoats.add(boat); - } -// try{ -// initializeTimelines(); -// } -// catch (Exception e){ -// e.printStackTrace(); -// } + + startingBoats = new ArrayList<>(Arrays.asList(race.getBoats())); includedCanvasController.setup(this); includedCanvasController.initializeCanvas(); - initializeTimer(); - initializeSettings(); - initialiseWindDirection(); - initialisePositionVBox(); - //set wind direction!!!!!!! can't find another place to put my code --haoming -// double windDirection = new ConfigParser("/config/config.xml").getWindDirection(); -// windDirectionText.setText(String.format("%.1f°", windDirection)); -// windArrowText.setRotate(windDirection); + 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"; } @@ -110,162 +156,126 @@ 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(); - } - - private void initialisePositionVBox() { - - Timeline posVBoxTimeline = new Timeline(); - posVBoxTimeline.setCycleCount(Timeline.INDEFINITE); - posVBoxTimeline.getKeyFrames().add( - new KeyFrame(Duration.seconds(1), - event -> { - showOrder(); - }) - ); - posVBoxTimeline.playFromStart(); - - } /** - * Generates time line for each boat, and stores time time into timelineInfos hash map + * Updates the wind direction arrow and text as from info from the StreamParser */ - private void initializeTimelines() { - HashMap boat_events = race.getEvents(); - for (Yacht boat : boat_events.keySet()) { - startingBoats.add(boat); -// // x, y are the real time coordinates -// DoubleProperty x = new SimpleDoubleProperty(); -// DoubleProperty y = new SimpleDoubleProperty(); -// -// List keyFrames = new ArrayList<>(); -// List events = boat_events.get(boat); -// -// // iterates all events and convert each event to keyFrame, then add them into a list -// for (Event event : events) { -// if (event.getIsFinishingEvent()) { -// keyFrames.add( -// new KeyFrame(Duration.seconds(event.getTime()), -// onFinished -> {race.setBoatFinished(boat); handleEvent(event);}, -// new KeyValue(x, event.getThisMark().getLatitude()), -// new KeyValue(y, event.getThisMark().getLongitude()) -// ) -// ); -// } else { -// keyFrames.add( -// new KeyFrame(Duration.seconds(event.getTime()), -// onFinished ->{ -// handleEvent(event); -// boat.setHeading(event.getBoatHeading()); -// }, -// new KeyValue(x, event.getThisMark().getLatitude()), -// new KeyValue(y, event.getThisMark().getLongitude()) -// ) -// ); -// } -// } -// timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y)); - } - setRaceDuration(); + private void updateWindDirection() { + windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection())); + windArrowText.setRotate(StreamParser.getWindDirection()); } - private void setRaceDuration(){ - Double maxDuration = 0.0; - Timeline maxTimeline = null; - for (TimelineInfo timelineInfo : timelineInfos.values()) { + /** + * Updates the clock for the race + */ + private void updateRaceTime() { + if (StreamParser.isRaceFinished()) { + timerLabel.setFill(Color.RED); + timerLabel.setText("Race Finished!"); + } else { + timerLabel.setText(getTimeSinceStartOfRace()); + } + } - Timeline timeline = timelineInfo.getTimeline(); - if (timeline.getTotalDuration().toMillis() >= maxDuration) { - maxDuration = timeline.getTotalDuration().toMillis(); - maxTimeline = timeline; + + /** + * 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 + Text textToAdd = new Text(boat.getPosition() + ". " + + boat.getShortName() + " (Finished)"); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + positionVbox.getChildren().add(textToAdd); + + } else { + Text textToAdd = new Text(boat.getPosition() + ". " + + boat.getShortName() + " "); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + textToAdd.setStyle(""); + positionVbox.getChildren().add(textToAdd); } - // Timelines are paused by default - timeline.play(); - timeline.pause(); } + } - maxTimeline.setOnFinished(event -> { - race.setRaceFinished(); - loadRaceResultView(); + + /** + * 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); + } }); } - /** - * Play each boats timerTimeline - */ - public void playTimelines(){ - for (TimelineInfo timelineInfo : timelineInfos.values()){ - Timeline timeline = timelineInfo.getTimeline(); - - if (timeline.getStatus() == Animation.Status.PAUSED){ - timeline.play(); - } - } - } - - /** - * Pause each boats timerTimeline - */ - public void pauseTimelines(){ - for (TimelineInfo timelineInfo : timelineInfos.values()){ - Timeline timeline = timelineInfo.getTimeline(); - - if (timeline.getStatus() == Animation.Status.RUNNING){ - timeline.pause(); - } - } - } /** * Display the list of boats in the order they finished the race @@ -286,39 +296,6 @@ public class RaceViewController extends Thread{ } } - public void handleEvent(Event event) { - Yacht boat = event.getBoat(); - boatOrder.remove(boat); - boat.setMarkLastPast(event.getMarkPosInRace()); - boatOrder.add(boat); - boatOrder.sort(new Comparator() { - @Override - public int compare(Yacht b1, Yacht b2) { - return b2.getMarkLastPast() - b1.getMarkLastPast(); - } - }); - showOrder(); - } - - private void showOrder() { - positionVbox.getChildren().clear(); - positionVbox.getChildren().removeAll(); - -// for (Boat boat : boatOrder) { -// positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots")); -// } - - for (Yacht boat : StreamParser.getBoatsPos().values()) { - if (boat.getBoatStatus() == 3) { // 3 is finish status - positionVbox.getChildren().add(new Text(boat.getPosition() + ". " + - boat.getShortName() + " (Finished)")); - } else { - positionVbox.getChildren().add(new Text(boat.getPosition() + ". " + - boat.getShortName() + " ")); - } - - } - } /** * Convert seconds to a string of the format mm:ss @@ -333,7 +310,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); @@ -353,12 +330,6 @@ public class RaceViewController extends Thread{ return timerString; } - public void stopTimer() { - timerTimeline.stop(); - } - public void startTimer() { - timerTimeline.play(); - } public boolean isDisplayFps() { return displayFps; @@ -368,56 +339,83 @@ public class RaceViewController extends Thread{ return race; } - public Map getTimelineInfos() { - return timelineInfos; - } - public ArrayList getStartingBoats(){ - return startingBoats; - } + /** + * 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); + } + + 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 (RaceObject ro : includedCanvasController.getRaceObjects()) { - if(ro instanceof BoatGroup) { + 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 (RaceObject ro : includedCanvasController.getRaceObjects()) { - if(ro instanceof BoatGroup) { + if (ro instanceof BoatGroup) { BoatGroup bg = (BoatGroup) ro; - bg.setTeamNameObjectVisible(true); - bg.setVelocityObjectVisible(false); - bg.setLineGroupVisible(false); - bg.setWakeVisible(false); + setBoatGroupImportantAnnotations(bg); } } break; + // All Annotations case 2: for (RaceObject ro : includedCanvasController.getRaceObjects()) { - if(ro instanceof BoatGroup) { - BoatGroup bg = (BoatGroup) ro; - bg.setTeamNameObjectVisible(true); - bg.setVelocityObjectVisible(false); - bg.setLineGroupVisible(true); - bg.setWakeVisible(false); - } - } - break; - case 3: - for (RaceObject ro : includedCanvasController.getRaceObjects()) { - if(ro instanceof BoatGroup) { + 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); } @@ -426,11 +424,33 @@ public class RaceViewController extends Thread{ } } - void setStage (Stage stage) { + + /** + * 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); + } + } + } + } + + void setStage(Stage stage) { this.stage = stage; } - Stage getStage () { + 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..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 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 d4c08344..b2d72099 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -9,24 +9,32 @@ import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import javafx.stage.Stage; +import seng302.models.parsers.StreamParser; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; /** - * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat. - * It contains a single polygon for the boat, a group of lines to show it's path, a wake object and two text labels to - * annotate the boat teams name and the boats velocity. The boat will update it's position onscreen everytime - * UpdatePosition is called unless the window is minimized in which case it attempts to store animations and apply them - * when the window is maximised. + * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 + * dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path, + * a wake object and two text labels to annotate the boat teams name and the boats velocity. The + * boat will update it's position onscreen everytime UpdatePosition is called unless the window is + * minimized in which case it attempts to store animations and apply them when the window is + * maximised. */ -public class BoatGroup extends RaceObject{ +public class BoatGroup extends RaceObject { //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. @@ -39,48 +47,59 @@ public class BoatGroup extends RaceObject{ private Polygon boatPoly; private Text teamNameObject; private Text velocityObject; + private Text estTimeToNextMarkObject; + private Text legTimeObject; private Wake wake; + private boolean isSelected = true; //Boats annotations are visible by default at the start //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 boolean isMaximized = true; private List lineStorage = new ArrayList<>(); private int setCallCount = 5; /** * Creates a BoatGroup with the default triangular boat polygon. - * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which - * BoatGroup to update. + * + * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used + * to tell which BoatGroup to update. * @param color The colour of the boat polygon and the trailing line. */ - public BoatGroup (Yacht boat, Color color){ + public BoatGroup(Yacht boat, Color color) { this.boat = boat; initChildren(color); } /** - * Creates a BoatGroup with the boat being the default polygon. The head of the boat should be at point (0,0). - * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which - * BoatGroup to update. - * @param color The colour of the boat polygon and the trailing line. - * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon. + * Creates a BoatGroup with the boat being the default polygon. The head of the boat should be + * at point (0,0). + * + * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used + * to tell which BoatGroup to update. + * @param color The colour of the boat polygon and the trailing line. + * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat + * polygon. */ - public BoatGroup (Yacht boat, Color color, double... points) - { + public BoatGroup(Yacht boat, Color color, double... points) { this.boat = boat; initChildren(color, points); } /** * Creates the javafx objects that will be the in the group by default. - * @param color The colour of the boat polygon and the trailing line. - * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon. + * + * @param color The colour of the boat polygon and the trailing line. + * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat + * polygon. */ - private void initChildren (Color color, double... points) { + 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); @@ -89,6 +108,17 @@ public class BoatGroup extends RaceObject{ 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); @@ -101,15 +131,27 @@ public class BoatGroup extends RaceObject{ 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); } /** * Creates the javafx objects that will be the in the group by default. + * * @param color The colour of the boat polygon and the trailing line. */ - private void initChildren (Color color) { + private void initChildren(Color color) { initChildren(color, -BOAT_WIDTH / 2, BOAT_HEIGHT / 2, 0.0, -BOAT_HEIGHT / 2, @@ -117,7 +159,9 @@ public class BoatGroup extends RaceObject{ } /** - * Moves the boat and its children annotations from its current coordinates by specified amounts. + * Moves the boat and its children annotations from its current coordinates by specified + * amounts. + * * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ @@ -128,6 +172,10 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); + estTimeToNextMarkObject.setLayoutX(estTimeToNextMarkObject.getLayoutX() + dx); + estTimeToNextMarkObject.setLayoutY(estTimeToNextMarkObject.getLayoutY() + dy); + legTimeObject.setLayoutX(legTimeObject.getLayoutX() + dx); + legTimeObject.setLayoutY(legTimeObject.getLayoutY() + dy); wake.setLayoutX(wake.getLayoutX() + dx); wake.setLayoutY(wake.getLayoutY() + dy); rotateTo(rotation + currentRotation); @@ -135,27 +183,33 @@ public class BoatGroup extends RaceObject{ /** * 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 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) { + 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) { + 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); + estTimeToNextMarkObject.setLayoutX(x); + estTimeToNextMarkObject.setLayoutY(y); + legTimeObject.setLayoutX(x); + legTimeObject.setLayoutY(y); wake.setLayoutX(x); wake.setLayoutY(y); wake.rotate(currentRotation); @@ -163,9 +217,11 @@ public class BoatGroup extends RaceObject{ /** * 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. + * + * @param timeInterval The interval, in milliseconds, the boat should update it's position based + * on. */ - public void updatePosition (long timeInterval) { + public void updatePosition(long timeInterval) { //Calculate the movement of the boat. if (isMaximized) { double dx = pixelVelocityX * timeInterval; @@ -178,18 +234,13 @@ public class BoatGroup extends RaceObject{ distanceTravelled = 0; if (lastPoint != null) { Line l = new Line( - lastPoint.getX() * 3 - 1000, - lastPoint.getY() * 3 - 1000, - boatPoly.getLayoutX() * 3 - 1000, - boatPoly.getLayoutY() * 3 - 1000 + lastPoint.getX(), + lastPoint.getY(), + boatPoly.getLayoutX(), + boatPoly.getLayoutY() ); l.getStrokeDashArray().setAll(3d, 7d); - l.setStroke(boatPoly.getFill()); - if (lastPoint.getX() * 3 - 1000 < 0 || lastPoint.getY() * 3 - 1000 < 0 || boatPoly.getLayoutX() * 3 - 1000 < 0 || boatPoly.getLayoutY() * 3 - 1000 < 0) - l.setVisible(false); - else - l.setVisible(true); - + l.setStroke(boat.getColour()); lineGroup.getChildren().add(l); } if (destinationSet) { //Only begin drawing after the first destination is set @@ -202,18 +253,21 @@ public class BoatGroup extends RaceObject{ /** * Sets the destination of the boat and the headng it should have once it reaches + * * @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 rotation Rotation to move graphics to. + * @param raceIds RaceID of the object to move. */ - public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int... raceIds) { + 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) + 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 @@ -230,11 +284,28 @@ public class BoatGroup extends RaceObject{ if (Math.abs(rotationalVelocity) > 0.075) { System.out.println("rotationalVelocity = " + rotationalVelocity); rotationalVelocity = 0; + wakeGenerationDelay--; + } else { + wake.setRotationalVelocity(rotationalVelocity, rotationalGoal, + boat.getVelocity()); rotateTo(rotationalGoal); wake.rotate(rotationalGoal); } wake.setRotationalVelocity(rotationalVelocity, boat.getVelocity()); velocityObject.setText(String.format("%.2f m/s", boat.getVelocity())); + DateFormat format = new SimpleDateFormat("mm:ss"); + // estimate time to next mark + String timeToNextMark = format + .format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong()); + estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); + // elapsed time + if (boat.getMarkRoundingTime() != null) { + String elapsedTime = format + .format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime()); + legTimeObject.setText("Last mark: " + elapsedTime); + } else { + legTimeObject.setText("Last mark: -"); + } } else { setToInitialLocation = true; rotationalGoal = rotation; @@ -245,14 +316,14 @@ public class BoatGroup extends RaceObject{ if (!isMaximized) { setToInitialLocation = false; wakeGenerationDelay = 2; - if(setCallCount-- == 0) { + if (setCallCount-- == 0) { setCallCount = 5; if (lastPoint != null) { Line l = new Line( - lastPoint.getX() * 1.5, - lastPoint.getY() * 1.5, - newXValue * 1.5, - newYValue * 1.5 + lastPoint.getX(), + lastPoint.getY(), + newXValue, + newYValue ); l.getStrokeDashArray().setAll(3d, 7d); l.setStroke(boatPoly.getFill()); @@ -265,7 +336,8 @@ public class BoatGroup extends RaceObject{ } } - public void setDestination (double newXValue, double newYValue, double groundSpeed, int... raceIDs) { + public void setDestination(double newXValue, double newYValue, double groundSpeed, + int... raceIDs) { destinationSet = true; if (hasRaceId(raceIDs)) { @@ -280,16 +352,20 @@ public class BoatGroup extends RaceObject{ } } - public void rotateTo (double rotation) { + public void rotateTo(double rotation) { currentRotation = rotation; boatPoly.getTransforms().setAll(new Rotate(rotation)); } - public void forceRotation () { - rotateTo (rotationalGoal); + public void forceRotation() { + rotateTo(rotationalGoal); wake.rotate(rotationalGoal); } + public void paintBoat(Color color) { + boatPoly.setFill(color); + } + public void setTeamNameObjectVisible(Boolean visible) { teamNameObject.setVisible(visible); } @@ -298,6 +374,14 @@ public class BoatGroup extends RaceObject{ 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); } @@ -310,16 +394,35 @@ public class BoatGroup extends RaceObject{ return boat; } + /** + * This function sets the boats isSelected property AS WELL as actually acting upon the value of + * that selection. (Painting or not painting annotations) + * + * @param isSelected A Boolean indicating whether or not the boat is selected + */ + public void setIsSelected(Boolean isSelected) { + this.isSelected = isSelected; + setTeamNameObjectVisible(isSelected); + setVelocityObjectVisible(isSelected); + setLineGroupVisible(isSelected); + setWakeVisible(isSelected); + setEstTimeToNextMarkObjectVisible(isSelected); + setLegTimeObjectVisible(isSelected); + // TODO: 17/05/17 wmu16 - this should iterate over some list of annotations which we should make to easily make extensible +// paintBoat((isSelected) ? Color.WHITE : boat.getColour()); + } + /** * 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) { + public boolean hasRaceId(int... raceIds) { for (int id : raceIds) { - if (id == boat.getSourceID()) + if (id == boat.getSourceID()) { return true; + } } return false; } @@ -329,36 +432,37 @@ public class BoatGroup extends RaceObject{ * * @return An array containing all ID's associated with this RaceObject. */ - public int[] getRaceIds () { - return new int[] {boat.getSourceID()}; + public int[] getRaceIds() { + return new int[]{boat.getSourceID()}; } /** - * Due to javaFX limitations annotations associated with a boat that you want to appear below all boats in the - * Z-axis need to be pulled out of the BoatGroup and added to the parent group of the BoatGroups. This function - * returns these annotations as a group. + * Due to javaFX limitations annotations associated with a boat that you want to appear below + * all boats in the Z-axis need to be pulled out of the BoatGroup and added to the parent group + * of the BoatGroups. This function returns these annotations as a group. * * @return A group containing low priority annotations. */ - public Group getLowPriorityAnnotations () { + public Group getLowPriorityAnnotations() { Group group = new Group(); group.getChildren().addAll(wake, lineGroup); 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. + * 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) { + 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 -> { + this.stage.iconifiedProperty().addListener(e -> { isMaximized = !stage.isIconified(); if (!lineStorage.isEmpty()) { lineGroup.getChildren().addAll(lineStorage); @@ -366,4 +470,9 @@ public class BoatGroup extends RaceObject{ } }); } + + @Override + public String toString() { + return boat.toString(); + } } 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 29931e01..d58f3b78 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -1,6 +1,7 @@ package seng302.models.mark; import javafx.geometry.Point2D; +import javafx.scene.CacheHint; import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; diff --git a/src/main/java/seng302/models/parsers/PacketType.java b/src/main/java/seng302/models/parsers/PacketType.java deleted file mode 100644 index 66b86207..00000000 --- a/src/main/java/seng302/models/parsers/PacketType.java +++ /dev/null @@ -1,53 +0,0 @@ -package seng302.models.parsers; - -/** - * Created by Kusal on 4/24/2017. - */ -public enum PacketType { - HEARTBEAT, - RACE_STATUS, - DISPLAY_TEXT_MESSAGE, - XML_MESSAGE, - RACE_START_STATUS, - YACHT_EVENT_CODE, - YACHT_ACTION_CODE, - CHATTER_TEXT, - BOAT_LOCATION, - MARK_ROUNDING, - COURSE_WIND, - AVG_WIND, - OTHER; - - static PacketType assignPacketType(int packetType){ - switch(packetType){ - case 1: - return HEARTBEAT; - case 12: - return RACE_STATUS; - case 20: - return DISPLAY_TEXT_MESSAGE; - case 26: - return XML_MESSAGE; - case 27: - return RACE_START_STATUS; - case 29: - return YACHT_EVENT_CODE; - case 31: - return YACHT_ACTION_CODE; - case 36: - return CHATTER_TEXT; - case 37: - return BOAT_LOCATION; - case 38: - return MARK_ROUNDING; - case 44: - return COURSE_WIND; - case 47: - return AVG_WIND; - default: - } - return OTHER; - } - - -} diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java deleted file mode 100644 index 5c2c0706..00000000 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ /dev/null @@ -1,44 +0,0 @@ -package seng302.models.parsers; - -/** - * 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/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 12e4b538..837f459f 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -25,13 +25,13 @@ 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; @@ -40,6 +40,7 @@ public class StreamParser extends Thread{ 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; @@ -123,6 +124,7 @@ public class StreamParser extends Thread{ extractDisplayMessage(packet); break; case XML_MESSAGE: + newRaceXmlReceived = true; extractXmlMessage(packet); break; case RACE_START_STATUS: @@ -197,9 +199,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())); @@ -207,7 +211,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"); @@ -224,10 +227,10 @@ public class StreamParser extends Thread{ //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<>(); @@ -237,11 +240,11 @@ public class StreamParser extends Thread{ Yacht boat = boats.get((int)(long) 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; @@ -295,9 +298,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(); @@ -314,6 +316,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; + } } /** @@ -430,6 +435,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); } /** @@ -576,9 +584,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/parsers/StreamReceiverTest.java b/src/test/java/seng302/models/parsers/StreamReceiverTest.java index c7951e3b..698ee3c0 100644 --- a/src/test/java/seng302/models/parsers/StreamReceiverTest.java +++ b/src/test/java/seng302/models/parsers/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.parsers.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])); + } +}