diff --git a/src/main/java/org/nwapw/abacus/Abacus.java b/src/main/java/org/nwapw/abacus/Abacus.java index bf8328f..649bb1a 100644 --- a/src/main/java/org/nwapw/abacus/Abacus.java +++ b/src/main/java/org/nwapw/abacus/Abacus.java @@ -57,7 +57,7 @@ public class Abacus { * Creates a new instance of the Abacus calculator. */ public Abacus() { - pluginManager = new PluginManager(); + pluginManager = new PluginManager(this); numberReducer = new NumberReducer(this); configuration = new Configuration(CONFIG_FILE); configuration.saveTo(CONFIG_FILE); diff --git a/src/main/java/org/nwapw/abacus/config/Configuration.java b/src/main/java/org/nwapw/abacus/config/Configuration.java index 6253632..951edee 100644 --- a/src/main/java/org/nwapw/abacus/config/Configuration.java +++ b/src/main/java/org/nwapw/abacus/config/Configuration.java @@ -5,6 +5,9 @@ import com.moandjiezana.toml.TomlWriter; import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; /** * The configuration object that stores @@ -12,26 +15,38 @@ import java.io.IOException; */ public class Configuration { + /** + * The defaults TOML string. + */ + private static final String DEFAULT_CONFIG = + "numberImplementation = \"naive\"\n" + + "disabledPlugins = []"; + /** + * The defaults TOML object, parsed from the string. + */ + private static final Toml DEFAULT_TOML = new Toml().read(DEFAULT_CONFIG); /** * The TOML writer used to write this configuration to a file. */ private static final TomlWriter TOML_WRITER = new TomlWriter(); - /** - * The TOML reader used to load this config from a file. - */ - private static final Toml TOML_READER = new Toml(); /** * The implementation of the number that should be used. */ - private String numberImplementation = "naive"; + private String numberImplementation = ""; + /** + * The list of disabled plugins in this Configuration. + */ + private Set disabledPlugins = new HashSet<>(); /** * Creates a new configuration with the given values. * @param numberImplementation the number implementation, like "naive" or "precise" + * @param disabledPlugins the list of disabled plugins. */ - public Configuration(String numberImplementation){ + public Configuration(String numberImplementation, String[] disabledPlugins){ this.numberImplementation = numberImplementation; + this.disabledPlugins.addAll(Arrays.asList(disabledPlugins)); } /** @@ -40,7 +55,7 @@ public class Configuration { */ public Configuration(File fromFile){ if(!fromFile.exists()) return; - copyFrom(TOML_READER.read(fromFile).to(Configuration.class)); + copyFrom(new Toml(DEFAULT_TOML).read(fromFile).to(Configuration.class)); } /** @@ -49,6 +64,7 @@ public class Configuration { */ public void copyFrom(Configuration otherConfiguration){ this.numberImplementation = otherConfiguration.numberImplementation; + this.disabledPlugins.addAll(otherConfiguration.disabledPlugins); } /** @@ -80,4 +96,13 @@ public class Configuration { public void setNumberImplementation(String numberImplementation) { this.numberImplementation = numberImplementation; } + + /** + * Gets the list of disabled plugins. + * @return the list of disabled plugins. + */ + public Set getDisabledPlugins() { + return disabledPlugins; + } + } diff --git a/src/main/java/org/nwapw/abacus/fx/AbacusController.java b/src/main/java/org/nwapw/abacus/fx/AbacusController.java index aa898d8..ebd7437 100644 --- a/src/main/java/org/nwapw/abacus/fx/AbacusController.java +++ b/src/main/java/org/nwapw/abacus/fx/AbacusController.java @@ -4,18 +4,25 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.*; +import javafx.scene.control.cell.CheckBoxListCell; import javafx.scene.text.Text; import javafx.util.Callback; +import javafx.util.StringConverter; import org.nwapw.abacus.Abacus; +import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.number.NumberInterface; +import org.nwapw.abacus.plugin.PluginListener; +import org.nwapw.abacus.plugin.PluginManager; import org.nwapw.abacus.tree.TreeNode; +import java.util.Set; + /** * The controller for the abacus FX UI, responsible * for all the user interaction. */ -public class AbacusController { +public class AbacusController implements PluginListener { /** * Constant string that is displayed if the text could not be lexed or parsed. @@ -42,6 +49,8 @@ public class AbacusController { private Button inputButton; @FXML private ComboBox numberImplementationBox; + @FXML + private ListView enabledPluginView; /** * The list of history entries, created by the users. @@ -54,23 +63,39 @@ public class AbacusController { */ private ObservableList numberImplementationOptions; + /** + * The list of plugin objects that can be toggled on and off, + * and, when reloaded, get added to the plugin manager's black list. + */ + private ObservableList enabledPlugins; + private Abacus abacus; @FXML public void initialize(){ Callback, TableCell> cellFactory = param -> new CopyableCell<>(); + Callback, ListCell> pluginCellFactory = + param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter() { + @Override + public String toString(ToggleablePlugin object) { + return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1); + } + + @Override + public ToggleablePlugin fromString(String string) { + return new ToggleablePlugin(true, string); + } + }); historyData = FXCollections.observableArrayList(); historyTable.setItems(historyData); numberImplementationOptions = FXCollections.observableArrayList(); numberImplementationBox.setItems(numberImplementationOptions); - numberImplementationBox.valueProperty().addListener((observable, oldValue, newValue) - -> { - abacus.getConfiguration().setNumberImplementation(newValue); - abacus.getConfiguration().saveTo(Abacus.CONFIG_FILE); - }); historyTable.getSelectionModel().setCellSelectionEnabled(true); + enabledPlugins = FXCollections.observableArrayList(); + enabledPluginView.setItems(enabledPlugins); + enabledPluginView.setCellFactory(pluginCellFactory); inputColumn.setCellFactory(cellFactory); inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty()); parsedColumn.setCellFactory(cellFactory); @@ -79,10 +104,8 @@ public class AbacusController { outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty()); abacus = new Abacus(); - numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumbers()); - String actualImplementation = abacus.getConfiguration().getNumberImplementation(); - String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : "naive"; - numberImplementationBox.getSelectionModel().select(toSelect); + abacus.getPluginManager().addListener(this); + abacus.getPluginManager().reload(); } @FXML @@ -107,4 +130,41 @@ public class AbacusController { inputField.setText(""); } + @FXML + private void performReload(){ + Configuration configuration = abacus.getConfiguration(); + Set disabledPlugins = configuration.getDisabledPlugins(); + disabledPlugins.clear(); + for(ToggleablePlugin pluginEntry : enabledPlugins){ + if(!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName()); + } + abacus.getPluginManager().reload(); + } + + @FXML + private void performSave(){ + Configuration configuration = abacus.getConfiguration(); + configuration.setNumberImplementation(numberImplementationBox.getSelectionModel().getSelectedItem()); + configuration.saveTo(Abacus.CONFIG_FILE); + } + + @Override + public void onLoad(PluginManager manager) { + Configuration configuration = abacus.getConfiguration(); + Set disabledPlugins = configuration.getDisabledPlugins(); + numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumbers()); + String actualImplementation = configuration.getNumberImplementation(); + String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : ""; + numberImplementationBox.getSelectionModel().select(toSelect); + for(Class pluginClass : abacus.getPluginManager().getLoadedPluginClasses()){ + String fullName = pluginClass.getName(); + enabledPlugins.add(new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName)); + } + } + + @Override + public void onUnload(PluginManager manager) { + enabledPlugins.clear(); + numberImplementationOptions.clear(); + } } diff --git a/src/main/java/org/nwapw/abacus/fx/ToggleablePlugin.java b/src/main/java/org/nwapw/abacus/fx/ToggleablePlugin.java new file mode 100644 index 0000000..e311819 --- /dev/null +++ b/src/main/java/org/nwapw/abacus/fx/ToggleablePlugin.java @@ -0,0 +1,29 @@ +package org.nwapw.abacus.fx; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; + +public class ToggleablePlugin { + + private final BooleanProperty enabled; + private final String className; + + public ToggleablePlugin(boolean enabled, String className){ + this.enabled = new SimpleBooleanProperty(); + this.enabled.setValue(enabled); + this.className = className; + } + + public BooleanProperty enabledProperty() { + return enabled; + } + + public boolean isEnabled() { + return enabled.get(); + } + + public String getClassName() { + return className; + } + +} diff --git a/src/main/java/org/nwapw/abacus/plugin/PluginManager.java b/src/main/java/org/nwapw/abacus/plugin/PluginManager.java index 757ac67..ab116dc 100644 --- a/src/main/java/org/nwapw/abacus/plugin/PluginManager.java +++ b/src/main/java/org/nwapw/abacus/plugin/PluginManager.java @@ -1,5 +1,6 @@ package org.nwapw.abacus.plugin; +import org.nwapw.abacus.Abacus; import org.nwapw.abacus.function.Function; import org.nwapw.abacus.function.Operator; import org.nwapw.abacus.number.NumberInterface; @@ -52,11 +53,17 @@ public class PluginManager { * The list of plugin listeners attached to this instance. */ private Set listeners; + /** + * The abacus instance used to access other + * components of the application. + */ + private Abacus abacus; /** * Creates a new plugin manager. */ - public PluginManager() { + public PluginManager(Abacus abacus) { + this.abacus = abacus; loadedPluginClasses = new HashSet<>(); plugins = new HashSet<>(); cachedFunctions = new HashMap<>(); @@ -160,8 +167,13 @@ public class PluginManager { * Loads all the plugins in the PluginManager. */ public void load() { - for (Plugin plugin : plugins) plugin.enable(); + Set disabledPlugins = abacus.getConfiguration().getDisabledPlugins(); for (Plugin plugin : plugins) { + if(disabledPlugins.contains(plugin.getClass().getName())) continue; + plugin.enable(); + } + for (Plugin plugin : plugins) { + if(disabledPlugins.contains(plugin.getClass().getName())) continue; allFunctions.addAll(plugin.providedFunctions()); allOperators.addAll(plugin.providedOperators()); allNumbers.addAll(plugin.providedNumbers()); @@ -173,11 +185,18 @@ public class PluginManager { * Unloads all the plugins in the PluginManager. */ public void unload() { - for (Plugin plugin : plugins) plugin.disable(); + listeners.forEach(e -> e.onUnload(this)); + Set disabledPlugins = abacus.getConfiguration().getDisabledPlugins(); + for (Plugin plugin : plugins) { + if(disabledPlugins.contains(plugin.getClass().getName())) continue; + plugin.disable(); + } + cachedFunctions.clear(); + cachedOperators.clear(); + cachedNumbers.clear(); allFunctions.clear(); allOperators.clear(); allNumbers.clear(); - listeners.forEach(e -> e.onUnload(this)); } /** @@ -185,7 +204,7 @@ public class PluginManager { */ public void reload() { unload(); - reload(); + load(); } /** @@ -233,4 +252,12 @@ public class PluginManager { listeners.remove(listener); } + /** + * Gets a list of all the plugin class files that have been + * added to the plugin manager. + * @return the list of all the added plugin classes. + */ + public Set> getLoadedPluginClasses() { + return loadedPluginClasses; + } } diff --git a/src/main/resources/abacus.fxml b/src/main/resources/abacus.fxml index 183e247..8d8567c 100644 --- a/src/main/resources/abacus.fxml +++ b/src/main/resources/abacus.fxml @@ -46,6 +46,13 @@