package com.twasyl.slideshowfx.setup.controllers; import com.twasyl.slideshowfx.ui.controls.PluginFileButton; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.css.PseudoClass; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.TitledPane; import javafx.scene.control.Tooltip; import javafx.scene.layout.TilePane; import java.io.File; import java.net.URL; import java.util.*; import java.util.logging.Logger; /** * Controller for the {@code PluginsView.xml} file. * * @author Thierry Wasylczenko * @version 1.2 * @since SlideshowFX 1.0 */ public class PluginsViewController implements Initializable { protected static final PseudoClass INVALID_STATE = PseudoClass.getPseudoClass("invalid"); protected final String MARKUP_PLUGINS_DIRECTORY_NAME = "markups"; protected final String SNIPPET_EXECUTORS_PLUGINS_DIRECTORY_NAME = "executors"; protected final String CONTENT_EXTENSION_PLUGINS_DIRECTORY_NAME = "extensions"; protected final String HOSTING_CONNECTOR_PLUGINS_DIRECTORY_NAME = "hostingConnectors"; @FXML private TitledPane markupPluginsContainer; @FXML private FontAwesomeIconView markupErrorSign; @FXML private TilePane markupPlugins; @FXML private TilePane contentExtensionPlugins; @FXML private TilePane snippetExecutorPlugins; @FXML private TilePane hostingConnectorsPlugins; @FXML private CheckBox installAllMarkupPlugins; @FXML private CheckBox installAllContentExtensionPlugins; @FXML private CheckBox installAllSnippetExecutorPlugins; @FXML private CheckBox installAllHostingConnectorPlugins; private final ObjectProperty<File> pluginsDirectory = new SimpleObjectProperty<>(); private final List<File> pluginsToInstall = new ArrayList<>(); private final IntegerProperty numberOfSelectedMarkup = new SimpleIntegerProperty(); /** * Get the list of the plugins the user has chosen to install. Each {@link File} corresponds to the plugin file. * * @return The list of the plugins the user has chosen to install. */ public List<File> getPluginsToInstall() { return this.pluginsToInstall; } /** * Set the directory that contains the plugins to be installed. The directory is not the directory of specialized * plugins but the directory that contains the other directories containing those specialized plugins. * * @param directory The directory containing the plugins. * @return This instance of the controller. */ public PluginsViewController setPluginsDirectory(final File directory) { this.pluginsDirectory.set(directory); return this; } /** * Return the number of markup plugins that are selected in the view. * * @return The property indicating the number of markup plugins selected in the view. */ public IntegerProperty numberOfSelectedMarkup() { return this.numberOfSelectedMarkup; } @FXML private void actionOnInstallAllMarkupPlugins(final ActionEvent event) { this.actionOnInstallAllPlugins(this.installAllMarkupPlugins.isSelected(), this.markupPlugins); } @FXML private void actionOnInstallAllContentExtensionPlugins(final ActionEvent event) { this.actionOnInstallAllPlugins(this.installAllContentExtensionPlugins.isSelected(), this.contentExtensionPlugins); } @FXML private void actionOnInstallAllSnippetExecutorPlugins(final ActionEvent event) { this.actionOnInstallAllPlugins(this.installAllSnippetExecutorPlugins.isSelected(), this.snippetExecutorPlugins); } @FXML private void actionOnInstallAllHostingConnectorPlugins(final ActionEvent event) { this.actionOnInstallAllPlugins(this.installAllHostingConnectorPlugins.isSelected(), this.hostingConnectorsPlugins); } @Override public void initialize(URL location, ResourceBundle resources) { this.numberOfSelectedMarkup.addListener((value, oldNumber, newNumber) -> { if (newNumber.intValue() == 0) this.makeMarkupPluginsContainerInvalid(); else this.makeMarkupPluginsContainerValid(); }); this.makeMarkupPluginsContainerInvalid(); this.pluginsDirectory.addListener((dirValue, newDir, oldDir) -> { this.numberOfSelectedMarkup.bind(fillMarkupPluginsView()); fillContentExtensionPluginsView(); fillSnippetExecutorPluginsView(); fillHostingConnectorPluginsView(); }); } protected final void makeMarkupPluginsContainerInvalid() { this.markupPluginsContainer.pseudoClassStateChanged(INVALID_STATE, true); this.markupPluginsContainer.setTooltip(new Tooltip("At least one markup plugin must be selected")); this.markupErrorSign.setVisible(true); } protected final void makeMarkupPluginsContainerValid() { this.markupPluginsContainer.pseudoClassStateChanged(INVALID_STATE, false); this.markupPluginsContainer.setTooltip(null); this.markupErrorSign.setVisible(false); } /** * Fill the {@link Node} that will list the available markup plugins. * * @return An {@link IntegerProperty} indicating the number of selected markup plugins in the view. * @see #fillPluginsView(String, TilePane, CheckBox) */ protected final IntegerProperty fillMarkupPluginsView() { return this.fillPluginsView(MARKUP_PLUGINS_DIRECTORY_NAME, this.markupPlugins, this.installAllMarkupPlugins); } /** * Fill the {@link Node} that will list the available content extension plugins. * * @return An {@link IntegerProperty} indicating the number of selected content extension plugins in the view. * @see #fillPluginsView(String, TilePane, CheckBox) */ protected final IntegerProperty fillContentExtensionPluginsView() { return this.fillPluginsView(CONTENT_EXTENSION_PLUGINS_DIRECTORY_NAME, this.contentExtensionPlugins, this.installAllContentExtensionPlugins); } /** * Fill the {@link Node} that will list the available snippet executor plugins. * * @return An {@link IntegerProperty} indicating the number of selected snippet executor plugins in the view. * @see #fillPluginsView(String, TilePane, CheckBox) */ protected final IntegerProperty fillSnippetExecutorPluginsView() { return this.fillPluginsView(SNIPPET_EXECUTORS_PLUGINS_DIRECTORY_NAME, this.snippetExecutorPlugins, this.installAllSnippetExecutorPlugins); } /** * Fill the {@link Node} that will list the available hosting connector plugins. * * @return An {@link IntegerProperty} indicating the number of selected hosting connector plugins in the view. * @see #fillPluginsView(String, TilePane, CheckBox) */ protected final IntegerProperty fillHostingConnectorPluginsView() { return this.fillPluginsView(HOSTING_CONNECTOR_PLUGINS_DIRECTORY_NAME, this.hostingConnectorsPlugins, this.installAllHostingConnectorPlugins); } /** * Fill the given {@code view} with the plugins contained within the {@code specializedPluginsDirectoryName}. * * @param specializedPluginsDirectoryName The name of directory containing the plugins to list. * @param view The view to be filled. * @param installAllPluginsBox The checkbox allowing to select/unselect all plugins in the {@code view}. * @return An {@link IntegerProperty} indicating the number of selected plugins in the view. */ protected final IntegerProperty fillPluginsView(final String specializedPluginsDirectoryName, final TilePane view, final CheckBox installAllPluginsBox) { final IntegerProperty numberOfSelectedPlugins = new SimpleIntegerProperty(0); final File specializedPluginsDir = new File(this.pluginsDirectory.get(), specializedPluginsDirectoryName); view.getChildren().clear(); Arrays.stream(specializedPluginsDir.listFiles()) .filter(file -> file.getName().endsWith(".jar")) .map(file -> new PluginFileButton(file)) .sorted(Comparator.comparing(PluginFileButton::getLabel)) .forEach(button -> { button.selectedProperty().addListener((selectedValue, oldSelected, newSelected) -> { if (newSelected) { this.pluginsToInstall.add(button.getFile()); numberOfSelectedPlugins.set(numberOfSelectedPlugins.get() + 1); } else { this.pluginsToInstall.remove(button.getFile()); if (numberOfSelectedPlugins.get() > 0) { numberOfSelectedPlugins.set(numberOfSelectedPlugins.get() - 1); } } this.manageCheckBoxStateForPlugins(view, installAllPluginsBox); }); view.getChildren().add(button); }); return numberOfSelectedPlugins; } /** * Check or uncheck the given {@code box} according the fact all plugins are selected or not in the given {@code view}. * * @param view The view determining of the box should be checked or not. * @param box The box to check or not. */ protected final void manageCheckBoxStateForPlugins(final TilePane view, final CheckBox box) { final Node unselectedNode = view.getChildren() .stream() .filter(node -> node instanceof PluginFileButton && !((PluginFileButton) node).isSelected()) .findFirst() .orElse(null); box.setSelected(unselectedNode == null); } /** * Check/Uncheck all plugins in the view according the {@code install} value. * * @param install Indicates if the plugins should be installed or not. * @param view The view to update. */ protected final void actionOnInstallAllPlugins(final boolean install, final TilePane view) { view.getChildren() .stream() .filter(node -> node instanceof PluginFileButton) .map(node -> (PluginFileButton) node) .forEach(plugin -> plugin.setSelected(install)); } }