mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Merge branch 'Issue#4_boat_movement' into issue#10_unifying_marks
# Conflicts: # src/main/java/seng302/controllers/CanvasController.java # src/main/java/seng302/controllers/RaceViewController.java # src/main/java/seng302/models/BoatGroup.java # src/main/java/seng302/models/mark/MarkGroup.java
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<MarkGroup> markGroups = new ArrayList<>();
|
||||
private List<BoatGroup> boatGroups = new ArrayList<>();
|
||||
private List<Mark> 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();
|
||||
}
|
||||
@@ -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)
|
||||
// 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())) {
|
||||
moveBoatGroup(boatGroup);
|
||||
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 checkForCourseChanges() {
|
||||
if (StreamParser.isNewRaceXmlReceived()){
|
||||
gc.setFill(Color.SKYBLUE);
|
||||
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
gc.restore();
|
||||
addRaceBorder();
|
||||
canvas.toBack();
|
||||
}
|
||||
}
|
||||
|
||||
private void moveBoatGroup(BoatGroup boatGroup) {
|
||||
private void updateBoatGroup(BoatGroup boatGroup) {
|
||||
PriorityBlockingQueue<BoatPositionPacket> 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<BoatPositionPacket> 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<Integer, Yacht> 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<Boat, TimelineInfo> timelineInfos = raceViewController.getTimelineInfos();
|
||||
// List<Boat> boats = raceViewController.getStartingBoats();
|
||||
Map<Integer, Yacht> 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<Integer, Mark> allPoints = StreamParser.getXmlObject().getRaceXML().getCompoundMarks();
|
||||
List<Mark> processed = new ArrayList<>();
|
||||
MarkGroup markGroup;
|
||||
|
||||
for (Map.Entry<Integer, Mark> 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());
|
||||
}
|
||||
|
||||
@@ -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<Yacht> teamList;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> boatNameCol;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> shortNameCol;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> countryCol;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> 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<Yacht> 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();
|
||||
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
setContentPane("/views/StartScreenView.fxml");
|
||||
StreamParser.boatPositions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Yacht> startingBoats = new ArrayList<>();
|
||||
private boolean displayFps;
|
||||
private Timeline timerTimeline;
|
||||
private ArrayList<Yacht> 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void initializeSettings() {
|
||||
displayFps = true;
|
||||
|
||||
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||
displayFps = !displayFps;
|
||||
}
|
||||
selectAnnotationBtn.setOnAction(event -> {
|
||||
loadSelectAnnotationView();
|
||||
});
|
||||
}
|
||||
|
||||
//SLIFER STUFF BELOW
|
||||
/**
|
||||
* 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 initialiseFPSCheckBox() {
|
||||
displayFps = true;
|
||||
toggleFps.selectedProperty().addListener(
|
||||
(observable, oldValue, newValue) -> displayFps = !displayFps);
|
||||
}
|
||||
|
||||
private void initialiseAnnotationSlider() {
|
||||
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
||||
@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,38 +148,41 @@ 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());
|
||||
}
|
||||
updateRaceTime();
|
||||
updateWindDirection();
|
||||
updateOrder();
|
||||
updateBoatSelectionComboBox();
|
||||
|
||||
})
|
||||
);
|
||||
|
||||
@@ -121,46 +190,104 @@ public class RaceViewController extends Thread{
|
||||
timerTimeline.playFromStart();
|
||||
}
|
||||
|
||||
private void initialiseWindDirection() {
|
||||
Timeline windDirTimeline = new Timeline();
|
||||
windDirTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||
windDirTimeline.getKeyFrames().add(
|
||||
new KeyFrame(Duration.seconds(1),
|
||||
event -> {
|
||||
|
||||
/**
|
||||
* 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());
|
||||
})
|
||||
);
|
||||
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();
|
||||
|
||||
/**
|
||||
* 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<Yacht> 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.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.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;
|
||||
}
|
||||
}
|
||||
@@ -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<Yacht> teamList;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> boatNameCol;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> shortNameCol;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> countryCol;
|
||||
@FXML
|
||||
private TableColumn<Yacht, String> 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<Yacht> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package seng302.controllers.annotations;
|
||||
|
||||
/**
|
||||
* Annotations the user can select as important
|
||||
*/
|
||||
public enum Annotation {
|
||||
SPEED,
|
||||
WAKE,
|
||||
TRACK,
|
||||
NAME,
|
||||
ESTTIMETONEXTMARK,
|
||||
LEGTIME
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
@@ -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<Annotation, Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<Line> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
public void rotateTo (double rotation) {
|
||||
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
||||
}
|
||||
if (destinationSet) { //Only begin drawing after the first destination is set
|
||||
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||
}
|
||||
}
|
||||
wake.updatePosition(timeInterval);
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Long, PriorityBlockingQueue<BoatPositionPacket>> 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<Integer, Yacht> boats = new HashMap<>();
|
||||
private static Map<Long, Yacht> boatsPos = new TreeMap<>();
|
||||
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
|
||||
private static Map<Long, Yacht> 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<String> 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<BoatPositionPacket>(256, new Comparator<BoatPositionPacket>() {
|
||||
boatPositions.put(boatId, new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Boat>) arg).size()) {
|
||||
startSendingRaceFinishedBoatPostions();
|
||||
startSendingRaceFinishedBoatPositions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Race>
|
||||
<CreationTimeDate>2015-08-29T13:12:40+02:00</CreationTimeDate>
|
||||
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False"/>
|
||||
<RaceID>15082901</RaceID>
|
||||
<RaceType>Fleet</RaceType>
|
||||
<Participants>
|
||||
<Yacht SourceID="101"/>
|
||||
<Yacht SourceID="102"/>
|
||||
<Yacht SourceID="103"/>
|
||||
<Yacht SourceID="104"/>
|
||||
<Yacht SourceID="105"/>
|
||||
<Yacht SourceID="106"/>
|
||||
</Participants>
|
||||
<Course>
|
||||
<CompoundMark CompoundMarkID="1" Name="Mark0">
|
||||
<Mark SeqID="1" Name="Start Line 1" TargetLat="57.6703330" TargetLng="11.8278330"
|
||||
SourceID="122"/>
|
||||
<Mark SeqID="2" Name="Start Line 2" TargetLat="57.6703330" TargetLng="11.8278330"
|
||||
SourceID="123"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="2" Name="Mark1">
|
||||
<Mark SeqID="1" Name="Mark1" TargetLat="57.6675700" TargetLng="11.8359880" SourceID="131"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="3" Name="Mark2">
|
||||
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="124"/>
|
||||
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="125"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="4" Name="Mark3">
|
||||
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="126"/>
|
||||
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="127"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="5" Name="Mark2">
|
||||
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="124"/>
|
||||
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="125"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="6" Name="Mark3">
|
||||
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="126"/>
|
||||
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="127"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="7" Name="Mark2">
|
||||
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="124"/>
|
||||
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="125"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="8" Name="Mark3">
|
||||
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="126"/>
|
||||
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="127"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="9" Name="Mark2">
|
||||
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="124"/>
|
||||
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900"
|
||||
SourceID="125"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="10" Name="Mark3">
|
||||
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="126"/>
|
||||
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170"
|
||||
SourceID="127"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="11" Name="Mark4">
|
||||
<Mark SeqID="1" Name="Finish Line 1" TargetLat="57.6715240" TargetLng="11.8444950"
|
||||
SourceID="128"/>
|
||||
<Mark SeqID="2" Name="Finish Line 2" TargetLat="57.6715240" TargetLng="11.8444950"
|
||||
SourceID="129"/>
|
||||
</CompoundMark>
|
||||
</Course>
|
||||
<CompoundMarkSequence>
|
||||
<Corner SeqID="1" CompoundMarkID="1" Rounding="PS" ZoneSize="3"/>
|
||||
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3"/>
|
||||
<Corner SeqID="3" CompoundMarkID="3" Rounding="SP" ZoneSize="3"/>
|
||||
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="3"/>
|
||||
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
|
||||
<Corner SeqID="6" CompoundMarkID="6" Rounding="PS" ZoneSize="3"/>
|
||||
<Corner SeqID="7" CompoundMarkID="7" Rounding="SP" ZoneSize="3"/>
|
||||
<Corner SeqID="8" CompoundMarkID="8" Rounding="PS" ZoneSize="3"/>
|
||||
<Corner SeqID="9" CompoundMarkID="9" Rounding="SP" ZoneSize="3"/>
|
||||
<Corner SeqID="10" CompoundMarkID="10" Rounding="PS" ZoneSize="3"/>
|
||||
<Corner SeqID="11" CompoundMarkID="11" Rounding="PS" ZoneSize="3"/>
|
||||
</CompoundMarkSequence>
|
||||
<CourseLimit>
|
||||
<Limit SeqID="1" Lat="57.6739450" Lon="11.8417100"/>
|
||||
<Limit SeqID="2" Lat="57.6709520" Lon="11.8485010"/>
|
||||
<Limit SeqID="3" Lat="57.6690260" Lon="11.8472790"/>
|
||||
<Limit SeqID="4" Lat="57.6693140" Lon="11.8457610"/>
|
||||
<Limit SeqID="5" Lat="57.6665370" Lon="11.8432910"/>
|
||||
<Limit SeqID="6" Lat="57.6641400" Lon="11.8385840"/>
|
||||
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030"/>
|
||||
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660"/>
|
||||
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920"/>
|
||||
<Limit SeqID="10" Lat="57.6708220" Lon="11.8321340"/>
|
||||
</CourseLimit>
|
||||
</Race>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Race>
|
||||
<CreationTimeDate>2015-08-29T13:12:40+02:00</CreationTimeDate>
|
||||
<CreationTimeDate>2015-08-29T11:27:15+02:00</CreationTimeDate>
|
||||
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" />
|
||||
<RaceID>15082901</RaceID>
|
||||
<RaceType>Fleet</RaceType>
|
||||
@@ -80,6 +80,8 @@
|
||||
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
|
||||
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
|
||||
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
|
||||
<Limit SeqID="10" Lat="57.6708220" Lon="11.8321340" />
|
||||
<Limit SeqID="10" Lat="57.6692230" Lon="11.8231430" />
|
||||
<Limit SeqID="11" Lat="57.6725370" Lon="11.8272480" />
|
||||
<Limit SeqID="12" Lat="57.6708220" Lon="11.8321340" />
|
||||
</CourseLimit>
|
||||
</Race>
|
||||
@@ -7,58 +7,4 @@
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller">
|
||||
<children>
|
||||
<GridPane nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints percentHeight="10.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="52.0" minHeight="52.0" prefHeight="52.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="0.0" percentHeight="8.0" prefHeight="0.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="28.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="55.0" minHeight="55.0" percentHeight="5.0" prefHeight="55.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="23.0" prefHeight="0.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="93.0" minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="283.0" minHeight="262.0" prefHeight="283.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label alignment="CENTER" text="Welcome to Race Vision" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
|
||||
<font>
|
||||
<Font size="40.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label text="Your live AC35 livestream" GridPane.halignment="CENTER" GridPane.rowIndex="1">
|
||||
<font>
|
||||
<Font size="20.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label text="Livestream Status:" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM">
|
||||
<font>
|
||||
<Font size="28.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label fx:id="timeTillLive" text="0:00 minutes" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="4">
|
||||
<font>
|
||||
<Font size="27.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
|
||||
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" />
|
||||
<TableView fx:id="teamList" maxWidth="500.0" prefHeight="200.0" prefWidth="210.0" GridPane.halignment="CENTER" GridPane.rowIndex="5">
|
||||
<columns>
|
||||
<TableColumn fx:id="posCol" editable="false" maxWidth="74.0" minWidth="74.0" prefWidth="74.0" resizable="false" sortable="false" text="Position" />
|
||||
<TableColumn fx:id="boatNameCol" editable="false" maxWidth="171.0" minWidth="171.0" prefWidth="171.0" resizable="false" sortable="false" text="Boat Name" />
|
||||
<TableColumn fx:id="shortNameCol" editable="false" maxWidth="107.0" minWidth="107.0" prefWidth="107.0" resizable="false" sortable="false" text="Short Name" />
|
||||
<TableColumn fx:id="countryCol" editable="false" maxWidth="147.0" minWidth="147.0" prefWidth="147.0" resizable="false" sortable="false" text="Country" />
|
||||
</columns>
|
||||
<GridPane.margin>
|
||||
<Insets />
|
||||
</GridPane.margin>
|
||||
</TableView>
|
||||
<Label fx:id="realTime" text="Local time" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
|
||||
</children>
|
||||
</GridPane>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller" />
|
||||
|
||||
@@ -16,36 +16,39 @@
|
||||
<RowConstraints />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowSpan="3">
|
||||
<AnchorPane prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3">
|
||||
<children>
|
||||
<Label layoutX="11.0" layoutY="259.0" text="Team Position" />
|
||||
<Label layoutX="13.0" layoutY="432.0" text="Settings" />
|
||||
<Label layoutX="11.0" layoutY="14.0" text="Timer" />
|
||||
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" />
|
||||
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#76baf8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#686868" strokeType="INSIDE" strokeWidth="3.0" />
|
||||
<Text fx:id="windArrowText" fill="#686868" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↑">
|
||||
<Label layoutX="11.0" layoutY="259.0" text="Team Position" textFill="WHITE" />
|
||||
<Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" />
|
||||
<Label layoutX="11.0" layoutY="14.0" text="Timer" textFill="WHITE" />
|
||||
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" textFill="WHITE" />
|
||||
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#3dcdc8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#d7d7d7" strokeType="INSIDE" strokeWidth="3.0" />
|
||||
<Text fx:id="windArrowText" fill="#a8a8a8" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↑">
|
||||
<font>
|
||||
<Font name="AdobeArabic-Regular" size="55.0" />
|
||||
</font>
|
||||
</Text>
|
||||
<Text fx:id="windDirectionText" fill="#a8a7a7" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
|
||||
<Text fx:id="windDirectionText" fill="#d3d3d3" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
</Text>
|
||||
<CheckBox fx:id="toggleFps" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" text="Show FPS" />
|
||||
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" />
|
||||
<CheckBox fx:id="toggleFps" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" />
|
||||
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" styleClass="text-white" />
|
||||
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
|
||||
<children>
|
||||
<Text fx:id="timerLabel" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
|
||||
<Text fx:id="timerLabel" fill="#f8f8f8" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
|
||||
<font>
|
||||
<Font size="25.0" />
|
||||
</font>
|
||||
</Text>
|
||||
</children>
|
||||
</Pane>
|
||||
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="3.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" />
|
||||
<Label layoutX="10.0" layoutY="499.0" text="Annotations" />
|
||||
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="2.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" />
|
||||
<Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" />
|
||||
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
|
||||
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
|
||||
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane fx:id="contentAnchorPane" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import javafx.scene.canvas.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<GridPane fx:id="gridPane" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.StartScreenController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints percentHeight="10.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="52.0" minHeight="52.0" prefHeight="52.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="0.0" percentHeight="8.0" prefHeight="0.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="28.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="55.0" minHeight="55.0" percentHeight="9.0" prefHeight="55.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="29.0" prefHeight="0.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="93.0" minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="283.0" minHeight="262.0" prefHeight="283.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label alignment="CENTER" text="Welcome to Race Vision" textFill="WHITE" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
|
||||
<font>
|
||||
<Font size="40.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label text="Your live AC35 livestream" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="1">
|
||||
<font>
|
||||
<Font size="20.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label text="Livestream Status:" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM">
|
||||
<font>
|
||||
<Font size="28.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label fx:id="timeTillLive" text="0:00 minutes" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="4">
|
||||
<font>
|
||||
<Font size="27.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" styleClass="blue-ui-btn" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
|
||||
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" styleClass="blue-ui-btn" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" />
|
||||
<TableView fx:id="teamList" maxWidth="661.0" prefHeight="324.0" prefWidth="629.0" styleClass="ui-table" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="5" GridPane.vgrow="NEVER">
|
||||
<columns>
|
||||
<TableColumn fx:id="posCol" editable="false" maxWidth="74.0" minWidth="74.0" prefWidth="74.0" resizable="false" sortable="false" text="Position" />
|
||||
<TableColumn fx:id="boatNameCol" editable="false" maxWidth="171.0" minWidth="171.0" prefWidth="171.0" resizable="false" sortable="false" text="Boat Name" />
|
||||
<TableColumn fx:id="shortNameCol" editable="false" maxWidth="155.18472290039062" minWidth="107.0" prefWidth="155.18472290039062" resizable="false" sortable="false" text="Short Name" />
|
||||
<TableColumn fx:id="countryCol" editable="false" maxWidth="258.9999694824219" minWidth="147.0" prefWidth="258.9999694824219" resizable="false" sortable="false" text="Country" />
|
||||
</columns>
|
||||
<GridPane.margin>
|
||||
<Insets top="10.0" />
|
||||
</GridPane.margin>
|
||||
</TableView>
|
||||
<Label fx:id="realTime" text="Local time" textFill="WHITE" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
|
||||
</children>
|
||||
</GridPane>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane fx:id="annotationSelectWindow" maxHeight="270.0" maxWidth="469.0" minHeight="270.0" minWidth="469.0" prefHeight="270.0" prefWidth="469.0" styleClass="background-blue" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<Text fill="WHITE" layoutX="26.0" layoutY="52.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Select important annotations">
|
||||
<font>
|
||||
<Font size="24.0" />
|
||||
</font>
|
||||
</Text>
|
||||
<CheckBox fx:id="boatWakeSelect" layoutX="26.0" layoutY="80.0" mnemonicParsing="false" style="-fx-border-width: 0; -fx-background-insets: 0;" text="Boat Wakes" textFill="#e7e7e7" />
|
||||
<CheckBox fx:id="boatSpeedSelect" layoutX="26.0" layoutY="111.0" mnemonicParsing="false" text="Boat Speed" textFill="#e7e7e7" />
|
||||
<CheckBox fx:id="boatTrackSelect" layoutX="26.0" layoutY="142.0" mnemonicParsing="false" text="Boat Tracks" textFill="#e7e7e7" />
|
||||
<CheckBox fx:id="boatNameSelect" layoutX="26.0" layoutY="173.0" mnemonicParsing="false" text="Boat Name" textFill="#e7e7e7" />
|
||||
<CheckBox fx:id="boatEstTimeToNextMarkSelect" layoutX="26.0" layoutY="204.0" mnemonicParsing="false" text="Boat Estimated Time To Next Mark" textFill="#e7e7e7" />
|
||||
<Button fx:id="closeButton" layoutX="424.0" layoutY="-1.0" mnemonicParsing="false" prefHeight="11.0" prefWidth="49.0" style=": 0;" text="X" textFill="#ffffff4e">
|
||||
<font>
|
||||
<Font size="24.0" />
|
||||
</font>
|
||||
<styleClass>
|
||||
<String fx:value="background-blue" />
|
||||
<String fx:value="clearExitButton" />
|
||||
</styleClass>
|
||||
</Button>
|
||||
<CheckBox fx:id="boatElapsedTimeSelect" layoutX="26.0" layoutY="235.0" mnemonicParsing="false" text="Boat Elapsed Time Since Last Mark" textFill="#e7e7e7" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
@@ -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;
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user