diff --git a/.gitignore b/.gitignore index 6ba8608..1431ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /.settings/ /bin/ /target/ -/*.iml \ No newline at end of file +/*.iml +.pmd \ No newline at end of file diff --git a/.project b/.project index c429fdf..470e4bd 100644 --- a/.project +++ b/.project @@ -20,10 +20,16 @@ + + net.sourceforge.pmd.eclipse.plugin.pmdBuilder + + + org.eclipse.m2e.core.maven2Nature org.eclipse.jdt.core.javanature net.sf.eclipsecs.core.CheckstyleNature + net.sourceforge.pmd.eclipse.plugin.pmdNature diff --git a/.ruleset b/.ruleset new file mode 100644 index 0000000..5f7408a --- /dev/null +++ b/.ruleset @@ -0,0 +1,354 @@ + + + PMD Plugin preferences rule set + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index e66b197..81ee086 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,6 @@ The software is made for the Windows operating system and might not work as inte ###IDE This software can be developed with both Eclipse and IntelliJ, though they require a different process to set up. + +###Note about preset creation/editing +For now, VLC is required to make the connection to the camera or to the mocked live feed. It is important to have the same VLC version as your java version, e.g. 32bit java requires 32bit VLC, and 64bit java requires 64bit VLC. Also, other systems than Windows are untested and might not work with the current implementation. \ No newline at end of file diff --git a/doc/Architecture Design.pdf b/doc/Architecture Design.pdf index 429d1ac..9606843 100644 Binary files a/doc/Architecture Design.pdf and b/doc/Architecture Design.pdf differ diff --git a/doc/Sprint plans/Sprintplan7.pdf b/doc/Sprint plans/Sprintplan7.pdf new file mode 100644 index 0000000..ff423b8 Binary files /dev/null and b/doc/Sprint plans/Sprintplan7.pdf differ diff --git a/doc/Sprint retrospectives/Sprint retrospective 6.pdf b/doc/Sprint retrospectives/Sprint retrospective 6.pdf new file mode 100644 index 0000000..4e8a684 Binary files /dev/null and b/doc/Sprint retrospectives/Sprint retrospective 6.pdf differ diff --git a/pmd.xml b/pmd.xml new file mode 100644 index 0000000..518a955 --- /dev/null +++ b/pmd.xml @@ -0,0 +1,39 @@ + + + + + Custom rules for PMD. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8da962a..dccc96e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ nl.tudelft Context-TFP - 0.5 + 0.6 Team Free Pizza TU Delft Contextproject Multi-Media 2016 from Team Free Pizza @@ -77,6 +77,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + true + true + + + + @@ -92,6 +104,26 @@ mockito-core 2.0.53-beta + + uk.co.caprica + vlcj + 3.10.1 + + + net.java.dev.jna + jna-platform + 4.2.2 + + + org.slf4j + slf4j-api + 1.7.21 + + + net.java.dev.jna + jna + 4.2.2 + diff --git a/src/main/java/nl/tudelft/contextproject/ContextTFP.java b/src/main/java/nl/tudelft/contextproject/ContextTFP.java index a5dc2a7..6601f3f 100644 --- a/src/main/java/nl/tudelft/contextproject/ContextTFP.java +++ b/src/main/java/nl/tudelft/contextproject/ContextTFP.java @@ -9,13 +9,15 @@ package nl.tudelft.contextproject; import javafx.application.Application; +import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; - import nl.tudelft.contextproject.camera.Camera; import nl.tudelft.contextproject.camera.CameraSettings; +import nl.tudelft.contextproject.camera.LiveCameraConnection; +import nl.tudelft.contextproject.camera.MockedCameraConnection; import nl.tudelft.contextproject.gui.MenuController; import nl.tudelft.contextproject.presets.InstantPreset; import nl.tudelft.contextproject.script.Script; @@ -45,9 +47,11 @@ public class ContextTFP extends Application { private Stage primaryStage; @Override - public void start(Stage primaryStage) throws Exception { - this.primaryStage = primaryStage; - this.primaryStage.setTitle("TFP Camera Control"); + public void start(Stage pStage) throws Exception { + primaryStage = pStage; + primaryStage.setTitle("TFP Camera Control"); + primaryStage.minWidthProperty().set(800); + primaryStage.minHeightProperty().set(575); // Create the script to be used by the application. script = new Script(new ArrayList()); @@ -60,6 +64,17 @@ public void start(Stage primaryStage) throws Exception { Camera e = new Camera(); Camera f = new Camera(); + LiveCameraConnection live = new LiveCameraConnection("192.168.0.13"); + live.setUpConnection(); + a.setConnection(live); + + MockedCameraConnection mocked = new MockedCameraConnection(); + b.setConnection(mocked); + + MockedCameraConnection mocked2 = new MockedCameraConnection(); + mocked2.setStreamLink("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"); + c.setConnection(mocked2); + List list = new ArrayList(); list.addAll(Arrays.asList(a, b, c, d, e, f)); @@ -69,14 +84,13 @@ public void start(Stage primaryStage) throws Exception { cam.addPreset(new InstantPreset(new CameraSettings(), 2, "awesome")); cam.addPreset(new InstantPreset(new CameraSettings(), 3, "wuq")); } - // initRootLayout(); MenuController.show(); } /** - * Initializes the root layout of the application. + * Initialises the root layout of the application. */ public void initRootLayout() { try { @@ -87,6 +101,10 @@ public void initRootLayout() { Scene scene = new Scene(rootLayout); primaryStage.setScene(scene); primaryStage.show(); + primaryStage.setOnCloseRequest(e -> { + Platform.exit(); + System.exit(0); + }); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/nl/tudelft/contextproject/camera/Camera.java b/src/main/java/nl/tudelft/contextproject/camera/Camera.java index f555b38..695d0a1 100644 --- a/src/main/java/nl/tudelft/contextproject/camera/Camera.java +++ b/src/main/java/nl/tudelft/contextproject/camera/Camera.java @@ -8,31 +8,34 @@ import java.util.Observable; /** - * Class to represent a camera. - * Extends Observables so its settings can be observed. + * This class represents a camera. Every camera has its + * own {@link CameraSettings}. This class extends {@link Observable} + * so its settings can be observed. + * * @since 0.2 */ public class Camera extends Observable { - + private static final HashMap CAMERAS = new HashMap(); private static int numCams = 0; - - private int num; + + private CameraConnection connection; private CameraSettings camSet; private HashMap presets; - private CameraConnection connection; - + + private int camId; + /** * Creates a Camera object with initial camera settings * set to the lower limits of the camera. */ public Camera() { camSet = new CameraSettings(); - num = numCams++; + camId = numCams++; presets = new HashMap(); - CAMERAS.put(num, this); + CAMERAS.put(camId, this); } - + /** * Creates a Camera object with initial camera settings * as specified in the CameraSettings object. @@ -41,97 +44,105 @@ public Camera() { */ public Camera(CameraSettings init) { camSet = init; - num = numCams++; + camId = numCams++; presets = new HashMap(); - CAMERAS.put(num, this); + CAMERAS.put(camId, this); } - + /** * Returns the camera with a specific number, or null if it - * does not yet exist. - * @param camNum number of the camera to get - * @return the camera with the associated number, or null if + * does not exist (yet). + * + * @param camNum The number of the camera to get. + * @return The camera with the associated number, or null if * it does not exist. */ public static Camera getCamera(int camNum) { return CAMERAS.get(camNum); } - + /** - * Returns all cameras currently made. - * @return a collection of all cameras currently specified. + * Returns all cameras that have been made. + * @return A collection of all cameras currently specified. */ public static Collection getAllCameras() { return CAMERAS.values(); } - + + /** + * Clears all cameras and resets the numCams to 0. + */ public static void clearAllCameras() { CAMERAS.clear(); numCams = 0; } - + /** * Gets the camera number assigned to the camera. * @return Camera number assigned to camera. */ public int getNumber() { - return num; + return camId; } - + /** - * Returns the camera settings. - * @return Camera settings + * Returns the camera settings attached to the camera. + * @return Camera settings. */ public CameraSettings getSettings() { return camSet; } - + /** - * Sets the settings for this camera. - * Updates the observers. - * + * Sets the settings for this camera and updates the observers. * @param settings Camera settings to set. */ public void setSettings(CameraSettings settings) { - camSet = settings; - setChanged(); - notifyObservers(settings); + if (hasConnection() && connection.isConnected()) { + camSet = settings; + setChanged(); + notifyObservers(settings); + } } - + /** * Returns true iff the camera has a non-null CameraConnection * object. - * @return true iff the camera has a non-null CameraConnection + * + * @return True iff the camera has a non-null CameraConnection. */ public boolean hasConnection() { return connection != null; } - + /** * Returns the CameraConnection object used for communicating with * the actual camera. May be null if it has not yet been initialized. - * @return the CameraConnection object used for communicating with + * + * @return The CameraConnection object used for communicating with * the actual camera. */ public CameraConnection getConnection() { return connection; } - + /** * Sets the CameraConnection object used for communicating with * the actual camera. It adds this new connection as an observer * and if there was a previous connection, removes the previous * connection as observer. - * @param connect the new connection to the camera. + * + * @param connect The new connection to the camera. */ public void setConnection(CameraConnection connect) { if (hasConnection()) { this.deleteObserver(connection); } + connection = connect; this.addObserver(connect); } - + /** * Get the total amount of cameras connected to the system. * @return The number of cameras. @@ -139,7 +150,7 @@ public void setConnection(CameraConnection connect) { public static int getCameraAmount() { return numCams; } - + /** * Pans the camera a certain offset. Cannot pan past * the pan limits. @@ -148,13 +159,15 @@ public static int getCameraAmount() { */ public void pan(int offset) { camSet.pan(offset); + if (hasConnection()) { connection.relPan(offset); } + setChanged(); notifyObservers(); } - + /** * Tilts the camera a certain offset. Cannot tilt past * the tilt limits. @@ -163,13 +176,15 @@ public void pan(int offset) { */ public void tilt(int offset) { camSet.tilt(offset); + if (hasConnection()) { connection.relTilt(offset); } + setChanged(); notifyObservers(); } - + /** * Zooms the camera a certain offset. Cannot zoom past * the zoom limits. @@ -178,9 +193,11 @@ public void tilt(int offset) { */ public void zoom(int offset) { camSet.zoom(offset); + if (hasConnection()) { connection.relZoom(offset); } + setChanged(); notifyObservers(); } @@ -193,67 +210,81 @@ public void zoom(int offset) { */ public void focus(int offset) { camSet.focus(offset); + if (hasConnection()) { connection.relFocus(offset); } + setChanged(); notifyObservers(); } /** * Pans and Tilts the camera to a certain pan and tilt value. - * It cannot past the pan or tilt limit - * @param panValue the value to pan the Camera. - * @param tiltValue the value to tilt the Camera. + * It cannot go past the pan or tilt limit. + * + * @param panValue The value to pan the Camera. + * @param tiltValue The value to tilt the Camera. */ public void absPanTilt(int panValue, int tiltValue) { camSet.setPan(panValue); camSet.setTilt(tiltValue); + if (hasConnection()) { connection.absPanTilt(panValue, tiltValue); } + setChanged(); notifyObservers(); } /** - * Pans the camera to a certain value. Value cannot + * Pans the camera to a certain value. Value cannot go * past the pan limits. + * * @param value The new value to pan the Camera. */ public void absPan(int value) { camSet.setPan(value); + if (hasConnection()) { connection.absPan(value); } + setChanged(); notifyObservers(); } /** - * Tilts the camera to a certain value. Value cannot + * Tilts the camera to a certain value. Value cannot go * past the tilt limits. + * * @param value The new value to tilt the Camera. */ public void absTilt(int value) { camSet.setTilt(value); + if (hasConnection()) { connection.absTilt(value); } + setChanged(); notifyObservers(); } /** - * Zooms the camera to a certain value. Value cannot + * Zooms the camera to a certain value. Value cannot go * past the zoom limits. + * * @param value The new value to zoom the Camera. */ public void absZoom(int value) { camSet.setZoom(value); + if (hasConnection()) { connection.absZoom(value); } + setChanged(); notifyObservers(); } @@ -261,20 +292,24 @@ public void absZoom(int value) { /** * Focuses the camera to a certain value. Value cannot * past the focus limits. + * * @param value The new value to focus the Camera. */ public void absFocus(int value) { camSet.setFocus(value); + if (hasConnection()) { connection.absFocus(value); } + setChanged(); notifyObservers(); } - + /** * Adds a preset to the camera, if there is not already * a preset with the same id. Returns true if successful. + * * @param p The preset to add to this camera. * @return True if the preset was added, otherwise false. */ @@ -282,11 +317,13 @@ public boolean addPreset(Preset p) { if (presets.get(p.getId()) == null) { presets.put(p.getId(), p); notifyObservers(); + return true; } + return false; } - + /** * Adds a preset, overwriting if it already exists. * @param p The preset to add. @@ -295,7 +332,7 @@ public void overwritePreset(Preset p) { presets.put(p.getId(), p); notifyObservers(); } - + /** * Removes a preset from the camera. * @param p The preset to remove. @@ -303,17 +340,18 @@ public void overwritePreset(Preset p) { public void removePreset(Preset p) { presets.remove(p.getId()); } - + /** * Returns the preset with the specified id. * Returns null if the preset doesn't exist. + * * @param id The id of the preset to get. * @return The requested preset. */ public Preset getPreset(int id) { return presets.get(id); } - + /** * Returns a hashmap with all the presets of this camera. * @return A hashmap with all the presets of this camera. @@ -321,7 +359,7 @@ public Preset getPreset(int id) { public HashMap getPresets() { return presets; } - + /** * Returns the amount of presets currently registered to this camera. * @return Amount of presets. @@ -331,32 +369,33 @@ public int getPresetAmount() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Camera)) { - return false; + public boolean equals(Object obj) { + if (obj instanceof Camera) { + Camera camera = (Camera) obj; + + return camId == camera.camId + && Objects.equals(camSet, camera.camSet) + && Objects.equals(presets, camera.presets); } - Camera camera = (Camera) o; - boolean result = num == camera.num - && Objects.equals(camSet, camera.camSet) - && Objects.equals(presets, camera.presets); - return result; + + return false; } @Override public int hashCode() { - return Objects.hash(num, camSet, presets); + return Objects.hash(camId, camSet, presets); } /** - * Returns the list of presets currently registered to this camera. - * @return the list of presets registered to this camera. + * @return The list of presets registered to this camera. */ public Collection getAllPresets() { return presets.values(); } + @Override + public String toString() { + return "Camera: " + camId; + } } diff --git a/src/main/java/nl/tudelft/contextproject/camera/CameraConnection.java b/src/main/java/nl/tudelft/contextproject/camera/CameraConnection.java index bce2191..d0646cc 100644 --- a/src/main/java/nl/tudelft/contextproject/camera/CameraConnection.java +++ b/src/main/java/nl/tudelft/contextproject/camera/CameraConnection.java @@ -3,130 +3,143 @@ import java.util.Observer; /** - * Abstract class to represent a connection with a camera. A class + * Abstract class to represent a connection with a {@link Camera}. A class * that extends this class should observe a Camera-object and send * its operations to the actual camera. * * @since 0.4 */ public abstract class CameraConnection implements Observer { - + /** * Sets up the connection to the camera. - * @return true iff the connection was set up successfully. + * @return True iff the connection was set up successfully. */ public abstract boolean setUpConnection(); - + /** * Returns true iff there is an active connection to the camera. - * @return true iff the camera is connected. + * @return True iff the camera is connected. */ public abstract boolean isConnected(); - + /** * Returns the link to a live feed from the camera. - * @return the link to a live feed from the camera. + * @return The link to a live feed from the camera. */ public abstract String getStreamLink(); - + /** * Returns the current camera settings of the camera as a * CameraSettings object. - * @return the current camera settings of the camera. + * + * @return The current camera settings of the camera. */ public abstract CameraSettings getCurrentCameraSettings(); - + /** * Returns the current pan and tilt values of the camera in * the form of an array. - * @return an array with the pan and tilt values. [0] holds + * + * @return An array with the pan and tilt values. [0] holds * the pan value, [1] holds the tilt value. */ public abstract int[] getCurrentPanTilt(); - + /** * Returns the current zoom value of the camera. - * @return the current zoom value of the camera. + * @return The current zoom value of the camera. */ public abstract int getCurrentZoom(); - + /** * Returns the current focus value of the camera. * In the case of a LiveCameraConnection, this method may * return -1 when auto focus is on. - * @return the current focus value of the camera. + * + * @return The current focus value of the camera. */ public abstract int getCurrentFocus(); - + /** * Pans and tilts to an absolute position. - * @param panValue the absolute pan value to pan to. - * @param tiltValue the absolute tilt value to tilt to. - * @return true iff the operation was performed successfully. + * + * @param panValue The absolute pan value to pan to. + * @param tiltValue The absolute tilt value to tilt to. + * @return True iff the operation was performed successfully. */ protected abstract boolean absPanTilt(int panValue, int tiltValue); - + /** * Pans to an absolute position. - * @param value the absolute value to pan to. - * @return true iff the operation was performed successfully. + * + * @param value The absolute value to pan to. + * @return True iff the operation was performed successfully. */ protected abstract boolean absPan(int value); - + /** * Tilts to an absolute position. - * @param value the absolute value to tilt to. - * @return true iff the operation was performed successfully. + * + * @param value The absolute value to tilt to. + * @return True iff the operation was performed successfully. */ protected abstract boolean absTilt(int value); - + /** * Sets the zoom level to an absolute position. - * @param value the absolute value to zoom to. - * @return true iff the operation was performed successfully. + * + * @param value The absolute value to zoom to. + * @return True iff the operation was performed successfully. */ protected abstract boolean absZoom(int value); - + /** * Sets the focus to an absolute position. - * @param value the absolute value to focus to. - * @return true iff the operation was performed successfully. + * + * @param value The absolute value to focus to. + * @return True iff the operation was performed successfully. */ protected abstract boolean absFocus(int value); - + /** * Pans and tilts the camera a certain offset. - * @param panOffset offset to pan. - * @param tiltOffset offset to tilt. - * @return true iff the operation was performed successfully. + * + * @param panOffset Relative offset to pan. + * @param tiltOffset Relative offset to tilt. + * @return True iff the operation was performed successfully. */ protected abstract boolean relPanTilt(int panOffset, int tiltOffset); - + /** * Pans the camera a certain offset. - * @param offset offset to pan. - * @return true iff the operation was performed successfully. + * + * @param offset Relative offset to pan. + * @return True iff the operation was performed successfully. */ protected abstract boolean relPan(int offset); - + /** * Tilts the camera a certain offset. - * @param offset offset to tilt. - * @return true iff the operation was performed successfully. + * + * @param offset Relative offset to tilt. + * @return True iff the operation was performed successfully. */ protected abstract boolean relTilt(int offset); - + /** * Zooms the camera a certain offset. - * @param offset offset to zoom. - * @return true iff the operation was performed successfully. + * + * @param offset Relative offset to zoom. + * @return True iff the operation was performed successfully. */ protected abstract boolean relZoom(int offset); - + /** * Changes the focus of the camera a certain offset. - * @param offset offset to change the focus. - * @return true iff the operation was performed successfully. + * + * @param offset Relative offset to change the focus. + * @return True iff the operation was performed successfully. */ protected abstract boolean relFocus(int offset); } diff --git a/src/main/java/nl/tudelft/contextproject/camera/CameraSettings.java b/src/main/java/nl/tudelft/contextproject/camera/CameraSettings.java index 3798279..b3f2929 100644 --- a/src/main/java/nl/tudelft/contextproject/camera/CameraSettings.java +++ b/src/main/java/nl/tudelft/contextproject/camera/CameraSettings.java @@ -1,20 +1,21 @@ package nl.tudelft.contextproject.camera; /** - * Class to represent camera settings. Can be extended with colour + * Class to represent camera settings. Can be extended with color * settings etc. + * * @since 0.2 */ public class CameraSettings { - + private int pan; private int tilt; private int zoom; private int focus; - + /** * Constructs a camera settings object with orientation, - * zoom level and focus level initialised to 0. + * zoom level and focus level initialized to 0. */ public CameraSettings() { pan = 0; @@ -22,10 +23,11 @@ public CameraSettings() { zoom = 0; focus = 0; } - + /** * Constructs a camera settings object with a certain orientation, * zoom level and focus. + * * @param panPos Horizontal orientation level. * @param tiltPos Vertical orientation level. * @param zoomPos Zoom level. @@ -37,7 +39,7 @@ public CameraSettings(int panPos, int tiltPos, int zoomPos, int focusPos) { zoom = zoomPos; focus = focusPos; } - + /** * Gets the pan level (horizontal orientation). * @return Pan level (horizontal orientation). @@ -85,7 +87,7 @@ public void setFocus(int focusPos) { public int getTilt() { return tilt; } - + /** * Gets the zoom level. * @return Zoom level. @@ -109,7 +111,7 @@ public int getFocus() { protected void pan(int offset) { pan += offset; } - + /** * Tilts the camera settings a certain offset. * @param offset The offset to tilt the camera. @@ -117,7 +119,7 @@ protected void pan(int offset) { protected void tilt(int offset) { tilt += offset; } - + /** * Zooms the camera settings a certain offset. * @param offset The offset to zoom the camera. @@ -138,29 +140,26 @@ protected void focus(int offset) { public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + focus; result = prime * result + pan; result = prime * result + tilt; result = prime * result + zoom; + return result; } @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof CameraSettings)) { - return false; + if (obj instanceof CameraSettings) { + CameraSettings other = (CameraSettings) obj; + + return focus == other.focus + && pan == other.pan + && tilt == other.tilt + && zoom == other.zoom; } - CameraSettings other = (CameraSettings) obj; - boolean result = focus == other.focus - && pan == other.pan - && tilt == other.tilt - && zoom == other.zoom; - return result; + + return false; } } diff --git a/src/main/java/nl/tudelft/contextproject/camera/LiveCameraConnection.java b/src/main/java/nl/tudelft/contextproject/camera/LiveCameraConnection.java index a217177..0766f3d 100644 --- a/src/main/java/nl/tudelft/contextproject/camera/LiveCameraConnection.java +++ b/src/main/java/nl/tudelft/contextproject/camera/LiveCameraConnection.java @@ -20,77 +20,88 @@ * @since 0.4 */ public class LiveCameraConnection extends CameraConnection { - + public static final String CAMERA_MODEL = "AW-HE130"; - + public static final int PAN_LIMIT_LOW = 11528; public static final int PAN_LIMIT_HIGH = 54005; - + public static final int TILT_LIMIT_LOW = 7283; public static final int TILT_LIMIT_HIGH = 36408; - + public static final int ZOOM_LIMIT_LOW = 1365; public static final int ZOOM_LIMIT_HIGH = 4095; public static final int FOCUS_LIMIT_LOW = 1365; public static final int FOCUS_LIMIT_HIGH = 4095; - + private static final int READ_TIMEOUT = 3000; - - private String address; + + private final String errorString = "Wrong response from camera: "; + private boolean connected; private boolean autoFocus; private CameraSettings lastKnown; - + private String address; + /** * Creates a LiveCameraConnection object. Assumes that the * address given is the correctly formulated IP address of the * camera to connect to. + * * @param address IP address of the camera to connect to. */ public LiveCameraConnection(String address) { this.address = address; this.connected = false; } - + + /** + * Returns the last known camera settings. + * @return The last known camera settings. + */ protected CameraSettings getLastKnownSettings() { return lastKnown; } - + @Override public boolean setUpConnection() { try { String cameraModel = sendRequest(buildCamControlURL("QID")); + if (cameraModel.equals("OID:" + CAMERA_MODEL)) { String autoFocusResponse = sendRequest(buildPanTiltHeadControlURL("%23D1")); int hasAutoFocus = Integer.parseInt(autoFocusResponse.substring(2)); autoFocus = hasAutoFocus == 1; connected = true; lastKnown = new CameraSettings(); + return true; } + return false; } catch (IOException e) { e.printStackTrace(); return false; } } - + @Override public boolean isConnected() { return connected; } - + @Override public String getStreamLink() { return "rtsp://" + address + "/MediaInput/h264"; } - + /** * Builds the URL for the command specified, which must be a command * from section 3.1 Pan-tilt Head Control. + * * @param command Command to be sent. - * @return the formed URL, according to the {@link #address} and the command. + * @return The formed URL, according to the {@link #address} and the command. * @throws MalformedURLException if the parameter command is null. */ private URL buildPanTiltHeadControlURL(String command) throws MalformedURLException { @@ -101,12 +112,13 @@ private URL buildPanTiltHeadControlURL(String command) throws MalformedURLExcept throw new MalformedURLException("Given command is null"); } } - + /** * Builds the URL for the command specified, which must be a command * from section 3.2 Camera Control. + * * @param command Command to be sent. - * @return the formed URL, according to the {@link #address} and the command. + * @return The formed URL, according to the {@link #address} and the command. * @throws MalformedURLException if the parameter command is null. */ private URL buildCamControlURL(String command) throws MalformedURLException { @@ -117,14 +129,15 @@ private URL buildCamControlURL(String command) throws MalformedURLException { throw new MalformedURLException("Given command is null"); } } - + /** * Sends the HTTP request specified in the URL as a POST request. * It waits for a response from the server until a response is * received or until the connection times out, which happens after * the amount of milliseconds specified in {@link #READ_TIMEOUT}. - * @param url the URL containing the full HTTP request - * @return the response of the server. + * + * @param url The URL containing the full HTTP request + * @return The response of the server. * @throws IOException when something goes wrong in opening the * the connection or reading the response from the server. * @throws java.net.ConnectException when a timeout has @@ -132,6 +145,7 @@ private URL buildCamControlURL(String command) throws MalformedURLException { */ private String sendRequest(URL url) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + try { connection.setRequestMethod("GET"); connection.setConnectTimeout(READ_TIMEOUT); @@ -140,37 +154,42 @@ private String sendRequest(URL url) throws IOException { connected = false; return ""; } + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); String response = reader.readLine(); reader.close(); + return response != null ? response : ""; } - + /** * Returns true if the camera is on auto focus. - * @return true if the camera is on auto focus. + * @return True if the camera is on auto focus. */ public boolean hasAutoFocus() { if (connected) { try { String autoFocusRes = sendRequest(buildPanTiltHeadControlURL("%23D1")); + if (autoFocusRes.startsWith("d1")) { autoFocus = Integer.parseInt(autoFocusRes.substring(2)) == 1; } else { - throw new IOException("Wrong response from camera: " + autoFocusRes); + throw new IOException(errorString + autoFocusRes); } } catch (IOException e) { e.printStackTrace(); return autoFocus; } } + return autoFocus; } - + /** * Sets the auto focus setting on the camera to on (true) or off (false). + * * @param autoFocus true for ON, false for OFF - * @return true iff the camera was set to the specified setting. + * @return True iff the camera was set to the specified setting. */ public boolean setAutoFocus(boolean autoFocus) { if (this.autoFocus == autoFocus) { @@ -179,11 +198,12 @@ public boolean setAutoFocus(boolean autoFocus) { try { int set = autoFocus ? 1 : 0; String autoFocusRes = sendRequest(buildPanTiltHeadControlURL("%23D1" + set)); + if (autoFocusRes.equals("d1" + set)) { this.autoFocus = autoFocus; return true; } else { - throw new IOException("Wrong response from camera: " + autoFocusRes); + throw new IOException(errorString + autoFocusRes); } } catch (IOException e) { e.printStackTrace(); @@ -197,31 +217,36 @@ public void update(Observable o, Object arg) { if (!(o instanceof Camera)) { return; } - //Camera cam = (Camera) o; + if (arg instanceof CameraSettings) { mutateSettings((CameraSettings) arg); } } - + /** * Finds the least amount of commands to send to the - * camera in order to apply the specified camera settings - * @param toSet camera settings to apply to the camera. - * @return true iff the camera was set to the specified settings. + * camera in order to apply the specified camera settings. + * + * @param toSet Camera settings to apply to the camera. + * @return True iff the camera was set to the specified settings. */ private boolean mutateSettings(CameraSettings toSet) { CameraSettings curSettings = getCurrentCameraSettings(); boolean result = true; + if (curSettings.getPan() != toSet.getPan() || curSettings.getTilt() != toSet.getTilt()) { result = result && absPanTilt(toSet.getPan(), toSet.getTilt()); } + if (curSettings.getZoom() != toSet.getZoom()) { result = result && absZoom(toSet.getZoom()); } + if (!hasAutoFocus() && curSettings.getFocus() != toSet.getFocus()) { result = result && absFocus(toSet.getFocus()); } + return result; } @@ -230,46 +255,51 @@ public CameraSettings getCurrentCameraSettings() { int[] panTilt = getCurrentPanTilt(); int zoom = getCurrentZoom(); int focus = getCurrentFocus(); + lastKnown = new CameraSettings(panTilt[0], panTilt[1], zoom, focus); return lastKnown; } - + @Override public int[] getCurrentPanTilt() { try { String panTiltRes = sendRequest(buildPanTiltHeadControlURL("%23APC")); + if (panTiltRes.startsWith("aPC")) { int pan = Integer.parseInt(panTiltRes.substring(3, 7), 16); int tilt = Integer.parseInt(panTiltRes.substring(7, 11), 16); lastKnown.setPan(pan); lastKnown.setTilt(tilt); + return new int[]{pan, tilt}; } else { - throw new IOException("Wrong response from camera: " + panTiltRes); + throw new IOException(errorString + panTiltRes); } } catch (IOException e) { e.printStackTrace(); return new int[]{lastKnown.getPan(), lastKnown.getTilt()}; } } - + @Override public int getCurrentZoom() { try { String zoomRes = sendRequest(buildPanTiltHeadControlURL("%23GZ")); + if (zoomRes.startsWith("gz")) { int zoom = Integer.parseInt(zoomRes.substring(2, 5), 16); lastKnown.setZoom(zoom); + return zoom; } else { - throw new IOException("Wrong response from camera: " + zoomRes); + throw new IOException(errorString + zoomRes); } } catch (IOException e) { e.printStackTrace(); return lastKnown.getZoom(); } } - + @Override public int getCurrentFocus() { if (autoFocus) { @@ -277,11 +307,12 @@ public int getCurrentFocus() { } else { try { String focusRes = sendRequest(buildPanTiltHeadControlURL("%23GF")); + if (focusRes.startsWith("gf")) { int focus = Integer.parseInt(focusRes.substring(2), 16); return focus; } else { - throw new IOException("Wrong response from camera: " + focusRes); + throw new IOException(errorString + focusRes); } } catch (IOException e) { e.printStackTrace(); @@ -289,25 +320,36 @@ public int getCurrentFocus() { } } } - + @Override protected boolean absPanTilt(int panValue, int tiltValue) { - panValue = (panValue < PAN_LIMIT_LOW) ? PAN_LIMIT_LOW : - (panValue > PAN_LIMIT_HIGH) ? PAN_LIMIT_HIGH : panValue; - tiltValue = (tiltValue < TILT_LIMIT_LOW) ? TILT_LIMIT_LOW : - (tiltValue > TILT_LIMIT_HIGH) ? TILT_LIMIT_HIGH : tiltValue; + if (panValue < PAN_LIMIT_LOW) { + panValue = PAN_LIMIT_LOW; + } else if (panValue > PAN_LIMIT_HIGH) { + panValue = PAN_LIMIT_HIGH; + } + + if (tiltValue < TILT_LIMIT_LOW) { + tiltValue = TILT_LIMIT_LOW; + } else if (tiltValue > TILT_LIMIT_HIGH) { + tiltValue = TILT_LIMIT_HIGH; + } + try { String res = sendRequest(buildPanTiltHeadControlURL( - "%23APS" - + Integer.toHexString(0x10000 | panValue).substring(1).toUpperCase() - + Integer.toHexString(0x10000 | tiltValue).substring(1).toUpperCase() - + "1D" + "2" + "%23APS" + + Integer.toHexString(0x10000 | panValue).substring(1).toUpperCase() + + Integer.toHexString(0x10000 | tiltValue).substring(1).toUpperCase() + + "1D" + "2" )); + if (res.startsWith("aPS")) { lastKnown.setPan(panValue); lastKnown.setTilt(tiltValue); + return true; } + return false; } catch (IOException e) { e.printStackTrace(); @@ -331,12 +373,14 @@ protected boolean absZoom(int value) { (value > ZOOM_LIMIT_HIGH) ? ZOOM_LIMIT_HIGH : value; try { String res = sendRequest(buildPanTiltHeadControlURL("%23AXZ" - + Integer.toHexString(0x1000 | value).substring(1).toUpperCase() + + Integer.toHexString(0x1000 | value).substring(1).toUpperCase() )); + if (res.startsWith("axz")) { lastKnown.setZoom(value); return true; } + return false; } catch (IOException e) { e.printStackTrace(); @@ -348,13 +392,16 @@ protected boolean absZoom(int value) { protected boolean absFocus(int value) { value = (value < FOCUS_LIMIT_LOW) ? FOCUS_LIMIT_LOW : (value > FOCUS_LIMIT_HIGH) ? FOCUS_LIMIT_HIGH : value; + try { if (autoFocus) { throw new IOException("Autofocus is on"); } + String res = sendRequest(buildPanTiltHeadControlURL("%23AXF" - + Integer.toHexString(0x1000 | value).substring(1).toUpperCase() + + Integer.toHexString(0x1000 | value).substring(1).toUpperCase() )); + if (res.startsWith("axf")) { lastKnown.setFocus(value); return true; @@ -362,34 +409,43 @@ protected boolean absFocus(int value) { autoFocus = true; throw new IOException("Autofocus is on"); } + return false; } catch (IOException e) { e.printStackTrace(); return false; } } - + @Override protected boolean relPanTilt(int panOffset, int tiltOffset) { CameraSettings curSet = getCurrentCameraSettings(); - panOffset = (curSet.getPan() + panOffset < PAN_LIMIT_LOW) - ? curSet.getPan() - PAN_LIMIT_LOW : - (curSet.getPan() + panOffset > PAN_LIMIT_HIGH) - ? PAN_LIMIT_HIGH - curSet.getPan() : panOffset; - tiltOffset = (curSet.getTilt() + tiltOffset < TILT_LIMIT_LOW) - ? curSet.getTilt() - TILT_LIMIT_LOW : - (curSet.getTilt() + tiltOffset > TILT_LIMIT_HIGH) - ? TILT_LIMIT_HIGH - curSet.getTilt() : tiltOffset; + + if (curSet.getPan() + panOffset < PAN_LIMIT_LOW) { + panOffset = curSet.getPan() - PAN_LIMIT_LOW; + } else if (curSet.getPan() + panOffset > PAN_LIMIT_HIGH) { + panOffset = PAN_LIMIT_HIGH - curSet.getPan(); + } + + if (curSet.getTilt() + tiltOffset < TILT_LIMIT_LOW) { + tiltOffset = curSet.getTilt() - TILT_LIMIT_LOW; + } else if (curSet.getTilt() + tiltOffset > TILT_LIMIT_HIGH) { + tiltOffset = TILT_LIMIT_HIGH - curSet.getTilt(); + } + try { String res = sendRequest(buildPanTiltHeadControlURL("%23RPC" - + Integer.toHexString(0x10000 | 32768 + panOffset).substring(1).toUpperCase() - + Integer.toHexString(0x10000 | 32768 + tiltOffset).substring(1).toUpperCase() + + Integer.toHexString(0x10000 | 32768 + panOffset).substring(1).toUpperCase() + + Integer.toHexString(0x10000 | 32768 + tiltOffset).substring(1).toUpperCase() )); + if (res.startsWith("rPC")) { lastKnown.pan(panOffset); lastKnown.tilt(tiltOffset); + return true; } + return false; } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/nl/tudelft/contextproject/camera/MockedCameraConnection.java b/src/main/java/nl/tudelft/contextproject/camera/MockedCameraConnection.java index 7a1f865..944de57 100644 --- a/src/main/java/nl/tudelft/contextproject/camera/MockedCameraConnection.java +++ b/src/main/java/nl/tudelft/contextproject/camera/MockedCameraConnection.java @@ -3,13 +3,14 @@ import java.util.Observable; /** - * Class to represent a mocked camera. It mimics or mocks the behaviour of a specific camera + * Class to represent a mocked camera. It mimics or mocks the behavior of a specific camera * without having a real connection to the cameras. * * @since 0.4 */ public class MockedCameraConnection extends CameraConnection { private CameraSettings camSet = new CameraSettings(30, 30, 30, 1365); + private String streamLink = "http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8"; @Override public boolean setUpConnection() { @@ -23,7 +24,15 @@ public boolean isConnected() { @Override public String getStreamLink() { - return "placeholder_picture.jpg"; + return streamLink; + } + + /** + * Sets the link to the 'live stream' of this mocked camera connection + * @param link Link to the 'live stream' of this mocked camera connection. + */ + public void setStreamLink(String link) { + streamLink = link; } @Override @@ -113,9 +122,9 @@ public void update(Observable o, Object arg) { if (!(o instanceof Camera)) { return; } + if (arg instanceof CameraSettings) { camSet = (CameraSettings) arg; } - } } diff --git a/src/main/java/nl/tudelft/contextproject/gui/AlertDialog.java b/src/main/java/nl/tudelft/contextproject/gui/AlertDialog.java new file mode 100644 index 0000000..45f1912 --- /dev/null +++ b/src/main/java/nl/tudelft/contextproject/gui/AlertDialog.java @@ -0,0 +1,190 @@ +package nl.tudelft.contextproject.gui; + +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonType; + +import nl.tudelft.contextproject.script.Shot; + +import java.io.File; +import java.util.Optional; + +/** + * Utility class that holds all generated alert dialogs. This handles + * the creation and showing of info, warning and error dialogs + * across the entire application. + * + * @since 0.6 + */ +public final class AlertDialog { + + /** + * Abstract classes should not have a public constructor, + * so defining it as private below. + */ + private AlertDialog() { + throw new UnsupportedOperationException(); + } + + /** + * Shows a confirmation dialog when trying to save a {@link Script}, when + * the script is invalid. + * + * @param error The first shot that causes the invalid script. + * @return The alert. + */ + public static Alert confirmInvalidScriptSaving(Shot error) { + Alert alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Confirm saving"); + alert.setHeaderText("Trying to save invalid script"); + alert.setContentText("Error at shot ID: " + + error.getNumber() + + "\nYou are trying to save an invalid script. " + + "Are you sure you want to continue?"); + + return alert; + } + + /** + * Shows a warning dialog when trying to load a {@link Script}, when + * the script is invalid. + * + * @param error The first shot that causes the invalid script. + * @return The alert. + */ + public static Alert warningInvalidScriptLoading(Shot error) { + Alert alert = new Alert(AlertType.WARNING); + alert.setTitle("Script invalid"); + alert.setHeaderText("Loaded an invalid script"); + alert.setContentText("Error at shot ID: " + + error.getNumber() + + "\nThe script you loaded is invalid. You can change " + + "it in the edit script menu"); + + return alert; + } + + /** + * Displays a confirm to exit dialog when the + * script has been saved. + * + * @param file The file that has been saved. + */ + public static void confirmExitingAfterSaving(File file) { + Alert alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Confirm exiting"); + alert.setHeaderText("Saving script was succesful!"); + alert.setContentText("Succesful save of script: " + + file.getName() + + " Do you want to quit to menu?"); + + Optional result = alert.showAndWait(); + + if (result.get() == ButtonType.OK) { + MenuController.show(); + } + } + + /** + * Shows the dialog when an invalid script is detected. + * + * @param number The camera that will have a consecutive shot. + * @return True if the user ignores the error, false otherwise. + */ + public static boolean confirmInvalidScriptAdding(Number number) { + Alert alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Confirm adding shot"); + alert.setHeaderText("Invalid script!"); + alert.setContentText("Are you sure you want to add this " + + "shot? It will create an invalid script since " + + "camera " + + number + + " will have two presets in a row!"); + + Optional result = alert.showAndWait(); + + if (result.get() == ButtonType.OK) { + return true; + } + + return false; + } + + /** + * Displays an error dialog when saving of the script + * was unsuccessful. + * + * @param e The exception that was thrown. + * @param file The file that was supposed to be saved. + */ + public static void errorSaveUnsuccesful(Exception e, File file) { + Alert alert = new Alert(AlertType.ERROR); + alert.setTitle(e.getMessage()); + alert.setHeaderText("Saving script was unsuccesful!"); + alert.setContentText("Error when trying to save script at location: " + + file.getAbsolutePath() + + "\n\nError: " + + e.getCause()); + + alert.showAndWait(); + } + + /** + * Shows a dialog when exiting a screen without saving, thus + * not saving currently made changes. + * + * @return True if the user wants to quit, false otherwise. + */ + public static boolean confirmExiting() { + Alert alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Confirm quitting"); + alert.setHeaderText("Exiting will erase any unsaved changes"); + alert.setContentText("Are you sure you want to quit? Any unsaved changes " + + "will not be kept."); + + Optional result = alert.showAndWait(); + + if (result.get() == ButtonType.CANCEL) { + return false; + } + + return true; + } + + /** + * Shows the dialog that notifies the user that the loading + * of a script was successful. + * + * @param file The location of the script that was loaded. + */ + public static void infoSuccesfulLoading(File file) { + Alert alert = new Alert(AlertType.INFORMATION); + alert.setTitle("Info Dialog"); + alert.setHeaderText("Loading script was succesful!"); + alert.setContentText("Successful load of script: " + file.getName()); + + alert.showAndWait(); + } + + /** + * Displays an error dialog when saving of the script + * was unsuccessful. + * + * @param e The exception that was thrown. + * @param file The file that was supposed to be saved. + */ + public static void errorLoadUnsuccesful(Exception e, File file) { + String c = (e.getCause() == null) ? "" : "\nCause: " + e.getCause(); + + Alert alert = new Alert(AlertType.ERROR); + alert.setTitle("Unsuccesful load!"); + alert.setHeaderText("Loading script was unsuccesful!"); + alert.setContentText("Error when trying to load script at location: " + + file.getAbsolutePath() + + "\n\nError: " + + e.getMessage() + + c); + + alert.showAndWait(); + } +} diff --git a/src/main/java/nl/tudelft/contextproject/gui/CameramanUIController.java b/src/main/java/nl/tudelft/contextproject/gui/CameramanLiveController.java similarity index 63% rename from src/main/java/nl/tudelft/contextproject/gui/CameramanUIController.java rename to src/main/java/nl/tudelft/contextproject/gui/CameramanLiveController.java index 3c58998..25ec341 100644 --- a/src/main/java/nl/tudelft/contextproject/gui/CameramanUIController.java +++ b/src/main/java/nl/tudelft/contextproject/gui/CameramanLiveController.java @@ -1,9 +1,11 @@ package nl.tudelft.contextproject.gui; +import javafx.collections.FXCollections; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.Button; +import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.TabPane; import javafx.scene.control.TextArea; @@ -13,10 +15,9 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.VBox; + import nl.tudelft.contextproject.ContextTFP; import nl.tudelft.contextproject.camera.Camera; -import nl.tudelft.contextproject.camera.CameraSettings; -import nl.tudelft.contextproject.presets.InstantPreset; import nl.tudelft.contextproject.presets.MovementType; import nl.tudelft.contextproject.presets.Preset; import nl.tudelft.contextproject.script.Script; @@ -24,17 +25,24 @@ import java.io.IOException; /** - * This class controls the live view of the camera. + * This class controls the screen that shows the live view of + * the {@link Camera}, for the cameraman. This screen allows a cameraman to see the + * current live view, look at the {@link Preset Presets} of the camera, and apply + * both new and pre-configured presets. + * + *

The view section is defined under view/CameramanLiveView.fxml * * @since 0.3 */ -public class CameramanUIController { +public class CameramanLiveController { private static Script script; - + + @FXML private AnchorPane movementPane; @FXML private TabPane tabs; - + @FXML private Button btnBack; + @FXML private Button btnConfirm; @FXML private Button swap; @FXML private Button moveOne; @FXML private Button moveTwo; @@ -43,66 +51,63 @@ public class CameramanUIController { @FXML private Button moveFive; @FXML private Button moveSix; @FXML private Button moveSeven; - @FXML private Button btnConfirm; + + @FXML private ChoiceBox currentCam; @FXML private ImageView bigView; @FXML private ImageView smallView; - - @FXML private TextArea descriptionField; - - @FXML private TextField speed; - - @FXML private VBox smallViewBox; - @FXML private VBox bigViewBox; - + @FXML private Label bigStatusLabel; @FXML private Label smallStatusLabel; @FXML private Label cameraNumberLabel; @FXML private Label presetLabel; @FXML private Label movement; @FXML private Label infoLabel; - - private int currentView; - + + @FXML private TextArea descriptionField; + @FXML private TextField speed; + + @FXML private VBox bigViewBox; + @FXML private VBox smallViewBox; + private Camera liveCam; - private Camera currentCam; + + private boolean live; private int liveSpeed; private int currentSpeed; - + private MovementType liveMovement; private MovementType currentMovement; + /** + * Initialize method used by JavaFX. + */ @FXML private void initialize() { script = ContextTFP.getScript(); + //set an initial live camera + liveCam = Camera.getCamera(0); + bigView.fitWidthProperty().bind(bigViewBox.widthProperty()); bigView.fitHeightProperty().bind(bigViewBox.heightProperty()); - + smallView.fitWidthProperty().bind(smallViewBox.widthProperty()); smallView.fitHeightProperty().bind(smallViewBox.heightProperty()); - currentView = 0; - - //Initialize dummy cameras and presets - liveCam = new Camera(); - currentCam = new Camera(); - - CameraSettings dummySettings = new CameraSettings(); - - Preset presetOne = new InstantPreset(dummySettings, 1); - Preset presetTwo = new InstantPreset(dummySettings, 2); - - liveCam.addPreset(presetOne); - currentCam.addPreset(presetTwo); + live = true; initializeLabels(); initializeViews(); initializeButtons(); initializeMovements(); initializeTextFields(); + initializeCurrentCam(); + + movementPane.setDisable(true); + descriptionField.setEditable(false); } - + /** * Initializes the labels in the gui. */ @@ -110,16 +115,17 @@ private void initializeLabels() { bigStatusLabel.setText("LIVE"); bigStatusLabel.setStyle("-fx-text-fill: red;"); smallStatusLabel.setText("Current camera"); - + if (script.getCurrentShot() != null) { cameraNumberLabel.setText(Integer.toString(script.getCurrentShot().getCamera().getNumber())); presetLabel.setText(Integer.toString(script.getCurrentShot().getPreset().getId())); descriptionField.setText(script.getCurrentShot().getDescription()); } + cameraNumberLabel.setText(String.valueOf(liveCam.getNumber())); presetLabel.setText(String.valueOf(liveCam.getPreset(1).getId())); } - + /** * Initializes the views. */ @@ -130,18 +136,48 @@ private void initializeViews() { Image current = new Image("test3.jpg"); smallView.setImage(current); } - + /** * Initialize button styling and functionality. */ private void initializeButtons() { - swap.setOnAction((event) -> { + initSwapButton(); + + btnBack.setOnAction(event -> { + MenuController.show(); + }); + + btnConfirm.setOnAction(event -> { + if (!speed.getText().isEmpty()) { + int spd = Integer.parseInt(speed.getText()); + + if (spd > 100) { + spd = 100; + } + + if (live) { + liveSpeed = spd; + speed.setText(String.valueOf(spd)); + } else { + currentSpeed = spd; + speed.setText(String.valueOf(spd)); + } + } + }); + } + + /** + * Initializes the swap button. + */ + private void initSwapButton() { + swap.setOnAction(event -> { Image three = bigView.getImage(); bigView.setImage(smallView.getImage()); smallView.setImage(three); String text = bigStatusLabel.getText(); bigStatusLabel.setText(smallStatusLabel.getText()); smallStatusLabel.setText(text); + if (text.equals("LIVE")) { smallStatusLabel.setStyle("-fx-text-fill: red;"); bigStatusLabel.setStyle(""); @@ -149,74 +185,57 @@ private void initializeButtons() { smallStatusLabel.setStyle(""); bigStatusLabel.setStyle("-fx-text-fill: red;"); } + swapCurrentView(); swapInfoTable(); swapSpeed(); }); - - btnBack.setOnAction((event) -> { - MenuController.show(); - }); - - btnConfirm.setOnAction((event) -> { - if (!speed.getText().isEmpty()) { - int spd = Integer.parseInt(speed.getText()); - if (spd > 100) { - spd = 100; - } - if (live()) { - liveSpeed = spd; - speed.setText(String.valueOf(spd)); - } else { - currentSpeed = spd; - speed.setText(String.valueOf(spd)); - } - } - }); } - + /** - * Initialize movements. + * Initialises movements. */ private void initializeMovements() { - moveOne.setOnAction((event) -> { + moveOne.setOnAction(event -> { movement.setText(moveOne.getText()); setMovement(MovementType.ZOOM_IN); }); - - moveTwo.setOnAction((event) -> { + + moveTwo.setOnAction(event -> { movement.setText(moveTwo.getText()); setMovement(MovementType.ZOOM_OUT); }); - - moveThree.setOnAction((event) -> { + + moveThree.setOnAction(event -> { movement.setText(moveThree.getText()); setMovement(MovementType.LEFT); }); - - moveFour.setOnAction((event) -> { + + moveFour.setOnAction(event -> { movement.setText(moveFour.getText()); setMovement(MovementType.RIGHT); }); - - moveFive.setOnAction((event) -> { + + moveFive.setOnAction(event -> { movement.setText(moveFive.getText()); setMovement(MovementType.UP); }); - - moveSix.setOnAction((event) -> { + + moveSix.setOnAction(event -> { movement.setText(moveSix.getText()); setMovement(MovementType.DOWN); }); - - moveSeven.setOnAction((event) -> { + + moveSeven.setOnAction(event -> { movement.setText(moveSeven.getText()); setMovement(MovementType.CUSTOM); }); + + } - + /** - * Initialize the textfields. + * Initialises the text fields. */ private void initializeTextFields() { speed.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler() { @@ -232,44 +251,72 @@ public void handle(KeyEvent event) { } }); } + + /** + * Initialises the ChoiceBox of cameras to choose from. + */ + private void initializeCurrentCam() { + currentCam.setItems(FXCollections.observableArrayList(Camera.getAllCameras())); + currentCam.setVisible(false); + + currentCam.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> { + changeInfoTable(newV); + }); + + //set initial current camera + currentCam.setValue(Camera.getCamera(1)); + } /** * Swap currentView. */ private void swapCurrentView() { - if (live()) { - currentView = 1; + if (live) { + live = false; + currentCam.setVisible(true); + movementPane.setDisable(false); } else { - currentView = 0; + live = true; + currentCam.setVisible(false); + movementPane.setDisable(true); } } - + /** * Swap info table according to the big view. */ private void swapInfoTable() { - if (live()) { - Preset preset = liveCam.getPreset(1); - infoLabel.setText("Information about live camera"); - cameraNumberLabel.setText(String.valueOf(liveCam.getNumber())); - presetLabel.setText(String.valueOf(preset.getId())); - movement.setText(MovementType.getName(liveMovement)); - descriptionField.setText(preset.getDescription()); + if (live) { + setActualInfo(); } else { - Preset preset = currentCam.getPreset(2); - infoLabel.setText("Information about current camera"); - cameraNumberLabel.setText(String.valueOf(currentCam.getNumber())); - presetLabel.setText(String.valueOf(preset.getId())); - movement.setText(MovementType.getName(currentMovement)); - descriptionField.setText(preset.getDescription()); + setCurrentInfo(); } } + /** + * Changes the info table according to the currently selected camera. + * + * @param newCam The new camera to switch the info to. + */ + private void changeInfoTable(Camera newCam) { + Preset preset; + if (newCam.getPreset(1) == null) { + preset = newCam.getPreset(2); + } else { + preset = newCam.getPreset(1); + } + infoLabel.setText("Information about current camera"); + cameraNumberLabel.setText(String.valueOf(newCam.getNumber())); + presetLabel.setText(String.valueOf(preset.getId())); + movement.setText(MovementType.getName(currentMovement)); + descriptionField.setText(preset.getDescription()); + } + /** * Swap speed according to the big view. */ private void swapSpeed() { - if (live()) { + if (live) { if (liveSpeed > 0) { speed.setText(String.valueOf(liveSpeed)); } else { @@ -283,38 +330,51 @@ private void swapSpeed() { } } } - + /** - * set movement type to the big view. - * @param mt - the movement type to set. + * Set movement type to the big view. + * @param mt The movement type to set. */ private void setMovement(MovementType mt) { - if (live()) { + if (live) { liveMovement = mt; } else { currentMovement = mt; } } + + /** + * Set the table info to the actual camera info. + */ + private void setActualInfo() { + Preset preset = liveCam.getPreset(1); + infoLabel.setText("Information about live camera"); + cameraNumberLabel.setText(String.valueOf(liveCam.getNumber())); + presetLabel.setText(String.valueOf(preset.getId())); + movement.setText(MovementType.getName(liveMovement)); + descriptionField.setText(preset.getDescription()); + } /** - * See what is currently on the big view. - * @return - whether it's the live camera on the view or the current camera. + * Set the table info to the current camera info. */ - private boolean live() { - if (currentView == 0) { - return true; - } else { - return false; - } + private void setCurrentInfo() { + Preset preset = currentCam.getValue().getPreset(2); + infoLabel.setText("Information about current camera"); + cameraNumberLabel.setText(String.valueOf(currentCam.getValue().getNumber())); + presetLabel.setText(String.valueOf(preset.getId())); + movement.setText(MovementType.getName(currentMovement)); + descriptionField.setText(preset.getDescription()); } /** - * Shows this view. + * Calling this method shows this view in the middle of the rootLayout, + * forcing the current view to disappear. */ public static void show() { try { FXMLLoader loader = new FXMLLoader(); - loader.setLocation(ContextTFP.class.getResource("view/CameramanUIView.fxml")); + loader.setLocation(ContextTFP.class.getResource("view/CameramanLiveView.fxml")); AnchorPane cameraLiveUI = (AnchorPane) loader.load(); ContextTFP.getRootLayout().setCenter(cameraLiveUI); diff --git a/src/main/java/nl/tudelft/contextproject/gui/CreateScriptController.java b/src/main/java/nl/tudelft/contextproject/gui/CreateScriptController.java index 95463f8..f25ffdf 100644 --- a/src/main/java/nl/tudelft/contextproject/gui/CreateScriptController.java +++ b/src/main/java/nl/tudelft/contextproject/gui/CreateScriptController.java @@ -11,7 +11,6 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.ChoiceBox; @@ -63,6 +62,7 @@ */ public class CreateScriptController { + private static final String REDBORDER = "-fx-border-color: red;"; private static boolean fill = false; private ObjectProperty> lastSelectedRow; @@ -100,7 +100,7 @@ public class CreateScriptController { @FXML private TextField editDescription; /** - * Initialize method used by JavaFX. + * Initialise method used by JavaFX. */ @FXML private void initialize() { @@ -167,6 +167,315 @@ private void fillTable(List shots) { tableEvents.getItems().addAll(shots); } + /** + * Fills the choiceboxes for selecting a camera, both the box that + * adds a new camera as the box that is shown when editing a shot. + */ + private void initCamera() { + final List cameraList = new ArrayList(); + + for (int i = 0; i < Camera.getCameraAmount(); ++i) { + cameraList.add(i + 1); + } + + addCamera.setItems(FXCollections.observableArrayList(cameraList)); + editCamera.setItems(FXCollections.observableArrayList(cameraList)); + } + + /** + * Fills the choiceboxes for selecting a preset, given the selection + * of a certain camera. + */ + private void initPreset(String prepend) { + final List presetList = new ArrayList(); + final ChoiceBox cam = (prepend.equals("add")) ? addCamera : editCamera; + final ChoiceBox preset = (prepend.equals("add")) ? addPreset : editPreset; + + cam.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> { + presetList.clear(); + // None option disabled till it is implemented in the backend. + //TODO presetList.add("None"); + + if (newV != null) { + preset.setDisable(false); + + for (int i = 0; i < Camera. + getCamera(cam.getSelectionModel().getSelectedIndex()).getPresetAmount(); ++i) { + presetList.add(Integer.toString(i + 1)); + } + + preset.setItems(FXCollections.observableArrayList(presetList)); + } else { + preset.setDisable(true); + } + }); + + addPreset.setDisable(true); + } + + /** + * Sets the factories of the table columns, aka where they should + * get their value from. + */ + private void setFactories() { + columnID.setCellValueFactory( + new PropertyValueFactory("number")); + + columnShot.setCellValueFactory( + new PropertyValueFactory("shotId")); + + columnCamera.setCellValueFactory(cellData -> + new ReadOnlyObjectWrapper<>(cellData.getValue().getCamera().getNumber() + 1)); + + columnPreset.setCellValueFactory(cellData -> { + if (cellData.getValue().getPreset() == null) { + return new ReadOnlyObjectWrapper<>(); + } else { + return new ReadOnlyObjectWrapper<>( + Integer.toString(cellData.getValue().getPreset().getId() + 1)); + } + }); + + columnDescription.setCellValueFactory( + new PropertyValueFactory("description")); + + columnAction.setCellValueFactory(cellData -> + new ReadOnlyObjectWrapper<>(cellData.getValue())); + + columnAction.setCellFactory(cellData -> new TableCell() { + final Button btnRemove = new Button("Remove"); + + @Override + protected void updateItem(Shot shot, boolean empty) { + super.updateItem(shot, empty); + + if (shot == null) { + setGraphic(null); + return; + } + + setGraphic(btnRemove); + + btnRemove.setOnAction(event -> { + getTableView().getItems().remove(shot); + }); + } + }); + } + + /** + * Sets the onAction for the add new camera button. + */ + private void setAddButton() { + final ObservableList data = FXCollections.observableArrayList(); + + tableEvents.setItems(data); + + btnAdd.setOnAction(event -> { + if (isValidInput()) { + addCamera.setStyle(""); + addPreset.setStyle(""); + addDescription.setStyle(""); + + if (!validateScript(data) + && !AlertDialog.confirmInvalidScriptAdding( + addCamera.getSelectionModel().getSelectedItem())) { + return; + } + + Shot newShot = createNewShot(); + data.add(newShot); + + addShot.clear(); + addCamera.getSelectionModel().clearSelection(); + addPreset.getSelectionModel().clearSelection(); + addDescription.clear(); + } + }); + } + + /** + * Checks if the new shot being added to the script + * is valid. + * + * @return True iff it has no errors. + */ + private boolean isValidInput() { + boolean isValid = true; + + if (addCamera.getSelectionModel().isEmpty()) { + addCamera.setStyle(REDBORDER); + isValid = false; + } + + if (addPreset.getSelectionModel().isEmpty()) { + addPreset.setStyle(REDBORDER); + isValid = false; + } + + if (addDescription.getText().isEmpty()) { + addDescription.setStyle(REDBORDER); + isValid = false; + } + + return isValid; + } + + /** + * Checks if a newly created shot does not create an + * invalid script. + * + * @param data The table data. + * @return True if the script is valid, false otherwise. + */ + private boolean validateScript(List data) { + if (data.isEmpty()) { + return true; + } + + Shot last = data.get(data.size() - 1); + + if (last.getCamera().getNumber() + == addCamera.getSelectionModel().getSelectedIndex()) { + return false; + } + + return true; + } + + /** + * Creates a new shot based on the users' input. + * @return The newly created shot. + */ + private Shot createNewShot() { + maximumId++; + + if (addPreset.getSelectionModel().getSelectedItem().equals("None")) { + final Shot newShot = new Shot( + maximumId, + addShot.getText(), + Camera.getCamera(addCamera.getSelectionModel().getSelectedIndex()), + addDescription.getText() + ); + + return newShot; + } else { + final Shot newShot = new Shot( + maximumId, + addShot.getText(), + Camera.getCamera(addCamera.getSelectionModel().getSelectedIndex()), + Camera.getCamera(addCamera.getSelectionModel().getSelectedIndex()) + .getPreset(new Integer(addPreset.getSelectionModel().getSelectedItem()) - 1), + addDescription.getText() + ); + + return newShot; + } + } + + /** + * Sets the onAction for the back button. + */ + private void setBackButton() { + btnBack.setOnAction(event -> { + if (!tableEvents.getItems().isEmpty() && !AlertDialog.confirmExiting()) { + return; + } + + Script backup = new Script(backupList); + backup.setName(ContextTFP.getScript().getName()); + + ContextTFP.setScript(backup); + MenuController.show(); + }); + } + + /** + * Sets the onAction for the save buttons. + */ + private void setSaveButton() { + if (backupList.isEmpty()) { + btnSave.setDisable(true); + } + + btnSave.setOnAction(event -> { + setSaveAction(event, false); + }); + + btnSaveAs.setOnAction(event -> { + setSaveAction(event, true); + }); + } + + /** + * Handles the saving of a file. + */ + private void setSaveAction(ActionEvent event, boolean showDialog) { + final Script script = new Script(tableEvents.getItems()); + + if (!showValid(script, 1)) { + return; + } + + File file; + + if (showDialog) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Save script"); + fileChooser.setInitialFileName("script"); + fileChooser.getExtensionFilters().add(new ExtensionFilter("XML (*.xml)", "*.xml")); + + file = fileChooser.showSaveDialog(((Node) event.getTarget()).getScene().getWindow()); + } else { + file = new File(SaveScript.getSaveLocation()); + } + + if (file != null) { + try { + SaveScript.setSaveLocation(file.getAbsolutePath()); + SaveScript.save(script); + + script.setName(file.getName()); + ContextTFP.setScript(script); + + AlertDialog.confirmExitingAfterSaving(file); + } catch (Exception e) { + AlertDialog.errorSaveUnsuccesful(e, file); + } + } + } + + /** + * Checks if a script is valid and gives an error message when it isn't. + * + * @param level The level of alert. Should be 1 for CONFIRMATION + * or 2 for WARNING. Other values are ignored. + * @return True if the user wants to continue and ignore the error. + */ + public static boolean showValid(Script script, int level) { + Shot error = script.isValid(); + + if (error != null) { + Alert alert = null; + + if (level == 1) { + alert = AlertDialog.confirmInvalidScriptSaving(error); + } else if (level == 2) { + alert = AlertDialog.warningInvalidScriptLoading(error); + } else { + return true; + } + + Optional result = alert.showAndWait(); + + if (result.get() == ButtonType.CANCEL) { + return false; + } + } + + return true; + } + /** * Allows for editable rows in the table. This method defines the * necessary components for row by row editing of the table. @@ -218,24 +527,24 @@ private void allowEditing() { */ private void initEditButtons() { btnEditConfirm.setOnAction(event -> { - boolean emptyField = false; + boolean isValid = true; if (editCamera.getSelectionModel().isEmpty()) { - editCamera.setStyle("-fx-border-color: red;"); - emptyField = true; + editCamera.setStyle(REDBORDER); + isValid = false; } if (editPreset.getSelectionModel().isEmpty()) { - editPreset.setStyle("-fx-border-color: red;"); - emptyField = true; + editPreset.setStyle(REDBORDER); + isValid = false; } if (editDescription.getText().isEmpty()) { - editDescription.setStyle("-fx-border-color: red;"); - emptyField = true; + editDescription.setStyle(REDBORDER); + isValid = false; } - if (!emptyField) { + if (isValid) { editCamera.setStyle(""); editPreset.setStyle(""); editDescription.setStyle(""); @@ -261,7 +570,7 @@ private void initTable() { editShot.setText(nv.getShotId()); editCamera.getSelectionModel().select(nv.getCamera().getNumber()); if (nv.getPreset() != null) { - editPreset.getSelectionModel().select(nv.getPreset().getId()); //+1 + editPreset.getSelectionModel().select(nv.getPreset().getId() + 1); } else { editPreset.getSelectionModel().select(0); } @@ -299,7 +608,7 @@ private void editConfirmAction(Shot shot) { shot.setCamera(Camera.getCamera(editCamera.getSelectionModel().getSelectedIndex())); if (!editPreset.getSelectionModel().getSelectedItem().equals("None")) { shot.setPreset(Camera.getCamera(editCamera.getSelectionModel().getSelectedIndex()) - .getPreset(new Integer(editPreset.getSelectionModel().getSelectedIndex()))); //-1 + .getPreset(new Integer(editPreset.getSelectionModel().getSelectedItem()) - 1)); } else { shot.setPreset(null); } @@ -429,304 +738,6 @@ private EventHandler createDragDroppedHandler(TableCell cell }; } - /** - * Fills the choiceboxes for selecting a camera, both the box that - * adds a new camera as the box that is shown when editing a shot. - */ - private void initCamera() { - final List cameraList = new ArrayList(); - - for (int i = 0; i < Camera.getCameraAmount(); ++i) { - cameraList.add(i + 1); - } - - addCamera.setItems(FXCollections.observableArrayList(cameraList)); - editCamera.setItems(FXCollections.observableArrayList(cameraList)); - } - - /** - * Fills the choiceboxes for selecting a preset, given the selection - * of a certain camera. - */ - private void initPreset(String prepend) { - final List presetList = new ArrayList(); - final ChoiceBox cam = (prepend.equals("add")) ? addCamera : editCamera; - final ChoiceBox preset = (prepend.equals("add")) ? addPreset : editPreset; - - cam.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> { - presetList.clear(); - // None option disabled till it is implemented in the backend. - //TODO presetList.add("None"); - - if (newV != null) { - preset.setDisable(false); - - for (int i = 0; i < Camera. - getCamera(cam.getSelectionModel().getSelectedIndex()).getPresetAmount(); ++i) { - presetList.add(Integer.toString(i + 1)); - } - - preset.setItems(FXCollections.observableArrayList(presetList)); - } else { - preset.setDisable(true); - } - }); - - addPreset.setDisable(true); - } - - /** - * Sets the factories of the table columns, aka where they should - * get their value from. - */ - private void setFactories() { - columnID.setCellValueFactory( - new PropertyValueFactory("number")); - - columnShot.setCellValueFactory( - new PropertyValueFactory("shotId")); - - columnCamera.setCellValueFactory(cellData -> - new ReadOnlyObjectWrapper<>(cellData.getValue().getCamera().getNumber() + 1)); - - columnPreset.setCellValueFactory(cellData -> { - if (cellData.getValue().getPreset() == null) { - return new ReadOnlyObjectWrapper<>(); - } else { - return new ReadOnlyObjectWrapper<>( - Integer.toString(cellData.getValue().getPreset().getId() + 1)); - } - }); - - columnDescription.setCellValueFactory( - new PropertyValueFactory("description")); - - columnAction.setCellValueFactory(cellData -> - new ReadOnlyObjectWrapper<>(cellData.getValue())); - - columnAction.setCellFactory(cellData -> new TableCell() { - final Button btnRemove = new Button("Remove"); - - @Override - protected void updateItem(Shot shot, boolean empty) { - super.updateItem(shot, empty); - - if (shot == null) { - setGraphic(null); - return; - } - - setGraphic(btnRemove); - - btnRemove.setOnAction(event -> { - getTableView().getItems().remove(shot); - }); - } - }); - } - - /** - * Sets the onAction for the add new camera button. - */ - private void setAddButton() { - final ObservableList data = FXCollections.observableArrayList(); - - tableEvents.setItems(data); - - btnAdd.setOnAction(event -> { - if (isValidInput()) { - addCamera.setStyle(""); - addPreset.setStyle(""); - addDescription.setStyle(""); - - maximumId++; - createNewShot(data); - - addShot.clear(); - addCamera.getSelectionModel().clearSelection(); - addPreset.getSelectionModel().clearSelection(); - addDescription.clear(); - } - }); - } - - /** - * Checks if the new shot being added to the script - * is valid. - * - * @return True if it has no errors. - */ - private boolean isValidInput() { - boolean isValid = true; - - if (addCamera.getSelectionModel().isEmpty()) { - addCamera.setStyle("-fx-border-color: red;"); - isValid = false; - } - - if (addPreset.getSelectionModel().isEmpty()) { - addPreset.setStyle("-fx-border-color: red;"); - isValid = false; - } - - if (addDescription.getText().isEmpty()) { - addDescription.setStyle("-fx-border-color: red;"); - isValid = false; - } - - return isValid; - } - - /** - * Creates a new shot based on the users' input. - * - * @param data The data already in the table. - */ - private void createNewShot(ObservableList data) { - if (addPreset.getSelectionModel().getSelectedItem().equals("None")) { - final Shot newShot = new Shot( - maximumId, - addShot.getText(), - Camera.getCamera(addCamera.getSelectionModel().getSelectedIndex()), - addDescription.getText() - ); - - data.add(newShot); - } else { - final Shot newShot = new Shot( - maximumId, - addShot.getText(), - Camera.getCamera(addCamera.getSelectionModel().getSelectedIndex()), - Camera.getCamera(addCamera.getSelectionModel().getSelectedIndex()) - .getPreset(new Integer(addPreset.getSelectionModel().getSelectedItem()) - 1), - addDescription.getText() - ); - - data.add(newShot); - } - } - - /** - * Sets the onAction for the back button. - */ - private void setBackButton() { - btnBack.setOnAction(event -> { - if (!tableEvents.getItems().isEmpty()) { - Alert alert = new Alert(AlertType.CONFIRMATION); - alert.setTitle("Confirm quitting"); - alert.setHeaderText("Exiting will erase any unsaved changes"); - alert.setContentText("Are you sure you want to quit? Any unsaved changes " - + "will not be kept."); - - Optional result = alert.showAndWait(); - - if (result.get() == ButtonType.CANCEL) { - return; - } - } - - Script backup = new Script(backupList); - backup.setName(ContextTFP.getScript().getName()); - - ContextTFP.setScript(backup); - MenuController.show(); - }); - } - - /** - * Sets the onAction for the save buttons. - */ - private void setSaveButton() { - if (backupList.isEmpty()) { - btnSave.setDisable(true); - } - - btnSave.setOnAction(event -> { - setSavePopup(event, false); - }); - - btnSaveAs.setOnAction(event -> { - setSavePopup(event, true); - }); - } - - /** - * Handles the saving of a file. - */ - private void setSavePopup(ActionEvent event, boolean showDialog) { - final Script script = new Script(tableEvents.getItems()); - - if (!script.showValid(1)) { - return; - } - - File file; - - if (showDialog) { - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Save script"); - fileChooser.setInitialFileName("script"); - fileChooser.getExtensionFilters().add(new ExtensionFilter("XML (*.xml)", "*.xml")); - - file = fileChooser.showSaveDialog(((Node) event.getTarget()).getScene().getWindow()); - } else { - file = new File(SaveScript.getSaveLocation()); - } - - if (file != null) { - try { - SaveScript.setSaveLocation(file.getAbsolutePath()); - SaveScript.save(script); - - script.setName(file.getName()); - ContextTFP.setScript(script); - - showConfirmExitDialog(file); - } catch (Exception e) { - showErrorDialog(e, file); - } - } - } - - /** - * Displays a confirm to exit dialog when the - * script has been saved. - * - * @param file The file that has been saved. - */ - private void showConfirmExitDialog(File file) { - Alert alert = new Alert(AlertType.CONFIRMATION); - alert.setTitle("Confirm exiting"); - alert.setHeaderText("Saving script was succesful!"); - alert.setContentText("Succesful save of script: " - + file.getName() - + " Do you want to quit to menu?"); - - Optional result = alert.showAndWait(); - - if (result.get() == ButtonType.OK) { - MenuController.show(); - } - } - - /** - * Displayes an error dialog when saving of the script - * was unsuccesful. - * - * @param file The file that was supposed to be saved. - */ - private void showErrorDialog(Exception e, File file) { - Alert alert = new Alert(AlertType.ERROR); - alert.setTitle(e.getMessage()); - alert.setHeaderText("Saving script was unsuccesful!"); - alert.setContentText("Error when trying to save script at location: " - + file.getAbsolutePath() - + "\n\nError: " - + e.getCause()); - - alert.showAndWait(); - } - /** * Calling this method shows this view in the middle of the rootLayout, * forcing the current view to disappear. diff --git a/src/main/java/nl/tudelft/contextproject/gui/CameraLiveController.java b/src/main/java/nl/tudelft/contextproject/gui/DirectorLiveController.java similarity index 79% rename from src/main/java/nl/tudelft/contextproject/gui/CameraLiveController.java rename to src/main/java/nl/tudelft/contextproject/gui/DirectorLiveController.java index 5f9fa07..181ece0 100644 --- a/src/main/java/nl/tudelft/contextproject/gui/CameraLiveController.java +++ b/src/main/java/nl/tudelft/contextproject/gui/DirectorLiveController.java @@ -11,66 +11,80 @@ import javafx.scene.layout.VBox; import nl.tudelft.contextproject.ContextTFP; +import nl.tudelft.contextproject.camera.Camera; +import nl.tudelft.contextproject.presets.Preset; import nl.tudelft.contextproject.script.Script; import java.io.IOException; /** - * This class controls the live view of the camera. + * This class controls the screen that shows the live view + * of the {@link Camera} for the director. The director can use this screen + * to use the application during live production to get an overview + * of the live camera, a preview camera and the {@link Script}, along with controls + * to control the cameras and the {@link Preset Presets} set to these cameras. + * + *

The view section is defined under view/DirectorLiveView.fxml * * @since 0.3 */ -public class CameraLiveController { +public class DirectorLiveController { private static Script script; - + @FXML private Button btnBack; - @FXML private Button swap; + @FXML private Button btnSwap; @FXML private ImageView bigView; @FXML private ImageView smallView; - - @FXML private TextArea smallDescriptionField; - @FXML private TextArea bigDescriptionField; - - @FXML private VBox smallViewBox; - @FXML private VBox bigViewBox; - + + @FXML private Label bigCameraNumberLabel; + @FXML private Label bigShotNumberLabel; + @FXML private Label bigPresetLabel; @FXML private Label bigStatusLabel; - @FXML private Label smallStatusLabel; - @FXML private Label smallShotNumberLabel; @FXML private Label smallCameraNumberLabel; @FXML private Label smallPresetLabel; - @FXML private Label bigShotNumberLabel; - @FXML private Label bigCameraNumberLabel; - @FXML private Label bigPresetLabel; + @FXML private Label smallShotNumberLabel; + @FXML private Label smallStatusLabel; + + @FXML private TextArea bigDescriptionField; + @FXML private TextArea smallDescriptionField; + @FXML private VBox bigViewBox; + @FXML private VBox smallViewBox; + + /** + * Initialize method used by JavaFX. + */ @FXML private void initialize() { script = ContextTFP.getScript(); - + bigView.fitWidthProperty().bind(bigViewBox.widthProperty()); bigView.fitHeightProperty().bind(bigViewBox.heightProperty()); - + smallView.fitWidthProperty().bind(smallViewBox.widthProperty()); smallView.fitHeightProperty().bind(smallViewBox.heightProperty()); - + initializeLabels(); initializeViews(); initializeButtons(); } + /** + * Initializes the labels. + */ private void initializeLabels() { bigStatusLabel.setText("LIVE"); bigStatusLabel.setStyle("-fx-text-fill: red;"); smallStatusLabel.setText("Up next"); - + if (script.getNextShot() != null) { smallShotNumberLabel.setText(script.getNextShot().getShotId()); smallCameraNumberLabel.setText(Integer.toString(script.getNextShot().getCamera().getNumber())); smallPresetLabel.setText(Integer.toString(script.getNextShot().getPreset().getId())); smallDescriptionField.setText(script.getNextShot().getDescription()); } - + if (script.getCurrentShot() != null) { bigShotNumberLabel.setText(script.getCurrentShot().getShotId()); bigCameraNumberLabel.setText(Integer.toString(script.getCurrentShot().getCamera().getNumber())); @@ -78,7 +92,10 @@ private void initializeLabels() { bigDescriptionField.setText(script.getCurrentShot().getDescription()); } } - + + /** + * Initializes the views. + */ private void initializeViews() { Image actual = new Image("placeholder_picture.jpg"); bigView.setImage(actual); @@ -87,14 +104,18 @@ private void initializeViews() { smallView.setImage(current); } + /** + * Initializes the swap and back onAction events. + */ private void initializeButtons() { - swap.setOnAction((event) -> { + btnSwap.setOnAction((event) -> { Image three = bigView.getImage(); bigView.setImage(smallView.getImage()); smallView.setImage(three); String text = bigStatusLabel.getText(); bigStatusLabel.setText(smallStatusLabel.getText()); smallStatusLabel.setText(text); + if (text.equals("LIVE")) { smallStatusLabel.setStyle("-fx-text-fill: red;"); bigStatusLabel.setStyle(""); @@ -103,7 +124,7 @@ private void initializeButtons() { bigStatusLabel.setStyle("-fx-text-fill: red;"); } }); - + btnBack.toFront(); btnBack.setOnAction((event) -> { MenuController.show(); @@ -111,12 +132,13 @@ private void initializeButtons() { } /** - * Shows this view. + * Calling this method shows this view in the middle of the rootLayout, + * forcing the current view to disappear. */ public static void show() { try { FXMLLoader loader = new FXMLLoader(); - loader.setLocation(ContextTFP.class.getResource("view/CameraLiveView.fxml")); + loader.setLocation(ContextTFP.class.getResource("view/DirectorLiveView.fxml")); AnchorPane cameraLiveUI = (AnchorPane) loader.load(); ContextTFP.getRootLayout().setCenter(cameraLiveUI); diff --git a/src/main/java/nl/tudelft/contextproject/gui/LiveStreamHandler.java b/src/main/java/nl/tudelft/contextproject/gui/LiveStreamHandler.java new file mode 100644 index 0000000..0890b11 --- /dev/null +++ b/src/main/java/nl/tudelft/contextproject/gui/LiveStreamHandler.java @@ -0,0 +1,101 @@ +package nl.tudelft.contextproject.gui; + +import com.sun.jna.Memory; + +import javafx.application.Platform; +import javafx.beans.property.FloatProperty; +import javafx.beans.property.SimpleFloatProperty; +import javafx.scene.image.ImageView; +import javafx.scene.image.PixelFormat; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; +import javafx.scene.image.WritablePixelFormat; +import uk.co.caprica.vlcj.component.DirectMediaPlayerComponent; +import uk.co.caprica.vlcj.player.direct.BufferFormat; +import uk.co.caprica.vlcj.player.direct.DirectMediaPlayer; +import uk.co.caprica.vlcj.player.direct.format.RV32BufferFormat; + +import java.nio.ByteBuffer; + +/** + * Handler for streaming media into the GUI through VLC. + * + *

Most credits to Mark Lee for creating this solution. + * + * @see + * Github Repo + * @since 0.6 + */ +public class LiveStreamHandler { + + private PixelWriter pixelWriter; + private WritablePixelFormat pixelFormat; + private DirectMediaPlayerComponent mediaPlayer; + private String streamLink; + private ImageView imageView; + private FloatProperty videoSourceRatioProperty; + + /** + * Creates a LiveStreamHandler object. + */ + public LiveStreamHandler() { + videoSourceRatioProperty = new SimpleFloatProperty(0.5625f); + } + + /** + * Starts playing the media. + */ + public void start() { + mediaPlayer.getMediaPlayer().playMedia(streamLink); + } + + /** + * Stops playing the media and release associated resources. + */ + public void stop() { + mediaPlayer.getMediaPlayer().stop(); + mediaPlayer.getMediaPlayer().release(); + } + + /** + * Returns an ImageView object that can be put into the JavaFX framework. + * @param streamLink link to media stream. + * @param width Width of the ImageView. + * @param height Height of the ImageView. + * @return a ImageView object displaying the stream. + */ + public ImageView createImageView(String streamLink, double width, double height) { + WritableImage writableImage = new WritableImage((int) width, (int) height); + imageView = new ImageView(writableImage); + this.streamLink = streamLink; + this.pixelWriter = writableImage.getPixelWriter(); + this.pixelFormat = PixelFormat.getByteBgraPreInstance(); + this.mediaPlayer = new DirectMediaPlayerComponent((sourceWidth, sourceHeight) -> { + Platform.runLater( () -> { + videoSourceRatioProperty.set((float) sourceHeight / (float) sourceWidth); + }); + return new RV32BufferFormat((int) width, (int) height); + }) { + @Override + public void display(DirectMediaPlayer mediaPlayer, Memory[] nativeBuffers, BufferFormat bufferFormat) { + Memory nativeBuffer = mediaPlayer.lock()[0]; + try { + ByteBuffer byteBuffer = nativeBuffer.getByteBuffer(0, nativeBuffer.size()); + pixelWriter.setPixels(0, 0, bufferFormat.getWidth(), bufferFormat.getHeight(), + pixelFormat, byteBuffer, bufferFormat.getPitches()[0]); + } finally { + mediaPlayer.unlock(); + } + } + }; + return imageView; + } + + /** + * Returns the ratio of the media currently playing. + * @return Ratio of the media currently playing. + */ + public FloatProperty getRatio() { + return videoSourceRatioProperty; + } +} diff --git a/src/main/java/nl/tudelft/contextproject/gui/MenuController.java b/src/main/java/nl/tudelft/contextproject/gui/MenuController.java index 851cb3f..5597cf3 100644 --- a/src/main/java/nl/tudelft/contextproject/gui/MenuController.java +++ b/src/main/java/nl/tudelft/contextproject/gui/MenuController.java @@ -3,8 +3,6 @@ import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.AnchorPane; @@ -14,13 +12,15 @@ import nl.tudelft.contextproject.ContextTFP; import nl.tudelft.contextproject.saveLoad.LoadScript; import nl.tudelft.contextproject.saveLoad.SaveScript; +import nl.tudelft.contextproject.script.Script; import java.io.File; import java.io.IOException; /** * Controller class for the main menu. This class controls the actions to be taken - * when one of the menu buttons is clicked. + * when one of the menu buttons is clicked. Additionally, this class is responsible + * for the displaying of the logo and the label that indicates the active {@link Script}. * * @since 0.1 */ @@ -34,6 +34,7 @@ public class MenuController { @FXML private Button btnPreview; @FXML private Button btnLoadScript; + @FXML private Label lblVersion; @FXML private Label lblScript; /** @@ -42,16 +43,19 @@ public class MenuController { @FXML private void initialize() { final String name = ContextTFP.getScript().getName(); - if (name.equals("") || name == null) { - setLabel("None"); + if (name.equals("")) { + setScriptLabel("None"); } else { - setLabel(name); + setScriptLabel(name); } - btnCreateScript.setOnAction(event -> { - CreateScriptController.show(); - }); + setVersionLabel("0.6"); + initLoadButton(); + initOtherButtons(); + } + + private void initLoadButton() { btnLoadScript.setOnAction(event -> { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Select script to use"); @@ -63,34 +67,27 @@ public class MenuController { try { LoadScript.setLoadLocation(file.getAbsolutePath()); SaveScript.setSaveLocation(file.getAbsolutePath()); - + ContextTFP.setScript(LoadScript.load()); ContextTFP.getScript().setName(file.getName()); + setScriptLabel(file.getName()); - Alert alert = new Alert(AlertType.INFORMATION); - alert.setTitle("Info Dialog"); - alert.setHeaderText("Loading script was succesful!"); - alert.setContentText("Succesful load of script: " + file.getName()); - - setLabel(file.getName()); - - alert.showAndWait(); - - ContextTFP.getScript().showValid(2); - + AlertDialog.infoSuccesfulLoading(file); + CreateScriptController.showValid(ContextTFP.getScript(), 2); } catch (Exception e) { - Alert alert = new Alert(AlertType.ERROR); - alert.setTitle(e.getMessage()); - alert.setHeaderText("Loading script was unsuccesful!"); - alert.setContentText("Error when trying to load script at location: " - + file.getAbsolutePath() - + "\n\nError: " - + e.getCause()); - - alert.showAndWait(); + AlertDialog.errorSaveUnsuccesful(e, file); } } }); + } + + /** + * Initializes the rest of the buttons. + */ + private void initOtherButtons() { + btnCreateScript.setOnAction(event -> { + CreateScriptController.show(); + }); btnPreview.setOnAction(event -> { PreviewController.show(); @@ -101,11 +98,11 @@ public class MenuController { }); btnDirector.setOnAction(event -> { - CameraLiveController.show(); + DirectorLiveController.show(); }); - - btnCameraman.setOnAction((event) -> { - CameramanUIController.show(); + + btnCameraman.setOnAction(event -> { + CameramanLiveController.show(); }); btnEditScript.setOnAction(event -> { @@ -120,12 +117,23 @@ public class MenuController { * @param text The text to set to the label. This will be appended to the * string: "Current script: " */ - public void setLabel(String text) { + private void setScriptLabel(String text) { lblScript.setText("Current script: " + text); } /** - * Shows this view. + * Sets the text of the version label on the menu. + * + * @param text The text to set to the label. This will be appended to the + * string: "Current version: " + */ + private void setVersionLabel(String text) { + lblVersion.setText("Current version: " + text); + } + + /** + * Calling this method shows this view in the middle of the rootLayout, + * forcing the current view to disappear. */ public static void show() { try { diff --git a/src/main/java/nl/tudelft/contextproject/gui/PresetController.java b/src/main/java/nl/tudelft/contextproject/gui/PresetController.java index 11d37b8..da06d64 100644 --- a/src/main/java/nl/tudelft/contextproject/gui/PresetController.java +++ b/src/main/java/nl/tudelft/contextproject/gui/PresetController.java @@ -1,5 +1,7 @@ package nl.tudelft.contextproject.gui; +import javafx.application.Platform; +import javafx.beans.property.FloatProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -15,11 +17,9 @@ import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.VBox; - import nl.tudelft.contextproject.ContextTFP; import nl.tudelft.contextproject.camera.Camera; import nl.tudelft.contextproject.presets.InstantPreset; @@ -42,7 +42,6 @@ public class PresetController { @FXML private ChoiceBox cameraSelecter; @FXML private TextField presetID; @FXML private TextField description; - @FXML private ImageView cameraView; @FXML private Button btnBack; @FXML private Button btnSave; @FXML private Button btnRemove; @@ -52,7 +51,9 @@ public class PresetController { @FXML private TableColumn descColumn; @FXML private VBox vBox; + private LiveStreamHandler streamHandler; private ObservableList data = FXCollections.observableArrayList(); + private ImageView imageView; @FXML private void initialize() { @@ -63,7 +64,6 @@ private void initialize() { } cameraSelecter.setItems(FXCollections.observableArrayList(cameraList)); - cameraView.setImage(new Image("placeholder_picture.jpg")); applySettings(); setFactories(); @@ -73,12 +73,32 @@ private void initialize() { } /** - * Applies javafx settings that can't be specified in the fxml. + * Applies JavaFX settings that can't be specified in the FXML. */ private void applySettings() { vBox.setAlignment(Pos.CENTER); - cameraView.fitWidthProperty().bind(vBox.widthProperty()); - cameraView.fitHeightProperty().bind(vBox.heightProperty()); + + vBox.getChildren().clear(); + + System.setProperty("jna.library.path", "C:\\Program Files\\VideoLAN\\VLC"); + } + + /** + * Updates the camera stream to the stream referenced by the specified link. + * @param streamLink the link to the video stream to be played next. + */ + public void updateStream(String streamLink) { + if (streamHandler != null) { + streamHandler.stop(); + } + streamHandler = new LiveStreamHandler(); + imageView = streamHandler.createImageView(streamLink, 1920, 1080); + Platform.runLater(() -> { + fitImageViewSize((float) vBox.getWidth(), (float) vBox.getHeight()); + }); + vBox.getChildren().clear(); + vBox.getChildren().add(imageView); + streamHandler.start(); } /** @@ -93,12 +113,16 @@ private void setFactories() { } /** - * Adds all actions and listeners to the javafx components. + * Adds all actions and listeners to the JavaFX components. */ private void setActions() { tableView.setItems(data); btnBack.setOnAction((event) -> { + if (streamHandler != null) { + streamHandler.stop(); + } + MenuController.show(); }); @@ -113,7 +137,6 @@ private void setActions() { } }); - //TODO: Update current view of camera with livefeed from camera cameraSelecter.setOnAction((event) -> { Camera cam = Camera.getCamera(cameraSelecter.getValue() - 1); HashMap presets = cam.getPresets(); @@ -121,6 +144,11 @@ private void setActions() { for (Preset p : presets.values()) { data.add(p); } + if (cam.hasConnection()) { + updateStream(cam.getConnection().getStreamLink()); + } else { + updateStream("http://www.formisimo.com/blog/wp-content/uploads/2014/04/error-mesage.png"); + } }); btnRemove.setOnAction((event) -> { @@ -132,6 +160,40 @@ private void setActions() { data.remove(selected); } }); + + vBox.widthProperty().addListener((observable, oldValue, newValue) -> { + if (streamHandler != null) { + fitImageViewSize(newValue.floatValue(), (float) vBox.getHeight()); + } + }); + + vBox.heightProperty().addListener((observable, oldValue, newValue) -> { + if (streamHandler != null) { + fitImageViewSize((float) vBox.getWidth(), newValue.floatValue()); + } + }); + } + + /** + * Resizes the ImageView. + * @param width The new width of the ImageView. + * @param height The new height of the ImageView. + */ + private void fitImageViewSize(float width, float height) { + FloatProperty videoSourceRatioProperty = streamHandler.getRatio(); + float fitHeight = videoSourceRatioProperty.get() * width; + if (fitHeight > height) { + imageView.setFitHeight(height); + double fitWidth = height / videoSourceRatioProperty.get(); + imageView.setFitWidth(fitWidth); + imageView.setX((width - fitWidth) / 2); + imageView.setY(0); + } else { + imageView.setFitWidth(width); + imageView.setFitHeight(fitHeight); + imageView.setY((height - fitHeight) / 2); + imageView.setX(0); + } } /** diff --git a/src/main/java/nl/tudelft/contextproject/gui/PreviewController.java b/src/main/java/nl/tudelft/contextproject/gui/PreviewController.java index 6309554..e2746c9 100644 --- a/src/main/java/nl/tudelft/contextproject/gui/PreviewController.java +++ b/src/main/java/nl/tudelft/contextproject/gui/PreviewController.java @@ -4,10 +4,8 @@ import javafx.animation.Timeline; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.Button; @@ -22,6 +20,7 @@ import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; import javafx.util.Duration; + import nl.tudelft.contextproject.ContextTFP; import nl.tudelft.contextproject.presets.Preset; import nl.tudelft.contextproject.script.Script; @@ -29,10 +28,14 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** - * Controller class for the script preview screen. + * Controller class for the script preview screen. This screen allows + * users to preview a {@link Script} with {@link Preset Presets}, and + * set the duration of a show all while live previewing what the shot + * will capture. * * @since 0.2 */ @@ -46,16 +49,13 @@ public class PreviewController { @FXML private Button rightArrow; @FXML private ChoiceBox cb; - - @FXML private StackPane imagePane; @FXML private ImageView actualCam; - @FXML private ImageView viewOne; @FXML private ImageView viewTwo; @FXML private ImageView viewThree; @FXML private ImageView viewFour; - + @FXML private Label camNum; @FXML private Label presetNum; @@ -64,22 +64,27 @@ public class PreviewController { @FXML private Rectangle highlight3; @FXML private Rectangle highlight4; + @FXML private StackPane imagePane; + @FXML private TextArea descArea; @FXML private TextField duration; private SimpleIntegerProperty currentShot; private Shot currentFirst; - private int currentHighlight; private Timeline timeline; private List shots; private List views; private Script script; - @FXML private void initialize() { + private int currentHighlight; + /** + * Initialize method used by JavaFX. + */ + @FXML private void initialize() { script = ContextTFP.getScript(); shots = script.getShots(); - + currentShot = new SimpleIntegerProperty(0); currentHighlight = -1; @@ -89,81 +94,90 @@ public class PreviewController { initializeActions(); initializeListeners(); initializeTextFields(); + if (!shots.isEmpty()) { initializeTimeline(); } - + actualCam.fitWidthProperty().bind(imagePane.widthProperty()); actualCam.fitHeightProperty().bind(imagePane.heightProperty()); - + disableHighlight(); + descArea.setEditable(false); } /** * Initialize functionality on action. */ private void initializeActions() { - back.setOnAction((event) -> { + back.setOnAction(event -> { MenuController.show(); }); - + int check = 0; + if (shots.size() < views.size()) { check = shots.size(); } else { check = views.size(); } + for (int i = 0; i < check; i++) { initializeViewActions(views.get(i), i + 1); } + if (shots.size() > 0) { initializeShotActions(); } } - + /** * Initialize actions which require shots to function. */ private void initializeShotActions() { - play.setOnAction((event) -> { + play.setOnAction(event -> { timeline.playFromStart(); }); - stop.setOnAction((event) -> { + stop.setOnAction(event -> { timeline.stop(); }); - - confirm.setOnAction((event) -> { + + confirm.setOnAction(event -> { Shot current = shots.get(currentShot.get() - 1); current.setDuration(Double.parseDouble(duration.getText())); }); - - leftArrow.setOnAction((event) -> { + + leftArrow.setOnAction(event -> { int shotNumFirst = currentFirst.getNumber() - 1; + if (shotNumFirst > 0) { switchViews(shots.get(shotNumFirst - 1), false); - highlightRight(); + moveHighlight(true); } timeline.stop(); }); - - rightArrow.setOnAction((event) -> { + + rightArrow.setOnAction(event -> { int shotNumFirst = currentFirst.getNumber() - 1; + if (shotNumFirst < shots.size() - 4) { switchViews(shots.get(shotNumFirst + 1), false); - highlightLeft(); + moveHighlight(false); } + timeline.stop(); }); } - + /** * Applies the standard settings to an ImageView. + * * @param view The ImageView to apply the setting to. * @param id The id of the ImageView. */ private void initializeViewActions(ImageView view, int id) { - view.setOnMouseClicked((event) -> { + view.setOnMouseClicked(event -> { int diff = checkShots(shots.get(currentFirst.getNumber() - 1)) - id; int shotNum = currentFirst.getNumber() - diff; @@ -177,58 +191,59 @@ private void initializeViewActions(ImageView view, int id) { * Initialize the listeners. */ private void initializeListeners() { - final ChangeListener changeListener = new ChangeListener() { - @Override - public void changed(ObservableValue observable, Number oldValue, Number newValue) { - cb.setValue(shots.get(currentShot.get() - 1)); - Shot shot = shots.get(currentShot.get() - 1); - - if (shot.getDuration() > 0) { - duration.setText(Double.toString(shots.get(currentShot.get() - 1).getDuration())); - } else { - duration.setText(""); - } + initChangeListener(); + duration.focusedProperty().addListener((obs, oldV, newV) -> { + if (!newV && !confirm.focusedProperty().get()) { double shotDuration = shots.get(currentShot.get() - 1).getDuration(); + if (shotDuration > 0) { - timeline.getKeyFrames().clear(); - timeline.getKeyFrames().add(new KeyFrame( - Duration.millis(shots.get(currentShot.get() - 1).getDuration() * 1000), ae -> nextImage())); + duration.setText(Double.toString(shotDuration)); } else { - timeline.getKeyFrames().add(new KeyFrame( - Duration.millis(1000), ae -> nextImage())); + duration.setText(""); } - timeline.stop(); } - }; + }); - currentShot.addListener(changeListener); + cb.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> { + showShot(newV); - duration.focusedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue arg0, Boolean oldValue, Boolean newValue) { - if (!newValue && !confirm.focusedProperty().get()) { - double shotDuration = shots.get(currentShot.get() - 1).getDuration(); - - if (shotDuration > 0) { - duration.setText(Double.toString(shotDuration)); - } else { - duration.setText(""); - } - } + if (cb.getValue() == null) { + duration.setDisable(true); + } else { + duration.setDisable(false); } }); + } - cb.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { - public void changed(ObservableValue ov, Shot oldVal, Shot newVal) { - showShot(newVal); - if (cb.getValue() == null) { - duration.setDisable(true); - } else { - duration.setDisable(false); - } + /** + * Initializes the changelistener. + */ + private void initChangeListener() { + final ChangeListener changeListener = (obs, oldV, newV) -> { + Shot shot = shots.get(currentShot.get() - 1); + cb.setValue(shot); + + if (shot.getDuration() > 0) { + duration.setText(Double.toString(shot.getDuration())); + } else { + duration.setText(""); } - }); + + double shotDuration = shot.getDuration(); + if (shotDuration > 0) { + timeline.getKeyFrames().clear(); + timeline.getKeyFrames().add(new KeyFrame( + Duration.millis(shotDuration * 1000), ae -> nextImage())); + } else { + timeline.getKeyFrames().add(new KeyFrame( + Duration.millis(1000), ae -> nextImage())); + } + + timeline.stop(); + }; + + currentShot.addListener(changeListener); } /** @@ -237,20 +252,17 @@ public void changed(ObservableValue ov, Shot oldVal, Shot newVal private void initializeTextFields() { duration.setDisable(true); - duration.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler() { - @Override - public void handle(KeyEvent event) { - String character = event.getCharacter(); + duration.addEventFilter(KeyEvent.KEY_TYPED, event -> { + String character = event.getCharacter(); - if (!character.matches("[0-9.]")) { - event.consume(); - } else if (character.equals(".")) { - if (duration.getText().contains(".")) { - event.consume(); - } - } else if (duration.getText().length() == 5) { + if (!character.matches("[0-9.]")) { + event.consume(); + } else if (character.equals(".")) { + if (duration.getText().contains(".")) { event.consume(); } + } else if (duration.getText().length() == 5) { + event.consume(); } }); } @@ -260,13 +272,13 @@ public void handle(KeyEvent event) { */ private void initializeImages() { actualCam.setImage(new Image("placeholder_picture.jpg")); - + views = new ArrayList(); views.add(viewOne); views.add(viewTwo); views.add(viewThree); views.add(viewFour); - + for (int i = 0; i < 4; i++) { views.get(i).setImage(createInitialImage(i)); } @@ -281,6 +293,7 @@ private void initializeShots() { shots.get(i).getPreset().setImageLocation(getImageLoc(i)); checkPresetImages(i); } + if (!shots.isEmpty()) { currentFirst = shots.get(0); } @@ -291,9 +304,11 @@ private void initializeShots() { */ private void initializeChoices() { ObservableList choices = FXCollections.observableArrayList(); + for (int i = 0; i < shots.size(); i++) { choices.add(shots.get(i)); } + cb.setItems(choices); } @@ -303,6 +318,7 @@ private void initializeChoices() { */ private void initializeTimeline() { double initialDuration = shots.get(0).getDuration(); + if (initialDuration > 0) { timeline = new Timeline(new KeyFrame(Duration.millis(initialDuration * 1000), ae -> nextImage())); } else { @@ -312,11 +328,11 @@ private void initializeTimeline() { /** * Checks which shot in the shotlist is being viewed. - * @return - the shotNumber in the shotlist + * @return The shotNumber in the shotlist. */ private int checkShots(Shot shot) { int returnValue = -1; - + if (shot.getNumber() <= shots.size() - 3) { returnValue = 1; } else if (shot.getNumber() == shots.size() - 2) { @@ -326,23 +342,31 @@ private int checkShots(Shot shot) { } else if (shot.getNumber() == shots.size()) { returnValue = 4; } - + return returnValue; } - + + /** + * Checks if shot #i has an image. If not, set the image + * to be displayed as the error image. + * + * @param i The shot to be checked. + */ private void checkPresetImages(int i) { Preset preset = shots.get(i).getPreset(); + try { new Image(preset.getImage()); } catch (IllegalArgumentException e) { preset.setImageLocation("error.jpg"); } } - + /** - * gets an image location as an example for each dummy shot. - * @param i - number of the shot. - * @return - image location. + * Gets an image location as an example for each dummy shot. + * + * @param i The number of the shot. + * @return The image location. */ private String getImageLoc(int i) { int check = i % 6; @@ -363,7 +387,7 @@ private String getImageLoc(int i) { return "error.jpg"; } } - + /** * Creates initial preview images for the shotbox. */ @@ -374,7 +398,7 @@ private Image createInitialImage(int i) { return new Image("gray.jpg"); } } - + /** * Shows the next image on the actual camera screen. */ @@ -390,7 +414,7 @@ private void nextImage() { /** * Shows the shot that is selected. - * @param shot - The shot to be showed + * @param shot The shot to be showed. */ private void showShot(Shot shot) { currentShot.set(shot.getNumber()); @@ -398,55 +422,50 @@ private void showShot(Shot shot) { switchViews(shot, true); switchInfo(shot); } - + + /** + * Switches the views of the shots. + */ private void switchViews(Shot shot, boolean shotSwitch) { int check = checkShots(shot); + + Image active = new Image(shot.getPreset().getImage()); + currentFirst = shots.get(shot.getNumber() - check); + + if (shotSwitch) { + switchHighlights(check); + } - switch (check) { - case 1: - viewOne.setImage(new Image(shot.getPreset().getImage())); - viewTwo.setImage(new Image(shots.get(shot.getNumber()).getPreset().getImage())); - viewThree.setImage(new Image(shots.get(shot.getNumber() + 1).getPreset().getImage())); - viewFour.setImage(new Image(shots.get(shot.getNumber() + 2).getPreset().getImage())); - currentFirst = shot; - if (shotSwitch) { - switchHighlights(1); - } - break; - case 2: - viewOne.setImage(new Image(shots.get(shot.getNumber() - 2).getPreset().getImage())); - viewTwo.setImage(new Image(shot.getPreset().getImage())); - viewThree.setImage(new Image(shots.get(shot.getNumber()).getPreset().getImage())); - viewFour.setImage(new Image(shots.get(shot.getNumber() + 1).getPreset().getImage())); - currentFirst = shots.get(shot.getNumber() - 2); - if (shotSwitch) { - switchHighlights(2); - } - break; - case 3: - viewOne.setImage(new Image(shots.get(shot.getNumber() - 3).getPreset().getImage())); - viewTwo.setImage(new Image(shots.get(shot.getNumber() - 2).getPreset().getImage())); - viewThree.setImage(new Image(shot.getPreset().getImage())); - viewFour.setImage(new Image(shots.get(shot.getNumber()).getPreset().getImage())); - currentFirst = shots.get(shot.getNumber() - 3); - if (shotSwitch) { - switchHighlights(3); - } - break; - case 4: - viewOne.setImage(new Image(shots.get(shot.getNumber() - 4).getPreset().getImage())); - viewTwo.setImage(new Image(shots.get(shot.getNumber() - 3).getPreset().getImage())); - viewThree.setImage(new Image(shots.get(shot.getNumber() - 2).getPreset().getImage())); - viewFour.setImage(new Image(shot.getPreset().getImage())); - currentFirst = shots.get(shot.getNumber() - 4); - if (shotSwitch) { - switchHighlights(4); - } - break; - default: return; + List list = new ArrayList(); + list.addAll(Arrays.asList(viewOne, viewTwo, viewThree, viewFour)); + + List offset = new ArrayList(); + offset.addAll(Arrays.asList(-1, 0, 1, 2)); + + for (int i = 0; i < offset.size(); ++i) { + offset.set(i, offset.get(i) - (check - 1)); } + + viewOne.setImage(getImageByNumber(shot, offset.get(0))); + viewTwo.setImage(getImageByNumber(shot, offset.get(1))); + viewThree.setImage(getImageByNumber(shot, offset.get(2))); + viewFour.setImage(getImageByNumber(shot, offset.get(3))); + + list.get(check - 1).setImage(active); } - + + /** + * Returns an image from the list of shots, retrieving + * the shot + the number param number. + * + * @param shot The shot that is clicked on. + * @param number The number to offset the images with. + * @return A new Image object as defined above. + */ + private Image getImageByNumber(Shot shot, int number) { + return new Image(shots.get(shot.getNumber() + number).getPreset().getImage()); + } + /** * Switch info of the shot info box to the corresponding selected shot. */ @@ -455,14 +474,14 @@ private void switchInfo(Shot shot) { presetNum.setText(String.valueOf(shot.getPreset().getId())); descArea.setText(shot.getDescription()); } - + /** * Switches the highlight box. */ private void switchHighlights(int id) { disableHighlight(); currentHighlight = id; - + switch (id) { case 1: highlight1.setOpacity(1); break; case 2: highlight2.setOpacity(1); break; @@ -471,42 +490,33 @@ private void switchHighlights(int id) { default: break; } } - + /** - * Moves the highlightbox one shot to the right. + * Moves the highlightbox one shot. + * @param right True if the highlight should move right, + * false if the highlight should move left. */ - private void highlightRight() { - currentHighlight++; - if (highlightCheck()) { - switchHighlights(currentHighlight); + private void moveHighlight(boolean right) { + if (right) { + currentHighlight++; } else { - disableHighlight(); + currentHighlight--; } - } - - /** - * Moves the highlightbox one shot to the left. - */ - private void highlightLeft() { - currentHighlight--; + if (highlightCheck()) { switchHighlights(currentHighlight); } else { disableHighlight(); } } - + /** * Checks if the current view to be highlighted is in bounds. */ private boolean highlightCheck() { - if (currentHighlight < 1 || currentHighlight > 4) { - return false; - } else { - return true; - } + return (currentHighlight < 1 || currentHighlight > 4) ? false : true; } - + /** * Sets the highlight box invisible. */ @@ -516,9 +526,10 @@ private void disableHighlight() { highlight3.setOpacity(0); highlight4.setOpacity(0); } - + /** - * Shows this view. + * Calling this method shows this view in the middle of the rootLayout, + * forcing the current view to disappear. */ public static void show() { try { diff --git a/src/main/java/nl/tudelft/contextproject/presets/InstantPreset.java b/src/main/java/nl/tudelft/contextproject/presets/InstantPreset.java index b70cb2a..22ae177 100644 --- a/src/main/java/nl/tudelft/contextproject/presets/InstantPreset.java +++ b/src/main/java/nl/tudelft/contextproject/presets/InstantPreset.java @@ -7,10 +7,11 @@ * Class to represent an instant camera preset. * When {@link #applyTo(Camera)} is called, this sets the specified camera * settings instantly to the camera. + * * @since 0.2 */ public class InstantPreset extends Preset { - + /** * Creates an InstantPreset object with to set camera settings toSet. * @@ -20,10 +21,11 @@ public class InstantPreset extends Preset { public InstantPreset(CameraSettings toSet, int id) { super(toSet, id); } - + /** * Creates an InstantPreset object with to set camera settings toSet. * Also a description is added. + * * @param toSet Camera settings to set when applied. * @param id The identifier of this preset. * @param desc The description of the preset. @@ -40,5 +42,4 @@ public InstantPreset(CameraSettings toSet, int id, String desc) { public void applyTo(Camera cam) { cam.setSettings(getToSet()); } - } diff --git a/src/main/java/nl/tudelft/contextproject/presets/MovementType.java b/src/main/java/nl/tudelft/contextproject/presets/MovementType.java index 2b517ee..3eea0c0 100644 --- a/src/main/java/nl/tudelft/contextproject/presets/MovementType.java +++ b/src/main/java/nl/tudelft/contextproject/presets/MovementType.java @@ -10,34 +10,38 @@ */ public enum MovementType { ZOOM_IN, ZOOM_OUT, LEFT, RIGHT, UP, DOWN, CUSTOM; - + private static EnumMap typeToCameraSettings; private static EnumMap typeToName; - + /** * Get the camera settings belonging to the MovementType. - * @param mt - The MovementType of which the camera settings are requested. - * @return - The camera settings for the type requested. + * + * @param mt The MovementType of which the camera settings are requested. + * @return The camera settings for the type requested. */ public static CameraSettings getCameraSettings(MovementType mt) { if (typeToCameraSettings == null) { initMapping(); } + return typeToCameraSettings.get(mt); } - + /** * Get the name corresponding to the MovementType. - * @param mt - The MovementType of which the name is requested. - * @return - The name for the type requested. + * + * @param mt The MovementType of which the name is requested. + * @return The name for the type requested. */ public static String getName(MovementType mt) { if (typeToCameraSettings == null) { initMapping(); } + return typeToName.get(mt); } - + /** * Initiate the different MovementTypes and put them in a mapping * along with their camera settings and their names. @@ -45,15 +49,15 @@ public static String getName(MovementType mt) { private static void initMapping() { typeToCameraSettings = new EnumMap<>(MovementType.class); typeToName = new EnumMap<>(MovementType.class); - + typeToCameraSettings.put(MovementType.ZOOM_IN, new CameraSettings(0, 0, 0, 0)); typeToCameraSettings.put(MovementType.ZOOM_OUT, new CameraSettings(0, 0, 0, 0)); typeToCameraSettings.put(MovementType.LEFT, new CameraSettings(0, 0, 0, 0)); typeToCameraSettings.put(MovementType.RIGHT, new CameraSettings(0, 0, 0, 0)); typeToCameraSettings.put(MovementType.UP, new CameraSettings(0, 0, 0, 0)); typeToCameraSettings.put(MovementType.DOWN, new CameraSettings(0, 0, 0, 0)); - typeToCameraSettings.put(MovementType.CUSTOM, new CameraSettings()); - + typeToCameraSettings.put(MovementType.CUSTOM, new CameraSettings(0, 0, 0, 0)); + typeToName.put(MovementType.ZOOM_IN, "Constant zoom in"); typeToName.put(MovementType.ZOOM_OUT, "Constant zoom out"); typeToName.put(MovementType.LEFT, "Constant pan left"); @@ -61,6 +65,5 @@ private static void initMapping() { typeToName.put(MovementType.UP, "Constant tilt up"); typeToName.put(MovementType.DOWN, "Constant tilt down"); typeToName.put(MovementType.CUSTOM, "Custom"); - } } diff --git a/src/main/java/nl/tudelft/contextproject/presets/Preset.java b/src/main/java/nl/tudelft/contextproject/presets/Preset.java index dd96d39..e5d42df 100644 --- a/src/main/java/nl/tudelft/contextproject/presets/Preset.java +++ b/src/main/java/nl/tudelft/contextproject/presets/Preset.java @@ -12,6 +12,7 @@ * * {@link #applyTo(Camera)} should be implemented to apply the * preset to the camera, in its respective way. + * * @since 0.2 */ public abstract class Preset { @@ -20,7 +21,7 @@ public abstract class Preset { private CameraSettings toSet; private int id; private String imageLocation; - + /** * Creates a Preset object with to set camera settings toSet. * @@ -33,7 +34,7 @@ protected Preset(CameraSettings toSet, int identifier) { this.id = identifier; this.imageLocation = ""; } - + /** * Returns the identifier of this preset. * @return The identifier of this preset. @@ -44,18 +45,15 @@ public int getId() { @Override public boolean equals(Object o) { - if (this == o) { - return true; + if (o instanceof Preset) { + Preset preset = (Preset) o; + return getId() == preset.getId() + && Objects.equals(getDescription(), preset.getDescription()) + && Objects.equals(getToSet(), preset.getToSet()) + && Objects.equals(imageLocation, preset.imageLocation); } - if (!(o instanceof Preset)) { - return false; - } - Preset preset = (Preset) o; - boolean result = getId() == preset.getId() - && Objects.equals(getDescription(), preset.getDescription()) - && Objects.equals(getToSet(), preset.getToSet()) - && Objects.equals(imageLocation, preset.imageLocation); - return result; + + return false; } @Override @@ -64,14 +62,13 @@ public int hashCode() { } /** - * Returns the settings the camera should be set to. * @return To be applied camera settings */ public CameraSettings getToSet() { return toSet; } - + /** * Sets the location of the preview image of the preset. * @param loc Location of the preview image. @@ -79,7 +76,7 @@ public CameraSettings getToSet() { public void setImageLocation(String loc) { this.imageLocation = loc; } - + /** * Returns the location of the preview image. * @return Location of the preview image. @@ -87,7 +84,7 @@ public void setImageLocation(String loc) { public String getImage() { return this.imageLocation; } - + /** * Sets the description of the preset. * @param desc The description of the preset. @@ -95,7 +92,7 @@ public String getImage() { public void setDescription(String desc) { this.description = desc; } - + /** * Returns the description of the preset. * @return The description of the preset. @@ -103,7 +100,7 @@ public void setDescription(String desc) { public String getDescription() { return this.description; } - + /** * Applies the preset to the camera. * @param cam Camera the preset should be applied to. diff --git a/src/main/java/nl/tudelft/contextproject/saveLoad/LoadScript.java b/src/main/java/nl/tudelft/contextproject/saveLoad/LoadScript.java index 547ed75..5b3c357 100644 --- a/src/main/java/nl/tudelft/contextproject/saveLoad/LoadScript.java +++ b/src/main/java/nl/tudelft/contextproject/saveLoad/LoadScript.java @@ -30,6 +30,8 @@ */ public final class LoadScript { + private static final Object MUTEX = new Object(); + /** * Location of the save file to load from. * This is set to savefile.xml per default. @@ -41,8 +43,6 @@ public final class LoadScript { */ private static XMLEventReader reader; - private static final Object MUTEX = new Object(); - /** * Since this is a utility class, the constructor may not be called. */ @@ -52,8 +52,7 @@ private LoadScript() { /** * Returns the location of the save file this class loads from. - * - * @return the location of the save file this class loads from. + * @return The location of the save file this class loads from. */ public static String getLoadLocation() { return loadLocation; @@ -64,7 +63,7 @@ public static String getLoadLocation() { * Also creates a new instance of {@link #reader} so it may load from the * new load location when {@link #load()} is called. * - * @param s the new location of the save file this class should load from. + * @param s The new location of the save file this class should load from. */ public static void setLoadLocation(String s) { synchronized (MUTEX) { @@ -77,13 +76,16 @@ public static void setLoadLocation(String s) { * {@link #loadLocation}. * It loads the cameras from the save file and puts them in {@link Camera#CAMERAS}. * It then loads the shots from the save file and returns them as a Script object. - * @return the loaded script + * + * @return The loaded script */ public static Script load() throws XMLStreamException { synchronized (MUTEX) { Camera.clearAllCameras(); List shots = new LinkedList(); reader = createReader(); + checkCorrectDocument(); + while (reader.hasNext()) { XMLEvent event = reader.nextEvent(); if (event.isStartElement()) { @@ -95,20 +97,42 @@ public static Script load() throws XMLStreamException { } } } + return new Script(shots); } } + + /** + * Checks if the document to be read is actually a save file from our + * application. Does this by reading the first two events in the file, + * namely the start of the document and the first tag afterwards, + * which should be 'script'. + * @throws XMLStreamException When the save file is not correct. + */ + private static void checkCorrectDocument() throws XMLStreamException { + if (reader.hasNext()) { + reader.nextEvent(); //skip start of document + } + if (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + if (event.isStartElement() + && "script".equals(event.asStartElement().getName().getLocalPart())) { + return; + } + } + throw new XMLStreamException("This is not a savefile from our program."); + } /** * Creates an XMLEventReader object using the file location specified * in {@link #loadLocation} for use as the loader class variable. * Throws a RuntimeException in the case the save file cannot be read. * - * @return an XMLEventWriter object for use as the writer class variable. + * @return An XMLEventWriter object for use as the writer class variable. */ private static XMLEventReader createReader() { try { - return (XMLInputFactory.newFactory()).createXMLEventReader(new FileInputStream(loadLocation)); + return (XMLInputFactory.newFactory()).createXMLEventReader(new FileInputStream(loadLocation), "UTF-8"); } catch (IOException | XMLStreamException e) { e.printStackTrace(); throw new RuntimeException("Your save file could not be found or read.", e); @@ -118,6 +142,7 @@ private static XMLEventReader createReader() { /** * Reads the 'cameras' section of the XML file. * Assumes that the start element of this section has already been read. + * * @throws XMLStreamException when an error occurs in the XML. */ private static void loadCameras() throws XMLStreamException { @@ -146,6 +171,7 @@ private static void loadCameras() throws XMLStreamException { /** * Reads a 'camera' section of the XML file. * Assumes that the start element of this section has already been read. + * * @throws XMLStreamException when an error occurs in the XML. */ private static void loadCamera() throws XMLStreamException { @@ -173,8 +199,9 @@ private static void loadCamera() throws XMLStreamException { * Reads a 'cameraSettings' section of the XML file. * Assumes that the start element of this section has already been read * and is inserted into this method as the start parameter. - * @param start The start element of the 'cameraSettings' section. - * @return the loaded CameraSettings object. + * + * @param Start The start element of the 'cameraSettings' section. + * @return The loaded CameraSettings object. * @throws XMLStreamException when an error occurs in the XML. */ private static CameraSettings loadCameraSettings(StartElement start) { @@ -183,13 +210,14 @@ private static CameraSettings loadCameraSettings(StartElement start) { Integer.parseInt(start.getAttributeByName(new QName("tilt")).getValue()), Integer.parseInt(start.getAttributeByName(new QName("zoom")).getValue()), Integer.parseInt(start.getAttributeByName(new QName("focus")).getValue()) - ); + ); } /** * Reads a 'presets' section associated with a camera of the XML file and * adds these presets to the camera they belong to. * Assumes that the start element of this section has already been read. + * * @param cam Camera object associated with this 'presets' section. * @throws XMLStreamException when an error occurs in the XML. */ @@ -214,8 +242,9 @@ private static void loadPresets(Camera cam) throws XMLStreamException { * Reads a 'preset' section of the XML file. * Assumes that the start element of this section has already been read * and is inserted into this method as the presetStart parameter. + * * @param presetStart The start element of the 'preset' section. - * @return the loaded Preset object. + * @return The loaded Preset object. * @throws XMLStreamException when an error occurs in the XML. */ private static Preset loadPreset(StartElement presetStart) throws XMLStreamException { @@ -223,6 +252,7 @@ private static Preset loadPreset(StartElement presetStart) throws XMLStreamExcep String description = ""; String imgLoc = ""; AtomicReference toSet = new AtomicReference(); + while (reader.hasNext()) { XMLEvent event = reader.nextEvent(); if (event.isStartElement()) { @@ -248,6 +278,7 @@ private static Preset loadPreset(StartElement presetStart) throws XMLStreamExcep toSet.set(loadCameraSettings(start)); } } + if (event.isEndElement()) { EndElement end = event.asEndElement(); if ("preset".equals(end.getName().getLocalPart())) { @@ -266,6 +297,7 @@ private static Preset loadPreset(StartElement presetStart) throws XMLStreamExcep /** * Gets the constructor from the right preset class as defined by parameter 'type' and * returns the Preset object instantiated with the rest of the arguments. + * * @param type Full name of the preset class, as returned by {@link Class#getName()} * @param toSet CameraSettings to set in the preset. * @param id Id of the preset. @@ -278,6 +310,7 @@ private static Preset loadPreset(StartElement presetStart) throws XMLStreamExcep private static Preset createPreset(String type, CameraSettings toSet, int id, String description, String imgLoc) throws XMLStreamException { Preset preset; + try { Constructor constructor = Class.forName(type).getConstructor(CameraSettings.class, int.class); preset = (Preset) constructor.newInstance(toSet, id); @@ -285,6 +318,7 @@ private static Preset createPreset(String type, CameraSettings toSet, int id, e.printStackTrace(); throw new XMLStreamException("Instantiating preset failed.", e); } + preset.setDescription(description); preset.setImageLocation(imgLoc); return preset; @@ -294,7 +328,8 @@ private static Preset createPreset(String type, CameraSettings toSet, int id, * Reads the 'shots' section of the XML file and returns this as a list * of Shot objects. * Assumes that the start element of this section has already been read. - * @return the loaded list of shots. + * + * @return The loaded list of shots. * @throws XMLStreamException when an error occurs in the XML. */ private static List loadShots() throws XMLStreamException { @@ -326,7 +361,8 @@ private static List loadShots() throws XMLStreamException { * Reads a 'shot' section of the XML file and returns this as a Shot object. * Assumes that the start element of this section has already been read and * is inserted into this method as the startShot parameter. - * @return the loaded shot. + * + * @return The loaded shot. * @throws XMLStreamException when an error occurs in the XML. */ private static Shot loadShot(StartElement startShot) throws XMLStreamException { @@ -335,6 +371,7 @@ private static Shot loadShot(StartElement startShot) throws XMLStreamException { int cameraId = -1; int presetId = -1; String description = ""; + while (reader.hasNext()) { XMLEvent event = reader.nextEvent(); if (event.isStartElement()) { @@ -360,8 +397,6 @@ private static Shot loadShot(StartElement startShot) throws XMLStreamException { XMLEvent presetIdEvent = reader.nextEvent(); if (presetIdEvent.isCharacters()) { presetId = Integer.parseInt(presetIdEvent.asCharacters().getData()); - } else { - throw new XMLStreamException("No preset id present in shot."); } } } @@ -372,12 +407,12 @@ private static Shot loadShot(StartElement startShot) throws XMLStreamException { } } } + Camera cam = Camera.getCamera(cameraId); - if (cam != null && cam.getPreset(presetId) != null) { + if (cam != null) { return new Shot(id, shotId, cam, cam.getPreset(presetId), description); } else { - throw new XMLStreamException("Camera or preset cannot be found with camera id: " + cameraId - + " and preset id: " + presetId); + throw new XMLStreamException("Camera cannot be found with camera id: " + cameraId); } } } diff --git a/src/main/java/nl/tudelft/contextproject/saveLoad/SaveScript.java b/src/main/java/nl/tudelft/contextproject/saveLoad/SaveScript.java index 4e343a5..0129ae1 100644 --- a/src/main/java/nl/tudelft/contextproject/saveLoad/SaveScript.java +++ b/src/main/java/nl/tudelft/contextproject/saveLoad/SaveScript.java @@ -6,7 +6,7 @@ import nl.tudelft.contextproject.script.Script; import nl.tudelft.contextproject.script.Shot; -import java.io.FileWriter; +import java.io.FileOutputStream; import java.io.IOException; import javax.xml.stream.XMLEventFactory; @@ -22,40 +22,56 @@ * @since 0.3 */ public final class SaveScript { - + /** * Location of the save file to save to. * This is set to savefile.xml per default. */ private static String saveLocation = "savefile.xml"; + private static final String TAG_CAMERA = "camera"; + private static final String TAG_CAMERAID = "cameraId"; + private static final String TAG_CAMERAS = "cameras"; + private static final String TAG_CAMERASETTINGS = "cameraSettings"; + private static final String TAG_DESCRIPTION = "description"; + private static final String TAG_ID = "id"; + private static final String TAG_IMGLOC = "imgLoc"; + private static final String TAG_PRESET = "preset"; + private static final String TAG_PRESETID = "presetId"; + private static final String TAG_PRESETS = "presets"; + private static final String TAG_SCRIPT = "script"; + private static final String TAG_SHOT = "shot"; + private static final String TAG_SHOTID = "shotId"; + private static final String TAG_SHOTS = "shots"; + /** * XMLEventWriter that writes everything away to an XML file. */ private static XMLEventWriter writer; private static XMLEventFactory eventFactory = XMLEventFactory.newFactory(); - + private static final Object MUTEX = new Object(); - + /** * Since this is a utility class, the constructor may not be called. */ private SaveScript() { throw new UnsupportedOperationException(); } - + /** * Returns the location of the save file this class saves to. - * @return the location of the save file this class saves to. + * @return The location of the save file this class saves to. */ public static String getSaveLocation() { return saveLocation; } - + /** * Sets the location of the save file this class saves to. * Also creates a new instance of {@link #writer} so it may save to the * new save location when {@link #save(Script)} is called. + * * @param s the new location of the save file this class should save to. */ public static void setSaveLocation(String s) { @@ -63,11 +79,12 @@ public static void setSaveLocation(String s) { saveLocation = s; } } - + /** * Saves a script to an XML file at the location specified in * {@link #saveLocation}. This also saves the cameras currently found in * {@link Camera#CAMERAS}, including their defined presets. + * * @param script To be saved script. * @throws XMLStreamException In the case anything goes wrong. */ @@ -75,31 +92,32 @@ public static void save(Script script) throws XMLStreamException { synchronized (MUTEX) { writer = createWriter(); writer.add(eventFactory.createStartDocument()); - writer.add(eventFactory.createStartElement("", "", "script")); + writer.add(eventFactory.createStartElement("", "", TAG_SCRIPT)); generateCamerasSection(); generateShotsSection(script); - writer.add(eventFactory.createEndElement("", "", "script")); + writer.add(eventFactory.createEndElement("", "", TAG_SCRIPT)); writer.add(eventFactory.createEndDocument()); writer.flush(); writer.close(); } } - + /** * Creates an XMLEventWriter object using the file location specified * in {@link #saveLocation} for use as the writer class variable. * May throw a RuntimeException in the case something goes wrong in * creating the save file. - * @return an XMLEventWriter object for use as the writer class variable. + * + * @return An XMLEventWriter object for use as the writer class variable. */ private static XMLEventWriter createWriter() { try { - return (XMLOutputFactory.newFactory()).createXMLEventWriter(new FileWriter(saveLocation)); + return (XMLOutputFactory.newFactory()).createXMLEventWriter(new FileOutputStream(saveLocation), "UTF-8"); } catch (IOException | XMLStreamException e) { throw new RuntimeException("Something went wrong in creating your save file", e); } } - + /** * Generates and adds to the {@link #writer} the section of XML that * represents the cameras. @@ -113,13 +131,15 @@ private static XMLEventWriter createWriter() { * @throws XMLStreamException Thrown from {@link #writer} */ private static void generateCamerasSection() throws XMLStreamException { - writer.add(eventFactory.createStartElement("", "", "cameras")); + writer.add(eventFactory.createStartElement("", "", TAG_CAMERAS)); + for (Camera cam : Camera.getAllCameras()) { generateCameraXML(cam); } - writer.add(eventFactory.createEndElement("", "", "cameras")); + + writer.add(eventFactory.createEndElement("", "", TAG_CAMERAS)); } - + /** * Generates and adds to the {@link #writer} the section of XML that * represents the list of shots found within a script. @@ -133,89 +153,99 @@ private static void generateCamerasSection() throws XMLStreamException { * @throws XMLStreamException Thrown from {@link #writer} */ private static void generateShotsSection(Script script) throws XMLStreamException { - writer.add(eventFactory.createStartElement("", "", "shots")); + writer.add(eventFactory.createStartElement("", "", TAG_SHOTS)); + for (Shot shot1 : script.getShots()) { generateShotXML(shot1); } - writer.add(eventFactory.createEndElement("", "", "shots")); + + writer.add(eventFactory.createEndElement("", "", TAG_SHOTS)); } /** * Generates and adds to the {@link #writer} the section of XML that * represents the specified camera. + * * @param cam The camera specified for which its XML should be added to the writer. * @throws XMLStreamException Thrown from {@link #writer} */ private static void generateCameraXML(Camera cam) throws XMLStreamException { - writer.add(eventFactory.createStartElement("", "", "camera")); + writer.add(eventFactory.createStartElement("", "", TAG_CAMERA)); writer.add(eventFactory.createAttribute("id", cam.getNumber() + "")); generateCameraSettingsXML(cam.getSettings()); - writer.add(eventFactory.createStartElement("", "", "presets")); + writer.add(eventFactory.createStartElement("", "", TAG_PRESETS)); + for (Preset p : cam.getAllPresets()) { generatePresetXML(p); } - writer.add(eventFactory.createEndElement("", "", "presets")); - writer.add(eventFactory.createEndElement("", "", "camera")); + + writer.add(eventFactory.createEndElement("", "", TAG_PRESETS)); + writer.add(eventFactory.createEndElement("", "", TAG_CAMERA)); } /** * Generates and adds to the {@link #writer} the section of XML that * represents the specified camera settings . + * * @param camSet The camera settings specified for which its XML should be added to the writer. * @throws XMLStreamException Thrown from {@link #writer} */ private static void generateCameraSettingsXML(CameraSettings camSet) throws XMLStreamException { - writer.add(eventFactory.createStartElement("", "", "cameraSettings")); + writer.add(eventFactory.createStartElement("", "", TAG_CAMERASETTINGS)); writer.add(eventFactory.createAttribute("pan", camSet.getPan() + "")); writer.add(eventFactory.createAttribute("tilt", camSet.getTilt() + "")); writer.add(eventFactory.createAttribute("zoom", camSet.getZoom() + "")); writer.add(eventFactory.createAttribute("focus", camSet.getFocus() + "")); - writer.add(eventFactory.createEndElement("", "", "cameraSettings")); + writer.add(eventFactory.createEndElement("", "", TAG_CAMERASETTINGS)); } /** * Generates and adds to the {@link #writer} the section of XML that * represents the specified preset. + * * @param preset The preset specified for which its XML should be added to the writer. * @throws XMLStreamException Thrown from {@link #writer} */ private static void generatePresetXML(Preset preset) throws XMLStreamException { - writer.add(eventFactory.createStartElement("", "", "preset")); + writer.add(eventFactory.createStartElement("", "", TAG_PRESET)); writer.add(eventFactory.createAttribute("type", preset.getClass().getName())); - writer.add(eventFactory.createStartElement("", "", "id")); + writer.add(eventFactory.createStartElement("", "", TAG_ID)); writer.add(eventFactory.createCharacters(preset.getId() + "")); - writer.add(eventFactory.createEndElement("", "", "id")); - writer.add(eventFactory.createStartElement("", "", "description")); + writer.add(eventFactory.createEndElement("", "", TAG_ID)); + writer.add(eventFactory.createStartElement("", "", TAG_DESCRIPTION)); writer.add(eventFactory.createCharacters(preset.getDescription())); - writer.add(eventFactory.createEndElement("", "", "description")); - writer.add(eventFactory.createStartElement("", "", "imgLoc")); + writer.add(eventFactory.createEndElement("", "", TAG_DESCRIPTION)); + writer.add(eventFactory.createStartElement("", "", TAG_IMGLOC)); writer.add(eventFactory.createCharacters(preset.getImage())); - writer.add(eventFactory.createEndElement("", "", "imgLoc")); + writer.add(eventFactory.createEndElement("", "", TAG_IMGLOC)); generateCameraSettingsXML(preset.getToSet()); - writer.add(eventFactory.createEndElement("", "", "preset")); + writer.add(eventFactory.createEndElement("", "", TAG_PRESET)); } /** * Generates and adds to the {@link #writer} the section of XML that * represents the specified shot. + * * @param shot The shot specified for which its XML should be added to the writer. * @throws XMLStreamException Thrown from {@link #writer} */ private static void generateShotXML(Shot shot) throws XMLStreamException { - writer.add(eventFactory.createStartElement("", "", "shot")); + writer.add(eventFactory.createStartElement("", "", TAG_SHOT)); writer.add(eventFactory.createAttribute("number", shot.getNumber() + "")); - writer.add(eventFactory.createStartElement("", "", "shotId")); + writer.add(eventFactory.createStartElement("", "", TAG_SHOTID)); writer.add(eventFactory.createCharacters(shot.getShotId())); - writer.add(eventFactory.createEndElement("", "", "shotId")); - writer.add(eventFactory.createStartElement("", "", "description")); + writer.add(eventFactory.createEndElement("", "", TAG_SHOTID)); + writer.add(eventFactory.createStartElement("", "", TAG_DESCRIPTION)); writer.add(eventFactory.createCharacters(shot.getDescription())); - writer.add(eventFactory.createEndElement("", "", "description")); - writer.add(eventFactory.createStartElement("", "", "cameraId")); + writer.add(eventFactory.createEndElement("", "", TAG_DESCRIPTION)); + writer.add(eventFactory.createStartElement("", "", TAG_CAMERAID)); writer.add(eventFactory.createCharacters(shot.getCamera().getNumber() + "")); - writer.add(eventFactory.createEndElement("", "", "cameraId")); - writer.add(eventFactory.createStartElement("", "", "presetId")); - writer.add(eventFactory.createCharacters(shot.getPreset().getId() + "")); - writer.add(eventFactory.createEndElement("", "", "presetId")); - writer.add(eventFactory.createEndElement("", "", "shot")); + writer.add(eventFactory.createEndElement("", "", TAG_CAMERAID)); + writer.add(eventFactory.createStartElement("", "", TAG_PRESETID)); + if (shot.getPreset() != null) { + writer.add(eventFactory.createCharacters(shot.getPreset().getId() + "")); + } + writer.add(eventFactory.createEndElement("", "", TAG_PRESETID)); + writer.add(eventFactory.createEndElement("", "", TAG_SHOT)); } } diff --git a/src/main/java/nl/tudelft/contextproject/script/Script.java b/src/main/java/nl/tudelft/contextproject/script/Script.java index 8b9b50f..e6405d6 100644 --- a/src/main/java/nl/tudelft/contextproject/script/Script.java +++ b/src/main/java/nl/tudelft/contextproject/script/Script.java @@ -1,50 +1,46 @@ package nl.tudelft.contextproject.script; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.ButtonType; - import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Set; /** - * Class to represent a script of presets. - * Implements the Iterator interface so it can apply + * Class to represent a Script of {@link Shot Shots}. + * Implements the {@link Iterator} interface so it can apply * presets as the list of presets is being traversed. * * @since 0.2 */ public class Script implements Iterator { - + /** * Contains the Timelines per camera number. */ private HashMap timelines; - + /** * Contains the actual script of shots, like the director defined * it on paper. */ private List shots; - + /** * Keeps track of the current shot. */ private int current; - + /** * The name of the script as displayed on the ui. */ private String name; - + /** * Creates a script that starts from the beginning with specified shots. * Current is initialized with -1, so the first call of next() returns the first shot. + * * @param shots The actual script of the different shots in order of appearance. */ public Script(List shots) { @@ -63,12 +59,12 @@ public Script(List shots) { public List getShots() { return shots; } - + /** * Valid means that one camera doesn't have two adjacent shots with different presets. * @return The first shot to cause an error. */ - protected Shot isValid() { + public Shot isValid() { if (shots.size() <= 1) { return null; } @@ -76,6 +72,7 @@ protected Shot isValid() { Shot prev = shots.get(0); for (int i = 1; i < shots.size(); i++) { Shot next = shots.get(i); + if (next.getCamera().equals(prev.getCamera()) && !next.getPreset().equals(prev.getPreset())) { return next; } @@ -84,45 +81,10 @@ protected Shot isValid() { return null; } - - /** - * Checks if a script is valid and gives an error message when it isn't. - * - * @param level The level of alert. Should be 1 for CONFIRMATION - * or 2 for WARNING. - * @return True if the user wants to continue and ignore the error. - */ - public boolean showValid(int level) { - Shot error = isValid(); - - if (error != null) { - Alert alert = new Alert(AlertType.CONFIRMATION); - alert.setTitle("Confirm saving"); - alert.setHeaderText("Trying to save invalid script"); - - if (level == 1) { - alert.setContentText("Error at shot ID: " + error.getNumber() - + "\nYou are trying to save an invalid script. " - + "Are you sure you want to continue?"); - } else if (level == 2) { - alert = new Alert(AlertType.WARNING); - alert.setContentText("Error at shot ID: " + error.getNumber() - + "\nThe script you loaded is invalid. You can change " - + "it in the edit script menu"); - } - - Optional result = alert.showAndWait(); - - if (result.get() == ButtonType.CANCEL) { - return false; - } - } - - return true; - } /** * Returns the timeline for a specific Camera. + * * @param camNum is the camera number of the timeline to be returned. * @return the timeline for the camera number camNum. */ @@ -153,17 +115,16 @@ private void initTimelines() { } } } - + /** * Loads the first presets of all the cameras. */ private void initPresetLoading() { - Set keys = timelines.keySet(); - for (Integer i : keys) { - timelines.get(i).initPreset(); + for (Timeline t : timelines.values()) { + t.initPreset(); } } - + /** * Adds a shot to the Script, also adds it to the timelines. * If the shot is associated to a camera that does not have @@ -174,6 +135,7 @@ private void initPresetLoading() { */ public void addShot(Shot s) { shots.add(s); + if (timelines.containsKey(s.getCamera().getNumber())) { timelines.get(s.getCamera().getNumber()).addShot(s); } else { @@ -182,7 +144,7 @@ public void addShot(Shot s) { timelines.put(s.getCamera().getNumber(), t); } } - + /** * Returns the current shot, null if there is no such shot. * @return Current shot. @@ -194,7 +156,7 @@ public Shot getCurrentShot() { return null; } } - + /** * Returns the shot after the current shot, null if there is no such shot. * @return Shot after the current shot. @@ -205,7 +167,7 @@ public Shot getNextShot() { } return null; } - + /** * Returns the name of the script. The name is set when a script is saved. * @return The name of the script. @@ -213,7 +175,7 @@ public Shot getNextShot() { public String getName() { return name; } - + /** * Sets the name of the script. * @param name The name of the script. @@ -232,14 +194,12 @@ public boolean hasNext() { @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Script)) { - return false; + if (o instanceof Script) { + Script script = (Script) o; + return Objects.equals(getShots(), script.getShots()); } - Script script = (Script) o; - return Objects.equals(getShots(), script.getShots()); + + return false; } @Override @@ -258,11 +218,10 @@ public Shot next() { Shot old = shots.get(current); timelines.get(old.getCamera().getNumber()).nextPreset(old); } - + current++; Shot next = shots.get(current); next.execute(); return next; - } } diff --git a/src/main/java/nl/tudelft/contextproject/script/Shot.java b/src/main/java/nl/tudelft/contextproject/script/Shot.java index c612276..3a87045 100644 --- a/src/main/java/nl/tudelft/contextproject/script/Shot.java +++ b/src/main/java/nl/tudelft/contextproject/script/Shot.java @@ -6,21 +6,27 @@ import java.util.Objects; /** - * Class to represent a shot to be taken by a Camera. + * Class to represent a shot to be taken by a {@link Camera}. A shot + * consists of an unique ID, a separate identifier, a camera that is + * assigned to a shot, an optional preset that is assigned to that camera, + * and a description of what should happen during the shot. + * * @since 0.2 */ public class Shot { - private int number; private Camera camera; private Preset preset; private String shotId; private String description; + private double duration; + private int number; /** * Creates a shot instance with each shot having a number, * camera and preset. + * * @param num is the shot number * @param shotId The identifier for the shot. * @param cam is the camera used to make the shot. @@ -33,12 +39,12 @@ public Shot(int num, String shotId, Camera cam, Preset pres, String description) this.camera = cam; this.preset = pres; this.description = description; - if (cam != null) { + if (cam != null && pres != null) { cam.addPreset(pres); } this.duration = -1; } - + /** * Creates a shot instance with each shot having a number, * camera and no preset. @@ -59,6 +65,7 @@ public Shot(int num, String shotId, Camera cam, String description) { /** * Creates a shot instance with each shot having a number, * camera and preset. + * * @param num is the shot number * @param cam is the camera used to make the shot. * @param pres is the preset used for the shot. @@ -69,10 +76,10 @@ public Shot(int num, Camera cam, Preset pres) { this.camera = cam; this.preset = pres; this.description = ""; - if (cam != null) { + this.duration = -1; + if (cam != null && pres != null) { cam.addPreset(pres); } - duration = -1; } /** @@ -157,19 +164,17 @@ public String getDescription() { @Override public boolean equals(Object o) { - if (this == o) { - return true; + if (o instanceof Shot) { + Shot shot = (Shot) o; + + return getNumber() == shot.getNumber() + && Objects.equals(getCamera(), shot.getCamera()) + && Objects.equals(getPreset(), shot.getPreset()) + && Objects.equals(getShotId(), shot.getShotId()) + && Objects.equals(getDescription(), shot.getDescription()); } - if (!(o instanceof Shot)) { - return false; - } - Shot shot = (Shot) o; - boolean result = getNumber() == shot.getNumber() - && Objects.equals(getCamera(), shot.getCamera()) - && Objects.equals(getPreset(), shot.getPreset()) - && Objects.equals(getShotId(), shot.getShotId()) - && Objects.equals(getDescription(), shot.getDescription()); - return result; + + return false; } @Override diff --git a/src/main/java/nl/tudelft/contextproject/script/Timeline.java b/src/main/java/nl/tudelft/contextproject/script/Timeline.java index f82371b..d031536 100644 --- a/src/main/java/nl/tudelft/contextproject/script/Timeline.java +++ b/src/main/java/nl/tudelft/contextproject/script/Timeline.java @@ -6,11 +6,13 @@ import java.util.List; /** - * Class to represent a timeline of shots for a single camera. + * Class to represent a timeline of shots for a single {@link Camera}. + * A timeline represents a list of shots that is ordered by timestamp. + * * @since 0.2 */ public class Timeline { - + private Camera camera; private List shots; @@ -20,7 +22,7 @@ public class Timeline { public Timeline() { this.shots = new LinkedList(); } - + /** * Creates a Timeline object with a certain list of shots. * @@ -31,7 +33,7 @@ public Timeline(Camera cam, List shot1) { this.shots = shot1; this.camera = cam; } - + /** * Returns the Camera object this Timeline applies to. * @return the Camera object this Timeline applies to. @@ -39,7 +41,7 @@ public Timeline(Camera cam, List shot1) { public Camera getCamera() { return camera; } - + /** * Sets the camera this Timeline applies to. * @param cam Camera this Timeline applies to. @@ -63,7 +65,7 @@ public void addShot(Shot shot) { public List getShots() { return shots; } - + /** * Loads the initial preset of the timeline, if shots is not empty. */ @@ -72,14 +74,14 @@ public void initPreset() { shots.get(0).getPreset().applyTo(camera); } } - + /** * Loads the next preset for a camera, if there is one. * @param oldShot The shot that just finished. */ public void nextPreset(Shot oldShot) { int oldIndex = shots.indexOf(oldShot); - + if (oldIndex + 1 < shots.size()) { Shot nextShot = shots.get(oldIndex + 1); nextShot.getPreset().applyTo(camera); @@ -98,6 +100,4 @@ public void executeScript() { shot1.execute(); } } - } - diff --git a/src/main/java/nl/tudelft/contextproject/view/CameramanUIView.fxml b/src/main/java/nl/tudelft/contextproject/view/CameramanLiveView.fxml similarity index 96% rename from src/main/java/nl/tudelft/contextproject/view/CameramanUIView.fxml rename to src/main/java/nl/tudelft/contextproject/view/CameramanLiveView.fxml index e275a02..01ae2ad 100644 --- a/src/main/java/nl/tudelft/contextproject/view/CameramanUIView.fxml +++ b/src/main/java/nl/tudelft/contextproject/view/CameramanLiveView.fxml @@ -2,6 +2,7 @@ + @@ -15,7 +16,7 @@ - + @@ -46,6 +47,7 @@ +