1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-25 16:15:19 +00:00

Compare commits

...

17 Commits

Author SHA1 Message Date
eb51d5d3e4 Add a save and reload button. 2017-08-03 19:00:13 -07:00
8ae28f2dab Add the apply warnings when switching tabs. 2017-08-03 18:16:48 -07:00
0bade4a7df Add warnings that trigger if configuration has been changed. 2017-08-03 18:12:40 -07:00
f0e1b85dcf Make sure disk writes are only on save, and add an alert dialog. 2017-08-03 13:55:39 -07:00
37261c2f58 Fix order of operations bug. 2017-08-03 13:14:09 -07:00
20f6e0b0b2 Merge branch 'plugin-list' 2017-08-03 09:34:14 -07:00
4056013d1f Add defaults that actually work. 2017-08-02 21:57:53 -07:00
be28e26607 Stop autosaving, switch to save + reload buttons. 2017-08-02 19:40:22 -07:00
2f1ed5f0d1 Change the default implementation string to "<default>" 2017-08-02 19:26:14 -07:00
2615273d28 Refresh all settings on plugin load. 2017-08-02 19:18:33 -07:00
6e1d2ce629 Clear caches on unload and call onUnload before plugins are removed. 2017-08-02 19:14:50 -07:00
44b8efd9bc Actually disable loading the plugin functions in the PluginManager. 2017-08-02 19:06:16 -07:00
2502c90837 Write disabled / enabled plugins to the configuration. 2017-08-02 19:01:01 -07:00
e49f28a850 Add a check box list cell generator. 2017-08-02 18:48:42 -07:00
88e4a87d81 Add a data model for the plugins displayed in the enabled plugins list. 2017-08-02 18:39:00 -07:00
cda09518c3 Add the disabled plugins configuration option. 2017-08-02 18:38:37 -07:00
56510d97de Add the new UI components required for the plugin loading. 2017-08-02 18:24:20 -07:00
7 changed files with 247 additions and 27 deletions

View File

@@ -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);

View File

@@ -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 = "<default>";
/**
* The list of disabled plugins in this Configuration.
*/
private Set<String> 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<String> getDisabledPlugins() {
return disabledPlugins;
}
}

View File

