From feff01e9e451511040a68baafb12653cbb43050f Mon Sep 17 00:00:00 2001 From: Arthur Poiret Date: Sat, 13 Jun 2026 20:44:26 +0200 Subject: [PATCH 1/4] chore: split the options view in different sections --- .../core/controllers/OptionsController.java | 345 ++++-------------- .../dialogs/WelcomeDialogController.java | 17 +- .../PluginPathFragmentController.java | 27 +- .../options/ApplicationOptionsController.java | 99 +++++ .../InstallationOptionsController.java | 145 ++++++++ .../options/PluginScanOptionsController.java | 156 ++++++++ .../options/ProjectsOptionsController.java | 92 +++++ .../owlplug/plugin/model/PluginFormat.java | 26 +- .../src/main/resources/fxml/OptionsView.fxml | 166 ++------- .../fxml/fragments/PluginPathFragment.fxml | 7 +- .../fxml/options/ApplicationOptionsView.fxml | 58 +++ .../fxml/options/InstallationOptionsView.fxml | 43 +++ .../fxml/options/PluginScanOptionsView.fxml | 48 +++ .../fxml/options/ProjectsOptionsView.fxml | 32 ++ .../main/resources/icons/heart-white-32.png | Bin 809 -> 0 bytes owlplug-client/src/main/resources/owlplug.css | 51 ++- 16 files changed, 886 insertions(+), 426 deletions(-) create mode 100644 owlplug-client/src/main/java/com/owlplug/core/controllers/options/ApplicationOptionsController.java create mode 100644 owlplug-client/src/main/java/com/owlplug/core/controllers/options/InstallationOptionsController.java create mode 100644 owlplug-client/src/main/java/com/owlplug/core/controllers/options/PluginScanOptionsController.java create mode 100644 owlplug-client/src/main/java/com/owlplug/core/controllers/options/ProjectsOptionsController.java create mode 100644 owlplug-client/src/main/resources/fxml/options/ApplicationOptionsView.fxml create mode 100644 owlplug-client/src/main/resources/fxml/options/InstallationOptionsView.fxml create mode 100644 owlplug-client/src/main/resources/fxml/options/PluginScanOptionsView.fxml create mode 100644 owlplug-client/src/main/resources/fxml/options/ProjectsOptionsView.fxml delete mode 100644 owlplug-client/src/main/resources/icons/heart-white-32.png diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java index f25a963b..9d4bbc41 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java @@ -18,321 +18,126 @@ package com.owlplug.core.controllers; -import com.owlplug.controls.Dialog; -import com.owlplug.controls.DialogLayout; import com.owlplug.core.components.ApplicationDefaults; -import com.owlplug.core.controllers.dialogs.DonateDialogController; -import com.owlplug.core.controllers.fragments.PluginPathFragmentController; +import com.owlplug.core.controllers.options.ApplicationOptionsController; +import com.owlplug.core.controllers.options.InstallationOptionsController; +import com.owlplug.core.controllers.options.PluginScanOptionsController; +import com.owlplug.core.controllers.options.ProjectsOptionsController; import com.owlplug.core.events.PreferencesChangedEvent; -import com.owlplug.core.model.OperatingSystem; -import com.owlplug.core.services.OptionsService; import com.owlplug.core.ui.SlidingLabel; import com.owlplug.core.utils.FX; -import com.owlplug.core.utils.PlatformUtils; -import com.owlplug.host.loaders.NativePluginLoader; -import com.owlplug.core.controllers.dialogs.ListDirectoryDialogController; -import com.owlplug.plugin.services.NativeHostService; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Hyperlink; +import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Label; -import javafx.scene.control.TextField; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.text.TextFlow; -import org.springframework.beans.factory.annotation.Autowired; +import org.kordamp.ikonli.javafx.FontIcon; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Controller; @Controller public class OptionsController extends BaseController { - @Autowired - private OptionsService optionsService; - @Autowired - private NativeHostService nativeHostService; - @Autowired - private ListDirectoryDialogController listDirectoryDialogController; - @Autowired - private DonateDialogController donateDialogController; @FXML - private CheckBox pluginNativeCheckbox; + private VBox navContainer; @FXML - private ComboBox pluginNativeComboBox; - @FXML - private TextField loaderTimeoutTextField; - @FXML - private CheckBox syncPluginsCheckBox; - @FXML - private CheckBox syncFileStatCheckbox; - @FXML - private Button removeDataButton; + private StackPane contentArea; @FXML private Label versionLabel; - - @FXML - private Button clearCacheButton; - @FXML - private CheckBox storeSubDirectoryCheckBox; - @FXML - private CheckBox storeByCreatorCheckBox; - @FXML - private Label storeByCreatorLabel; @FXML - private Label storeSubDirectoryLabel; - @FXML - private Label warningSubDirectory; - @FXML - private CheckBox storeDirectoryCheckBox; + private TextFlow contributorsFlow; + + // Section root nodes injected via fx:include fx:id convention @FXML - private TextField storeDirectoryTextField; + private ScrollPane pluginScanOptions; @FXML - private Label storeDirectorySeparator; + private ScrollPane installationOptions; @FXML - private Hyperlink owlplugWebsiteLink; + private ScrollPane projectsOptions; @FXML - private VBox pluginPathContainer; + private ScrollPane applicationOptions; + // Section controllers injected via {fx:id}Controller convention @FXML - private CheckBox telemetryCheckBox; - @FXML - private Hyperlink telemetryHyperlink; + private PluginScanOptionsController pluginScanOptionsController; @FXML - private Button moreFeaturesButton; + private InstallationOptionsController installationOptionsController; @FXML - private Button openLogsButton; + private ProjectsOptionsController projectsOptionsController; @FXML - private TextFlow versionTextFlow; + private ApplicationOptionsController applicationOptionsController; - private PluginPathFragmentController vst2PluginPathFragment; - private PluginPathFragmentController vst3PluginPathFragment; - private PluginPathFragmentController auPluginPathFragment; - private PluginPathFragmentController lv2PluginPathFragment; + private final ToggleGroup navToggleGroup = new ToggleGroup(); - /** - * FXML initialize method. - */ @FXML public void initialize() { - - vst2PluginPathFragment = new PluginPathFragmentController("VST2", - ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, - ApplicationDefaults.VST_DIRECTORY_KEY, - ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY, - this.getPreferences(), - this.listDirectoryDialogController); - - vst3PluginPathFragment = new PluginPathFragmentController("VST3", - ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, - ApplicationDefaults.VST3_DIRECTORY_KEY, - ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY, - this.getPreferences(), - this.listDirectoryDialogController); - - auPluginPathFragment = new PluginPathFragmentController("AU", - ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, - ApplicationDefaults.AU_DIRECTORY_KEY, - ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY, - this.getPreferences(), - this.listDirectoryDialogController); - - lv2PluginPathFragment = new PluginPathFragmentController("LV2", - ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, - ApplicationDefaults.LV2_DIRECTORY_KEY, - ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY, - this.getPreferences(), - this.listDirectoryDialogController); - - pluginPathContainer.getChildren().add(vst2PluginPathFragment.getNode()); - pluginPathContainer.getChildren().add(vst3PluginPathFragment.getNode()); - pluginPathContainer.getChildren().add(auPluginPathFragment.getNode()); - pluginPathContainer.getChildren().add(lv2PluginPathFragment.getNode()); - - storeByCreatorLabel.setVisible(false); - storeSubDirectoryLabel.setVisible(false); - - pluginNativeCheckbox.selectedProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().putBoolean(ApplicationDefaults.NATIVE_HOST_ENABLED_KEY, newValue); - this.pluginNativeComboBox.setDisable(!newValue); - updateScannerTimeoutFieldState(); - }); - - ObservableList pluginLoaders = FXCollections.observableArrayList( - nativeHostService.getAvailablePluginLoaders()); - pluginNativeComboBox.setItems(pluginLoaders); - - pluginNativeComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - this.getPreferences().put(ApplicationDefaults.PREFERRED_NATIVE_LOADER, newValue.getId()); - nativeHostService.setCurrentPluginLoader(newValue); - updateScannerTimeoutFieldState(); + hideSection(pluginScanOptions); + hideSection(installationOptions); + hideSection(projectsOptions); + hideSection(applicationOptions); + + addNavButton("Plugin Scan", "mdi2m-magnify", pluginScanOptions); + addNavButton("Installation", "mdi2d-download-outline", installationOptions); + addNavButton("Projects", "mdi2f-folder-music-outline", projectsOptions); + addNavButton("Application", "mdi2c-cog-outline", applicationOptions); + + // Prevent deselecting all buttons + navToggleGroup.selectedToggleProperty().addListener((obs, prev, next) -> { + if (next == null && prev != null) { + prev.setSelected(true); } }); - loaderTimeoutTextField.textProperty().addListener((observable, oldValue, newValue) -> { - if (!newValue.matches("\\d*")) { - loaderTimeoutTextField.setText(newValue.replaceAll("[^\\d]", "")); - return; - } - try { - long timeout = Long.parseLong(newValue); - if (timeout >= 0 && timeout <= 3600) { - this.getPreferences().putLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, timeout); - nativeHostService.setScannerTimeout(timeout); - } else { - loaderTimeoutTextField.setText(oldValue); - } - } catch (NumberFormatException ignored) { - // Ignore in case of invalid values (empty string) - } - }); + navToggleGroup.getToggles().get(0).setSelected(true); - syncPluginsCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().putBoolean(ApplicationDefaults.SYNC_PLUGINS_STARTUP_KEY, newValue); - }); - - syncFileStatCheckbox.selectedProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().putBoolean(ApplicationDefaults.SYNC_FILE_STAT_KEY, newValue); - }); - - storeSubDirectoryCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().putBoolean(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED, newValue); - warningSubDirectory.setVisible(!newValue); - storeSubDirectoryLabel.setVisible(newValue); - }); - - warningSubDirectory.managedProperty().bind(warningSubDirectory.visibleProperty()); - storeSubDirectoryLabel.managedProperty().bind(storeSubDirectoryLabel.visibleProperty()); - storeDirectorySeparator.managedProperty().bind(storeDirectorySeparator.visibleProperty()); - storeDirectoryTextField.managedProperty().bind(storeDirectoryTextField.visibleProperty()); - - storeDirectoryCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().putBoolean(ApplicationDefaults.STORE_DIRECTORY_ENABLED_KEY, newValue); - storeDirectorySeparator.setVisible(newValue); - storeDirectoryTextField.setVisible(newValue); - }); + versionLabel.setText("OwlPlug " + getApplicationDefaults().getVersion()); + contributorsFlow.getChildren().add(new SlidingLabel(ApplicationDefaults.getContributors())); - storeByCreatorCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().putBoolean(ApplicationDefaults.STORE_BY_CREATOR_ENABLED_KEY, newValue); - storeByCreatorLabel.setVisible(newValue); - }); - - storeByCreatorLabel.managedProperty().bind(storeByCreatorLabel.visibleProperty()); - - storeDirectoryTextField.textProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().put(ApplicationDefaults.STORE_DIRECTORY_KEY, newValue); - }); - - telemetryCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> { - this.getPreferences().putBoolean(ApplicationDefaults.TELEMETRY_ENABLED_KEY, newValue); - }); - - telemetryHyperlink.setOnAction(e -> { - PlatformUtils.openDefaultBrowser(this.getApplicationDefaults().getEnvProperty("owlplug.github.wiki.url") - + "/Telemetry"); - }); - - clearCacheButton.setOnAction(e -> { - optionsService.clearCache(); - }); - - removeDataButton.setOnAction(e -> { - Dialog dialog = this.getDialogManager().newDialog(); - DialogLayout layout = new DialogLayout(); - layout.setHeading(new Label("Remove user data")); - layout.setBody(new Label("Do you really want to remove all user data including accounts, " - + "stores and custom settings ? \n\nYou must restart OwlPlug for a complete reset.")); - - Button cancelButton = new Button("Cancel"); - cancelButton.setOnAction(cancelEvent -> { - dialog.close(); - }); - - Button removeButton = new Button("Remove data"); - removeButton.setOnAction(removeEvent -> { - dialog.close(); - optionsService.clearAllUserData(); - this.refreshView(); - - // User data cleared twice because the refreshView() triggers UI changes that may be replicated in data - optionsService.clearAllUserData(); - - }); - removeButton.getStyleClass().add("button-danger"); - - layout.setActions(removeButton, cancelButton); - dialog.setContent(layout); - dialog.show(); - }); - - versionLabel.setText(this.getApplicationDefaults().getVersion()); - - owlplugWebsiteLink.setOnAction(e -> { - PlatformUtils.openDefaultBrowser(owlplugWebsiteLink.getText()); - }); - - moreFeaturesButton.setOnAction(e -> { - donateDialogController.show(); - }); + refreshView(); + } - openLogsButton.setOnAction(e -> { - PlatformUtils.openFromDesktop(ApplicationDefaults.getLogDirectory()); + private void addNavButton(String label, String iconCode, Node section) { + FontIcon icon = new FontIcon(iconCode); + icon.setIconSize(15); + + ToggleButton btn = new ToggleButton(label, icon); + btn.setToggleGroup(navToggleGroup); + btn.setMaxWidth(Double.MAX_VALUE); + btn.setAlignment(Pos.CENTER_LEFT); + btn.getStyleClass().add("options-nav-button"); + btn.setGraphicTextGap(10); + + btn.selectedProperty().addListener((obs, wasSelected, isSelected) -> { + if (isSelected) { + showSection(section); + } else { + hideSection(section); + } }); - versionTextFlow.getChildren().add(new SlidingLabel(ApplicationDefaults.getContributors())); + navContainer.getChildren().add(btn); + } - refreshView(); + private void showSection(Node section) { + section.setVisible(true); + section.setManaged(true); } - private void updateScannerTimeoutFieldState() { - NativePluginLoader selected = pluginNativeComboBox.getSelectionModel().getSelectedItem(); - boolean isOwlPlugScanner = selected != null && "owlplug-scanner".equals(selected.getId()); - boolean nativeEnabled = pluginNativeCheckbox.isSelected() && !pluginNativeCheckbox.isDisable(); - loaderTimeoutTextField.setDisable(!nativeEnabled || !isOwlPlugScanner); + private void hideSection(Node section) { + section.setVisible(false); + section.setManaged(false); } public void refreshView() { - - vst2PluginPathFragment.refresh(); - vst3PluginPathFragment.refresh(); - auPluginPathFragment.refresh(); - lv2PluginPathFragment.refresh(); - - pluginNativeCheckbox.setDisable(!nativeHostService.isNativeHostAvailable()); - pluginNativeComboBox.setDisable(!nativeHostService.isNativeHostAvailable()); - pluginNativeCheckbox.setSelected(this.getPreferences().getBoolean(ApplicationDefaults.NATIVE_HOST_ENABLED_KEY, false)); - syncPluginsCheckBox.setSelected(this.getPreferences().getBoolean(ApplicationDefaults.SYNC_PLUGINS_STARTUP_KEY, false)); - syncFileStatCheckbox.setSelected(this.getPreferences().getBoolean(ApplicationDefaults.SYNC_FILE_STAT_KEY, true)); - storeSubDirectoryCheckBox.setSelected(this.getPreferences().getBoolean(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED, true)); - warningSubDirectory.setVisible(!this.getPreferences().getBoolean(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED, true)); - storeDirectoryCheckBox.setSelected(this.getPreferences().getBoolean(ApplicationDefaults.STORE_DIRECTORY_ENABLED_KEY, false)); - storeByCreatorCheckBox.setSelected(this.getPreferences().getBoolean(ApplicationDefaults.STORE_BY_CREATOR_ENABLED_KEY, false)); - storeDirectoryTextField.setText(this.getPreferences().get(ApplicationDefaults.STORE_DIRECTORY_KEY, "")); - telemetryCheckBox.setSelected(this.getPreferences().getBoolean(ApplicationDefaults.TELEMETRY_ENABLED_KEY, true)); - - NativePluginLoader pluginLoader = nativeHostService.getCurrentPluginLoader(); - pluginNativeComboBox.getSelectionModel().select(pluginLoader); - - long timeout = this.getPreferences().getLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, 10L); - loaderTimeoutTextField.setText(String.valueOf(timeout)); - updateScannerTimeoutFieldState(); - - if (!storeDirectoryCheckBox.isSelected()) { - storeDirectoryTextField.setVisible(false); - } - if (!storeByCreatorCheckBox.isSelected()) { - storeByCreatorLabel.setVisible(false); - } - - // Disable AU options for non MAC users - if (!this.getApplicationDefaults().getRuntimePlatform() - .getOperatingSystem().equals(OperatingSystem.MAC)) { - auPluginPathFragment.disable(); - } - + pluginScanOptionsController.refresh(); + installationOptionsController.refresh(); + projectsOptionsController.refresh(); + applicationOptionsController.refresh(); } @EventListener @@ -340,4 +145,4 @@ private void handle(PreferencesChangedEvent event) { FX.run(this::refreshView); } -} +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java index 48fde37f..77746b3a 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java @@ -27,6 +27,7 @@ import com.owlplug.core.model.OperatingSystem; import com.owlplug.core.utils.FX; import com.owlplug.plugin.components.PluginTaskFactory; +import com.owlplug.plugin.model.PluginFormat; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -70,28 +71,28 @@ public class WelcomeDialogController extends AbstractDialogController { */ public void initialize() { - vst2PluginPathFragment = new PluginPathFragmentController("VST2", + vst2PluginPathFragment = new PluginPathFragmentController(PluginFormat.VST2, ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, ApplicationDefaults.VST_DIRECTORY_KEY, ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY, - this.getPreferences(), + this.getPreferences(), this.getApplicationDefaults(), this.listDirectoryDialogController); - vst3PluginPathFragment = new PluginPathFragmentController("VST3", + vst3PluginPathFragment = new PluginPathFragmentController(PluginFormat.VST3, ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, ApplicationDefaults.VST3_DIRECTORY_KEY, ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY, - this.getPreferences(), + this.getPreferences(), this.getApplicationDefaults(), this.listDirectoryDialogController); - auPluginPathFragment = new PluginPathFragmentController("AU", + auPluginPathFragment = new PluginPathFragmentController(PluginFormat.AU, ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, ApplicationDefaults.AU_DIRECTORY_KEY, ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY, - this.getPreferences(), + this.getPreferences(), this.getApplicationDefaults(), this.listDirectoryDialogController); - lv2PluginPathFragment = new PluginPathFragmentController("LV2", + lv2PluginPathFragment = new PluginPathFragmentController(PluginFormat.LV2, ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, ApplicationDefaults.LV2_DIRECTORY_KEY, ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY, - this.getPreferences(), + this.getPreferences(), this.getApplicationDefaults(), this.listDirectoryDialogController); pluginPathContainer.getChildren().add(vst2PluginPathFragment.getNode()); diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/fragments/PluginPathFragmentController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/fragments/PluginPathFragmentController.java index db3ca0d2..8f8f5ba7 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/fragments/PluginPathFragmentController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/fragments/PluginPathFragmentController.java @@ -19,10 +19,13 @@ package com.owlplug.core.controllers.fragments; import atlantafx.base.controls.ToggleSwitch; +import com.owlplug.core.components.ApplicationDefaults; import com.owlplug.core.components.ApplicationPreferences; +import com.owlplug.core.controllers.dialogs.ListDirectoryDialogController; import com.owlplug.core.ui.SVGPaths; import com.owlplug.core.utils.FileUtils; -import com.owlplug.core.controllers.dialogs.ListDirectoryDialogController; +import com.owlplug.plugin.model.PluginFormat; +import com.owlplug.plugin.ui.PluginFormatBadgeView; import java.io.File; import java.io.IOException; import java.text.MessageFormat; @@ -34,6 +37,7 @@ import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import javafx.scene.shape.SVGPath; import javafx.stage.DirectoryChooser; @@ -48,6 +52,8 @@ public class PluginPathFragmentController { @FXML private Node mainNode; @FXML + private HBox headerBox; + @FXML private Label headerLabel; @FXML private ToggleSwitch activationToggleButton; @@ -69,21 +75,24 @@ public class PluginPathFragmentController { private SVGPath crossPath = new SVGPath(); - private String name; + private PluginFormat pluginFormat; private String enableOptionKey; private String directoryOptionKey; private String extraDirectoryOptionKey; private ApplicationPreferences prefs; + private ApplicationDefaults applicationDefaults; private ListDirectoryDialogController listDirectoryDialogController; - public PluginPathFragmentController(String name, String enableOptionKey, String directoryOptionKey, + public PluginPathFragmentController(PluginFormat pluginFormat, String enableOptionKey, String directoryOptionKey, String extraDirectoryOptionKey, ApplicationPreferences prefs, + ApplicationDefaults applicationDefaults, ListDirectoryDialogController listDirectoryDialogController) { - this.name = name; + this.pluginFormat = pluginFormat; this.enableOptionKey = enableOptionKey; this.directoryOptionKey = directoryOptionKey; this.extraDirectoryOptionKey = extraDirectoryOptionKey; this.prefs = prefs; + this.applicationDefaults = applicationDefaults; this.listDirectoryDialogController = listDirectoryDialogController; init(); @@ -104,10 +113,14 @@ public void init() { checkPath.setContent(SVGPaths.check); crossPath.setContent(SVGPaths.cross); - headerLabel.setText(name); - directoryTextField.setPromptText(name + " plugin directory"); + PluginFormatBadgeView badge = new PluginFormatBadgeView( + pluginFormat, applicationDefaults, PluginFormatBadgeView.DisplayMode.DEFAULT); + headerBox.getChildren().add(0, badge); + + headerLabel.setText(pluginFormat.getFullName()); + directoryTextField.setPromptText(pluginFormat.getName() + " plugin directory"); - activationToggleButton.setText("Explore " + name + " plugins"); + activationToggleButton.setText("Scan " + pluginFormat.getName() + " plugins"); activationToggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { prefs.putBoolean(enableOptionKey, newValue); refresh(); diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/options/ApplicationOptionsController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/ApplicationOptionsController.java new file mode 100644 index 00000000..082ec448 --- /dev/null +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/ApplicationOptionsController.java @@ -0,0 +1,99 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug 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 OwlPlug. If not, see . + */ + +package com.owlplug.core.controllers.options; + +import com.owlplug.controls.Dialog; +import com.owlplug.controls.DialogLayout; +import com.owlplug.core.components.ApplicationDefaults; +import com.owlplug.core.controllers.BaseController; +import com.owlplug.core.controllers.dialogs.DonateDialogController; +import com.owlplug.core.services.OptionsService; +import com.owlplug.core.utils.PlatformUtils; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class ApplicationOptionsController extends BaseController { + + @Autowired + private OptionsService optionsService; + @Autowired + private DonateDialogController donateDialogController; + + @FXML + private CheckBox telemetryCheckBox; + @FXML + private Hyperlink telemetryHyperlink; + @FXML + private Button clearCacheButton; + @FXML + private Button removeDataButton; + @FXML + private Hyperlink websiteLink; + @FXML + private Button contributeButton; + @FXML + private Button openLogsButton; + + @FXML + public void initialize() { + telemetryCheckBox.selectedProperty().addListener((obs, o, n) -> + getPreferences().putBoolean(ApplicationDefaults.TELEMETRY_ENABLED_KEY, n)); + + telemetryHyperlink.setOnAction(e -> PlatformUtils.openDefaultBrowser( + getApplicationDefaults().getEnvProperty("owlplug.github.wiki.url") + "/Telemetry")); + + clearCacheButton.setOnAction(e -> optionsService.clearCache()); + + removeDataButton.setOnAction(e -> { + Dialog dialog = getDialogManager().newDialog(); + DialogLayout layout = new DialogLayout(); + layout.setHeading(new Label("Remove User Data")); + layout.setBody(new Label( + "Do you really want to remove all user data including accounts, " + + "stores and custom settings?\n\nYou must restart OwlPlug for a complete reset.")); + Button cancelButton = new Button("Cancel"); + cancelButton.setOnAction(ce -> dialog.close()); + Button confirmButton = new Button("Remove Data"); + confirmButton.getStyleClass().add("button-danger"); + confirmButton.setOnAction(ce -> { + dialog.close(); + optionsService.clearAllUserData(); + }); + layout.setActions(confirmButton, cancelButton); + dialog.setContent(layout); + dialog.show(); + }); + + websiteLink.setOnAction(e -> PlatformUtils.openDefaultBrowser(websiteLink.getText())); + contributeButton.setOnAction(e -> donateDialogController.show()); + openLogsButton.setOnAction(e -> PlatformUtils.openFromDesktop(ApplicationDefaults.getLogDirectory())); + } + + public void refresh() { + telemetryCheckBox.setSelected( + getPreferences().getBoolean(ApplicationDefaults.TELEMETRY_ENABLED_KEY, true)); + } + +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/options/InstallationOptionsController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/InstallationOptionsController.java new file mode 100644 index 00000000..db43b15d --- /dev/null +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/InstallationOptionsController.java @@ -0,0 +1,145 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug 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 OwlPlug. If not, see . + */ + +package com.owlplug.core.controllers.options; + +import com.owlplug.core.components.ApplicationDefaults; +import com.owlplug.core.controllers.BaseController; +import com.owlplug.plugin.ui.PluginFormatBadgeView; +import java.io.File; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.springframework.stereotype.Controller; + +@Controller +public class InstallationOptionsController extends BaseController { + + @FXML + private CheckBox storeDirectoryCheckBox; + @FXML + private HBox storeDirectoryRow; + @FXML + private TextField storeDirectoryTextField; + @FXML + private CheckBox storeByCreatorCheckBox; + @FXML + private CheckBox storeSubDirectoryCheckBox; + @FXML + private Label warningSubDirectory; + @FXML + private VBox pathPreviewContainer; + + private Label vst2PathLabel; + private Label vst3PathLabel; + private Label auPathLabel; + private Label lv2PathLabel; + + @FXML + public void initialize() { + storeDirectoryRow.visibleProperty().bind(storeDirectoryCheckBox.selectedProperty()); + storeDirectoryRow.managedProperty().bind(storeDirectoryCheckBox.selectedProperty()); + warningSubDirectory.managedProperty().bind(warningSubDirectory.visibleProperty()); + + storeDirectoryCheckBox.selectedProperty().addListener((obs, o, n) -> { + getPreferences().putBoolean(ApplicationDefaults.STORE_DIRECTORY_ENABLED_KEY, n); + refreshPathPreview(); + }); + storeByCreatorCheckBox.selectedProperty().addListener((obs, o, n) -> { + getPreferences().putBoolean(ApplicationDefaults.STORE_BY_CREATOR_ENABLED_KEY, n); + refreshPathPreview(); + }); + storeSubDirectoryCheckBox.selectedProperty().addListener((obs, o, n) -> { + getPreferences().putBoolean(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED, n); + warningSubDirectory.setVisible(!n); + refreshPathPreview(); + }); + storeDirectoryTextField.textProperty().addListener((obs, o, n) -> { + getPreferences().put(ApplicationDefaults.STORE_DIRECTORY_KEY, n); + refreshPathPreview(); + }); + + vst2PathLabel = previewLabel(); + vst3PathLabel = previewLabel(); + auPathLabel = previewLabel(); + lv2PathLabel = previewLabel(); + + pathPreviewContainer.getChildren().addAll( + previewRow("vst2", vst2PathLabel), + previewRow("vst3", vst3PathLabel), + previewRow("au", auPathLabel), + previewRow("lv2", lv2PathLabel)); + } + + public void refresh() { + boolean storeSubDir = getPreferences().getBoolean(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED, true); + storeSubDirectoryCheckBox.setSelected(storeSubDir); + warningSubDirectory.setVisible(!storeSubDir); + storeDirectoryCheckBox.setSelected( + getPreferences().getBoolean(ApplicationDefaults.STORE_DIRECTORY_ENABLED_KEY, false)); + storeByCreatorCheckBox.setSelected( + getPreferences().getBoolean(ApplicationDefaults.STORE_BY_CREATOR_ENABLED_KEY, false)); + storeDirectoryTextField.setText( + getPreferences().get(ApplicationDefaults.STORE_DIRECTORY_KEY, "")); + refreshPathPreview(); + } + + private void refreshPathPreview() { + vst2PathLabel.setText(simulatePath(ApplicationDefaults.VST_DIRECTORY_KEY)); + vst3PathLabel.setText(simulatePath(ApplicationDefaults.VST3_DIRECTORY_KEY)); + auPathLabel.setText(simulatePath(ApplicationDefaults.AU_DIRECTORY_KEY)); + lv2PathLabel.setText(simulatePath(ApplicationDefaults.LV2_DIRECTORY_KEY)); + } + + private String simulatePath(String formatDirKey) { + String baseDir = getPreferences().get(formatDirKey, ""); + if (baseDir == null || baseDir.isBlank()) return "Format directory not configured"; + + File path = new File(baseDir); + if (storeDirectoryCheckBox.isSelected()) { + String sub = storeDirectoryTextField.getText(); + if (sub != null && !sub.isBlank()) path = new File(path, sub); + } + if (storeByCreatorCheckBox.isSelected()) path = new File(path, "Acme Audio"); + if (storeSubDirectoryCheckBox.isSelected()) path = new File(path, "MyPlugin"); + return path.getAbsolutePath(); + } + + private Label previewLabel() { + Label l = new Label(); + l.getStyleClass().add("label-disabled"); + l.setWrapText(true); + HBox.setHgrow(l, Priority.ALWAYS); + return l; + } + + private HBox previewRow(String formatValue, Label pathLabel) { + PluginFormatBadgeView badge = new PluginFormatBadgeView( + formatValue, getApplicationDefaults(), PluginFormatBadgeView.DisplayMode.TEXT_ONLY); + badge.setMinWidth(42); + HBox row = new HBox(12, badge, pathLabel); + row.setAlignment(Pos.CENTER_LEFT); + return row; + } + +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/options/PluginScanOptionsController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/PluginScanOptionsController.java new file mode 100644 index 00000000..b70b8cbe --- /dev/null +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/PluginScanOptionsController.java @@ -0,0 +1,156 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug 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 OwlPlug. If not, see . + */ + +package com.owlplug.core.controllers.options; + +import com.owlplug.core.components.ApplicationDefaults; +import com.owlplug.core.controllers.BaseController; +import com.owlplug.core.controllers.dialogs.ListDirectoryDialogController; +import com.owlplug.core.controllers.fragments.PluginPathFragmentController; +import com.owlplug.core.model.OperatingSystem; +import com.owlplug.host.loaders.NativePluginLoader; +import com.owlplug.plugin.model.PluginFormat; +import com.owlplug.plugin.services.NativeHostService; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.VBox; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class PluginScanOptionsController extends BaseController { + + @Autowired + private NativeHostService nativeHostService; + @Autowired + private ListDirectoryDialogController listDirectoryDialogController; + + @FXML + private VBox formatsContainer; + @FXML + private CheckBox pluginNativeCheckbox; + @FXML + private ComboBox pluginNativeComboBox; + @FXML + private Spinner loaderTimeoutSpinner; + @FXML + private CheckBox syncPluginsCheckBox; + @FXML + private CheckBox syncFileStatCheckbox; + + private PluginPathFragmentController vst2Fragment; + private PluginPathFragmentController vst3Fragment; + private PluginPathFragmentController auFragment; + private PluginPathFragmentController lv2Fragment; + + @FXML + public void initialize() { + vst2Fragment = new PluginPathFragmentController(PluginFormat.VST2, + ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, ApplicationDefaults.VST_DIRECTORY_KEY, + ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY, getPreferences(), + getApplicationDefaults(), listDirectoryDialogController); + vst3Fragment = new PluginPathFragmentController(PluginFormat.VST3, + ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, ApplicationDefaults.VST3_DIRECTORY_KEY, + ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY, getPreferences(), + getApplicationDefaults(), listDirectoryDialogController); + auFragment = new PluginPathFragmentController(PluginFormat.AU, + ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, ApplicationDefaults.AU_DIRECTORY_KEY, + ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY, getPreferences(), + getApplicationDefaults(), listDirectoryDialogController); + lv2Fragment = new PluginPathFragmentController(PluginFormat.LV2, + ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, ApplicationDefaults.LV2_DIRECTORY_KEY, + ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY, getPreferences(), + getApplicationDefaults(), listDirectoryDialogController); + + formatsContainer.getChildren().addAll( + vst2Fragment.getNode(), + vst3Fragment.getNode(), + auFragment.getNode(), + lv2Fragment.getNode()); + + pluginNativeCheckbox.selectedProperty().addListener((obs, o, n) -> { + getPreferences().putBoolean(ApplicationDefaults.NATIVE_HOST_ENABLED_KEY, n); + pluginNativeComboBox.setDisable(!n); + updateScannerTimeoutFieldState(); + }); + + pluginNativeComboBox.setItems( + FXCollections.observableArrayList(nativeHostService.getAvailablePluginLoaders())); + pluginNativeComboBox.getSelectionModel().selectedItemProperty().addListener((obs, o, n) -> { + if (n != null) { + getPreferences().put(ApplicationDefaults.PREFERRED_NATIVE_LOADER, n.getId()); + nativeHostService.setCurrentPluginLoader(n); + updateScannerTimeoutFieldState(); + } + }); + + loaderTimeoutSpinner.setValueFactory( + new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 3600, 10)); + loaderTimeoutSpinner.valueProperty().addListener((obs, o, n) -> { + getPreferences().putLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, n.longValue()); + nativeHostService.setScannerTimeout(n.longValue()); + }); + + syncPluginsCheckBox.selectedProperty().addListener((obs, o, n) -> + getPreferences().putBoolean(ApplicationDefaults.SYNC_PLUGINS_STARTUP_KEY, n)); + syncFileStatCheckbox.selectedProperty().addListener((obs, o, n) -> + getPreferences().putBoolean(ApplicationDefaults.SYNC_FILE_STAT_KEY, n)); + } + + public void refresh() { + vst2Fragment.refresh(); + vst3Fragment.refresh(); + auFragment.refresh(); + lv2Fragment.refresh(); + + boolean nativeAvailable = nativeHostService.isNativeHostAvailable(); + pluginNativeCheckbox.setDisable(!nativeAvailable); + pluginNativeComboBox.setDisable(!nativeAvailable); + pluginNativeCheckbox.setSelected( + getPreferences().getBoolean(ApplicationDefaults.NATIVE_HOST_ENABLED_KEY, false)); + + NativePluginLoader loader = nativeHostService.getCurrentPluginLoader(); + pluginNativeComboBox.getSelectionModel().select(loader); + + int timeout = (int) getPreferences().getLong(ApplicationDefaults.NATIVE_LOADER_TIMEOUT_KEY, 10L); + loaderTimeoutSpinner.getValueFactory().setValue(timeout); + updateScannerTimeoutFieldState(); + + syncPluginsCheckBox.setSelected( + getPreferences().getBoolean(ApplicationDefaults.SYNC_PLUGINS_STARTUP_KEY, false)); + syncFileStatCheckbox.setSelected( + getPreferences().getBoolean(ApplicationDefaults.SYNC_FILE_STAT_KEY, true)); + + if (!getApplicationDefaults().getRuntimePlatform() + .getOperatingSystem().equals(OperatingSystem.MAC)) { + auFragment.disable(); + } + } + + private void updateScannerTimeoutFieldState() { + NativePluginLoader selected = pluginNativeComboBox.getSelectionModel().getSelectedItem(); + boolean isOwlPlugScanner = selected != null && "owlplug-scanner".equals(selected.getId()); + boolean nativeEnabled = pluginNativeCheckbox.isSelected() && !pluginNativeCheckbox.isDisable(); + loaderTimeoutSpinner.setDisable(!nativeEnabled || !isOwlPlugScanner); + } + +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/options/ProjectsOptionsController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/ProjectsOptionsController.java new file mode 100644 index 00000000..f70c9fe1 --- /dev/null +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/options/ProjectsOptionsController.java @@ -0,0 +1,92 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug 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 OwlPlug. If not, see . + */ + +package com.owlplug.core.controllers.options; + +import com.owlplug.core.components.ApplicationDefaults; +import com.owlplug.core.controllers.BaseController; +import java.io.File; +import java.util.ArrayList; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.stage.DirectoryChooser; +import javafx.stage.Window; +import org.kordamp.ikonli.javafx.FontIcon; +import org.springframework.stereotype.Controller; + +@Controller +public class ProjectsOptionsController extends BaseController { + + @FXML + private ListView projectListView; + @FXML + private Button addDirButton; + @FXML + private Button removeDirButton; + + private ObservableList projectDirectories; + + @FXML + public void initialize() { + Label placeholder = new Label("No project directories configured."); + placeholder.getStyleClass().add("label-disabled"); + projectListView.setPlaceholder(placeholder); + + FontIcon addIcon = new FontIcon("mdi2p-plus"); + addIcon.setIconSize(14); + addDirButton.setGraphic(addIcon); + + FontIcon removeIcon = new FontIcon("mdi2m-minus"); + removeIcon.setIconSize(14); + removeDirButton.setGraphic(removeIcon); + + projectDirectories = FXCollections.observableArrayList( + getPreferences().getList(ApplicationDefaults.PROJECT_DIRECTORY_KEY, new ArrayList<>())); + projectListView.setItems(projectDirectories); + + projectDirectories.addListener((ListChangeListener) change -> + getPreferences().putList( + ApplicationDefaults.PROJECT_DIRECTORY_KEY, new ArrayList<>(projectDirectories))); + + addDirButton.setOnAction(e -> { + DirectoryChooser chooser = new DirectoryChooser(); + Window window = addDirButton.getScene().getWindow(); + File dir = chooser.showDialog(window); + if (dir != null) { + String path = dir.getAbsolutePath(); + if (!projectDirectories.contains(path)) projectDirectories.add(path); + } + }); + + removeDirButton.setOnAction(e -> { + String selected = projectListView.getSelectionModel().getSelectedItem(); + if (selected != null) projectDirectories.remove(selected); + }); + } + + public void refresh() { + projectDirectories.setAll( + getPreferences().getList(ApplicationDefaults.PROJECT_DIRECTORY_KEY, new ArrayList<>())); + } + +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFormat.java b/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFormat.java index 5958d131..60963614 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFormat.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/model/PluginFormat.java @@ -19,16 +19,30 @@ package com.owlplug.plugin.model; public enum PluginFormat { - VST2("VST2"), VST3("VST3"), AU("AU"), LV2("LV2"); + VST2("VST2", "Virtual Instrument (v2)"), + VST3("VST3", "Virtual Instrument (v3)"), + AU("AU", "Audio Unit"), + LV2("LV2", "LADSPA (v2)"); - private final String text; + private final String name; + private final String fullName; - PluginFormat(String text) { - this.text = text; + PluginFormat(String name, String fullName) { + this.name = name; + this.fullName = fullName; } + @Deprecated public String getText() { - return text; + return name; + } + + public String getName() { + return name; + } + + public String getFullName() { + return fullName; } /** @@ -43,7 +57,7 @@ public static PluginFormat fromBundleString(String text) { return PluginFormat.VST2; } for (PluginFormat f : PluginFormat.values()) { - if (f.text.equalsIgnoreCase(text)) { + if (f.name.equalsIgnoreCase(text)) { return f; } } diff --git a/owlplug-client/src/main/resources/fxml/OptionsView.fxml b/owlplug-client/src/main/resources/fxml/OptionsView.fxml index cccce37e..e0bd9a5d 100644 --- a/owlplug-client/src/main/resources/fxml/OptionsView.fxml +++ b/owlplug-client/src/main/resources/fxml/OptionsView.fxml @@ -1,141 +1,45 @@ - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - + + + +
+ + + + + + +
+ + \ No newline at end of file diff --git a/owlplug-client/src/main/resources/fxml/fragments/PluginPathFragment.fxml b/owlplug-client/src/main/resources/fxml/fragments/PluginPathFragment.fxml index 29dff3fe..d39e1c8f 100644 --- a/owlplug-client/src/main/resources/fxml/fragments/PluginPathFragment.fxml +++ b/owlplug-client/src/main/resources/fxml/fragments/PluginPathFragment.fxml @@ -10,13 +10,16 @@ + -