|
@@ -13,31 +13,37 @@ import org.slf4j.LoggerFactory;
|
|
|
|
|
|
import javax.inject.Inject;
|
|
|
import javax.inject.Named;
|
|
|
+import javafx.application.Platform;
|
|
|
+import javafx.beans.binding.Bindings;
|
|
|
import javafx.beans.binding.BooleanBinding;
|
|
|
import javafx.beans.property.BooleanProperty;
|
|
|
import javafx.beans.property.ObjectProperty;
|
|
|
import javafx.beans.property.SimpleBooleanProperty;
|
|
|
import javafx.beans.property.StringProperty;
|
|
|
import javafx.beans.value.ObservableValue;
|
|
|
+import javafx.collections.FXCollections;
|
|
|
+import javafx.collections.ObservableList;
|
|
|
import javafx.fxml.FXML;
|
|
|
+import javafx.scene.Node;
|
|
|
import javafx.scene.Scene;
|
|
|
import javafx.scene.control.Label;
|
|
|
import javafx.scene.control.RadioButton;
|
|
|
import javafx.scene.control.Toggle;
|
|
|
import javafx.scene.control.ToggleGroup;
|
|
|
+import javafx.scene.layout.HBox;
|
|
|
import javafx.scene.layout.VBox;
|
|
|
import javafx.stage.DirectoryChooser;
|
|
|
import javafx.stage.Stage;
|
|
|
+import javafx.stage.WindowEvent;
|
|
|
import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.nio.file.Files;
|
|
|
import java.nio.file.Path;
|
|
|
import java.nio.file.Paths;
|
|
|
import java.nio.file.StandardOpenOption;
|
|
|
-import java.util.Comparator;
|
|
|
-import java.util.List;
|
|
|
import java.util.Optional;
|
|
|
import java.util.ResourceBundle;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
|
|
|
@AddVaultWizardScoped
|
|
|
public class CreateNewVaultLocationController implements FxController {
|
|
@@ -49,19 +55,23 @@ public class CreateNewVaultLocationController implements FxController {
|
|
|
private final Stage window;
|
|
|
private final Lazy<Scene> chooseNameScene;
|
|
|
private final Lazy<Scene> chooseExpertSettingsScene;
|
|
|
- private final List<RadioButton> locationPresetBtns;
|
|
|
private final ObjectProperty<Path> vaultPath;
|
|
|
private final StringProperty vaultName;
|
|
|
+ private final ExecutorService backgroundExecutor;
|
|
|
private final ResourceBundle resourceBundle;
|
|
|
private final ObservableValue<VaultPathStatus> vaultPathStatus;
|
|
|
private final ObservableValue<Boolean> validVaultPath;
|
|
|
private final BooleanProperty usePresetPath;
|
|
|
+ private final BooleanProperty loadingPresetLocations = new SimpleBooleanProperty(false);
|
|
|
+ private final ObservableList<Node> radioButtons;
|
|
|
+ private final ObservableList<Node> sortedRadioButtons;
|
|
|
|
|
|
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
|
|
|
|
|
//FXML
|
|
|
public ToggleGroup locationPresetsToggler;
|
|
|
public VBox radioButtonVBox;
|
|
|
+ public HBox customLocationRadioBtn;
|
|
|
public RadioButton customRadioButton;
|
|
|
public Label locationStatusLabel;
|
|
|
public FontAwesome5IconView goodLocation;
|
|
@@ -73,25 +83,20 @@ public class CreateNewVaultLocationController implements FxController {
|
|
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy<Scene> chooseExpertSettingsScene, //
|
|
|
ObjectProperty<Path> vaultPath, //
|
|
|
@Named("vaultName") StringProperty vaultName, //
|
|
|
- ResourceBundle resourceBundle) {
|
|
|
+ ExecutorService backgroundExecutor, ResourceBundle resourceBundle) {
|
|
|
this.window = window;
|
|
|
this.chooseNameScene = chooseNameScene;
|
|
|
this.chooseExpertSettingsScene = chooseExpertSettingsScene;
|
|
|
this.vaultPath = vaultPath;
|
|
|
this.vaultName = vaultName;
|
|
|
+ this.backgroundExecutor = backgroundExecutor;
|
|
|
this.resourceBundle = resourceBundle;
|
|
|
this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message"));
|
|
|
this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false);
|
|
|
this.vaultPathStatus.addListener(this::updateStatusLabel);
|
|
|
this.usePresetPath = new SimpleBooleanProperty();
|
|
|
- this.locationPresetBtns = LocationPresetsProvider.loadAll(LocationPresetsProvider.class) //
|
|
|
- .flatMap(LocationPresetsProvider::getLocations) //
|
|
|
- .sorted(Comparator.comparing(LocationPreset::name)) //
|
|
|
- .map(preset -> { //
|
|
|
- var btn = new RadioButton(preset.name());
|
|
|
- btn.setUserData(preset.path());
|
|
|
- return btn;
|
|
|
- }).toList();
|
|
|
+ this.radioButtons = FXCollections.observableArrayList();
|
|
|
+ this.sortedRadioButtons = radioButtons.sorted(this::compareLocationPresets);
|
|
|
}
|
|
|
|
|
|
private VaultPathStatus validatePath(Path p) throws NullPointerException {
|
|
@@ -137,12 +142,45 @@ public class CreateNewVaultLocationController implements FxController {
|
|
|
|
|
|
@FXML
|
|
|
public void initialize() {
|
|
|
- radioButtonVBox.getChildren().addAll(1, locationPresetBtns); //first item is the list header
|
|
|
- locationPresetsToggler.getToggles().addAll(locationPresetBtns);
|
|
|
+ var task = backgroundExecutor.submit(this::loadLocationPresets);
|
|
|
+ window.addEventHandler(WindowEvent.WINDOW_HIDING, _ -> task.cancel(true));
|
|
|
locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
|
|
usePresetPath.bind(locationPresetsToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
|
|
+ radioButtons.add(customLocationRadioBtn);
|
|
|
+ Bindings.bindContent(radioButtonVBox.getChildren(), sortedRadioButtons); //to prevent garbage collection of the binding, we bind explicitly to the sorted list
|
|
|
}
|
|
|
|
|
|
+ private void loadLocationPresets() {
|
|
|
+ Platform.runLater(() -> loadingPresetLocations.set(true));
|
|
|
+ try {
|
|
|
+ LocationPresetsProvider.loadAll(LocationPresetsProvider.class) //
|
|
|
+ .flatMap(LocationPresetsProvider::getLocations) //we do not use sorted(), because it evaluates the stream elements, blocking until all elements are gathered
|
|
|
+ .forEach(this::createRadioButtonFor);
|
|
|
+ } finally {
|
|
|
+ Platform.runLater(() -> loadingPresetLocations.set(false));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void createRadioButtonFor(LocationPreset preset) {
|
|
|
+ Platform.runLater(() -> {
|
|
|
+ var btn = new RadioButton(preset.name());
|
|
|
+ btn.setUserData(preset.path());
|
|
|
+ radioButtons.add(btn);
|
|
|
+ locationPresetsToggler.getToggles().add(btn);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private int compareLocationPresets(Node left, Node right) {
|
|
|
+ if (customLocationRadioBtn.getId().equals(left.getId())) {
|
|
|
+ return 1;
|
|
|
+ } else if (customLocationRadioBtn.getId().equals(right.getId())) {
|
|
|
+ return -1;
|
|
|
+ } else {
|
|
|
+ return ((RadioButton) left).getText().compareToIgnoreCase(((RadioButton) right).getText());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
|
|
var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath);
|
|
|
vaultPath.set(storagePath.resolve(vaultName.get()));
|
|
@@ -200,6 +238,14 @@ public class CreateNewVaultLocationController implements FxController {
|
|
|
return validVaultPath.getValue();
|
|
|
}
|
|
|
|
|
|
+ public boolean isLoadingPresetLocations() {
|
|
|
+ return loadingPresetLocations.getValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ public BooleanProperty loadingPresetLocationsProperty() {
|
|
|
+ return loadingPresetLocations;
|
|
|
+ }
|
|
|
+
|
|
|
public BooleanProperty usePresetPathProperty() {
|
|
|
return usePresetPath;
|
|
|
}
|