/* * 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.gui; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.shootoff.camera.CameraManager; import com.shootoff.camera.cameratypes.Camera; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import com.shootoff.util.SwingFXUtils; import javafx.geometry.Pos; import javafx.scene.control.CheckBox; import javafx.scene.control.cell.TextFieldListCell; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.util.converter.DefaultStringConverter; public class CheckableImageListCell extends TextFieldListCell<String> { private static final Logger logger = LoggerFactory.getLogger(CheckableImageListCell.class); private static final Map<Camera, Pane> containerCache = new HashMap<>(); private static final Map<Camera, CheckBox> checkCache = new HashMap<>(); private final List<Camera> webcams; private final List<String> configuredNames; private final List<Camera> configuredCameras; private final Optional<Set<Camera>> recordingCameras; public CheckableImageListCell(List<Camera> webcams, List<String> configuredNames, List<Camera> configuredCameras, CameraRenamedListener cameraRenamedListener, final DesignateShotRecorderListener designatedListener, final Optional<Set<Camera>> recordingCameras) { this.webcams = new ArrayList<>(webcams); this.configuredNames = configuredNames; this.configuredCameras = configuredCameras; this.recordingCameras = recordingCameras; setConverter(new DefaultStringConverter()); itemProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { if (oldValue == null || newValue == null) return; final Optional<Pane> webcamContainer = fetchWebcamControls(oldValue); if (webcamContainer.isPresent()) { setGraphic(webcamContainer.get()); } cameraRenamedListener.cameraRenamed(oldValue, newValue); } }); if (designatedListener != null) { setOnMouseClicked((event) -> { if (!fetchWebcamChecked(getText())) { setEditable(false); return; } if (event.getClickCount() > 1 && fetchWebcamChecked(getText())) { setEditable(true); startEdit(); } if (!event.isAltDown()) return; cancelEdit(); // If camera is not checked, don't designate it and start // editing if (!fetchWebcamChecked(getText())) { startEdit(); return; } if (getStyle().isEmpty()) { setStyle("-fx-background-color: green"); designatedListener.registerShotRecorder(getText()); } else { setStyle(""); designatedListener.unregisterShotRecorder(getText()); } final Optional<Pane> webcamContainer = fetchWebcamControls(CheckableImageListCell.this.getText()); if (webcamContainer.isPresent()) { setGraphic(webcamContainer.get()); } }); } } public static void createImageCache(List<Camera> webcams, CameraSelectionListener listener) { for (final Camera c : webcams) { if (containerCache.containsKey(c)) continue; cacheCamera(c, listener); } } @Override public void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { setGraphic(null); setText(null); return; } if (recordingCameras.isPresent()) { for (final Camera recordingCamera : recordingCameras.get()) { if (recordingCamera.getName().equals(item)) { setStyle("-fx-background-color: green"); break; } } } final Optional<Pane> webcamContainer = fetchWebcamControls(item); if (webcamContainer.isPresent()) { setGraphic(webcamContainer.get()); } setText(item); } public static void cacheCamera(Camera c, CameraSelectionListener listener) { final ImageView iv = new ImageView(); iv.setFitWidth(100); iv.setFitHeight(75); new Thread(() -> { final Optional<Image> img = fetchWebcamImage(c); if (img.isPresent()) { iv.setImage(img.get()); } }, "FetchImageCellWebcamImages").start(); final CheckBox cb = new CheckBox(); cb.setOnAction((event) -> { if (listener != null) listener.cameraSelectionChanged(c, cb.isSelected()); }); checkCache.put(c, cb); final HBox webcamContainer = new HBox(cb, iv); webcamContainer.setAlignment(Pos.CENTER); containerCache.put(c, webcamContainer); } public interface CameraRenamedListener { void cameraRenamed(String oldName, String newName); } public interface CameraSelectionListener { void cameraSelectionChanged(Camera camera, boolean isSelected); } public static Map<Camera, CheckBox> getCameraCheckBoxes() { return checkCache; } private Optional<Pane> fetchWebcamControls(String webcamName) { Optional<Pane> webcamContainer = Optional.empty(); if (configuredNames == null) { webcamContainer = fetchUnrenamedWebcamControls(webcamName); } else { try { final int cameraIndex = configuredNames.indexOf(webcamName); if (cameraIndex >= 0) { webcamContainer = Optional.of(containerCache.get(configuredCameras.get(cameraIndex))); } else { webcamContainer = fetchUnrenamedWebcamControls(webcamName); } } catch (final NullPointerException e) { logger.error("Error fetching cached controls for configured camera: " + webcamName, e); throw e; } } return webcamContainer; } private Optional<Pane> fetchUnrenamedWebcamControls(String webcamName) { for (final Camera webcam : webcams) { if (webcam.getName().equals(webcamName)) { return Optional.of(containerCache.get(webcam)); } } return Optional.empty(); } private boolean fetchWebcamChecked(String webcamName) { boolean isChecked = false; if (configuredNames == null) { isChecked = fetchUnrenamedWebcamChecked(webcamName); } else { try { final int cameraIndex = configuredNames.indexOf(webcamName); if (cameraIndex >= 0) { isChecked = checkCache.get(configuredCameras.get(cameraIndex)).isSelected(); } else { isChecked = fetchUnrenamedWebcamChecked(webcamName); } } catch (final NullPointerException e) { logger.error("Error fetching cached check state for configured camera: " + webcamName, e); throw e; } } return isChecked; } private boolean fetchUnrenamedWebcamChecked(String webcamName) { for (final Camera webcam : webcams) { if (webcam.getName().equals(webcamName)) { return checkCache.get(webcam).isSelected(); } } return false; } private static Optional<Image> fetchWebcamImage(Camera webcam) { boolean cameraOpened = false; synchronized (webcam) { if (!webcam.isOpen()) { webcam.setViewSize(new Dimension(CameraManager.DEFAULT_FEED_WIDTH, CameraManager.DEFAULT_FEED_HEIGHT)); webcam.open(); cameraOpened = true; } Image webcamImg = null; if (webcam.isOpen()) { final BufferedImage img = webcam.getBufferedImage(); if (img != null) { webcamImg = SwingFXUtils.toFXImage(img, null); } } if (cameraOpened == true) { webcam.close(); } return Optional.ofNullable(webcamImg); } } }