@@ -4,19 +4,39 @@ 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 {
/**
* The title for the apply alert dialog.
*/
private static final String APPLY_MSG_TITLE = "\"Apply\" Needed";
/**
* The text for the header of the apply alert dialog.
*/
private static final String APPLY_MSG_HEADER = "The settings have not been applied.";
/**
* The text for the dialog that is shown if settings haven't been applied.
*/
private static final String APPLY_MSG_TEXT = "You have made changes to the configuration, however, you haven't pressed \"Apply\". " +
"The changes to the configuration will not be present in the calculator until \"Apply\" is pressed.";
/**
* Constant string that is displayed if the text could not be lexed or parsed.
*/
@@ -26,6 +46,12 @@ public class AbacusController {
*/
private static final String ERR_EVAL = "Evaluation Error";
@FXML
private TabPane coreTabPane;
@FXML
private Tab calculateTab;
@FXML
private Tab settingsTab;
@FXML
private TableView<HistoryModel> historyTable;
@FXML
@@ -42,6 +68,8 @@ public class AbacusController {
private Button inputButton;
@FXML
private ComboBox<String> numberImplementationBox;
@FXML
private ListView<ToggleablePlugin> enabledPluginView;
/**
* The list of history entries, created by the users.
@@ -54,35 +82,88 @@ public class AbacusController {
*/
private ObservableList<String> 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<ToggleablePlugin> enabledPlugins;
/**
* The abacus instance used for changing the plugin configuration.
*/
private Abacus abacus;
/**
* Boolean which represents whether changes were made to the configuration.
*/
private boolean changesMade;
/**
* Whether an alert about changes to the configuration was already shown.
*/
private boolean reloadAlertShown;
/**
* The alert shown when a press to "apply" is needed.
*/
private Alert reloadAlert;
/**
* Alerts the user if the changes they made
* have not yet been applied.
*/
private void alertIfApplyNeeded(boolean ignorePrevious){
if(changesMade && (!reloadAlertShown || ignorePrevious)) {
reloadAlertShown = true;
reloadAlert.showAndWait();
}
}
@FXML
public void initialize(){
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
param -> new CopyableCell<>();
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter<ToggleablePlugin>() {
@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);
});
numberImplementationBox.getSelectionModel().selectedIndexProperty().addListener(e -> changesMade = true);
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);
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty());
outputColumn.setCellFactory(cellFactory);
outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty());
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if(oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
});
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();
changesMade = false;
reloadAlertShown = false;
reloadAlert = new Alert(Alert.AlertType.WARNING);
reloadAlert.setTitle(APPLY_MSG_TITLE);
reloadAlert.setHeaderText(APPLY_MSG_HEADER);
reloadAlert.setContentText(APPLY_MSG_TEXT);
}
@FXML
@@ -107,4 +188,53 @@ public class AbacusController {
inputField.setText("");
}
@FXML
private void performSaveAndReload(){
performSave();
performReload();
changesMade = false;
reloadAlertShown = false;
}
@FXML
private void performReload(){
alertIfApplyNeeded(true);
abacus.getPluginManager().reload();
}
@FXML
private void performSave(){
Configuration configuration = abacus.getConfiguration();
configuration.setNumberImplementation(numberImplementationBox.getSelectionModel().getSelectedItem());
Set<String> disabledPlugins = configuration.getDisabledPlugins();
disabledPlugins.clear();
for(ToggleablePlugin pluginEntry : enabledPlugins){
if(!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
}
configuration.saveTo(Abacus.CONFIG_FILE);
changesMade = false;
reloadAlertShown = false;
}
@Override
public void onLoad(PluginManager manager) {
Configuration configuration = abacus.getConfiguration();
Set<String> disabledPlugins = configuration.getDisabledPlugins();
numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumbers());
String actualImplementation = configuration.getNumberImplementation();
String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : "<default>";
numberImplementationBox.getSelectionModel().select(toSelect);
for(Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()){
String fullName = pluginClass.getName();
ToggleablePlugin plugin = new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName);
plugin.enabledProperty().addListener(e -> changesMade = true);
enabledPlugins.add(plugin);
}
}
@Override
public void onUnload(PluginManager manager) {
enabledPlugins.clear();
numberImplementationOptions.clear();
}
}

View File

@@ -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;
}
}

View File

@@ -89,7 +89,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
if (otherMatchType == TokenType.OP) {
int otherPrecedence = precedenceMap.get(match.getContent());
int otherPrecedence = precedenceMap.get(otherMatch.getContent());
if (otherPrecedence < precedence ||
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
break;

View File

@@ -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<PluginListener> 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<String> 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<String> 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<Class<?>> getLoadedPluginClasses() {
return loadedPluginClasses;
}
}

View File

@@ -7,12 +7,13 @@
<?import javafx.scene.text.Text?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.FlowPane?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.nwapw.abacus.fx.AbacusController">
<center>
<TabPane>
<Tab text="Calculator" closable="false">
<TabPane fx:id="coreTabPane">
<Tab fx:id="calculateTab" text="Calculator" closable="false">
<BorderPane>
<center>
<TableView fx:id="historyTable">
@@ -41,11 +42,19 @@
</bottom>
</BorderPane>
</Tab>
<Tab text="Settings" closable="false">
<Tab fx:id="settingsTab" text="Settings" closable="false">
<GridPane hgap="10" vgap="10">
<padding><Insets left="10" right="10" top="10" bottom="10"/></padding>
<Label text="Number Implementation" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<ComboBox fx:id="numberImplementationBox" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
<ListView fx:id="enabledPluginView"
GridPane.rowIndex="1" GridPane.columnIndex="0"
GridPane.columnSpan="2" maxHeight="100"/>
<FlowPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2" hgap="10" vgap="10">
<Button text="Apply" onAction="#performSave"/>
<Button text="Reload Plugins" onAction="#performReload"/>
<Button text="Apply and Reload" onAction="#performSaveAndReload"/>
</FlowPane>
</GridPane>
</Tab>
</TabPane>