/* * */ package xr3capture; import java.awt.AWTException; import java.awt.Rectangle; import java.awt.Robot; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import application.tools.ActionTool; import application.tools.NotificationType; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.Cursor; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseButton; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.Duration; /** * This is the Window which is used from the user to draw the rectangle representing an area on the screen to be captured. * * @author GOXR3PLUS */ public class CaptureWindowController extends Stage { /** The stack pane. */ @FXML private StackPane stackPane; /** The main canvas. */ @FXML private Canvas mainCanvas; // ----------------------------- /** * The Model of the CaptureWindow */ CaptureWindowModel data = new CaptureWindowModel(); /** The file saver. */ XR3CaptureFileChooser fileSaver = new XR3CaptureFileChooser(); /** The capture service. */ final CaptureService captureService = new CaptureService(); /** The graphics context of the canvas */ GraphicsContext gc; /** * When a key is being pressed into the capture window then this Animation Timer is doing it's magic. */ AnimationTimer yPressedAnimation = new AnimationTimer() { private long nextSecond = 0L; // private static final long ONE_SECOND_NANOS = 1_000_000_000L private long precisionLevel; @Override public void start() { nextSecond = 0L; precisionLevel = (long) (settingsWindowController.getPrecisionSlider().getValue() * 1_000_000L); super.start(); } @Override public void handle(long nanos) { System.out.println("TimeStamp: " + nanos + " Current: " + nextSecond); System.out.println("Milliseconds Delay: " + precisionLevel / 1_000_000); if (nanos >= nextSecond) { nextSecond = nanos + precisionLevel; // With special key pressed // (we want [LEFT] and [DOWN] side of the rectangle to be // movable) // No Special Key is Pressed // (we want [RIGHT] and [UP] side of the rectangle to be // movable) // ------------------------------ if (data.rightPressed.get()) { if (data.shiftPressed.get()) { // Special Key? if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? data.mouseXPressed += 1; } else { data.mouseXNow += 1; } } else { if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? data.mouseXNow += 1; } else { data.mouseXPressed += 1; } } } if (data.leftPressed.get()) { if (data.shiftPressed.get()) { // Special Key? if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? data.mouseXPressed -= 1; } else { data.mouseXNow -= 1; } } else { if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? data.mouseXNow -= 1; } else { data.mouseXPressed -= 1; } } } if (data.upPressed.get()) { if (data.shiftPressed.get()) { // Special Key? if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? data.mouseYNow -= 1; } else { data.mouseYPressed -= 1; } } else { if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? data.mouseYPressed -= 1; } else { data.mouseYNow -= 1; } } } if (data.downPressed.get()) { if (data.shiftPressed.get()) { // Special Key? if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? data.mouseYNow += 1; } else { data.mouseYPressed += 1; } } else { if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? data.mouseYPressed += 1; } else { data.mouseYNow += 1; } } } repaintCanvas(); } } }; /** * This AnimationTimer waits until the canvas is cleared before it can capture the screen. */ AnimationTimer waitFrameRender = new AnimationTimer() { private int frameCount = 0; @Override public void start() { frameCount = 0; super.start(); } @Override public void handle(long timestamp) { frameCount++; if (frameCount >= 5) { stop(); // Capture the Image BufferedImage image; int[] rect = getRectangleBounds(); try { image = new Robot().createScreenCapture(new Rectangle(rect[0], rect[1], rect[2], rect[3])); } catch (AWTException ex) { Logger.getLogger(getClass().getName()).log(Level.INFO, null, ex); return; } finally { mainCanvas.setDisable(false); } // System.out.println("Starting Service") // Start the Service captureService.startService(image); } } }; /** The counting thread. */ Thread countingThread; /** The main window controller. */ MainWindowController mainWindowController; /** The settings window controller. */ SettingsWindowController settingsWindowController; /** * Constructor. */ public CaptureWindowController() { setX(0); setY(0); getIcons().add(new Image(getClass().getResourceAsStream("/image/icon.png"))); initStyle(StageStyle.TRANSPARENT); setAlwaysOnTop(true); } /** * Add the needed references from the other controllers. * * @param mainWindowController * the main window controller * @param settingsWindowController * the settings window controller */ @SuppressWarnings("hiding") public void addControllerReferences(MainWindowController mainWindowController, SettingsWindowController settingsWindowController) { this.mainWindowController = mainWindowController; this.settingsWindowController = settingsWindowController; } /** * Will be called as soon as FXML file is loaded. */ @FXML public void initialize() { // System.out.println("CaptureWindow initialized") // Scene Scene scene = new Scene(stackPane, data.screenWidth, data.screenHeight, Color.TRANSPARENT); scene.setCursor(Cursor.NONE); setScene(scene); addKeyHandlers(); // Canvas mainCanvas.setWidth(data.screenWidth); mainCanvas.setHeight(data.screenHeight); mainCanvas.setOnMousePressed(m -> { if (m.getButton() == MouseButton.PRIMARY) { data.mouseXPressed = (int) m.getScreenX(); data.mouseYPressed = (int) m.getScreenY(); } }); mainCanvas.setOnMouseDragged(m -> { if (m.getButton() == MouseButton.PRIMARY) { data.mouseXNow = (int) m.getScreenX(); data.mouseYNow = (int) m.getScreenY(); repaintCanvas(); } }); // graphics context 2D gc = mainCanvas.getGraphicsContext2D(); gc.setLineDashes(6); gc.setFont(Font.font("null", FontWeight.BOLD, 14)); // HideFeaturesPressed data.hideExtraFeatures.addListener((observable, oldValue, newValue) -> repaintCanvas()); } /** * Adds the KeyHandlers to the Scene. */ private void addKeyHandlers() { // -------------Read the below to understand the Code------------------- // the default prototype of the below code is // 1->when the user is pressing RIGHT ARROW -> The rectangle is // increasing from the RIGHT side // 2->when the user is pressing LEFT ARROW -> The rectangle is // decreasing from the RIGHT side // 3->when the user is pressing UP ARROW -> The rectangle is increasing // from the UP side // 4->when the user is pressing DOWN ARROW -> The rectangle is // decreasing from the UP side // when ->LEFT KEY <- is pressed // 1->when the user is pressing RIGHT ARROW -> The rectangle is // increasing from the LEFT side // 2->when the user is pressing LEFT ARROW -> The rectangle is // decreasing from the LEFT side // 3->when the user is pressing UP ARROW -> The rectangle is increasing // from the DOWN side // 4->when the user is pressing DOWN ARROW -> The rectangle is // decreasing from the DOWN side // kemodel.yPressed getScene().setOnKeyPressed(key -> { if (key.isShiftDown()) data.shiftPressed.set(true); if (key.getCode() == KeyCode.LEFT) data.leftPressed.set(true); if (key.getCode() == KeyCode.RIGHT) data.rightPressed.set(true); if (key.getCode() == KeyCode.UP) data.upPressed.set(true); if (key.getCode() == KeyCode.DOWN) data.downPressed.set(true); if (key.getCode() == KeyCode.H) data.hideExtraFeatures.set(true); }); // keyReleased getScene().setOnKeyReleased(key -> { if (key.getCode() == KeyCode.SHIFT) data.shiftPressed.set(false); if (key.getCode() == KeyCode.RIGHT) { if (key.isControlDown()) { data.mouseXNow = (int) getWidth(); repaintCanvas(); } data.rightPressed.set(false); } if (key.getCode() == KeyCode.LEFT) { if (key.isControlDown()) { data.mouseXPressed = 0; repaintCanvas(); } data.leftPressed.set(false); } if (key.getCode() == KeyCode.UP) { if (key.isControlDown()) { data.mouseYPressed = 0; repaintCanvas(); } data.upPressed.set(false); } if (key.getCode() == KeyCode.DOWN) { if (key.isControlDown()) { data.mouseYNow = (int) getHeight(); repaintCanvas(); } data.downPressed.set(false); } if (key.getCode() == KeyCode.A && key.isControlDown()) selectWholeScreen(); if (key.getCode() == KeyCode.H) data.hideExtraFeatures.set(false); if (key.getCode() == KeyCode.ESCAPE || key.getCode() == KeyCode.BACK_SPACE) { // Stop Counting Thread if (countingThread != null) countingThread.interrupt(); // Stop MaryTTS //Main.textToSpeech.stopSpeaking(); // Deactivate all keys deActivateAllKeys(); // show the appropriate windows CaptureWindow.stage.show(); close(); } else if (key.getCode() == KeyCode.ENTER || key.getCode() == KeyCode.SPACE) { // Stop MaryTTS //Main.textToSpeech.stopSpeaking(); // Deactivate all keys deActivateAllKeys(); // Capture Selected Area prepareImage(); } }); data.anyPressed.addListener((obs, wasPressed, isNowPressed) -> { if (isNowPressed.booleanValue()) { yPressedAnimation.start(); } else { yPressedAnimation.stop(); } }); } /** * Deactivates the keys contained into this method. */ private void deActivateAllKeys() { data.shiftPressed.set(false); data.upPressed.set(false); data.rightPressed.set(false); data.downPressed.set(false); data.leftPressed.set(false); data.hideExtraFeatures.set(false); } /** * Creates and saves the image. */ public void prepareImage() { // return if it is alive if ((countingThread != null && countingThread.isAlive()) || captureService.isRunning()) return; countingThread = new Thread(() -> { mainCanvas.setDisable(true); boolean interrupted = false; // CountDown if (!mainWindowController.getTimeSlider().isDisabled()) { for (int i = (int) mainWindowController.getTimeSlider().getValue(); i > 0; i--) { final int a = i; // Lock until it has been refreshed from JavaFX // Application Thread CountDownLatch count = new CountDownLatch(1); // Repaint the Canvas Platform.runLater(() -> { gc.clearRect(0, 0, getWidth(), getHeight()); gc.setFill(data.background); gc.fillRect(0, 0, getWidth(), getHeight()); gc.setFill(Color.BLACK); gc.fillOval(getWidth() / 2 - 90, getHeight() / 2 - 165, 250, 250); gc.setFill(Color.WHITE); gc.setFont(Font.font("", FontWeight.BOLD, 120)); gc.fillText(Integer.toString(a), getWidth() / 2, getHeight() / 2); // Unlock the Parent Thread count.countDown(); }); try { // Wait JavaFX Application Thread count.await(); // MaryTTS //if (settingsWindowController.getMarryTTSToggle().isSelected()) // Main.textToSpeech.speak(i); // Sleep 1 seconds after that Thread.sleep(980); } catch (InterruptedException ex) { interrupted = true; mainCanvas.setDisable(false); countingThread.interrupt(); Logger.getLogger(getClass().getName()).log(Level.INFO, null, ex); break; } } } // !interrupted? if (!Thread.interrupted()) { // MaryTTS //if (settingsWindowController.getMarryTTSToggle().isSelected()) // Main.textToSpeech.speak("Select where the image will be saved."); Platform.runLater(() -> { // Clear the canvas gc.clearRect(0, 0, getWidth(), getHeight()); // Wait for frame Render waitFrameRender.start(); }); } // !interrupted? }); countingThread.setDaemon(true); countingThread.start(); } /** * Repaint the canvas of the capture window. */ protected void repaintCanvas() { gc.clearRect(0, 0, getWidth(), getHeight()); gc.setFill(Color.rgb(0, 0, 0, 0.8)); gc.fillRect(0, 0, getWidth(), getHeight()); gc.setFont(data.font); // draw the actual rectangle gc.setStroke(Color.RED); // gc.setFill(model.background) gc.setLineWidth(1); // smart calculation of where the mouse has been dragged data.rectWidth = (data.mouseXNow > data.mouseXPressed) ? data.mouseXNow - data.mouseXPressed // RIGHT : data.mouseXPressed - data.mouseXNow // LEFT ; data.rectHeight = (data.mouseYNow > data.mouseYPressed) ? data.mouseYNow - data.mouseYPressed // DOWN : data.mouseYPressed - data.mouseYNow // UP ; data.rectUpperLeftX = // -------->UPPER_LEFT_X (data.mouseXNow > data.mouseXPressed) ? data.mouseXPressed // RIGHT : data.mouseXNow// LEFT ; data.rectUpperLeftY = // -------->UPPER_LEFT_Y (data.mouseYNow > data.mouseYPressed) ? data.mouseYPressed // DOWN : data.mouseYNow // UP ; gc.strokeRect(data.rectUpperLeftX - 1.00, data.rectUpperLeftY - 1.00, data.rectWidth + 2.00, data.rectHeight + 2.00); // gc.fillRect(model.rectUpperLeftX, model.rectUpperLeftY,model.rectWidth, model.rectHeight) gc.clearRect(data.rectUpperLeftX, data.rectUpperLeftY, data.rectWidth, data.rectHeight); // draw the text if (!data.hideExtraFeatures.getValue()) { // Show the Size double middle = data.rectUpperLeftX + data.rectWidth / 2.00; gc.setLineWidth(1); // gc.setStroke(Color.FIREBRICK); // gc.strokeRect(middle - 78, // model.rectUpperLeftY < 25 ? model.rectUpperLeftY + 2 : model.rectUpperLeftY - 26.00, 79, 25); gc.setFill(Color.FIREBRICK); gc.fillRect(middle - 77, data.rectUpperLeftY < 25 ? data.rectUpperLeftY + 2 : data.rectUpperLeftY - 25.00, 77, 23); gc.setFill(Color.WHITE); gc.fillText(data.rectWidth + "," + data.rectHeight, middle - 77 + 9, data.rectUpperLeftY < 25 ? data.rectUpperLeftY + 17.00 : data.rectUpperLeftY - 6.00); } } /** * Selects whole Screen. */ private void selectWholeScreen() { data.mouseXPressed = 0; data.mouseYPressed = 0; data.mouseXNow = (int) getWidth(); data.mouseYNow = (int) getHeight(); repaintCanvas(); } /** * Prepares the Window for the User. */ public void prepareForCapture() { show(); repaintCanvas(); CaptureWindow.stage.close(); settingsWindowController.close(); //if (settingsWindowController.getMarryTTSToggle().isSelected()) // Main.textToSpeech.speak("Select an area of the screen dragging your mouse and then press Enter or Space"); } /** * Return an array witch contains the (UPPER_LEFT) Point2D of the rectangle and the width and height of the rectangle. * * @return An array witch contains the (UPPER_LEFT) Point2D of the rectangle and the width and height of the rectangle */ public int[] getRectangleBounds() { return new int[] { data.rectUpperLeftX, data.rectUpperLeftY, data.rectWidth, data.rectHeight }; } /** * The work of the Service is to capture the Image based on the rectangle that user drawn of the Screen. * * @author GOXR3PLUS */ public class CaptureService extends Service<Boolean> { /** The file path. */ String filePath; /** The image. */ BufferedImage image; /** * Constructor. */ public CaptureService() { setOnSucceeded(s -> done()); setOnCancelled(c -> done()); setOnFailed(f -> done()); } /** * Starts the Service. * * @param image2 * The image to be saved. */ public void startService(BufferedImage image2) { if (isRunning()) //Check if running return; this.image = image2; // Show the SaveDialog fileSaver.get().setInitialFileName("ScreenShot" + data.random.nextInt(50000)); File file = fileSaver.get().showSaveDialog(CaptureWindowController.this); if (file == null) repaintCanvas(); else { filePath = file.getAbsolutePath(); reset(); start(); } } /** * Service has been done. */ private void done() { CaptureWindow.stage.show(); close(); //Was it seccussful? if (!getValue()) ActionTool.showNotification("Error", "Failed to capture the Screen!", Duration.millis(2000), NotificationType.ERROR); else ActionTool.showNotification("Successful Capturing", "Image is being saved at:\n" + filePath, Duration.millis(2000), NotificationType.INFORMATION); } /* (non-Javadoc) * @see javafx.concurrent.Service#createTask() */ @Override protected Task<Boolean> createTask() { return new Task<Boolean>() { @Override protected Boolean call() throws Exception { boolean written = false; // Try to write the file to the disc try { written = ImageIO.write(image, fileSaver.get().getSelectedExtensionFilter().getDescription(), new File(filePath)); } catch (IOException ex) { Logger.getLogger(getClass().getName()).log(Level.WARNING, null, ex); return written; } return written; } }; } } }