gt: 		if (!isPerspectiveInitialized()) return false;
context: /*
   * ShootOFF - Software for Laser Dry Fire Training
   * Copyright (C) 2016 phrack
   * 
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * 
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package com.shootoff.plugins;
  
  import java.io.File;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Optional;
  
  import com.shootoff.camera.CamerasSupervisor;
  import com.shootoff.config.Configuration;
  import com.shootoff.courses.Course;
  import com.shootoff.courses.io.CourseIO;
  import com.shootoff.gui.LocatedImage;
  import com.shootoff.gui.ShotEntry;
  import com.shootoff.gui.controller.ShootOFFController;
  import com.shootoff.gui.pane.ProjectorArenaPane;
  import com.shootoff.targets.Target;
  
  import javafx.application.Platform;
  import javafx.geometry.Dimension2D;
  import javafx.geometry.Insets;
  import javafx.geometry.Point2D;
  import javafx.scene.control.Label;
  import javafx.scene.control.TableView;
  import javafx.scene.layout.Background;
  import javafx.scene.layout.BackgroundFill;
  import javafx.scene.layout.CornerRadii;
  import javafx.scene.layout.VBox;
  import javafx.scene.paint.Color;
  import javafx.scene.text.Font;
  
  /**
   * The API for training exercises that only work on the projector arena.
   * Training exercises that are intended to be used with the projector arena only
   * (i.e. those that require a projector) should extend this class. If the
   * exercise is not intended to only work with a projector, extend
   * {@link TrainingExerciseBase} instead.
   * 
   * @author phrack
   */
  public class ProjectorTrainingExerciseBase extends TrainingExerciseBase {
  	private CamerasSupervisor camerasSupervisor;
  	private ProjectorArenaPane arenaPane;
  	private final List<Target> targets = new ArrayList<>();
  	private final Label exerciseLabel = new Label();
  
  	// Only exists to make it easy to call getInfo without having
  	// to do a bunch of unnecessary setup
  	public ProjectorTrainingExerciseBase() {}
  
  	public ProjectorTrainingExerciseBase(List<Target> targets) {
  		super(targets);
  	}
  
  	public void init(CamerasSupervisor camerasSupervisor, TrainingExerciseView exerciseView,
  			ProjectorArenaPane arenaPane) {
  		super.init(camerasSupervisor, exerciseView);
  		this.camerasSupervisor = camerasSupervisor;
  		this.arenaPane = arenaPane;
  		exerciseLabel.setTextFill(Color.WHITE);
  		Platform.runLater(() -> arenaPane.getCanvasManager().getCanvasGroup().getChildren().add(exerciseLabel));
  	}
  
  	// For unit tests
  	public void init(Configuration config, CamerasSupervisor camerasSupervisor, VBox buttonsContainer,
  			TableView<ShotEntry> shotEntryTable, ProjectorArenaPane arenaPane) {
  		super.init(config, camerasSupervisor, buttonsContainer, shotEntryTable);
  		this.config = config;
  		this.camerasSupervisor = camerasSupervisor;
  		this.arenaPane = arenaPane;
  	}
  
  	@Override
  	public void reset() {
  		camerasSupervisor.reset();
  		if (config.getExercise().isPresent())
  			config.getExercise().get().reset(arenaPane.getCanvasManager().getTargets());
  	}
  
  	/**
  	 * Add a target to the projector arena at specific coordinates.
  	 * 
  	 * @param target
  	 *            the file to load the target from
  	 * @param x
  	 *            the top left x coordinate of the target
  	 * @param y
  	 *            the top left y coordinate of the target
  	 * 
  	 * @return the group that was loaded from the target file
  	 * 
  	 * @since 2.1
  	 */
  	public Optional<Target> addTarget(File target, final double x, final double y) {
  		if ('@' != target.toString().charAt(0) && !target.isAbsolute())
  			target = new File(System.getProperty("shootoff.home") + File.separator + target.getPath());
  
  		final Optional<Target> newTarget = arenaPane.getCanvasManager().addTarget(target, false);
  
  		if (newTarget.isPresent()) {
  			final Target t = newTarget.get();
  
  			t.setPosition(x, y);
  
  			if (isPerspectiveInitialized()) {
  				arenaPane.resizeTargetToDefaultPerspective(t);
  			}
  
  			targets.add(t);
  		}
  
  		return newTarget;
  	}
  
  	public void removeTarget(Target target) {
  		arenaPane.getCanvasManager().removeTarget(target);
  		targets.remove(target);
  	}
  
  	/**
  	 * Get the width of the arena in pixels.
  	 * 
  	 * @return the arena's width in pixels
  	 * 
  	 * @since 2.1
  	 */
  	public double getArenaWidth() {
  		return arenaPane.getWidth();
  	}
  
  	/**
  	 * Get the height of the arena in pixels.
  	 * 
  	 * @return the arena's height in pixels
  	 * 
  	 * @since 2.1
  	 */
  	public double getArenaHeight() {
  		return arenaPane.getHeight();
  	}
  
  	/**
  	 * Get the coordinates of the origin for the display the arena is currently
  	 * located on. This is useful if you need to know what display the arena is
  	 * on.
  	 * 
  	 * @return the origin coordinates for the JavaFX screen the arena is located
  	 *         on, relative to other displays.
  	 * 
  	 * @since 3.8
  	 */
  	public Point2D getArenaScreenOrigin() {
  		return arenaPane.getArenaScreenOrigin();
  	}
  
  	/**
  	 * This function corrects coordinates in the arena area for the DPI of the
  	 * arena screen and relative to the origin.  This is for creating mouse
  	 * events and other javafx functions that need true coordinates rather 
  	 * than scaled ones.
  	 * 
  	 * @param point
  	 * 			The coordinates to be corrected
  	 * 
  	 * @return Translated coordinates
  	 * 
  	 * @since 3.8
  	 */
  	public Point2D translateToTrueArenaCoords(Point2D point)
  	{
  		final double dpiScaleFactor = ShootOFFController.getDpiScaleFactorForScreen();
  
  		final Point2D origin = arenaPane.getArenaScreenOrigin();
  
  		return new Point2D(origin.getX() + (point.getX() * dpiScaleFactor),
  				origin.getY() + (point.getY() * dpiScaleFactor));
  	}
  
  	@Override
  	public void showTextOnFeed(String message) {
  		super.showTextOnFeed(message);
  		Platform.runLater(() -> exerciseLabel.setText(message));
  	}
  
  	/**
  	 * Show a message on all webcam feeds, but optionally do not show the
  	 * message on the arena itself.
  	 * 
  	 * @param message
  	 *            the message to show
  	 * @param showOnArena
  	 *            <tt>false</tt> if the message should not show on the arena
  	 */
  	public void showTextOnFeed(String message, boolean showOnArena) {
  		if (showOnArena) {
  			showTextOnFeed(message);
  		} else {
  			super.showTextOnFeed(message);
  		}
  	}
  
  	/**
  	 * Show a message on all webcam feeds and the arena, but customize the
  	 * location, font, and colors used to display the message.
  	 * 
  	 * @param message
  	 *            the message to show
  	 * @param x
  	 *            the x coordinate of the top left of the message
  	 * @param y
  	 *            the y coordinate of the top left of the message
  	 * @param backgroundColor
  	 *            the background color for the message
  	 * @param textColor
  	 *            the color of the letters in the message
  	 * @param font
  	 *            the font to use to display the message
  	 * 
  	 * @since 3.7
  	 */
  	public void showTextOnFeed(String message, int x, int y, Color backgroundColor, Color textColor, Font font) {
  		showTextOnFeed(message);
  		Platform.runLater(() -> {
  			exerciseLabel.setLayoutX(x);
  			exerciseLabel.setLayoutY(y);
  			exerciseLabel.setBackground(
  					new Background(new BackgroundFill(backgroundColor, CornerRadii.EMPTY, Insets.EMPTY)));
  			exerciseLabel.setTextFill(textColor);
  			exerciseLabel.setFont(font);
  		});
  	}
  
  	/**
  	 * Returns the current instance of this class. This method exists so that we
  	 * can call methods in this class when in an internal class (e.g. to
  	 * implement Callable) that doesn't have access to super.
  	 * 
  	 * @return the current instance of this class
  	 */
  	@Override
  	public ProjectorTrainingExerciseBase getInstance() {
  		return this;
  	}
  
  	/**
  	 * Set the projector arena's background image.
  	 * 
  	 * @param background
  	 *            a file on the filesystem or a resource to set as the projector
  	 *            arena's background.
  	 * 
  	 * @since 3.7
  	 */
  	public void setArenaBackground(LocatedImage background) {
  		arenaPane.setArenaBackground(background);
  	}
  
  	/**
  	 * Set the projector arena's background image to one of the default
  	 * backgrounds using its resource path.
  	 * 
  	 * @param defaultResourcePath
  	 *            the resource path to the default ShootOFF background to set.
  	 * 
  	 * @since 3.10
  	 */
  	public void setArenaBackground(String defaultResourcePath) {
  		final InputStream is = ProjectorTrainingExerciseBase.class.getResourceAsStream(defaultResourcePath);
  		final LocatedImage img = new LocatedImage(is, defaultResourcePath);
  		setArenaBackground(img);
  	}
  	
  	/**
  	 * Remove all targets on the arena and replace them with the course
  	 * specified in the file <code>courseFile</code>.
  	 * 
  	 * @param courseFile
  	 *            a file specifying the course to use
  	 * @return a list of the targets loaded from <code>courseFile</code>
  	 * 
  	 * @since 3.8
  	 */
  	public List<Target> setCourse(File courseFile) {
  		final Optional<Course> newCourse = CourseIO.loadCourse(arenaPane, courseFile);
  		arenaPane.setCourse(newCourse.get());
  		return newCourse.get().getTargets();
  	}
  
  	/**
  	 * Determines whether or not ShootOFF has sufficient information to set a
  	 * target's distance to a real world distance.
  	 * 
  	 * @return <code>true</code> if ShootOFF can set a target to a real world
  	 *         distance
  	 * 
  	 * @since 3.8
  	 */
  	public boolean isPerspectiveInitialized() {
  		return arenaPane.getPerspectiveManager().isPresent()
  				&& arenaPane.getPerspectiveManager().get().isInitialized();
  	}
  
  	/**
  	 * Changes the size of a target to real world dimensions at a particular
  	 * distance (e.g. to simulate a target that is 36"x24" at 10 yards). To do
  	 * this, you must already know the size of the target and ShootOFF must be
  	 * initialized with the distance and camera specification information
  	 * required to measure distances in the real world. Look at the USPSA target
  	 * for an example of setting a default real world width, height, and
  	 * distance for a target.
  	 * 
  	 * Distance and camera specification information is typically determined
  	 * during the auto-calibration process. However, a user will have to
  	 * manually enter at least the camera distance by manually setting a
  	 * target's distance if they did not auto-calibrate or if they
  	 * auto-calibrated without the perspective calibration pattern present.
  	 * 
  	 * @param target
  	 *            the target to resize
  	 * @param currentRealWidth
  	 *            the current width of the target as it appears on the
  	 *            projection in mm
  	 * @param currentRealHeight
  	 *            the current height of the target as it appears on the
  	 *            projection in mm
  	 * @param currentRealDistance
  	 *            the distance the target is currently sized to appear at (e.g.
  	 *            the target appears to be currentRealWidth x currentRealHeight
  	 *            because it is currentRealDistance away in mm)
  	 * @param desiredDistance
  	 *            the new distance the target should appear at in mm (i.e.
  	 *            resize the target so it appears to be desiredDistance away
  	 *            based on its current size and distance)
  	 * @return <code>true</code> if ShootOFF has the data to resize a target and
  	 *         successfully calculated new target dimensions
  	 */
  	public boolean setTargetDistance(Target target, int currentRealWidth, int currentRealHeight,
  			int desiredDistance) {
  