package com.twasyl.slideshowfx.content.extension.image.controllers; import com.twasyl.slideshowfx.osgi.OSGiManager; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.FlowPane; import javafx.stage.FileChooser; import java.io.*; import java.net.URL; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; /** * This class is the controller for the {@code com.twasyl.slideshowfx.content.extension.images.fxmlImageContentExtension.fxml}. * * @author Thierry Wasylczenko * @version 1.2 * @since SlideshowFX 1.0 */ public class ImageContentExtensionController implements Initializable { private static final Logger LOGGER = Logger.getLogger(ImageContentExtensionController.class.getName()); public static final FileChooser.ExtensionFilter IMAGES_FILES = new FileChooser.ExtensionFilter("Image files", "*.png", "*.bmp", "*.jpg", "*.jpeg", "*.gif", "*.svg"); public static final FileFilter IMAGE_FILTER = new FileFilter() { private final String[] extensions = new String[]{".png", ".bmp", ".gif", ".jpg", ".jpeg", ".svg"}; @Override public boolean accept(File pathname) { boolean accept = false; int index = 0; while (!accept && index < extensions.length) { accept = pathname.getName().endsWith(extensions[index++]); } return accept; } }; @FXML private FlowPane imagesPane; @FXML private ImageView preview; private final ToggleGroup imagesGroup = new ToggleGroup(); @FXML private void chooseNewFile(ActionEvent event) { final FileChooser chooser = new FileChooser(); chooser.getExtensionFilters().add(IMAGES_FILES); File imageFile = chooser.showOpenDialog(null); if (imageFile != null) { final OSGiManager manager = OSGiManager.getInstance(); File targetFile = new File((File) manager.getPresentationProperty(OSGiManager.PRESENTATION_RESOURCES_FOLDER), imageFile.getName()); if (targetFile.exists()) { // If the file exists, add a timestamp to the source targetFile = new File((File) manager.getPresentationProperty(OSGiManager.PRESENTATION_RESOURCES_FOLDER), System.currentTimeMillis() + imageFile.getName()); } try { Files.copy(imageFile.toPath(), targetFile.toPath()); final ToggleButton newFileButton = this.addFile(targetFile); newFileButton.setSelected(true); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Can not copy the image file", e); } } } /** * This methods looks for all resources that are images in the resources directory and return the list of files. * * @return The list of files that are images or an empty list if there is none. */ private List<File> lookupResources() { final List<File> images = new ArrayList<>(); final File resourcesFolder = (File) OSGiManager.getInstance().getPresentationProperty(OSGiManager.PRESENTATION_RESOURCES_FOLDER); final File[] files = resourcesFolder.listFiles(IMAGE_FILTER); if (files != null) { Arrays.stream(files).forEach(images::add); } return images; } /** * This method adds a file to the list of resources. The file is added as user data to the node displaying the file. * The created button also defines a listener on the {@link ToggleButton#selectedProperty()} so * that when the button is selected, the image associated to it is displayed in the preview. * * @param file The file to add. * @return The button created for the given file. */ private ToggleButton addFile(File file) { Node buttonGraphic = null; try (final FileInputStream stream = new FileInputStream(file)) { final Image image = new Image(stream, 80, 80, true, true); buttonGraphic = new ImageView(image); } catch (IOException e) { LOGGER.log(Level.WARNING, "Can not load the image preview", e); } final ToggleButton buttonFile = new ToggleButton(); // If the image hasn't been load, set the text for this button. if (buttonGraphic == null) { buttonFile.setText(file.getName()); } else { buttonFile.setGraphic(buttonGraphic); } buttonFile.setTooltip(new Tooltip(file.getName())); buttonFile.setPrefSize(100, 100); buttonFile.setMinSize(100, 100); buttonFile.setMaxSize(100, 100); buttonFile.setUserData(file); buttonFile.setToggleGroup(this.imagesGroup); this.defineContextMenuForImageButton(buttonFile); /** * Defines the listener for the #selectedProperty. */ buttonFile.selectedProperty().addListener((value, oldValue, newValue) -> { if (newValue != null && newValue) { try { final Image image = new Image(new FileInputStream((File) buttonFile.getUserData())); ImageContentExtensionController.this.preview.setImage(image); } catch (FileNotFoundException e) { LOGGER.log(Level.WARNING, "Can not preview the image", e); } } }); this.imagesPane.getChildren().add(buttonFile); return buttonFile; } /** * Define a {@link ContextMenu} for the given {@link ToggleButton button} in order to interact with images in the * resources. * * @param button The button to set the context menu on. */ private void defineContextMenuForImageButton(final ToggleButton button) { final MenuItem delete = new MenuItem("Delete"); delete.setOnAction(event -> { final File image = (File) button.getUserData(); if (image != null && image.exists()) { final Alert confirmation = getAlert(Alert.AlertType.CONFIRMATION, "Delete image", String.format("Are you sure you want to delete the image %1$s?", image.getName()), ButtonType.NO, ButtonType.YES); final ButtonType answer = confirmation.showAndWait().orElse(ButtonType.NO); if (answer == ButtonType.YES) { if (image != null && image.exists()) { if (image.delete()) { this.imagesPane.getChildren().remove(button); } else { final Alert error = getAlert(Alert.AlertType.ERROR, "Delete image", String.format("The image %1$s can not be deleted", image.getName()), ButtonType.OK); error.showAndWait(); } } } } else { this.imagesPane.getChildren().remove(button); } }); final ContextMenu menu = new ContextMenu(delete); button.setContextMenu(menu); } /** * Buils an {@link Alert} to be used in order to display information. * * @param type The type of the alert to build. * @param title The title of the alert. * @param text The text of the alert. * @param buttons The buttons to include in the alert. * @return An alert that can be shown in the UI. */ private Alert getAlert(final Alert.AlertType type, final String title, final String text, final ButtonType... buttons) { final Alert alert = new Alert(type, text, buttons); alert.setGraphic(null); alert.setHeaderText(null); alert.setTitle(title); alert.getDialogPane().getStylesheets().add("/com/twasyl/slideshowfx/css/Default.css"); return alert; } /** * Return the file that is selected in this panel or <code>null</code> if none. * * @return The file that is selected or <code>null</code> if none. */ public File getSelectedFile() { File selection = null; if (this.imagesGroup.getSelectedToggle() != null) { selection = (File) this.imagesGroup.getSelectedToggle().getUserData(); } return selection; } /** * This methods returns the relative URL from the working directory of the presentation for the selected file. * * @return The relative URL of the selected file or {@code null} if no file is selected. */ public String getSelectedFileUrl() { String url = null; final File selection = this.getSelectedFile(); if (selection != null) { final File workingDir = (File) OSGiManager.getInstance().getPresentationProperty(OSGiManager.PRESENTATION_FOLDER); url = workingDir.toPath().relativize(selection.toPath()).toString().replace(File.separator, "/"); } return url; } @Override public void initialize(URL location, ResourceBundle resources) { List<File> images = this.lookupResources(); images.forEach(image -> this.addFile(image)); } }